玩过复古拼图的php高手来帮我一下

浏览:1179 发布日期:2014/09/16 分类:求助交流 关键字: 复古拼图
最近手机上安装了一个游戏叫复古拼图,因为自己有强迫症,非要把每一关都玩到最佳。但是玩到后面,实在试不出最佳了。后来想想是不是可以用php代码来算一算最佳移动方法。这里先简单介绍一下游戏:
游戏每一关,都是由5行7列的方块组成。有固定不动的方块,有可以移动的若干不同方块。将这些方块同时移动到各自的指定位置才算赢。移动时有个特点,比如往上移动一步,所有可移动方块都会同时移动一格,但如果上面是边缘或是有方块堵着了就不会移动。具体玩法,有空的可以去下载试玩一下^_^

我抽空就用php写了最佳算法的代码,但遇到一点百思不得其解的问题。

不过现在问题已经解决了,逻辑没有问题,是判断游戏结束的代码写错了,想当然地认为是那样的,检查几遍都没去细看,结果就是出错在那里。

哈哈,感觉这个例子用来面试或者锻炼新手比较好!

下面贴上自己的实现代码,已经可以用了:<?php

define('ASC', 1); // 正向排序
define('DESC', 0); // 逆向排序

define('MOVE_UP', 1); // 上移
define('MOVE_DOWN', 2); // 下移
define('MOVE_LEFT', 3); // 左移
define('MOVE_RIGHT', 4); // 右移

define('ROW', 5); // 地图有 5 排
define('COL', 7); // 地图有 7 列
// memory_limit,单个线程最大内存,为 -1 则不限制
// 会影响数组的最大长度
ini_set('memory_limit', -1);
// max_execution_time,最大响应时间,为 0 则不限制
// 有些地图的计算时间可能超过默认的30秒
ini_set('max_execution_time', 0);
// 地图数据
$mapArr = array(
    // 第 2 章,第 1 关
    // 最少步数:17,下 左 左 左 左 上 上 上 上 下 左 右 右 下 左 上 右
    array(
        'base'    => array('1112215152' => 9),
        'box'    => array('25' => 1, '45' => 2),
        'end'    => array('44' => 1, '24' => 2),
    ),
    // 第 2 章,第 2 关
    // 最少步数 16,左 上 右 右 下 左 下 下 左 左 上 右 右 右 右 右
    array(
        'base'    => array('44' => 9),
        'box'    => array('14' => 1, '54' => 2, '3335' => 3),
        'end'    => array('27' => 1, '41' => 2),
    ),
);
// 参数 a 用来指定地图
$a = isset($_GET['a']) ? $_GET['a'] - 1 : 0;
// 参数 s 用来决定循环次数
$s = isset($_GET['s']) ? $_GET['s'] : 0;
//初始化地图关卡
$map = new Map($mapArr[$a]);
//开始,计算出一个最少移动方案
$game = new Game($map);
$game->start($s);
/**
 * 关卡地图类
 */
class Map {
    //固定不动的方块。二位数组,角标即坐标
    public $base = array();
    //可移动方块(包括不需要对位的方块)。二位数组,角标即坐标
    public $box = array();
    //需要对齐的方块。二位数组,角标即坐标
    public $end = array();
    
    public function __construct($gameMap = array()) {
        foreach ($gameMap as $key => $value) {
            if (!empty($value)) {
                $this->$key = $this->createBoxArr($value);
            }
        }
    }
    
    private function createBoxArr($map = array()) {
        $arr = array();
        foreach ($map as $key => $value) {
            $tmp = str_split($key, 2);
            foreach ($tmp as $k => $v) {
                list($row, $col) = str_split($v, 1);
                $arr[$row][$col] = $value;
            }
        }
        return $arr;
    }
}
/**
 * 存储最简移动方案的类
 */
class Step {
    
    public $steps = array();
    
    public function getSteps() {
        $step_str = str_replace(array(MOVE_UP, MOVE_DOWN, MOVE_LEFT, MOVE_RIGHT), array('上', '下', '左', '右'), $this->steps);
        return array_reduce($step_str, 'arr2str');
    }
}
/**
 * 数组到字符串的转换函数
*/
function arr2str($val_a, $val_b) {
    return $val_a . " " . $val_b;
}
/**
 * 游戏主类
 */
