首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 迷宮生成與 A* 尋路算法實戰:深度優先生成完美迷宮 + 啟發式最短路徑搜索

     編輯:PHP 相關知識

專案概覽

本文實現兩大經典算法的 PHP 版本:

  1. 迷宮生成:基於「深度優先搜索 (DFS) + 隨機化」生成完美迷宮(任意兩點間有且僅有一條路徑)。
  2. A* 尋路:在生成的迷宮中尋找起點到終點的最短路徑。

代碼採用面向對象封裝,邏輯清晰,可直接集成至遊戲或教學演示項目。

核心數據結構

迷宮以二維網格扁平化為一維數組 $grids,每個單元格用 4 位掩碼表示四面牆壁通道:

  • Bit 0 (1) : 上
  • Bit 1 (2) : 右
  • Bit 2 (4) : 下
  • Bit 3 (8) : 左

值為 0 表示未訪問,非 0 表示已開通相應方向。

Maze 類:DFS 迷宮生成

<?php
class Maze {
    private $w, $h, $grids = [];
    private $walkHistory = [], $walkHistory2 = [], $targetSteps = [];

    public function set($width = 6, $height = 6) {
        $this->w = max(1, $width);
        $this->h = max(1, $height);
        return $this;
    }

    public function create() {
        $this->_init();
        $this->_walk(rand(0, count($this->grids) - 1));
        return $this;
    }

    public function get() { return $this->grids; }

    // 獲取死胡同(僅一面通道)用作起終點候選
    public function block($n = 0, $rand = false) {
        $deadEnds = [];
        foreach ($this->grids as $i => $v) {
            if (in_array($v, [1,2,4,8])) $deadEnds[] = $i;
        }
        if ($rand) shuffle($deadEnds);
        if ($n == 0) return $deadEnds;
        return $n == 1 ? array_pop($deadEnds) : array_slice($deadEnds, 0, $n);
    }

    private function _init() {
        $this->grids = array_fill(0, $this->w * $this->h, 0);
    }

    private function _walk($startPos) {
        $this->walkHistory = []; $this->walkHistory2 = [];
        $cur = $startPos;
        while ($this->_getNext0() != -1) {
            $cur = $this->_step($cur);
            if ($cur === false) break;
        }
    }

    // 獲取四個方向可走的下一步
    private function _getTargetSteps($cur) {
        $steps = [-1, -1, -1, -1]; // 上右下左
        $w = $this->w;
        // 上
        $p = $cur - $w;
        if ($p >= 0 && $this->grids[$p] === 0 && !$this->_isRepeating($p)) $steps[0] = $p;
        // 右
        $p = $cur + 1;
        if ($p % $w != 0 && $this->grids[$p] === 0 && !$this->_isRepeating($p)) $steps[1] = $p;
        // 下
        $p = $cur + $w;
        if ($p < count($this->grids) && $this->grids[$p] === 0 && !$this->_isRepeating($p)) $steps[2] = $p;
        // 左
        $p = $cur - 1;
        if ($cur % $w != 0 && $this->grids[$p] === 0 && !$this->_isRepeating($p)) $steps[3] = $p;
        return $steps;
    }

    private function _noStep() {
        foreach ($this->targetSteps as $v) if ($v != -1) return false;
        return true;
    }

    private function _step($cur) {
        $this->targetSteps = $this->_getTargetSteps($cur);
        if ($this->_noStep()) {
            if (empty($this->walkHistory)) return false;
            $back = array_pop($this->walkHistory);
            $this->walkHistory2[] = $back;
            return $this->_step($back); // 回溯
        }
        // 隨機選擇一個可用方向
        $r = rand(0, 3);
        while ($this->targetSteps[$r] == -1) $r = rand(0, 3);
        $next = $this->targetSteps[$r];

        // 打通雙向通道
        $walls = [
            [1, 4], // 上: 當前開上(1), 下一開下(4)
            [2, 8], // 右: 當前開右(2), 下一開左(8)
            [4, 1], // 下
            [8, 2], // 左
        ];
        list($curWall, $nextWall) = $walls[$r];
        $this->grids[$cur] ^= $curWall;
        $this->grids[$next] ^= $nextWall;

        $this->walkHistory[] = $cur;
        return $next;
    }

    private function _isRepeating($p) {
        return in_array($p, $this->walkHistory) || in_array($p, $this->walkHistory2);
    }

    private function _getNext0() {
        foreach ($this->grids as $i => $v) if ($v === 0) return $i;
        return -1;
    }
}
?>

AStar 類:A* 尋路算法

