Algoritmo para determinar o jogo do jogo da velha


97

Escrevi um jogo de jogo da velha em Java e meu método atual de determinar o final do jogo leva em consideração os seguintes cenários possíveis para o fim do jogo:

  1. O tabuleiro está cheio e nenhum vencedor ainda foi declarado: o jogo acabou.
  2. Cross venceu.
  3. O círculo venceu.

Infelizmente, para fazer isso, ele lê um conjunto predefinido desses cenários de uma tabela. Isso não é necessariamente ruim, considerando que há apenas 9 espaços em um tabuleiro e, portanto, a mesa é um pouco pequena, mas existe uma maneira algorítmica melhor de determinar se o jogo acabou? Determinar se alguém ganhou ou não é a essência do problema, pois verificar se 9 vagas estão cheias é trivial.

O método da mesa pode ser a solução, mas se não, qual é? Além disso, e se o tabuleiro não fosse do tamanho n=9? E se fosse uma placa muito maior, digamos n=16, n=25e assim por diante, fazendo com que o número de itens consecutivamente colocados para ganhar a ser x=4, x=5, etc? Um algoritmo geral para ser usado por todos n = { 9, 16, 25, 36 ... }?


Estou adicionando meus 2 centavos para todas as respostas: Você sempre sabe que precisa de pelo menos um número de Xs ou Os no tabuleiro para ganhar (no tabuleiro 3x3 normal é 3). Assim, você pode acompanhar as contagens de cada um e só começar a verificar as vitórias se forem maiores.
Yuval A.

Respostas:


133

Você sabe que uma jogada vencedora só pode acontecer depois que X ou O fizerem sua jogada mais recente, então você só pode pesquisar linha / coluna com diagramas opcionais contidos nessa jogada para limitar seu espaço de pesquisa ao tentar determinar uma placa vencedora. Além disso, como há um número fixo de movimentos em um jogo de jogo da velha empatado, uma vez que o último lance é feito, se não for um lance vencedor, é por padrão um jogo empatado.

editar: este código é para um tabuleiro n por n com n em uma linha para vencer (tabuleiro 3x3 requer 3 em uma linha, etc)

editar: código adicionado para verificar o anti diag, não consegui descobrir uma maneira sem loop de determinar se o ponto estava no anti diag, então é por isso que essa etapa está faltando

public class TripleT {

    enum State{Blank, X, O};

    int n = 3;
    State[][] board = new State[n][n];
    int moveCount;

    void Move(int x, int y, State s){
        if(board[x][y] == State.Blank){
            board[x][y] = s;
        }
        moveCount++;

        //check end conditions

        //check col
        for(int i = 0; i < n; i++){
            if(board[x][i] != s)
                break;
            if(i == n-1){
                //report win for s
            }
        }

        //check row
        for(int i = 0; i < n; i++){
            if(board[i][y] != s)
                break;
            if(i == n-1){
                //report win for s
            }
        }

        //check diag
        if(x == y){
            //we're on a diagonal
            for(int i = 0; i < n; i++){
                if(board[i][i] != s)
                    break;
                if(i == n-1){
                    //report win for s
                }
            }
        }

        //check anti diag (thanks rampion)
        if(x + y == n - 1){
            for(int i = 0; i < n; i++){
                if(board[i][(n-1)-i] != s)
                    break;
                if(i == n-1){
                    //report win for s
                }
            }
        }

        //check draw
        if(moveCount == (Math.pow(n, 2) - 1)){
            //report draw
        }
    }
}

6
Você se esqueceu de verificar o anti-diagonal.
rampion

1
Para um tabuleiro 3x3, x + y será sempre igual a 2 na anti-diagonal, será sempre par no centro e nos cantos do tabuleiro e ímpar em outro lugar.
Chris Doggett

5
Não entendo o draw check no final, não deveria subtrair 1?
Inez

4
Existem casos em que um jogador ganha na última jogada possível (9º). Nesse caso, tanto o vencedor quanto o empate serão relatados ...
Marc

5
@ Roamer-1888 não se trata de quantas linhas sua solução consiste, mas sim de reduzir a complexidade do tempo do algoritmo para verificar se há um vencedor.
Shady

38

você pode usar um quadrado mágico http://mathworld.wolfram.com/MagicSquare.html se qualquer linha, coluna ou diagrama somar 15, então um jogador ganhou.


3
Como isso se traduz no jogo da velha guarda?
Paul Alexander

É uma informação interessante que eu não sabia, então, definitivamente, agradeço a informação. Como Paul mencionou, não está imediatamente claro como isso ajudaria a resolver o problema em questão, mas parece que pode contribuir para uma solução mais completa.
dreadwail

4
sobreponha-o. 1 para branco, 2 para preto e multiplique. se alguma coisa der 15, as brancas ganharam e se der 30 então as pretas ganharam.
adk

1
Grande em O, é muito barato, especialmente se você misturá-lo com a verificação de Hardwareguy em termos de célula. Cada célula só pode ter 4 jogos da velha possíveis: linha, coluna e duas diagonais (barra e barra invertida). Então, uma vez que um movimento foi feito, você só precisa fazer no máximo 4 adições e comparações. A resposta de Hardwareguy requer 4 (n-1) verificações para cada movimento, por comparação.
rampion

29
Não poderíamos simplesmente fazer isso com 1 e -1 e somar cada linha / coluna / diag para ver se é n ou -n?
Nathan

24

Que tal este pseudocódigo:

Depois que um jogador coloca uma peça na posição (x, y):

col=row=diag=rdiag=0
winner=false
for i=1 to n
  if cell[x,i]=player then col++
  if cell[i,y]=player then row++
  if cell[i,i]=player then diag++
  if cell[i,n-i+1]=player then rdiag++
if row=n or col=n or diag=n or rdiag=n then winner=true

Eu usaria um array de char [n, n], com O, X e espaço para vazio.

  1. simples.
  2. Um loop.
  3. Cinco variáveis ​​simples: 4 inteiros e uma booleana.
  4. Escala para qualquer tamanho de n.
  5. Verifica apenas a peça atual.
  6. Sem mágica. :)

se célula [i, n- (i + 1)] = jogador então rdiag ++; - Parece que com parênteses ficará correto. Estou certo?
Pumych

@Pumych, no. Se i==1e n==3, rdiagdevem ser verificados em (1, 3)e (1, 3-1+1)é igual às coordenadas corretas, mas (1, 3-(1+1))não.
KgOfHedgehogs

Ele pode ter pensado que as células eram indexadas a zero.
Matias Grioni

foram apenas algumas coisas que
surgiram

21

Isso é semelhante à resposta de Osama ALASSIRY , mas troca espaço constante e tempo linear por espaço linear e tempo constante. Ou seja, não há loop após a inicialização.

Inicialize um par (0,0)para cada linha, cada coluna e as duas diagonais (diagonal e anti-diagonal). Esses pares representam o acumulado (sum,sum)das peças na linha, coluna ou diagonal correspondente, onde

