TicTacToe对角线检查功能

时间:2018-08-22 22:35:31

标签: php tic-tac-toe

class Board
{
    const MARK_0 = 0;
    const MARK_X = 1;

    /** @var int */
    private $sizeX;

    /** @var int */
    private $sizeY;

    /** @var int */
    private $requiredMarks;

    /** @var array */
    private $map = [];

    /**
     * @param int $sizeX
     * @param int $sizeY
     */
    public function __construct (int $sizeX = 3, int $sizeY = 3)
    {
        $this->sizeX = $sizeX;
        $this->sizeY = $sizeY;

        $this->requiredMarks = $sizeX;
    }

    /**
     * @return int
     */
    public function getSizeX() : int
    {
        return $this->sizeX;
    }

    /**
     * @return int
     */
    public function getSizeY() : int
    {
        return $this->sizeY;
    }

    /**
     * @return int
     */
    public function getRequiredMarks() : int
    {
        return $this->requiredMarks;
    }

    /**
     * @param int $count
     */
    public function setRequiredMarks (int $count) : void
    {
        $this->requiredMarks = $count;
    }

    /**
     * @param int $x
     * @param int $y
     * @param int $mark
     */
    public function setMark (int $x, int $y, int $mark) : void
    {
        $this->map[$x][$y] = $mark;
    }

    /**
     * @param int $x
     * @param int $y
     *
     * @return int|null
     */
    public function getMark (int $x, int $y) : ?int
    {
        return $this->map[$x][$y] ?? null;
    }

    /**
     * @return int|null
     */
    public function checkWin() : ?int
    {
        foreach([self::MARK_0, self::MARK_X] as $mark)
        {
            if(/* $this->checkLanes($mark) ||  */ $this->checkDiagonals($mark))
            {
                return $mark;
            }
        }

        return null;
    }

    /**
     * @param int $mark
     *
     * @return bool
     */
    private function checkDiagonals (int $mark) : bool
    {
        $sizeX = $this->getSizeX();
        $sizeY = $this->getSizeY();

        $required = $this->getRequiredMarks();

        $size = max($sizeX, $sizeY);

        for($k = $required - $size; $k <= ($size - $required); $k++)
        {
            $score1 = 0;
            $score2 = 0;

            $startI = max(0, $k);
            $endI = min($size, $size + $k);

            for($i = $startI; $i < $endI; $i++)
            {
                if($this->getMark($i, $k + $i) === $mark)
                {
                    if(++$score1 >= $required)
                    {
                        return true;
                    }
                }
                else
                {
                    $score1 = 0;
                }

                if($this->getMark($i, $size - 1 + $k - $i) === $mark)
                {
                    if(++$score2 >= $required)
                    {
                        return true;
                    }
                }
                else
                {
                    $score2 = 0;
                }
            }
        }

        return false;
    }
}

$b = new Board (4, 4);
$b->setRequiredMarks(3);

$b->setMark(0, 1, Board::MARK_X);
$b->setMark(1, 2, Board::MARK_X);
$b->setMark(2, 3, Board::MARK_X);

$winner = $b->checkWin();

if($winner === null)
{
    $winner = "nobody";
}
elseif($winner === Board::MARK_X)
{
    $winner = "X";
}
else
{
    $winner = "0";
}

var_dump($winner);

如何修复函数“ checkDiagonals”,以便正确处理photo中的对角线并返回正确的结果?

如果像photo中那样检查对角线,则它可以正常工作。

我想不出一种用于检查对角线的算法,因此我从这里取了它:https://stackoverflow.com/a/34257658/10261980

已注释的函数“ checkLanes”正常工作,因此已从代码中隐藏。

1 个答案:

答案 0 :(得分:0)

以下是您的算法当前正在检查的对角线的坐标:

x: 0, y: -1
x: 1, y: 0
x: 2, y: 1

x: 0, y: 0
x: 1, y: 1
x: 2, y: 2
x: 3, y: 3

x: 1, y: 2
x: 2, y: 3
x: 3, y: 4

您会看到一个索引超出范围,一次迭代正在检查4个正方形,这超出了要求,并且没有足够的检查来覆盖两个对角线。

您似乎正在尝试从每个索引开始,这些索引可以“拟合”所需大小的对角线,然后向下移动到右侧,以检查对角线是否不匹配。让我们手动枚举支票:

1...  .1..  ....  ....  ...1  ..1.  ....  ....
.2..  ..2.  1...  .1..  ..2.  .2..  ...1  ..1.
..3.  ...3  .2..  ..2.  .3..  3...  ..2.  .2..
....  ....  ..3.  ...3  ....  ....  .3..  3...

这是三个嵌套循环:行,列和对角线:

  • 行循环从0开始,一直运行到$sizeY - $requiredMarks
  • 第一列循环从0开始,运行到$sizeX - $requiredMarks,检查从左到右的对角线。
  • 第二列循环从$sizeX - $requiredMarks + 1$sizeX运行,检查从右到左对角线。
  • 最里面的对角线循环从0开始到$requiredMarks

索引到单元格如下:行:($row + $diag),列:($col + $diag * $xDirection)$xDirection乘数(1-1)使对角线检查功能可以向任一方向(向左或向右)移动。

这是执行此操作的代码:

private function checkDiagonals(int $mark) : bool {
    $required = $this->getRequiredMarks();

    for ($row = 0; $row <= $this->getSizeY() - $required; $row++) {
        for ($col = 0; $col <= $this->getSizeX() - $required; $col++) {
            if ($this->checkDiagonal($mark, $row, $col, 1)) {
                return true;
            }
        }

        for ($col = $this->getSizeX() - $required + 1; $col < $this->getSizeX(); $col++) {
            if ($this->checkDiagonal($mark, $row, $col, -1)) {
                return true;
            }
        }
    }

    return false;
}

private function checkDiagonal(int $mark, int $row, int $col, int $xDir) : bool {
    for ($i = 0; $i < $this->getRequiredMarks(); $i++) {
        if ($this->getMark($col + $i * $xDir, $row++) !== $mark) {
            return false;
        }
    }

    return true;
}

这些是它检查的对角线:

x: 0, y: 0
x: 1, y: 1
x: 2, y: 2

x: 1, y: 0
x: 2, y: 1
x: 3, y: 2

x: 2, y: 0
x: 1, y: 1
x: 0, y: 2

x: 3, y: 0
x: 2, y: 1
x: 1, y: 2

x: 0, y: 1
x: 1, y: 2
x: 2, y: 3

x: 1, y: 1
x: 2, y: 2
x: 3, y: 3

x: 2, y: 1
x: 1, y: 2
x: 0, y: 3

x: 3, y: 1
x: 2, y: 2
x: 1, y: 3

这并不是非常有效,因为它涉及多次访问广场,但至少应该可以使您操作。如果速度很重要,请考虑使用bitboards,它可以通过一些操作检查整个电路板,但需要相当多地调整您的方法。一种更简单的重构方法是跟踪玩家的最后举动,并仅检查从该方块可能获得的胜利。

这里是repl进行测试。