Как найти победителя игры в крестики-нолики любого размера? - PullRequest
22 голосов
/ 17 ноября 2010

Это вопрос для интервью ."Как бы вы определили, выиграл ли кто-нибудь игру в крестики-нолики на доске любого размера?"Я слышал, что сложность алгоритма была O (1).Имеет ли это смысл ?Кто-нибудь может объяснить алгоритм?

Ответы [ 6 ]

34 голосов
/ 17 ноября 2010

Ответ правильный на этой странице, но я все равно объясню.

Сложность алгоритма O (1) для определения, выиграет ли данный ход в игре. Это не может быть O (1) в общем, так как вам нужно знать состояние доски, чтобы определить победителя. Однако вы можете построить это состояние постепенно, чтобы определить, выиграл ли ход в O (1).

Для начала, иметь массив чисел для каждой строки, столбца и диагонали для каждого игрока. На каждом ходу увеличивайте элементы, соответствующие игроку, для строки, столбца и диагонали (ход не обязательно должен быть по диагонали) под влиянием этого хода. Если счет этого игрока равен размеру игрового поля, этот игрок выигрывает.

19 голосов
/ 07 сентября 2013

Самый быстрый способ определения условия выигрыша - отслеживать все строки, столбцы, диагональные и антидиагональные оценки.

Допустим, у вас есть сетка 3х3. Создайте массив партитур размером 2 * 3 + 2, который будет содержать баллы следующим образом [row1, row2, row3, col1, col2, col3, diag1, diag2] Конечно, не забудьте инициализировать его с 0.

Далее после каждого хода вы добавляете +1 для игрока 1 или вычитаете -1 для игрока 2 следующим образом.

оценка [строка] + = точка; // где точка равна +1 или -1

оценка [gridSize + col] + = точка;

if (row == col) Score [2 * gridSize] + = point;

if (gridSize - 1 - col == row) Score [2 * gridSize + 1] + = point;

Затем все, что вам нужно сделать, это перебрать массив результатов один раз и обнаружить +3 или -3 (GRID_SIZE или -GRID_SIZE).

Я знаю, что код говорит больше, чем слова, так что вот прототип на PHP. Это довольно просто, поэтому я не думаю, что у вас возникнут проблемы с переводом на другой язык.

<?php

class TicTacToe {
    const PLAYER1 = 'X';
    const PLAYER1_POINT = 1;

    const PLAYER2 = 'O';
    const PLAYER2_POINT = -1; // must be the opposite of PLAYER1_POINT

    const BLANK = '';

    /**
    * Level size.
    */
    private $gridSize;

    /** 
    * Level data.
    * Two dimensional array of size GRID_SIZE x GRID_SIZE.
    * Each player move is stored as either 'X' or 'O'
    */
    private $grid;

    /**
    * Score array that tracks score for rows, cols and diagonals.
    * e.g. for 3x3 grid [row1, row2, row3, col1, col2, col3, diag1, diag2]
    */
    private $score;

    /**
    * Avaialable moves count for current game.
    */
    private $movesLeft = 0;

    /**
    * Winner of the game. 
    */
    private $winner = null;

    public function __construct($size = 3) {
        $this->gridSize = $size;
        $this->grid = array();
        for ($i = 0; $i < $this->gridSize; ++$i) {
            $this->grid[$i] = array_fill(0, $this->gridSize, self::BLANK);
        }

        $this->score = array_fill(0, 2*$this->gridSize + 2, 0);
        $this->movesLeft = $this->gridSize * $this->gridSize;
        $this->winner = null;
    }

    public function getWinner() {
        return $this->winner;
    }

    public function displayGrid() {
        $this->display("--------\n");
        for ($row = 0; $row < $this->gridSize; ++$row) {
            for ($col = 0; $col < $this->gridSize; ++$col) {
                if (self::BLANK == $this->grid[$row][$col]) $this->display('  ');
                else $this->display($this->grid[$row][$col].' ');
            }
            $this->display("\n");
        }
        $this->display("--------\n");
    }

    public function play(MoveInput $input) {
        $this->display("NEW GAME\n");
        $nextPlayer = self::PLAYER1;
        $done = false;
        while(!$done) { 
            $move = $input->getNextMove($nextPlayer);
            if (NULL == $move) {
                $this->display("ERROR! NO MORE MOVES\n");
                break;
            }

            $error = false;
            $this->makeMove($move['player'], $move['row'], $move['col'], $error);
            if ($error) {
                $this->display("INVALID MOVE! Please try again.\n");
                continue;
            }
            $nextPlayer = ($nextPlayer == self::PLAYER1) ? self::PLAYER2 : self::PLAYER1;
            $this->displayGrid();
            $done = $this->checkScore();
        }
    }

    private function makeMove($player, $row, $col, &$error) {
        $error = false;
        if (!$this->isValidMove($row, $col) || self::BLANK != $this->grid[$row][$col]) {
            $error = true;
            return;
        }

        $this->grid[$row][$col] = $player;
        --$this->movesLeft;

        $point = 0;
        if (self::PLAYER1 == $player) $point = self::PLAYER1_POINT;
        if (self::PLAYER2 == $player) $point = self::PLAYER2_POINT;

        $this->score[$row] += $point;
        $this->score[$this->gridSize + $col] += $point;
        if ($row == $col) $this->score[2*$this->gridSize] += $point;
        if ($this->gridSize - 1 - $col == $row) $this->score[2*$this->gridSize + 1] += $point;
    }

    private function checkScore() {
        if (0 == $this->movesLeft) {
            $this->display("game is a DRAW\n");
            return true;
        }

        for ($i = 0; $i < count($this->score); ++$i) {
            if ($this->player1TargetScore() == $this->score[$i]) {
                $this->display("player 1 WIN\n");
                $this->winner = self::PLAYER1;
                return true;
            }

            if ($this->player2TargetScore() == $this->score[$i]) {
                $this->display("player 2 WIN\n");
                $this->winner = self::PLAYER2;
                return true;
            }
        }

        return false;
    }

    private function isValidMove($row, $col) {
        return (0 <= $row && $row < $this->gridSize) &&
                (0 <= $col && $col < $this->gridSize);
    }

    private function player1TargetScore() {
        return $this->gridSize * self::PLAYER1_POINT;
    }

    private function player2TargetScore() {
        return $this->gridSize * self::PLAYER2_POINT;
    }

    private function display($string) {
        echo $string;
    }
}

interface MoveInput {
    public function getNextMove($player);
}

class QueuedMoveInput implements MoveInput {
    private $moves;

    public function __construct($movesArray) {
        $this->moves = $movesArray;
    }

    public function getNextMove($player) {
        return array_shift($this->moves);
    }
}

class InteractiveMoveInput implements MoveInput {
    public function getNextMove($player) {
        while(true) {
            echo "Please enter next move for player $player: [row,col] ";
            $line = trim(fgets(STDIN));
            list($row, $col) = sscanf($line, "%D,%D");
            if (is_numeric($row) && is_numeric($col)) {
                return array('player' => $player, 'row' => $row, 'col' => $col);
            }
        }
    }
}

// helpers
function p1($row, $col) {
    return array('player' => TicTacToe::PLAYER1, 'row' => $row, 'col' => $col);
}

function p2($row, $col) {
    return array('player' => TicTacToe::PLAYER2, 'row' => $row, 'col' => $col);
}

// TESTING!!!!! ;]

// GAME 1 - testing diagonal (0,0) -> (2,2) win condition
$game = new TicTacToe();
$moves = new QueuedMoveInput(array(p1(1,1), p2(0,1), p1(2,0), p2(0,2), p1(0,0), p2(1,0), p1(2,2), p2(2,1)));
$game->play($moves);
assert($game->getWinner() == TicTacToe::PLAYER1);

// GAME 2 - using invalid move, testing straight line (0,0) -> (0,2) win condition
$game = new TicTacToe();
$moves = new QueuedMoveInput(array(p1(1,1), p2(1,1), p2(2,0), p1(2,1), p2(0,1), p1(2,2), p2(0,0), p1(1,0), p2(0,2)));
$game->play($moves);
assert($game->getWinner() == TicTacToe::PLAYER2);

// GAME 3 - testing draw condition
$game = new TicTacToe();
$moves = new QueuedMoveInput(array(p1(1,1), p2(2, 2), p1(1,2), p2(1,0), p1(2,0), p2(0,2), p1(0,1), p2(2,1), p1(0,0)));
$game->play($moves);
assert($game->getWinner() == NULL);

// GAME 4 - testing diagonal (2,0) -> (0,2) win condition
$game = new TicTacToe();
$moves = new QueuedMoveInput(array(p1(2,0), p2(1, 2), p1(0,2), p2(2,2), p1(0,1), p2(0,0), p1(1,1)));
$game->play($moves);
assert($game->getWinner() == TicTacToe::PLAYER1);

// GAME 5 - testing straight line (0,0) -> (2,0) win condition
$game = new TicTacToe();
$moves = new QueuedMoveInput(array(p2(1,1), p1(0,0), p2(0,2), p1(2,0), p2(2,1), p1(1,0)));
$game->play($moves);
assert($game->getWinner() == TicTacToe::PLAYER1);

// GAME 6 - 5x5 grid, testing diagonal (0,0) -> (4,4) win condition
$game = new TicTacToe(5);
$moves = new QueuedMoveInput(array(p1(1,1), p2(0,1), p1(2,0), p2(0,2), p1(0,0), p2(1,0), p1(2,2), p2(4,2), p1(3,3), p2(4,3), p1(4,4)));
$game->play($moves);
assert($game->getWinner() == TicTacToe::PLAYER1);

// GAME 7 - Interactive game.
$game = new TicTacToe();
$game->play(new InteractiveMoveInput());

Надеюсь, это поможет;]

5 голосов
/ 27 декабря 2015

Эта проблема и множество связанных проблем могут быть решены за O (1) время, при условии, что существуют по крайней мере 3^(n^2) областей памяти, и при условии, что таблица поиска может быть предварительно вычислена.Это решение не требует предыдущего отслеживания состояния, как описывают другие ответы, а часть времени выполнения алгоритма не требует суммирования столбцов или строк, как описывают другие ответы.