Uma peça do jogador A tem valor (1,0)
Uma peça do jogador B tem valor (0,1)

Quando um jogador colocar uma peça, atualize o par de linhas, o par de colunas e os pares diagonais correspondentes (se estiverem nas diagonais). Se qualquer linha, coluna ou par diagonal recém-atualizado for igual a (n,0)ou (0,n), A ou B venceram, respectivamente.

Análise assintótica:

O (1) tempo (por movimento)
O (n) espaço (geral)

Para o uso de memória, você usa 4*(n+1)números inteiros.

two_elements * n_rows + two_elements * n_columns +
dois_elementos * dois_diagonais = 4 * n + 4 inteiros = 4 (n + 1) inteiros

Exercício: Você consegue ver como testar um empate em O (1) vez por movimento? Nesse caso, você pode encerrar o jogo no início do empate.


1
Eu acho que isso é melhor que o de Osama ALASSIRY, já que está quase na O(sqrt(n))hora, mas tem que ser feito após cada jogada, onde n é o tamanho do tabuleiro. Então você acaba com O(n^1.5). Para esta solução, você obtém O(n)tempo total.
Matias Grioni

boa maneira de ver isso, faz sentido olhar para as "soluções" reais ... para 3x3, você teria apenas 8 pares de "booleanos" ... Pode ser ainda mais eficiente em termos de espaço se fosse de 2 bits cada ... 16 bits necessários e você pode apenas bit a bit OR 1 no jogador correto deslocado para a esquerda para o lugar correto :)
Osama Al-Maadeed

13

Aqui está minha solução que escrevi para um projeto no qual estou trabalhando em javascript. Se você não se importa com o custo de memória de alguns arrays, é provavelmente a solução mais rápida e simples que você encontrará. Pressupõe que você conhece a posição do último movimento.

/*
 * Determines if the last move resulted in a win for either player
 * board: is an array representing the board
 * lastMove: is the boardIndex of the last (most recent) move
 *  these are the boardIndexes:
 *
 *   0 | 1 | 2
 *  ---+---+---
 *   3 | 4 | 5
 *  ---+---+---
 *   6 | 7 | 8
 * 
 * returns true if there was a win
 */
var winLines = [
    [[1, 2], [4, 8], [3, 6]],
    [[0, 2], [4, 7]],
    [[0, 1], [4, 6], [5, 8]],
    [[4, 5], [0, 6]],
    [[3, 5], [0, 8], [2, 6], [1, 7]],
    [[3, 4], [2, 8]],
    [[7, 8], [2, 4], [0, 3]],
    [[6, 8], [1, 4]],
    [[6, 7], [0, 4], [2, 5]]
];
function isWinningMove(board, lastMove) {
    var player = board[lastMove];
    for (var i = 0; i < winLines[lastMove].length; i++) {
        var line = winLines[lastMove][i];
        if(player === board[line[0]] && player === board[line[1]]) {
            return true;
        }
    }
    return false;
}

2
Esta seria a abordagem do grande martelo, mas é de fato uma solução viável, especialmente para o site como uma de uma infinidade de soluções criativas e funcionais para este problema. Também é curto, elegante e muito legível - para uma grade 3x3 (Tx3 tradicional), gosto deste algoritmo.
nocarrier

Este é incrível !! Descobri que há um pequeno bug nos padrões de vitória, na posição 8, os padrões devem ser [6,7], [0,4] e [2,5]: var winLines = [[[1, 2] , [4, 8], [3, 6]], [[0, 2], [4, 7]], [[0, 1], [4, 6], [5, 8]], [[ 4, 5], [0, 6]], [[3, 5], [0, 8], [2, 6], [1, 7]], [[3, 4], [2, 8] ], [[7, 8], [2, 4], [0, 3]], [[6, 8], [1, 4]], [[6, 7], [ 0 , 4], [ 2, 5]]];
David Ruiz de

7

Acabei de escrever isso para minha aula de programação C.

Estou postando porque nenhum dos outros exemplos aqui funcionará com qualquer tamanho de grade retangular e qualquer número N -em-uma-linha de marcas consecutivas para vencer.

Você encontrará meu algoritmo, tal como é, na checkWinner()função. Ele não usa números mágicos nem nada sofisticado para verificar se há um vencedor, ele simplesmente usa quatro loops for - o código é bem comentado, então vou deixá-lo falar por si, acho.

// This program will work with any whole number sized rectangular gameBoard.
// It checks for N marks in straight lines (rows, columns, and diagonals).
// It is prettiest when ROWS and COLS are single digit numbers.
// Try altering the constants for ROWS, COLS, and N for great fun!    

// PPDs come first

    #include <stdio.h>
    #define ROWS 9              // The number of rows our gameBoard array will have
    #define COLS 9              // The number of columns of the same - Single digit numbers will be prettier!
    #define N 3                 // This is the number of contiguous marks a player must have to win
    #define INITCHAR ' '        // This changes the character displayed (a ' ' here probably looks the best)
    #define PLAYER1CHAR 'X'     // Some marks are more aesthetically pleasing than others
    #define PLAYER2CHAR 'O'     // Change these lines if you care to experiment with them


// Function prototypes are next

    int playGame    (char gameBoard[ROWS][COLS]);               // This function allows the game to be replayed easily, as desired
    void initBoard  (char gameBoard[ROWS][COLS]);               // Fills the ROWSxCOLS character array with the INITCHAR character
    void printBoard (char gameBoard[ROWS][COLS]);               // Prints out the current board, now with pretty formatting and #s!
    void makeMove   (char gameBoard[ROWS][COLS], int player);   // Prompts for (and validates!) a move and stores it into the array
    int checkWinner (char gameBoard[ROWS][COLS], int player);   // Checks the current state of the board to see if anyone has won

// The starting line
int main (void)
{
    // Inits
    char gameBoard[ROWS][COLS];     // Our gameBoard is declared as a character array, ROWS x COLS in size
    int winner = 0;
    char replay;

    //Code
    do                              // This loop plays through the game until the user elects not to
    {
        winner = playGame(gameBoard);
        printf("\nWould you like to play again? Y for yes, anything else exits: ");

        scanf("%c",&replay);        // I have to use both a scanf() and a getchar() in
        replay = getchar();         // order to clear the input buffer of a newline char
                                    // (http://cboard.cprogramming.com/c-programming/121190-problem-do-while-loop-char.html)

    } while ( replay == 'y' || replay == 'Y' );

    // Housekeeping
    printf("\n");
    return winner;
}