class Game {
    // 最简方案,键名为移动后的位置对应的md5值,键值即为最短步长
    public $minStepBoxArray = array();
    // 每次移动后的结束位置,即为下次移动时的所有起步位置
    public $startBoxArray = array();
    // 每次移动后的最简方案,即为下次移动前的所有已移动的最简方案
    public $stepObjArray = array();
    // 步长
    public $count = 0;
    // 方块地图
    public $map = null;
    // 游戏结束标识
    private $gameEnd = false;
    
    public function __construct($map = null) {
        $this->map = $map;
    }
    /**
     * 从移动第1步开始计算,直到循环结束
     * 存储所有最简方案到 $minStepBoxArray
     * 存储每次移动的起步位置到 $startBoxArray
     * 存储每次移动的最简方案到 $stepObjArray
     */
    public function start($total = 0) {
        $t0 = $this->runTime();
        while (true) {
            $this->count++;
            // 结束判断
            if ($this->gameEnd === true) {
                break;
            }
            if ($total != 0 && $this->count > $total) {
                break;
            }
            if ($this->count > 1 && empty($this->stepObjArray)) {
                break;
            }
            // 初始化此次移动的所有起步位置和各自对应的最简方案
            $startArray = $this->startBoxArray ? $this->startBoxArray : array($this->map->box);
            $stepsArray = $this->stepObjArray ? $this->stepObjArray : array(new Step);
            // 开始计算本次移动的最简方案
            $t1 = $this->runTime();
            $this->startBoxArray = array();
            $this->stepObjArray = array();
            foreach ($startArray as $key => $box) {
                $step = $stepsArray[$key];
                // 每个起步位置都要移动
                $this->move($box, $step);
            }
            
            $t2 = $this->runTime();
            echo '第 ' . $this->count . ' 次循环,得出 ' . sizeof($this->stepObjArray) . ' 种 ' . $this->count . ' 步长的最简方案,总计最简方案 ' . sizeof($this->minStepBoxArray) . ' 种, 用时: ' . ($t2 - $t1) . ' 秒,' . $this->count . ' 次循环总用时:' . ($t2 - $t0) . ' 秒<br>';
        }
    }
    