Обрабатывать состояние платы n * nкак одно целое число B. Чтобы сделать это, представьте одну ячейку c в позиции (x, y) как целое число, где c(x,y) = 0 обозначает O, c(x,y) =1 обозначает X, а c(x,y) = 2 обозначает пустую ячейку.

Далее, представляйте каждый квадрат S(x,y): 1<=x<=n, 1<=y<=n here как:

S(x,y)=c(x,y) dot 3^(n(x-1)+(y-1)

Затем представьте все состояние платы B как:

enter image description here

Предполагая, что вы представили своюТаким образом, вы можете посмотреть на позицию памяти B в предварительно вычисленной таблице, которая описывает ответ на поставленный вопрос.

Кодировка, которую я предоставил, может представлять любую n * n конфигурацию платы крестики-нолики.компактно, в том числе позиции, которые не могут быть достигнуты в обычной игре.Однако вы можете использовать любой уникальный метод кодирования платы, например, строки или массивы, если вы интерпретируете представление платы как длинное уникальное целое число, индексирующееся в таблице предварительно вычисленных решений.Я предоставил здесь одну такую ​​ совершенную хэш-функцию , но существует и много других.

Это представление на доске также позволяет использовать гандикап типа «ход», когда игроку предоставляется произвольное количество бесплатных начальных ходов.

Интересно, что если у вас достаточно памяти, вы также можете найти ответы на такие вопросы, как, например, выиграна или проиграна текущая игра с идеальной игрой, какой ход идеален с позиции, и еслиИгра - это гарантированный выигрыш или проигрыш, сколько максимальных ходов существует для выигрыша или проигрыша.Эта техника используется, что важно, в компьютерных шахматах;таблица подстановки, которую все используют, называется таблица таблиц Налимова .

Обобщение крестики-нолики на доске любого размера, где выигрывает игрок, который получает k камней подряд,называется m, n, k игра , и есть много интересных доказательств об этом типе игры.

tl: dr;если вы собираетесь набрать рекорд скорости, почти невозможно победить скромную таблицу поиска.

2 голосов
/ 30 июля 2013

Мне только что задали этот вопрос в интервью по программированию.«Учитывая, что крестики-нолики, как проверить ход, были выигрышным ходом в ПОСТОЯННОЕ время»

мне понадобилось более 20 минут, но я ДУМАЮ, что смог найти ответ и решить его вO (1)

Так скажем, давайте начнем с простой крестообразной доски 3 на 3, поместим число, соответствующее каждому блоку на доске 123 456 789

Итак, мой ответвопрос довольно простой, хешируйте все выигрышные комбинации в хэш-таблицу, такую ​​как 123, 456, 789, 159 и т. д.

Алгоритм описан ниже

    So when player_1 puts a X on a square, he will get the corresponding
    number into his number list
    When player_2 puts a O on a square, he will also get the corresponding
    number into his number list.
    At the end of every round, look through the Hash table to see if any 
    of the two players have the winning combination

Так что я думаю, что это O (1)

1 голос
/ 10 апреля 2017

Я написал сообщение в блоге на этот вопрос .

Суть в том, что вы отслеживаете, сколько X было размещено в каждой строке / столбцах + 2 диагонали в игреprogress.

Затем каждый раз, когда игрок заканчивает свой ход, вы проверяете, содержат ли строка и столбец последней размещенной координаты N число X. Если это так, то игрок выиграл.

0 голосов
/ 02 января 2013
    class TicTacToe
{

    //http://basicalgos.blogspot.com/#!/2012/03/13-test-winning-condition-of-tic-tac.html
    //No time for test case

    int[,] board = null;

    int diag = 0;
    int antiDiag = 0;
    int[] rows = null;
    int[] cols = null;


    int diagonalLen = 0;

    public TicTacToe(int rowLen, int colLen)
    {
        board = new int[rowLen, colLen];

        rows = new int[rowLen];
        cols = new int[colLen];

        if (rowLen == colLen)
            diag = (int)(rowLen * Math.Sqrt(2));//Square formula
        else
            diagonalLen = (int)Math.Sqrt(Math.Pow(rowLen, 2) + Math.Pow(colLen, 2));//rectangle formula

    }

    public bool hasWon(int rowIdx, int colIdx, int player)
    {
        if (player == 1)
        {
            rows[rowIdx]++;
            cols[colIdx]++;

            if (rowIdx == colIdx) diag++;
            if (rowIdx + colIdx == rows.Length - 1) antiDiag++;//This is IMPORTANT finding.............

        }
        else
        {
            rows[rowIdx]--;
            cols[colIdx]--;

            if (rowIdx == colIdx) diag--;
            if (rowIdx + colIdx == rows.Length - 1) antiDiag--;
        }

        return diag == diagonalLen || rows[rowIdx] == rows.Length || cols[colIdx] == cols.Length || antiDiag == diagonalLen;
    }

    public void markOnBoard(int row, int col, int player)
    {
        if (player == 1)
            board[row, col]++;
        else
            board[row, col]--;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...