int playGame(char gameBoard[ROWS][COLS])
{
    int turn = 0, player = 0, winner = 0, i = 0;

    initBoard(gameBoard);

    do
    {
        turn++;                                 // Every time this loop executes, a unique turn is about to be made
        player = (turn+1)%2+1;                  // This mod function alternates the player variable between 1 & 2 each turn
        makeMove(gameBoard,player);
        printBoard(gameBoard);
        winner = checkWinner(gameBoard,player);

        if (winner != 0)
        {
            printBoard(gameBoard);

            for (i=0;i<19-2*ROWS;i++)           // Formatting - works with the default shell height on my machine
                printf("\n");                   // Hopefully I can replace these with something that clears the screen for me

            printf("\n\nCongratulations Player %i, you've won with %i in a row!\n\n",winner,N);
            return winner;
        }

    } while ( turn < ROWS*COLS );                           // Once ROWS*COLS turns have elapsed

    printf("\n\nGame Over!\n\nThere was no Winner :-(\n");  // The board is full and the game is over
    return winner;
}


void initBoard (char gameBoard[ROWS][COLS])
{
    int row = 0, col = 0;

    for (row=0;row<ROWS;row++)
    {
        for (col=0;col<COLS;col++)
        {
            gameBoard[row][col] = INITCHAR;     // Fill the gameBoard with INITCHAR characters
        }
    }

    printBoard(gameBoard);                      // Having this here prints out the board before
    return;                             // the playGame function asks for the first move
}


void printBoard (char gameBoard[ROWS][COLS])    // There is a ton of formatting in here
{                                               // That I don't feel like commenting :P
    int row = 0, col = 0, i=0;                  // It took a while to fine tune
                                                // But now the output is something like:
    printf("\n");                               // 
                                                //    1   2   3
    for (row=0;row<ROWS;row++)                  // 1    |   |
    {                                           //   -----------
        if (row == 0)                           // 2    |   |
        {                                       //   -----------
            printf("  ");                       // 3    |   |

            for (i=0;i<COLS;i++)
            {
                printf(" %i  ",i+1);
            }

            printf("\n\n");
        }

        for (col=0;col<COLS;col++)
        {
            if (col==0)
                printf("%i ",row+1);

            printf(" %c ",gameBoard[row][col]);

            if (col<COLS-1)
                printf("|");
        }

        printf("\n");

        if (row < ROWS-1)
        {
            for(i=0;i<COLS-1;i++)
            {
                if(i==0)
                    printf("  ----");
                else
                    printf("----");
            }

            printf("---\n");
        }
    }

    return;
}


void makeMove (char gameBoard[ROWS][COLS],int player)
{
    int row = 0, col = 0, i=0;
    char currentChar;

    if (player == 1)                    // This gets the correct player's mark
        currentChar = PLAYER1CHAR;
    else
        currentChar = PLAYER2CHAR;

    for (i=0;i<21-2*ROWS;i++)           // Newline formatting again :-(
        printf("\n");

    printf("\nPlayer %i, please enter the column of your move: ",player);
    scanf("%i",&col);
    printf("Please enter the row of your move: ");
    scanf("%i",&row);

    row--;                              // These lines translate the user's rows and columns numbering
    col--;                              // (starting with 1) to the computer's (starting with 0)

    while(gameBoard[row][col] != INITCHAR || row > ROWS-1 || col > COLS-1)  // We are not using a do... while because
    {                                                                       // I wanted the prompt to change
        printBoard(gameBoard);
        for (i=0;i<20-2*ROWS;i++)
            printf("\n");
        printf("\nPlayer %i, please enter a valid move! Column first, then row.\n",player);
        scanf("%i %i",&col,&row);

        row--;                          // See above ^^^
        col--;
    }

    gameBoard[row][col] = currentChar;  // Finally, we store the correct mark into the given location
    return;                             // And pop back out of this function
}


int checkWinner(char gameBoard[ROWS][COLS], int player)     // I've commented the last (and the hardest, for me anyway)
{                                                           // check, which checks for backwards diagonal runs below >>>
    int row = 0, col = 0, i = 0;
    char currentChar;

    if (player == 1)
        currentChar = PLAYER1CHAR;
    else
        currentChar = PLAYER2CHAR;

    for ( row = 0; row < ROWS; row++)                       // This first for loop checks every row
    {
        for ( col = 0; col < (COLS-(N-1)); col++)           // And all columns until N away from the end
        {
            while (gameBoard[row][col] == currentChar)      // For consecutive rows of the current player's mark
            {
                col++;
                i++;
                if (i == N)
                {
                    return player;
                }
            }
            i = 0;
        }
    }

    for ( col = 0; col < COLS; col++)                       // This one checks for columns of consecutive marks
    {
        for ( row = 0; row < (ROWS-(N-1)); row++)
        {
            while (gameBoard[row][col] == currentChar)
            {
                row++;
                i++;
                if (i == N)
                {
                    return player;
                }
            }
            i = 0;
        }
    }

    for ( col = 0; col < (COLS - (N-1)); col++)             // This one checks for "forwards" diagonal runs
    {
        for ( row = 0; row < (ROWS-(N-1)); row++)
        {
            while (gameBoard[row][col] == currentChar)
            {
                row++;
                col++;
                i++;
                if (i == N)
                {
                    return player;
                }
            }
            i = 0;
        }
    }
                                                        // Finally, the backwards diagonals:
    for ( col = COLS-1; col > 0+(N-2); col--)           // Start from the last column and go until N columns from the first
    {                                                   // The math seems strange here but the numbers work out when you trace them
        for ( row = 0; row < (ROWS-(N-1)); row++)       // Start from the first row and go until N rows from the last
        {
            while (gameBoard[row][col] == currentChar)  // If the current player's character is there
            {
                row++;                                  // Go down a row
                col--;                                  // And back a column
                i++;                                    // The i variable tracks how many consecutive marks have been found
                if (i == N)                             // Once i == N
                {
                    return player;                      // Return the current player number to the
                }                                       // winnner variable in the playGame function
            }                                           // If it breaks out of the while loop, there weren't N consecutive marks
            i = 0;                                      // So make i = 0 again
        }                                               // And go back into the for loop, incrementing the row to check from
    }

    return 0;                                           // If we got to here, no winner has been detected,
}                                                       // so we pop back up into the playGame function

// The end!

// Well, almost.

// Eventually I hope to get this thing going
// with a dynamically sized array. I'll make
// the CONSTANTS into variables in an initGame
// function and allow the user to define them.

Muito útil. Eu estava tentando encontrar algo mais eficiente, como se você souber N = COL = ROW, você poderia reduzir isso para algo muito mais simples, mas não encontrei nada mais eficiente para tamanho de placa arbitrário e N.
Hassan

6

Se o tabuleiro for n × n, então haverá n linhas, n colunas e 2 diagonais. Verifique cada um para ver se há todos os Xs ou Os para encontrar um vencedor.

Se levar apenas x < n quadrados consecutivos para vencer, então é um pouco mais complicado. A solução mais óbvia é a de verificar cada x × x quadrado para um vencedor. Aqui está um código que demonstra isso.

(Eu realmente não testar esta tosse * *, mas tinha de compilação na primeira tentativa, yay mim!)

public class TicTacToe
{
    public enum Square { X, O, NONE }

    /**
     * Returns the winning player, or NONE if the game has
     * finished without a winner, or null if the game is unfinished.
     */
    public Square findWinner(Square[][] board, int lengthToWin) {
        // Check each lengthToWin x lengthToWin board for a winner.    
        for (int top = 0; top <= board.length - lengthToWin; ++top) {
            int bottom = top + lengthToWin - 1;

            for (int left = 0; left <= board.length - lengthToWin; ++left) {
                int right = left + lengthToWin - 1;

                // Check each row.
                nextRow: for (int row = top; row <= bottom; ++row) {
                    if (board[row][left] == Square.NONE) {
                        continue;
                    }

                    for (int col = left; col <= right; ++col) {
                        if (board[row][col] != board[row][left]) {
                            continue nextRow;
                        }
                    }

                    return board[row][left];
                }

                // Check each column.
                nextCol: for (int col = left; col <= right; ++col) {
                    if (board[top][col] == Square.NONE) {
                        continue;
                    }

                    for (int row = top; row <= bottom; ++row) {
                        if (board[row][col] != board[top][col]) {
                            continue nextCol;
                        }
                    }

                    return board[top][col];
                }

                // Check top-left to bottom-right diagonal.
                diag1: if (board[top][left] != Square.NONE) {
                    for (int i = 1; i < lengthToWin; ++i) {
                        if (board[top+i][left+i] != board[top][left]) {
                            break diag1;
                        }
                    }

                    return board[top][left];
                }

                // Check top-right to bottom-left diagonal.
                diag2: if (board[top][right] != Square.NONE) {
                    for (int i = 1; i < lengthToWin; ++i) {
                        if (board[top+i][right-i] != board[top][right]) {
                            break diag2;
                        }
                    }

                    return board[top][right];
                }
            }
        }

        // Check for a completely full board.
        boolean isFull = true;

        full: for (int row = 0; row < board.length; ++row) {
            for (int col = 0; col < board.length; ++col) {
                if (board[row][col] == Square.NONE) {
                    isFull = false;
                    break full;
                }
            }
        }

        // The board is full.
        if (isFull) {
            return Square.NONE;
        }
        // The board is not full and we didn't find a solution.
        else {
            return null;
        }
    }
}

Eu vejo o que você quer dizer. Haveria (n * n * 2) respostas totais em um jogo n = x tradicional. Isso não funcionaria se x (o número de consecutivas necessárias para vencer) fosse menor que n. É uma boa solução, porém, gosto mais dela do que da mesa por sua extensibilidade.
dreadwail

Eu não mencionei a possibilidade de x <n na postagem original, então sua resposta ainda está correta.
dreadwail

4

Não conheço Java muito bem, mas conheço C, então tentei a ideia do quadrado mágico do adk (junto com a restrição de pesquisa de Hardwareguy ).

// tic-tac-toe.c
// to compile:
//  % gcc -o tic-tac-toe tic-tac-toe.c
// to run:
//  % ./tic-tac-toe
#include <stdio.h>

// the two types of marks available
typedef enum { Empty=2, X=0, O=1, NumMarks=2 } Mark;
char const MarkToChar[] = "XO ";

// a structure to hold the sums of each kind of mark
typedef struct { unsigned char of[NumMarks]; } Sum;

// a cell in the board, which has a particular value
#define MAGIC_NUMBER 15
typedef struct {
  Mark mark;
  unsigned char const value;
  size_t const num_sums;
  Sum * const sums[4];
} Cell;

#define NUM_ROWS 3
#define NUM_COLS 3

// create a sum for each possible tic-tac-toe
Sum row[NUM_ROWS] = {0};
Sum col[NUM_COLS] = {0};
Sum nw_diag = {0};
Sum ne_diag = {0};

// initialize the board values so any row, column, or diagonal adds to
// MAGIC_NUMBER, and so they each record their sums in the proper rows, columns,
// and diagonals
Cell board[NUM_ROWS][NUM_COLS] = { 
  { 
    { Empty, 8, 3, { &row[0], &col[0], &nw_diag } },
    { Empty, 1, 2, { &row[0], &col[1] } },
    { Empty, 6, 3, { &row[0], &col[2], &ne_diag } },
  },
  { 
    { Empty, 3, 2, { &row[1], &col[0] } },
    { Empty, 5, 4, { &row[1], &col[1], &nw_diag, &ne_diag } },
    { Empty, 7, 2, { &row[1], &col[2] } },
  },
  { 
    { Empty, 4, 3, { &row[2], &col[0], &ne_diag } },
    { Empty, 9, 2, { &row[2], &col[1] } },
    { Empty, 2, 3, { &row[2], &col[2], &nw_diag } },
  }
};

// print the board
void show_board(void)
{
  size_t r, c;
  for (r = 0; r < NUM_ROWS; r++) 
  {
    if (r > 0) { printf("---+---+---\n"); }
    for (c = 0; c < NUM_COLS; c++) 
    {
      if (c > 0) { printf("|"); }
      printf(" %c ", MarkToChar[board[r][c].mark]);
    }
    printf("\n");
  }
}


// run the game, asking the player for inputs for each side
int main(int argc, char * argv[])
{
  size_t m;
  show_board();
  printf("Enter moves as \"<row> <col>\" (no quotes, zero indexed)\n");
  for( m = 0; m < NUM_ROWS * NUM_COLS; m++ )
  {
    Mark const mark = (Mark) (m % NumMarks);
    size_t c, r;

    // read the player's move
    do
    {
      printf("%c's move: ", MarkToChar[mark]);
      fflush(stdout);
      scanf("%d %d", &r, &c);
      if (r >= NUM_ROWS || c >= NUM_COLS)
      {
        printf("illegal move (off the board), try again\n");
      }
      else if (board[r][c].mark != Empty)
      {
        printf("illegal move (already taken), try again\n");
      }
      else
      {
        break;
      }
    }
    while (1);

    {
      Cell * const cell = &(board[r][c]);
      size_t s;

      // update the board state
      cell->mark = mark;
      show_board();

      // check for tic-tac-toe
      for (s = 0; s < cell->num_sums; s++)
      {
        cell->sums[s]->of[mark] += cell->value;
        if (cell->sums[s]->of[mark] == MAGIC_NUMBER)
        {
          printf("tic-tac-toe! %c wins!\n", MarkToChar[mark]);
          goto done;
        }
      }
    }
  }
  printf("stalemate... nobody wins :(\n");
done:
  return 0;
}

Compila e testa bem.

% gcc -o tic-tac-toe tic-tac-toe.c
% ./jogo da velha
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Insira movimentos como "" (sem aspas, indexado por zero)
  Movimento de X: 1 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | |
  O movimento: 1 2
  movimento ilegal (já realizado), tente novamente
  O movimento: 3 3
  movimento ilegal (fora do tabuleiro), tente novamente
  O movimento: 2 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | | O
  Movimento de X: 1 0
     | |
  --- + --- + ---
   X | | X
  --- + --- + ---
     | | O
  O movimento: 1 1
     | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | O
  Movimento de X: 0 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | O
  O movimento: 2 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | | O
  Movimento de X: 2 1
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | O
  O movimento: 0 2
   X | | O
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | O
  jogo da velha! Ó vitórias!