    private function runTime() {
        $mtime = microtime();
        list($ten_thousand, $sec) = explode(" ", $mtime);
        return ((float)$ten_thousand + (float)$sec);
    }
    /**
     * 移动方法,可以有 上 下 左 右 四个方向
     */
    private function move($box, $step) {
        $this->moveUp($box, $step);
        $this->moveDown($box, $step);
        $this->moveLeft($box, $step);
        $this->moveRight($box, $step);
        ob_flush();
    }
    /**
     * 向上移动一步,将活动方块的数组 竖直方向 正向 排序
     */
    private function moveUp($box, $step) {
        $this->sortBox($box, array('col' => ASC));
        $this->moveBox($box, $step, MOVE_UP);
    }
    /**
     * 向下移动一步,将活动方块的数组 竖直方向 逆向 排序
     */
    private function moveDown($box, $step) {
        $this->sortBox($box, array('col' => DESC));
        $this->moveBox($box, $step, MOVE_DOWN);
    }
    /**
     * 向左移动一步,将活动方块的数组 水平方向 正向 排序
     */
    private function moveLeft($box, $step) {
        $this->sortBox($box, array('row' => ASC));
        $this->moveBox($box, $step, MOVE_LEFT);
    }
    /**
     * 向右移动一步,将活动方块的数组 水平方向 逆向 排序
     */
    private function moveRight($box, $step) {
        $this->sortBox($box, array('row' => DESC));
        $this->moveBox($box, $step, MOVE_RIGHT);
    }
    /**
     * 将可移动的方块按 方向规则 重新排序
     * 主要是考虑到移动方块时会被前面的方块堵住的情况
     */
    private function sortBox(&$box, $sort = array()) {
        $_sort = array('col' => ASC, 'row' => ASC);
        
        $_sort = $sort ? array_merge($_sort, $sort) : $_sort;
        
        $_sort['col'] === ASC ? ksort($box) : krsort($box);
        foreach ($box as $key => & $value) {
            $_sort['row'] === ASC ? ksort($value) : krsort($value);
        }
    }
    /**
     * 按方向移动方块,并尝试添加一步,判断尝试结果
     */
    private function moveBox($box, $step, $move) {
        $_box = $box;
        // 遍历每一个方块,判断是否移动
        foreach ($box as $k1 => $v1) {
            foreach ($v1 as $k2 => $v2) {
                if (empty($v2)) {
                    continue;
                }
                switch ($move) {
                    case MOVE_UP:
                        if ($k1 > 1 && empty($this->map->base[$k1 - 1][$k2]) && empty($_box[$k1 - 1][$k2])) {
                            $_box[$k1 - 1][$k2] = $v2;
                            unset($_box[$k1][$k2]);
                        }
                    break;
                    case MOVE_DOWN:
                        if ($k1 < ROW && empty($this->map->base[$k1 + 1][$k2]) && empty($_box[$k1 + 1][$k2])) {
                            $_box[$k1 + 1][$k2] = $v2;
                            unset($_box[$k1][$k2]);
                        }
                    break;
                    case MOVE_LEFT:
                        if ($k2 > 1 && empty($this->map->base[$k1][$k2 - 1]) && empty($_box[$k1][$k2 - 1])) {
                            $_box[$k1][$k2 - 1] = $v2;
                            unset($_box[$k1][$k2]);
                        }
                    break;
                    case MOVE_RIGHT:
                        if ($k2 < COL && empty($this->map->base[$k1][$k2 + 1]) && empty($_box[$k1][$k2 + 1])) {
                            $_box[$k1][$k2 + 1] = $v2;
                            unset($_box[$k1][$k2]);
                        }
                    break;
                }
            }
        }
        // 克隆本次移动方案
        $_step = clone $step;
        // 尝试添加一步,若为最简方案则添加
        $result = $this->tryToAddStep($_step, $_box, $move);
        // 判断游戏是否结束
        if ($result === true) {
            $this->gameEnd = true;
            echo '************************************<br>';
            echo '最少步数:' . $this->count . '<br>';
            echo $_step->getSteps() . '<br>';
            echo '************************************<br>';
        } else {
            $_step = null;
            unset($_step);
        }
    }
    /**
     * 尝试添加一步,若为最简方案则添加
     */
    private function tryToAddStep($step, $box, $move) {
        
        $md5Box = $this->md5Box($box);
        // 步长大于等于已有步长的方案,不是最简方案或重复,将被忽略
        if (!empty($this->minStepBoxArray[$md5Box]) && $this->minStepBoxArray[$md5Box] <= $this->count) {
            return false;
        }
        // 组成此次步长下的最简方案
        $step->steps[] = $move;
        // 添加新的最简方案或更新已有的最简方案,键名为移动后的位置对应的md5值,键值即为当前步长
        $this->minStepBoxArray[$md5Box] = $this->count;
        // 添加本次移动的结束位置,即为下次移动时的所有起步位置
        $this->startBoxArray[$md5Box] = $box;
        // 添加本次移动的最简方案,即为下次移动前的所有已移动的最简方案
        $this->stepObjArray[$md5Box] = clone $step;
        // 判断是否已移动到结束位置
        $gameEnd = true;
        foreach ($this->map->end as $k1 => $v1) {
            foreach ($v1 as $k2 => $v2) {
                if (empty($box[$k1][$k2]) || $box[$k1][$k2] !== $v2) {
                    $gameEnd = false;
                    break;
                }
            }
        }
        return $gameEnd;
    }
    /**
     * 生成所有最简方案的md5唯一标识,作为数组的键名
     */
    private function md5Box($box) {
        // 先排序
        $this->sortBox($box);
        $str = "minStepBoxArray:";
        foreach ($box as $k1 => $v1) {
            foreach ($v1 as $k2 => $v2) {
                if (empty($v2)) {
                    continue;
                }
                $str.= $k1 . ":" . $k2 . ":\"" . $v2 . "\",";
            }
        }
        return md5($str);
    }
}
最佳答案
评论( 相关
后面还有条评论,点击查看>>