<?php
class AStar {
    private $w, $h, $grids;
    private $open = [], $closed = [];
    private $start, $end;

    public function set($width, $height, $grids) {
        $this->w = $width; $this->h = $height; $this->grids = $grids;
        return $this;
    }

    public function search($start = false, $end = false) {
        if ($start !== false) $this->start = $start;
        if ($end !== false) $this->end = $end;

        $this->open = []; $this->closed = [];
        $this->open[] = ['i'=>$this->start, 'g'=>0, 'h'=>$this->_h($this->start), 'f'=>$this->_h($this->start), 'p'=>null];
        $this->closed[$this->start] = end($this->open);

        while (!empty($this->open)) {
            // 找 open 中 f 最小的節點
            $minIdx = 0; $minF = PHP_INT_MAX;
            foreach ($this->open as $k => $node) {
                if ($node['f'] < $minF) { $minF = $node['f']; $minIdx = $k; }
            }
            $cur = $this->open[$minIdx];
            unset($this->open[$minIdx]);
            $this->open = array_values($this->open); // 重建索引

            if ($cur['i'] == $this->end) {
                // 重構路徑
                $path = [];
                while ($cur['p'] !== null) {
                    array_unshift($path, $cur['p']);
                    $cur = $this->closed[$cur['p']];
                }
                $path[] = $this->end;
                $this->open = $this->closed = [];
                return $path;
            }

            $this->_expand($cur['i']);
        }
        return []; // 無路徑
    }

    private function _expand($idx) {
        $val = $this->grids[$idx];
        $dirs = [
            1 => -$this->w, // 上
            2 => 1,         // 右
            4 => $this->w,  // 下
            8 => -1,        // 左
        ];
        foreach ($dirs as $bit => $delta) {
            if ($val & $bit) {
                $next = $idx + $delta;
                $this->_checkPoint($idx, $next);
            }
        }
    }

    private function _checkPoint($from, $next) {
        $g = $this->closed[$from]['g'] + 1; // 均勻代價 1
        if (isset($this->closed[$next])) {
            if ($g < $this->closed[$next]['g']) {
                $this->closed[$next]['g'] = $g;
                $this->closed[$next]['f'] = $g + $this->closed[$next]['h'];
                $this->closed[$next]['p'] = $from;
            }
        } else {
            $node = [
                'i' => $next,
                'g' => $g,
                'h' => $this->_h($next),
                'f' => $g + $this->_h($next),
                'p' => $from
            ];
            $this->open[] = $node;
            $this->closed[$next] = $node;
        }
    }

    // 啟發函數:曼哈頓距離(網格無對角線)
    private function _h($pos) {
        $x1 = $pos % $this->w; $y1 = intdiv($pos, $this->w);
        $x2 = $this->end % $this->w; $y2 = intdiv($this->end, $this->w);
        return abs($x1 - $x2) + abs($y1 - $y2);
    }
}
?>

使用示例

<?php
// 1. 生成 20x20 迷宮
$maze = new Maze();
$maze->set(20, 20)->create();
$grids = $maze->get();

// 2. 選取兩個死胡同作為起點終點
$blocks = $maze->block(2, true); // 隨機取 2 個
$start = $blocks[0]; $end = $blocks[1];

// 3. A* 尋路
$astar = new AStar();
$astar->set(20, 20, $grids);
$path = $astar->search($start, $end);

echo "起點: $start, 終點: $end\n";
echo "路徑長度: " . count($path) . " 步\n";
echo "路徑節點: " . implode(' -> ', $path) . "\n";

// 4. 可視化輸出(簡易文本)
$display = $grids;
foreach ($path as $p) $display[$p] = '*'; // 標記路徑
$display[$start] = 'S'; $display[$end] = 'E';
for ($y=0; $y<20; $y++) {
    for ($x=0; $x<20; $x++) echo str_pad($display[$y*20+$x] ?? ' ', 3);
    echo "\n";
}
?>

算法複雜度與優化

算法時間複雜度空間複雜度PHP 優化建議
DFS 迷宮生成O(W×H)O(W×H)使用位運算、避免函數調用開銷、大迷宮考慮 C 擴展
A* 尋路O((W×H) log(W×H))O(W×H)Open 列表用最小堆,Closed 用數組/哈希,啟發函數內聯

總結

PHP 雖非高性能計算首選,但憑藉靈活的數組與面向對象特性,足以實現教學級、輕量級遊戲邏輯。生產環境高併發遊戲服務建議使用 C++/Go/Rust 實現核心算法,PHP 做業務協調層。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved