Algoritmo para determinar Tic Tac Toe Game Over


97

Escribí un juego de tic-tac-toe en Java, y mi método actual para determinar el final del juego tiene en cuenta los siguientes escenarios posibles para el final del juego:

  1. El tablero está lleno y aún no se ha declarado ningún ganador: el juego es un empate.
  2. Cross ha ganado.
  3. Circle ha ganado.

Desafortunadamente, para hacerlo, lee un conjunto predefinido de estos escenarios de una tabla. Esto no es necesariamente malo considerando que solo hay 9 espacios en un tablero y, por lo tanto, la mesa es algo pequeña, pero ¿hay una mejor forma algorítmica de determinar si el juego ha terminado? La determinación de si alguien ha ganado o no es el meollo del problema, ya que comprobar si 9 espacios están llenos es trivial.

El método de la tabla podría ser la solución, pero si no, ¿cuál es? Además, ¿y si el tablero no fuera de tamaño n=9? ¿Y si fuera un tablero mucho más grande, por ejemplo n=16, n=25y así sucesivamente, haciendo que el número de artículos colocados consecutivamente a ganar para estar x=4, x=5etc? ¿Un algoritmo general para usar con todos n = { 9, 16, 25, 36 ... }?


Estoy agregando mis 2 centavos por todas las respuestas: siempre sabes que necesitas al menos un número de X u O en el tablero para ganar (en el tablero normal de 3x3 es 3). Por lo tanto, puede realizar un seguimiento de los conteos de cada uno y solo comenzar a verificar las ganancias si son más altas.
Yuval A.

Respuestas:


133

Usted sabe que un movimiento ganador solo puede ocurrir después de que X u O hayan hecho su movimiento más reciente, por lo que solo puede buscar filas / columnas con diag opcionales que están contenidas en ese movimiento para limitar su espacio de búsqueda al intentar determinar una mesa ganadora. Además, dado que hay un número fijo de movimientos en un juego de tres en raya una vez que se realiza el último movimiento, si no fue un movimiento ganador, por defecto es un juego de empate.

editar: este código es para un tablero de n por n con n en una fila para ganar (el tablero 3x3 requiere 3 en una fila, etc.)

editar: código agregado para verificar el anti diag, no pude encontrar una forma sin bucle para determinar si el punto estaba en el anti diag, por eso falta ese paso

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
Olvidaste comprobar el anti-diagonal.
rampion

1
Para un tablero de 3x3, x + y siempre será igual a 2 en la anti-diagonal, siempre será par en el centro y las esquinas del tablero, e impar en otros lugares.
Chris Doggett

5
No entiendo el cheque de giro al final, ¿no debería restar 1?
Inez

4
Hay casos en los que un jugador gana en el último movimiento posible (noveno). En ese caso, se informará tanto un ganador como un empate ...
Marc

5
@ Roamer-1888 no se trata de cuántas líneas constan de su solución, se trata de reducir la complejidad del tiempo del algoritmo para buscar un ganador.
Shady

38

puedes usar un cuadrado mágico http://mathworld.wolfram.com/MagicSquare.html si alguna fila, columna o diag suma 15, entonces un jugador ha ganado.


3
¿Cómo se traduce eso en el juego tic-tac-toe?
Paul Alexander

Es una buena información que no sabía, así que definitivamente les agradezco por la información. Como mencionó Paul, no está claro de inmediato cómo eso ayudaría a resolver el problema en cuestión, pero parece que podría formar parte de una solución más completa.
dreadwail

4
superponerlo. 1 para blanco, 2 para negro y multiplique. si algo sale a 15, entonces las blancas ganaron y si salió a 30, entonces las negras ganaron.
adk

1
En cuanto a O, es bastante barato, especialmente si lo mezcla con la comprobación de celda de Hardwareguy. Cada celda solo puede estar en 4 posibles tic-tac-toes: en fila, en columna y dos diagonales (barra y barra invertida). Entonces, una vez que se ha hecho un movimiento, solo tiene que hacer como máximo 4 adiciones y comparaciones. La respuesta de Hardwareguy requiere 4 (n-1) comprobaciones para cada movimiento, en comparación.
rampion

29
¿No podríamos hacer esto con 1 y -1 y sumar cada fila / columna / diag para ver si es n o -n?
Nathan

24

¿Qué tal este pseudocódigo:

Después de que un jugador coloca una pieza en la posición (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

Usaría una matriz de char [n, n], con O, X y espacio para vacío.

  1. sencillo.
  2. Un bucle.
  3. Cinco variables simples: 4 enteros y un booleano.
  4. Escalas a cualquier tamaño de n.
  5. Solo comprueba pieza actual.
  6. Sin magia. :)

si celda [i, n- (i + 1)] = jugador, entonces rdiag ++; - Parece que entre paréntesis será correcto. Estoy en lo cierto?
Pumych

@Pumych, no. Si i==1y n==3, se rdiagdebe marcar en (1, 3)y (1, 3-1+1)es igual a las coordenadas correctas, pero (1, 3-(1+1))no.
KgOfHedgehogs

Podría haber estado pensando que las celdas tenían un índice cero.
Matias Grioni

era solo algunas cosas que se me
salían

21

Esto es similar a la respuesta de Osama ALASSIRY , pero intercambia el espacio constante y el tiempo lineal por el espacio lineal y el tiempo constante. Es decir, no hay bucle después de la inicialización.

Inicialice un par (0,0)para cada fila, cada columna y las dos diagonales (diagonal y anti-diagonal). Estos pares representan el acumulado (sum,sum)de las piezas en la fila, columna o diagonal correspondiente, donde

Una pieza del jugador A tiene valor (1,0)
Una pieza del jugador B tiene valor (0,1)

Cuando un jugador coloca una pieza, actualiza el par de filas, el par de columnas y los pares diagonales correspondientes (si están en las diagonales). Si alguna fila, columna o par diagonal recién actualizado es igual a (n,0)o, (0,n)entonces A o B ganaron, respectivamente.

Análisis asintótico:

O (1) tiempo (por movimiento)
O (n) espacio (total)

Para el uso de la memoria, usa 4*(n+1) enteros.

dos_elementos * n_filas + dos_elementos * n_columnas +
dos_elementos * dos_diagonales = 4 * n + 4 enteros = 4 (n + 1) enteros

Ejercicio: ¿Puedes ver cómo probar un empate en O (1) tiempo por movimiento? Si es así, puede terminar el juego temprano en un empate.


1
Creo que esto es mejor que el de Osama ALASSIRY, ya que es más o menos O(sqrt(n))tiempo, pero debe hacerse después de cada movimiento, donde n es el tamaño del tablero. Entonces terminas con O(n^1.5). Para esta solución, obtienes O(n)tiempo en general.
Matias Grioni

Buena forma de ver esto, tiene sentido mirar las "soluciones" reales ... para 3x3, solo tendrías 8 pares de "booleanos" ... Puede ser aún más efectivo en el espacio si fueran 2 bits cada uno ... Se necesitan 16 bits y puedes solo bit a bit O 1 en el reproductor correcto desplazado a la izquierda al lugar correcto :)
Osama Al-Maadeed

13

Aquí está mi solución que escribí para un proyecto en el que estoy trabajando en javascript. Si no le importa el costo de memoria de algunas matrices, probablemente sea la solución más rápida y sencilla que encontrará. Supone que conoces la posición del último movimiento.