% ./jogo da velha
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Insira movimentos como "" (sem aspas, indexado por zero)
  Movimento de X: 0 0
   X | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  O movimento: 0 1
   X | O |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Movimento de X: 0 2
   X | O | X
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  O movimento: 1 0
   X | O | X
  --- + --- + ---
   O | |
  --- + --- + ---
     | |
  Movimento de X: 1 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
     | |
  O movimento: 2 0
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | |
  Movimento de X: 2 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X |
  O movimento: 2 2
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X | O
  Movimento de X: 1 2
   X | O | X
  --- + --- + ---
   O | X | X
  --- + --- + ---
   O | X | O
  impasse ... ninguém ganha :(
%

Foi divertido, obrigado!

Na verdade, pensando bem, você não precisa de um quadrado mágico, apenas uma contagem para cada linha / coluna / diagonal. Isso é um pouco mais fácil do que generalizar um quadrado mágico para matrizes n× n, já que você só precisa contar até n.


3

A mesma pergunta foi feita em uma de minhas entrevistas. Minhas idéias: Inicialize a matriz com 0. Mantenha 3 matrizes 1) sum_row (tamanho n) 2) sum_column (tamanho n) 3) diagonal (tamanho 2)

Para cada movimento por (X) diminua o valor da caixa em 1 e para cada movimento por (0) aumente-o em 1. Em qualquer ponto se a linha / coluna / diagonal que foi modificada no movimento atual tiver a soma -3 ou + 3 significa que alguém ganhou o jogo. Para um empate, podemos usar a abordagem acima para manter a variável moveCount.

Você acha que estou perdendo alguma coisa?

Editar: O mesmo pode ser usado para a matriz nxn. A soma deve ser igual a +3 ou -3.


2

uma forma não circular para determinar se o ponto estava no anti diag:

`if (x + y == n - 1)`

2

Estou atrasado para a festa, mas gostaria de apontar um benefício que descobri ao usar um quadrado mágico , ou seja, ele pode ser usado para obter uma referência ao quadrado que causaria a vitória ou derrota na próxima jogada, ao invés de apenas sendo usado para calcular quando um jogo acabou.

Pegue este quadrado mágico:

4 9 2
3 5 7
8 1 6

Primeiro, configure uma scoresmatriz que é incrementada toda vez que um movimento é feito. Veja esta resposta para detalhes. Agora, se jogarmos ilegalmente X duas vezes seguidas em [0,0] e [0,1], a scoresmatriz terá a seguinte aparência:

[7, 0, 0, 4, 3, 0, 4, 0];

E o quadro é assim:

X . .
X . .
. . .

Então, tudo o que temos que fazer para obter uma referência de qual quadrado vencer / bloquear é:

get_winning_move = function() {
  for (var i = 0, i < scores.length; i++) {
    // keep track of the number of times pieces were added to the row
    // subtract when the opposite team adds a piece
    if (scores[i].inc === 2) {
      return 15 - state[i].val; // 8
    }
  }
}

Na realidade, a implementação requer alguns truques adicionais, como manipular teclas numeradas (em JavaScript), mas achei muito simples e gostei da matemática recreativa.


1

Fiz algumas otimizações nas verificações de linha, coluna e diagonal. É decidido principalmente no primeiro loop aninhado se precisamos verificar uma determinada coluna ou diagonal. Assim, evitamos checar colunas ou diagonais economizando tempo. Isso causa grande impacto quando o tamanho do cartão é maior e um número significativo de células não é preenchido.

Aqui está o código Java para isso.

    int gameState(int values[][], int boardSz) {


    boolean colCheckNotRequired[] = new boolean[boardSz];//default is false
    boolean diag1CheckNotRequired = false;
    boolean diag2CheckNotRequired = false;
    boolean allFilled = true;


    int x_count = 0;
    int o_count = 0;
    /* Check rows */
    for (int i = 0; i < boardSz; i++) {
        x_count = o_count = 0;
        for (int j = 0; j < boardSz; j++) {
            if(values[i][j] == x_val)x_count++;
            if(values[i][j] == o_val)o_count++;
            if(values[i][j] == 0)
            {
                colCheckNotRequired[j] = true;
                if(i==j)diag1CheckNotRequired = true;
                if(i + j == boardSz - 1)diag2CheckNotRequired = true;
                allFilled = false;
                //No need check further
                break;
            }
        }
        if(x_count == boardSz)return X_WIN;
        if(o_count == boardSz)return O_WIN;         
    }


    /* check cols */
    for (int i = 0; i < boardSz; i++) {
        x_count = o_count = 0;
        if(colCheckNotRequired[i] == false)
        {
            for (int j = 0; j < boardSz; j++) {
                if(values[j][i] == x_val)x_count++;
                if(values[j][i] == o_val)o_count++;
                //No need check further
                if(values[i][j] == 0)break;
            }
            if(x_count == boardSz)return X_WIN;
            if(o_count == boardSz)return O_WIN;
        }
    }

    x_count = o_count = 0;
    /* check diagonal 1 */
    if(diag1CheckNotRequired == false)
    {
        for (int i = 0; i < boardSz; i++) {
            if(values[i][i] == x_val)x_count++;
            if(values[i][i] == o_val)o_count++;
            if(values[i][i] == 0)break;
        }
        if(x_count == boardSz)return X_WIN;
        if(o_count == boardSz)return O_WIN;
    }

    x_count = o_count = 0;
    /* check diagonal 2 */
    if( diag2CheckNotRequired == false)
    {
        for (int i = boardSz - 1,j = 0; i >= 0 && j < boardSz; i--,j++) {
            if(values[j][i] == x_val)x_count++;
            if(values[j][i] == o_val)o_count++;
            if(values[j][i] == 0)break;
        }
        if(x_count == boardSz)return X_WIN;
        if(o_count == boardSz)return O_WIN;
        x_count = o_count = 0;
    }

    if( allFilled == true)
    {
        for (int i = 0; i < boardSz; i++) {
            for (int j = 0; j < boardSz; j++) {
                if (values[i][j] == 0) {
                    allFilled = false;
                    break;
                }
            }

            if (allFilled == false) {
                break;
            }
        }
    }

    if (allFilled)
        return DRAW;

    return INPROGRESS;
}

1

Eu gosto desse algoritmo porque ele usa uma representação do tabuleiro 1x9 vs 3x3.

private int[] board = new int[9];
private static final int[] START = new int[] { 0, 3, 6, 0, 1, 2, 0, 2 };
private static final int[] INCR  = new int[] { 1, 1, 1, 3, 3, 3, 4, 2 };
private static int SIZE = 3;
/**
 * Determines if there is a winner in tic-tac-toe board.
 * @return {@code 0} for draw, {@code 1} for 'X', {@code -1} for 'Y'
 */
