PHPで幅優先探索法を用いて7パズル問題を解く

#PHPで幅優先探索法を用いて7パズル問題を解く

##7パズルの問題
12345670のブロックを一度混ぜて、12345670に並べる最短の経路を出せ

![7パズル.png](https://qiita-image-store.s3.amazonaws.com/0/25382/dfc2a3a7-9689-cbdf-8884-e2707baf81c4.png)

##コード
```php:Pattern.php
class Pattern {
    public $blockLine;
    public $patternFrom;
    
    public function __construct($blockLine, $patternFrom) {
        $this->blockLine   = $blockLine;
        $this->patternFrom = $patternFrom;
    }
}

```

```php:SevenPuzzle.php
/*
7パズルを解くクラス
 */
require 'Pattern.php';

class SevenPuzzle {
    public $history;
    public $queueBottom;
    const ANSWER = '1,2,3,4,5,6,7,0';

    public function __construct() {
        $this->history     = array();
        $this->queueBottom = 0;
    }

    public function createBlockLine($patternArray) {
        return implode(',', $patternArray);
    }
    
    public function revertPattern($blockLine) {
        return explode(',', $blockLine);
    }
    
    public function saveHistory($patternArray, $patternFrom) {
        $blockLine = self::createBlockLine($patternArray);
        //既に登録されていたら何もしない
        for ($i=0; $i < count($this->history); $i++) {
            if ($this->history[$i]->blockLine == $blockLine) {
                return false;
            }
        }
        $this->history[] = new Pattern($blockLine, $patternFrom);
    }
    
    public function solveSevenPuzzle() {
        $patternArray = array();
        $blankPos     = 0;

        //パターンが空になるまで探索を行う。
        while ($this->queueBottom != count($this->history)) {
            $blockLine = $this->history[$this->queueBottom]->blockLine;
            if ($blockLine == self::ANSWER) {
                //history配列のキーを返す。
                return $this->queueBottom;
            }
            $patternArray    = self::revertPattern($blockLine);
            //移動する前のパターンを控える
            $patternArrayOrg = self::revertPattern($blockLine);
            
            //ボックスに0(空白)がある位置を特定する
            for ($blankPos =0; $blankPos < 8; $blankPos++) {
                if ($patternArray[$blankPos] == 0) {
                    break;
                }
            }
            
            if ($blankPos > 3) {
                //下に空があって上のブロックを下にずらず
                $patternArray[$blankPos]   = $patternArray[$blankPos-4];
                $patternArray[$blankPos-4] = 0;
                self::saveHistory($patternArray, $this->queueBottom);
                //配列を移動する前に戻す
                $patternArray = $patternArrayOrg;
            }
            
            if ($blankPos < 4) {
                //上に空があって下のブロックを上にずらず
                $patternArray[$blankPos]     = $patternArray[$blankPos + 4];
                $patternArray[$blankPos + 4] = 0;
                self::saveHistory($patternArray, $this->queueBottom);
                //配列を移動する前に戻す
                $patternArray = $patternArrayOrg;
            }

            if ($blankPos != 0 && $blankPos != 4) {
                //右にずらず。右から持っていくの右端が空の時は何もしない
                $patternArray[$blankPos]     = $patternArray[$blankPos - 1];
                $patternArray[$blankPos - 1] = 0;
                self::saveHistory($patternArray, $this->queueBottom);
                //配列を移動する前に戻す
                $patternArray = $patternArrayOrg;
            }
            
            if ($blankPos != 3 && $blankPos != 7) {
                //左にずらず。左から持っていくの左端が空の時は何もしない
                $patternArray[$blankPos]     = $patternArray[$blankPos + 1];
                $patternArray[$blankPos + 1] = 0;
                self::saveHistory($patternArray, $this->queueBottom);
                //配列を移動する前に戻す
                $patternArray = $patternArrayOrg;
            }
            $this->queueBottom++;
        }
        //見つからなかった
        return false;
    }
    
    public function showAnswer($pattern, $endIndex = null) {
        if ($endIndex != null) {
            $patternNo = $endIndex;
            self::showBlock($this->history[$patternNo]->blockLine);
            echo '<br><br>';
        }
        $patternNo = $pattern->patternFrom;
        self::showBlock($this->history[$patternNo]->blockLine);
        echo '<br><br>';
        if ($patternNo == 0) {
            //最初にたどり着いたから終了
            return true;
        }
        $pattern = $this->history[$patternNo];
        self::showAnswer($pattern);
    }
    
    public function showBlock($blockLine) {
        echo (substr($blockLine, 0, 7));
        echo '<br>';
        echo (substr($blockLine, 8, 7));
    }
}

```

```php:sevenPuzzleTest.php
require_once './part/SevenPuzzle.php';

$savenPuzzle = new SevenPuzzle();
$firstValue = array(1,2,3,4,5,6,0,7);//1回目
$firstValue = array(1,2,0,4,5,6,3,7);//2回目
$firstValue = array(1,0,2,4,5,6,3,7);//3回目
$firstValue = array(0,1,2,4,5,6,3,7);//4回目
$firstValue = array(5,1,2,4,0,6,3,7);//5回目
$firstValue = array(5,1,2,4,6,0,3,7);//6回目
$firstValue = array(5,1,2,4,6,3,7,0);//7回目
$firstValue = array(5,1,2,0,6,3,7,4);//8回目

$savenPuzzle->saveHistory($firstValue,-1);

$endIndex = $savenPuzzle->solveSevenPuzzle();
$savenPuzzle->showAnswer($savenPuzzle->history[$endIndex], $endIndex);

```

##参考書籍
プログラミングの宝箱 アルゴリズムとデータ構造 第2版