/*
 * 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
Este sería el enfoque del gran martillo, pero de hecho es una solución viable, especialmente para ubicarlo como una de una multitud de soluciones creativas y funcionales para este problema. También es corto, elegante y muy legible: para una cuadrícula de 3x3 (Tx3 tradicional) me gusta este algoritmo.
nocarrier

¡Este es increíble! Descubrí que hay un pequeño error en los patrones ganadores, en la posición 8, los patrones deberían ser [6,7], [0,4] y [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

7

Acabo de escribir esto para mi clase de programación C.

Lo estoy publicando porque ninguno de los otros ejemplos aquí funcionará con cualquier tamaño de cuadrícula rectangular y cualquier número de marcas consecutivas de N -en-fila para ganar.

Encontrarás mi algoritmo, tal como está, en la checkWinner()función. No usa números mágicos ni nada elegante para buscar un ganador, simplemente usa cuatro bucles for - El código está bien comentado, así que lo dejaré hablar por sí mismo, supongo.

// 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.

Muy útil. Estaba tratando de encontrar algo más eficiente, como si supieras N = COL = ROW, podrías reducir esto a algo mucho más simple, pero no he encontrado nada más eficiente para el tamaño de placa arbitrario y N.
Hassan

6

Si el tablero es n × n, entonces hay n filas, n columnas y 2 diagonales. Marque cada uno de ellos en busca de todas las X o todas las O para encontrar un ganador.

Si solo se necesitan x < n cuadrados consecutivos para ganar, entonces es un poco más complicado. La solución más obvia es verificar cada cuadrado de x × x en busca de un ganador. Aquí hay un código que lo demuestra.

(En realidad, no probé este * tos *, pero se compiló en el primer intento, ¡sí!)

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;
        }
    }
}

Veo a que te refieres. Habría (n * n * 2) respuestas totales en un juego tradicional n = x. Sin embargo, esto no funcionaría si x (el número de consecutivos necesarios para ganar) fuera menor que n. Sin embargo, es una buena solución, me gusta más que la tabla por su extensibilidad.
dreadwail

Sin embargo, no mencioné la posibilidad de x <n en la publicación original, por lo que su respuesta sigue siendo acertada.
dreadwail

4

No conozco muy bien Java, pero sí sé C, así que probé la idea del cuadrado mágico de adk (junto con la restricción de búsqueda 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 y prueba bien.

% gcc -o tic-tac-toe tic-tac-toe.c
% ./tic-tac-toe
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Ingrese movimientos como "" (sin comillas, cero indexado)
  Movimiento de X: 1 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | |
  Movimiento de O: 1 2
  movimiento ilegal (ya realizado), inténtalo de nuevo
  Movimiento de O: 3 3
  movimiento ilegal (fuera del tablero), inténtalo de nuevo
  Movimiento de O: 2 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | | O
  Movimiento de X: 1 0
     | |
  --- + --- + ---
   X | | X
  --- + --- + ---
     | | O
  Movimiento de O: 1 1
     | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | O
  Movimiento de X: 0 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | O
  Movimiento de O: 2 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | | O
  Movimiento de X: 2 1
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | O
  Movimiento de O: 0 2
   X | | O
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | O
  tic-tac-toe! ¡O gana!
% ./tic-tac-toe
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Ingrese movimientos como "" (sin comillas, cero indexado)
  Movimiento de X: 0 0
   X | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Movimiento de O: 0 1
   X | O |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Movimiento de X: 0 2
   X | O | X
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Movimiento de O: 1 0
   X | O | X
  --- + --- + ---
   O | |
  --- + --- + ---
     | |
  Movimiento de X: 1 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
     | |
  Movimiento de O: 2 0
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | |
  Movimiento de X: 2 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X |
  Movimiento de O: 2 2
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X | O
  Movimiento de X: 1 2
   X | O | X
  --- + --- + ---
   O | X | X
  --- + --- + ---
   O | X | O
  estancamiento ... nadie gana :(
%

¡Eso fue divertido, gracias!

En realidad, pensándolo bien, no necesitas un cuadrado mágico, solo un recuento para cada fila / columna / diagonal. Esto es un poco más fácil que generalizar un cuadrado mágico a matrices n× n, ya que solo necesita contar n.


3

Me hicieron la misma pregunta en una de mis entrevistas. Mis pensamientos: Inicialice la matriz con 0. Mantenga 3 matrices 1) sum_row (tamaño n) 2) sum_column (tamaño n) 3) diagonal (tamaño 2)

Para cada movimiento de (X) disminuya el valor de la casilla en 1 y para cada movimiento de (0) increméntelo en 1. En cualquier momento si la fila / columna / diagonal que se ha modificado en el movimiento actual suma -3 o + 3 significa que alguien ha ganado el juego. Para un sorteo, podemos usar el enfoque anterior para mantener la variable moveCount.

¿Crees que me estoy perdiendo algo?

Editar: Lo mismo se puede usar para la matriz nxn. La suma debe ser incluso +3 o -3.


2

una forma sin bucle para determinar si el punto estaba en el anti diag:

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

2

Llegué tarde a la fiesta, pero quería señalar un beneficio que encontré al usar un cuadrado mágico , es decir, que se puede usar para obtener una referencia al cuadrado que causaría la victoria o la derrota en el siguiente turno, en lugar de solo se usa para calcular cuándo termina un juego.

Toma este cuadrado mágico:

4 9 2
3 5 7
8 1 6

Primero, configure una scoresmatriz que se incrementa cada vez que se realiza un movimiento. Consulte esta respuesta para obtener más detalles. Ahora, si jugamos ilegalmente X dos veces seguidas en [0,0] y [0,1], entonces la scoresmatriz se ve así:

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

Y el tablero se ve así:

X . .
X . .
. . .

Entonces, todo lo que tenemos que hacer para obtener una referencia a qué casilla ganar / bloquear es:

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
    }
  }
}

En realidad, la implementación requiere algunos trucos adicionales, como manejar claves numeradas (en JavaScript), pero lo encontré bastante sencillo y disfruté de las matemáticas recreativas.


1

Hice algunas optimizaciones en las verificaciones de filas, columnas y diagonales. Se decide principalmente en el primer ciclo anidado si necesitamos verificar una columna o diagonal en particular. Por lo tanto, evitamos revisar columnas o diagonales ahorrando tiempo. Esto tiene un gran impacto cuando el tamaño de la placa es mayor y un número significativo de celdas no está lleno.

Aquí está el código java para eso.

    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

Me gusta este algoritmo porque utiliza una representación de la placa de 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
Me gusta más este enfoque. Hubiera sido útil si hubiera explicado qué significan "inicio" e "incr". (Es una forma de expresar todas las "líneas" como un índice inicial y el número de índices para omitir.)
nafg

Por favor, agregue más explicaciones. ¿Cómo se te ocurrió este código?
Farzan

0

Otra opción: genera tu tabla con código. Hasta la simetría, solo hay tres formas de ganar: fila de borde, fila del medio o diagonal. Tome esos tres y gírelos de todas las formas posibles:

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))))

Estas simetrías pueden tener más usos en su código de juego: si llega a un tablero del que ya ha visto una versión rotada, puede simplemente tomar el valor almacenado en caché o el mejor movimiento almacenado en caché de ese (y anular la rotación). Esto suele ser mucho más rápido que evaluar el subárbol del juego.

(Girar hacia la izquierda y hacia la derecha puede ayudar de la misma manera; no fue necesario aquí porque el conjunto de rotaciones de los patrones ganadores es simétrico en espejo).


0

Aquí hay una solución que se me ocurrió, esto almacena los símbolos como caracteres y usa el valor int del char para averiguar si X u O han ganado (mire el código del á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;
        }
    }
}

También aquí están mis pruebas unitarias para validar que 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());
    }
}

Solución completa: https://github.com/nashjain/tictactoe/tree/master/java


0

¿Qué tal un siguiente enfoque para 9 ranuras? Declare 9 variables enteras para una matriz de 3x3 (a1, a2 .... a9) donde a1, a2, a3 representan fila-1 y a1, a4, a7 formarían la columna-1 (entiendes la idea). Utilice '1' para indicar Jugador-1 y '2' para indicar Jugador-2.

Hay 8 posibles combinaciones ganadoras: Win-1: a1 + a2 + a3 (la respuesta podría ser 3 o 6 según el jugador que ganó) Win-2: a4 + a5 + a6 Win-3: a7 + a8 + a9 Win-4 : a1 + a4 + a7 .... Win-7: a1 + a5 + a9 Win-8: a3 + a5 + a7

Ahora sabemos que si el jugador uno cruza a1, entonces necesitamos reevaluar la suma de 3 variables: Win-1, Win-4 y Win-7. Cualquiera que sea 'Win-?' las variables llega a 3 o 6 primero gana el juego. Si la variable Win-1 llega a 6 primero, el Jugador-2 gana.

Entiendo que esta solución no es escalable fácilmente.


0

Esta es una forma realmente sencilla de comprobarlo.

    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

Si tiene un campo de frontera 5 * 5 por ejemplo, utilicé el siguiente método de verificación:

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; 
        }

Creo que está más claro, pero probablemente no sea la forma más óptima.


0

Aquí está mi solución usando una 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

Solución de tiempo constante, se ejecuta en O (8).

Almacene el estado de la placa como un número binario. El bit más pequeño (2 ^ 0) es la fila superior izquierda del tablero. Luego va hacia la derecha, luego hacia abajo.

ES DECIR

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

Cada jugador tiene su propio número binario para representar el estado (porque tic-tac-toe) tiene 3 estados (X, O y espacio en blanco) por lo que un solo número binario no funcionará para representar el estado del tablero para varios jugadores.

Por ejemplo, una tabla 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 los bits del jugador X están separados de los bits del jugador O, esto es obvio porque X no puede poner una pieza donde O tiene una pieza y viceversa.

Para comprobar si un jugador ha ganado, debemos comparar todas las posiciones cubiertas por ese jugador con una posición que sabemos que es una posición ganadora. En este caso, la forma más fácil de hacerlo sería mediante la opción AND de la posición del jugador y la posición ganadora y ver si las dos son iguales.

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

p.ej.

X: 111001010
W: 111000000 // posición ganadora, todos iguales en la primera fila.
------------
&: 111000000

Nota:, X & W = Wentonces X está en un estado de victoria.

Esta es una solución de tiempo constante, depende solo del número de posiciones ganadoras, porque la aplicación de la puerta AND es una operación de tiempo constante y el número de posiciones ganadoras es finito.

También simplifica la tarea de enumerar todos los estados válidos de la placa, solo todos los números representables por 9 bits. Pero, por supuesto, necesita una condición adicional para garantizar que un número es un estado de placa válido (por ejemplo, 0b111111111es un número válido de 9 bits, pero no es un estado de placa válido porque X acaba de realizar todos los turnos).

El número de posibles posiciones ganadoras se puede generar sobre la marcha, pero aquí están de todos modos.

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

Para enumerar todas las posiciones de la placa, puede ejecutar el siguiente ciclo. Aunque dejaré a otra persona para determinar si un número es un estado válido de la junta.

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));

Agregue más descripción y cambie el código para que responda exactamente a la pregunta de OP.
Farzan

0

No estoy seguro de si este enfoque está publicado todavía. Esto debería funcionar para cualquier tablero m * n y se supone que un jugador debe ocupar la posición consecutiva de " pospuestos ". La idea se basa en ejecutar la ventana.

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

Una vez desarrollé un algoritmo para esto como parte de un proyecto científico.

Básicamente, divide el tablero de forma recursiva en un montón de rectos 2x2 superpuestos, probando las diferentes combinaciones posibles para ganar en un cuadrado 2x2.

Es lento, pero tiene la ventaja de funcionar en placas de cualquier tamaño, con requisitos de memoria bastante lineales.

Ojalá pudiera encontrar mi implementación


No es necesaria la recursividad para probar el acabado.
Gabriel Llamas
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.