public int hasWinner() {
    for (int i = 0; i < START.length; i++) {
        int sum = 0;
        for (int j = 0; j < SIZE; j++) {
            sum += board[START[i] + j * INCR[i]];
        }
        if (Math.abs(sum) == SIZE) {
            return sum / SIZE;
        }
    }
    return 0;
}

1
Eu gosto mais dessa abordagem. Teria ajudado se você explicasse o que significa "início" e "incr". (É uma maneira de expressar todas as "linhas" como um índice inicial e o número de índices a pular.)
nafg

Por favor, adicione mais explicações. Como você criou esse código?
Farzan

0

Outra opção: gere sua tabela com código. Até a simetria, existem apenas três maneiras de vencer: linha da borda, linha do meio ou diagonal. Pegue esses três e gire-os de todas as maneiras possíveis:

def spin(g): return set([g, turn(g), turn(turn(g)), turn(turn(turn(g)))])
def turn(g): return tuple(tuple(g[y][x] for y in (0,1,2)) for x in (2,1,0))

X,s = 'X.'
XXX = X, X, X
sss = s, s, s

ways_to_win = (  spin((XXX, sss, sss))
               | spin((sss, XXX, sss))
               | spin(((X,s,s),
                       (s,X,s),
                       (s,s,X))))

Essas simetrias podem ter mais utilidades em seu código de jogo: se você chegar a um tabuleiro do qual já viu uma versão girada, pode apenas pegar o valor em cache ou o melhor movimento em cache daquele (e removê-lo de volta). Isso geralmente é muito mais rápido do que avaliar a subárvore do jogo.

(Inverter para a esquerda e para a direita pode ajudar da mesma forma; não foi necessário aqui porque o conjunto de rotações dos padrões vencedores é simétrico em espelho.)


0

Aqui está uma solução que eu vim, isso armazena os símbolos como chars e usa o valor int do char para descobrir se X ou O ganhou (veja o código do Árbitro)

public class TicTacToe {
    public static final char BLANK = '\u0000';
    private final char[][] board;
    private int moveCount;
    private Referee referee;

    public TicTacToe(int gridSize) {
        if (gridSize < 3)
            throw new IllegalArgumentException("TicTacToe board size has to be minimum 3x3 grid");
        board = new char[gridSize][gridSize];
        referee = new Referee(gridSize);
    }

    public char[][] displayBoard() {
        return board.clone();
    }

    public String move(int x, int y) {
        if (board[x][y] != BLANK)
            return "(" + x + "," + y + ") is already occupied";
        board[x][y] = whoseTurn();
        return referee.isGameOver(x, y, board[x][y], ++moveCount);
    }

    private char whoseTurn() {
        return moveCount % 2 == 0 ? 'X' : 'O';
    }

    private class Referee {
        private static final int NO_OF_DIAGONALS = 2;
        private static final int MINOR = 1;
        private static final int PRINCIPAL = 0;
        private final int gridSize;
        private final int[] rowTotal;
        private final int[] colTotal;
        private final int[] diagonalTotal;

        private Referee(int size) {
            gridSize = size;
            rowTotal = new int[size];
            colTotal = new int[size];
            diagonalTotal = new int[NO_OF_DIAGONALS];
        }

        private String isGameOver(int x, int y, char symbol, int moveCount) {
            if (isWinningMove(x, y, symbol))
                return symbol + " won the game!";
            if (isBoardCompletelyFilled(moveCount))
                return "Its a Draw!";
            return "continue";
        }

        private boolean isBoardCompletelyFilled(int moveCount) {
            return moveCount == gridSize * gridSize;
        }

        private boolean isWinningMove(int x, int y, char symbol) {
            if (isPrincipalDiagonal(x, y) && allSymbolsMatch(symbol, diagonalTotal, PRINCIPAL))
                return true;
            if (isMinorDiagonal(x, y) && allSymbolsMatch(symbol, diagonalTotal, MINOR))
                return true;
            return allSymbolsMatch(symbol, rowTotal, x) || allSymbolsMatch(symbol, colTotal, y);
        }

        private boolean allSymbolsMatch(char symbol, int[] total, int index) {
            total[index] += symbol;
            return total[index] / gridSize == symbol;
        }

        private boolean isPrincipalDiagonal(int x, int y) {
            return x == y;
        }

        private boolean isMinorDiagonal(int x, int y) {
            return x + y == gridSize - 1;
        }
    }
}

Também aqui estão meus testes de unidade para validar se realmente funciona

import static com.agilefaqs.tdd.demo.TicTacToe.BLANK;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class TicTacToeTest {
    private TicTacToe game = new TicTacToe(3);

    @Test
    public void allCellsAreEmptyInANewGame() {
        assertBoardIs(new char[][] { { BLANK, BLANK, BLANK },
                { BLANK, BLANK, BLANK },
                { BLANK, BLANK, BLANK } });
    }

    @Test(expected = IllegalArgumentException.class)
    public void boardHasToBeMinimum3x3Grid() {
        new TicTacToe(2);
    }

    @Test
    public void firstPlayersMoveMarks_X_OnTheBoard() {
        assertEquals("continue", game.move(1, 1));
        assertBoardIs(new char[][] { { BLANK, BLANK, BLANK },
                { BLANK, 'X', BLANK },
                { BLANK, BLANK, BLANK } });
    }

    @Test
    public void secondPlayersMoveMarks_O_OnTheBoard() {
        game.move(1, 1);
        assertEquals("continue", game.move(2, 2));
        assertBoardIs(new char[][] { { BLANK, BLANK, BLANK },
                { BLANK, 'X', BLANK },
                { BLANK, BLANK, 'O' } });
    }

    @Test
    public void playerCanOnlyMoveToAnEmptyCell() {
        game.move(1, 1);
        assertEquals("(1,1) is already occupied", game.move(1, 1));
    }

    @Test
    public void firstPlayerWithAllSymbolsInOneRowWins() {
        game.move(0, 0);
        game.move(1, 0);
        game.move(0, 1);
        game.move(2, 1);
        assertEquals("X won the game!", game.move(0, 2));
    }

    @Test
    public void firstPlayerWithAllSymbolsInOneColumnWins() {
        game.move(1, 1);
        game.move(0, 0);
        game.move(2, 1);
        game.move(1, 0);
        game.move(2, 2);
        assertEquals("O won the game!", game.move(2, 0));
    }

    @Test
    public void firstPlayerWithAllSymbolsInPrincipalDiagonalWins() {
        game.move(0, 0);
        game.move(1, 0);
        game.move(1, 1);
        game.move(2, 1);
        assertEquals("X won the game!", game.move(2, 2));
    }

    @Test
    public void firstPlayerWithAllSymbolsInMinorDiagonalWins() {
        game.move(0, 2);
        game.move(1, 0);
        game.move(1, 1);
        game.move(2, 1);
        assertEquals("X won the game!", game.move(2, 0));
    }

    @Test
    public void whenAllCellsAreFilledTheGameIsADraw() {
        game.move(0, 2);
        game.move(1, 1);
        game.move(1, 0);
        game.move(2, 1);
        game.move(2, 2);
        game.move(0, 0);
        game.move(0, 1);
        game.move(1, 2);
        assertEquals("Its a Draw!", game.move(2, 0));
    }

    private void assertBoardIs(char[][] expectedBoard) {
        assertArrayEquals(expectedBoard, game.displayBoard());
    }
}

