TicTacToe 对角线检查功能

TicTacToe diagonal check function

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 那样检查对角线,它会正常工作。

我想不出检查对角线的算法,所以我从这里获取:

注释函数 "checkLanes" 工作正常,因此它在代码中被隐藏了。

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

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,它可以通过一些操作检查整个电路板,但需要对您的方法进行相当多的调整。一个更简单的重构是跟踪玩家的最后一步并只检查该方块可能获胜。