Solução completa: https://github.com/nashjain/tictactoe/tree/master/java


0

Que tal uma abordagem a seguir para 9 slots? Declare 9 variáveis ​​inteiras para uma matriz 3x3 (a1, a2 .... a9) onde a1, a2, a3 representam a linha-1 e a1, a4, a7 formariam a coluna-1 (você entendeu). Use '1' para indicar o jogador-1 e '2' para indicar o jogador-2.

Existem 8 combinações de vitória possíveis: Vitória-1: a1 + a2 + a3 (a resposta pode ser 3 ou 6 com base no jogador que ganhou) Vitória-2: a4 + a5 + a6 Vitória-3: a7 + a8 + a9 Vitória-4 : a1 + a4 + a7 .... Win-7: a1 + a5 + a9 Win-8: a3 + a5 + a7

Agora sabemos que se o jogador um cruza a1, então precisamos reavaliar a soma das 3 variáveis: Vitória-1, Vitória-4 e Vitória-7. Qualquer 'Win-?' a variável chega a 3 ou 6 primeiros ganha o jogo. Se a variável Win-1 atingir 6 primeiro, o Jogador 2 vence.

Eu entendo que esta solução não é escalonável facilmente.


0

Esta é uma forma muito simples de verificar.

    public class Game() { 

    Game player1 = new Game('x');
    Game player2 = new Game('o');

    char piece;

    Game(char piece) {
       this.piece = piece;
    }

public void checkWin(Game player) {

    // check horizontal win
    for (int i = 0; i <= 6; i += 3) {

        if (board[i] == player.piece &&
                board[i + 1] == player.piece &&
                board[i + 2] == player.piece)
            endGame(player);
    }

    // check vertical win
    for (int i = 0; i <= 2; i++) {

        if (board[i] == player.piece &&
                board[i + 3] == player.piece &&
                board[i + 6] == player.piece)
            endGame(player);
    }

    // check diagonal win
    if ((board[0] == player.piece &&
            board[4] == player.piece &&
            board[8] == player.piece) ||
            board[2] == player.piece &&
            board[4] == player.piece &&
            board[6] == player.piece)
        endGame(player);
    }

}


0

Se você tiver o campo de fronteira 5 * 5, por exemplo, usei o próximo método de verificação:

public static boolean checkWin(char symb) {
  int SIZE = 5;

        for (int i = 0; i < SIZE-1; i++) {
            for (int j = 0; j <SIZE-1 ; j++) {
                //vertical checking
            if (map[0][j] == symb && map[1][j] == symb && map[2][j] == symb && map[3][j] == symb && map[4][j] == symb) return true;      // j=0
            }
            //horisontal checking
            if(map[i][0] == symb && map[i][1] == symb && map[i][2] == symb && map[i][3] == symb && map[i][4] == symb) return true;  // i=0
        }
        //diagonal checking (5*5)
        if (map[0][0] == symb && map[1][1] == symb && map[2][2] == symb && map[3][3] == symb && map[4][4] == symb) return true;
        if (map[4][0] == symb && map[3][1] == symb && map[2][2] == symb && map[1][3] == symb && map[0][4] == symb) return true;

        return false; 
        }

Acho que é mais claro, mas provavelmente não é a maneira mais ideal.


0

Aqui está minha solução usando uma matriz bidimensional:

private static final int dimension = 3;
private static final int[][] board = new int[dimension][dimension];
private static final int xwins = dimension * 1;
private static final int owins = dimension * -1;

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int count = 0;
    boolean keepPlaying = true;
    boolean xsTurn = true;
    while (keepPlaying) {
        xsTurn = (count % 2 == 0);
        System.out.print("Enter i-j in the format:");
        if (xsTurn) {
            System.out.println(" X plays: ");
        } else {
            System.out.println(" O plays: ");
        }
        String result = null;
        while (result == null) {
            result = parseInput(scanner, xsTurn);
        }
        String[] xy = result.split(",");
        int x = Integer.parseInt(xy[0]);
        int y = Integer.parseInt(xy[1]);
        keepPlaying = makeMove(xsTurn, x, y);
        count++;
    }
    if (xsTurn) {
        System.out.print("X");
    } else {
        System.out.print("O");
    }
    System.out.println(" WON");
    printArrayBoard(board);
}

private static String parseInput(Scanner scanner, boolean xsTurn) {
    String line = scanner.nextLine();
    String[] values = line.split("-");
    int x = Integer.parseInt(values[0]);
    int y = Integer.parseInt(values[1]);
    boolean alreadyPlayed = alreadyPlayed(x, y);
    String result = null;
    if (alreadyPlayed) {
        System.out.println("Already played in this x-y. Retry");
    } else {
        result = "" + x + "," + y;
    }
    return result;
}

private static boolean alreadyPlayed(int x, int y) {
    System.out.println("x-y: " + x + "-" + y + " board[x][y]: " + board[x][y]);
    if (board[x][y] != 0) {
        return true;
    }
    return false;
}

private static void printArrayBoard(int[][] board) {
    for (int i = 0; i < dimension; i++) {
        int[] height = board[i];
        for (int j = 0; j < dimension; j++) {
            System.out.print(height[j] + " ");
        }
        System.out.println();
    }
}

private static boolean makeMove(boolean xo, int x, int y) {
    if (xo) {
        board[x][y] = 1;
    } else {
        board[x][y] = -1;
    }
    boolean didWin = checkBoard();
    if (didWin) {
        System.out.println("keep playing");
    }
    return didWin;
}

private static boolean checkBoard() {
    //check horizontal
    int[] horizontalTotal = new int[dimension];
    for (int i = 0; i < dimension; i++) {
        int[] height = board[i];
        int total = 0;
        for (int j = 0; j < dimension; j++) {
            total += height[j];
        }
        horizontalTotal[i] = total;
    }
    for (int a = 0; a < horizontalTotal.length; a++) {
        if (horizontalTotal[a] == xwins || horizontalTotal[a] == owins) {
            System.out.println("horizontal");
            return false;
        }
    }
    //check vertical
    int[] verticalTotal = new int[dimension];

    for (int j = 0; j < dimension; j++) {
        int total = 0;
        for (int i = 0; i < dimension; i++) {
            total += board[i][j];
        }
        verticalTotal[j] = total;
    }
    for (int a = 0; a < verticalTotal.length; a++) {
        if (verticalTotal[a] == xwins || verticalTotal[a] == owins) {
            System.out.println("vertical");
            return false;
        }
    }
    //check diagonal
    int total1 = 0;
    int total2 = 0;
    for (int i = 0; i < dimension; i++) {
        for (int j = 0; j < dimension; j++) {
            if (i == j) {
                total1 += board[i][j];
            }
            if (i == (dimension - 1 - j)) {
                total2 += board[i][j];
            }
        }
    }
    if (total1 == xwins || total1 == owins) {
        System.out.println("diagonal 1");
        return false;
    }
    if (total2 == xwins || total2 == owins) {
        System.out.println("diagonal 2");
        return false;
    }
    return true;
}

0

Solução de tempo constante, roda em O (8).

Armazene o estado da placa como um número binário. O menor bit (2 ^ 0) é a linha superior esquerda do tabuleiro. Então vai para a direita, depois para baixo.

IE

+ ----------------- +
| 2 ^ 0 | 2 ^ 1 | 2 ^ 2 |
| ----------------- |
| 2 ^ 3 | 2 ^ 4 | 2 ^ 5 |
| ----------------- |
| 2 ^ 6 | 2 ^ 7 | 2 ^ 8 |
+ ----------------- +

Cada jogador tem seu próprio número binário para representar o estado (porque o jogo da velha) tem 3 estados (X, O e branco), portanto, um único número binário não funcionará para representar o estado do tabuleiro para vários jogadores.

Por exemplo, uma placa como:

+ ----------- +
| X | O | X |
| ----------- |
| O | X | |
| ----------- |
| | O | |
+ ----------- +

   0 1 2 3 4 5 6 7 8
X: 1 0 1 0 1 0 0 0 0
O: 0 1 0 1 0 0 0 1 0

Observe que os bits para o jogador X são separados dos bits para o jogador O, isso é óbvio porque X não pode colocar uma peça onde O tem uma peça e vice-versa.

Para verificar se um jogador ganhou, precisamos comparar todas as posições cobertas por aquele jogador com uma posição que sabemos ser uma posição de vitória. Nesse caso, a maneira mais fácil de fazer isso seria alternar com AND a posição do jogador e a posição de vitória e ver se as duas são iguais.

boolean isWinner(short X) {
    for (int i = 0; i < 8; i++)
        if ((X & winCombinations[i]) == winCombinations[i])
            return true;
    return false;
}

por exemplo.

X: 111001010
W: 111000000 // posição vencedora, todos iguais na primeira linha.
------------
&: 111000000

Observação: X & W = Wentão X está em um estado de vitória.

Esta é uma solução de tempo constante, depende apenas do número de posições ganhadoras, porque a aplicação da porta AND é uma operação de tempo constante e o número de posições ganhadoras é finito.

Também simplifica a tarefa de enumerar todos os estados de placa válidos, apenas todos os números representáveis ​​por 9 bits. Mas é claro que você precisa de uma condição extra para garantir que um número seja um estado de placa válido (por exemplo, 0b111111111é um número de 9 bits válido, mas não é um estado de placa válido porque X acabou de dar todas as voltas).

O número de posições de vitória possíveis pode ser gerado instantaneamente, mas aqui estão eles de qualquer maneira.

short[] winCombinations = new short[] {
  // each row
  0b000000111,
  0b000111000,
  0b111000000,
  // each column
  0b100100100,
  0b010010010,
  0b001001001,
  // each diagonal
  0b100010001,
  0b001010100
};

Para enumerar todas as posições do tabuleiro, você pode executar o seguinte loop. Embora eu deixe a decisão de determinar se um número é um estado válido da placa para outra pessoa.

NOTA: (2 ** 9 - 1) = (2 ** 8) + (2 ** 7) + (2 ** 6) + ... (2 ** 1) + (2 ** 0)

for (short X = 0; X < (Math.pow(2,9) - 1); X++)
   System.out.println(isWinner(X));

Adicione mais descrição e altere o código para que ele responda exatamente à pergunta de OP.
Farzan

0

Não tenho certeza se esta abordagem já foi publicada. Isso deve funcionar para qualquer tabuleiro m * n e um jogador deve preencher a posição consecutiva do " vencedorPos ". A ideia é baseada na janela em execução.

private boolean validateWinner(int x, int y, int player) {
    //same col
    int low = x-winnerPos-1;
    int high = low;
    while(high <= x+winnerPos-1) {
        if(isValidPos(high, y) && isFilledPos(high, y, player)) {
            high++;
            if(high - low == winnerPos) {
                return true;
            }
        } else {
            low = high + 1;
            high = low;
        }
    }

    //same row
    low = y-winnerPos-1;
    high = low;
    while(high <= y+winnerPos-1) {
        if(isValidPos(x, high) && isFilledPos(x, high, player)) {
            high++;
            if(high - low == winnerPos) {
                return true;
            }
        } else {
            low = high + 1;
            high = low;
        }
    }
    if(high - low == winnerPos) {
        return true;
    }

    //diagonal 1
    int lowY = y-winnerPos-1;
    int highY = lowY;
    int lowX = x-winnerPos-1;
    int highX = lowX;
    while(highX <= x+winnerPos-1 && highY <= y+winnerPos-1) {
        if(isValidPos(highX, highY) && isFilledPos(highX, highY, player)) {
            highX++;
            highY++;
            if(highX - lowX == winnerPos) {
                return true;
            }
        } else {
            lowX = highX + 1;
            lowY = highY + 1;
            highX = lowX;
            highY = lowY;
        }
    }

    //diagonal 2
    lowY = y+winnerPos-1;
    highY = lowY;
    lowX = x-winnerPos+1;
    highX = lowX;
    while(highX <= x+winnerPos-1 && highY <= y+winnerPos-1) {
        if(isValidPos(highX, highY) && isFilledPos(highX, highY, player)) {
            highX++;
            highY--;
            if(highX - lowX == winnerPos) {
                return true;
            }
        } else {
            lowX = highX + 1;
            lowY = highY + 1;
            highX = lowX;
            highY = lowY;
        }
    }
    if(highX - lowX == winnerPos) {
        return true;
    }
    return false;
}

private boolean isValidPos(int x, int y) {
    return x >= 0 && x < row && y >= 0 && y< col;
}
public boolean isFilledPos(int x, int y, int p) throws IndexOutOfBoundsException {
    return arena[x][y] == p;
}

-2

Desenvolvi um algoritmo para isso como parte de um projeto de ciências uma vez.

Basicamente, você divide o tabuleiro de forma recursiva em um grupo de retalhos 2x2 sobrepostos, testando as diferentes combinações possíveis para vencer em um quadrado 2x2.

É lento, mas tem a vantagem de funcionar em placas de qualquer tamanho, com requisitos de memória bastante lineares.

Eu gostaria de poder encontrar minha implementação


A recursão para testar o acabamento não é necessária.
Gabriel Llamas,
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.