Algoritmo per determinare la fine del gioco del Tic Tac Toe


97

Ho scritto un gioco di tris in Java e il mio metodo attuale per determinare la fine del gioco tiene conto dei seguenti possibili scenari per la fine del gioco:

  1. Il tabellone è pieno e nessun vincitore è stato ancora dichiarato: il gioco è un pareggio.
  2. Cross ha vinto.
  3. Circle ha vinto.

Sfortunatamente, per farlo, legge un insieme predefinito di questi scenari da una tabella. Questo non è necessariamente un male considerando che ci sono solo 9 spazi su una scacchiera, e quindi il tavolo è un po 'piccolo, ma esiste un modo algoritmico migliore per determinare se il gioco è finito? Determinare se qualcuno ha vinto o meno è la carne del problema, poiché controllare se 9 spazi sono pieni è banale.

Il metodo table potrebbe essere la soluzione, ma in caso contrario, qual è? Inoltre, cosa succede se la tavola non fosse di dimensioni n=9? Che cosa succede se si trattasse di una scheda molto più grande, diciamo n=16, n=25e così via, causando il numero di elementi consecutivamente disposti a vincere per essere x=4, x=5ecc? Un algoritmo generale da usare per tutti n = { 9, 16, 25, 36 ... }?


Aggiungo i miei 2 centesimi per tutte le risposte: sai sempre che hai bisogno di almeno un numero di X o O sul tabellone per vincere (nel normale tabellone 3x3 è 3). Quindi puoi tenere traccia dei conteggi di ciascuno e iniziare a controllare le vincite solo se sono più alte.
Yuval A.

Risposte:


133

Sai che una mossa vincente può avvenire solo dopo che X o O hanno eseguito la loro mossa più recente, quindi puoi cercare solo righe / colonne con diag opzionale contenuto in quella mossa per limitare lo spazio di ricerca quando cerchi di determinare una tavola vincente. Inoltre, poiché c'è un numero fisso di mosse in una partita di pareggio tris una volta che l'ultima mossa è stata fatta se non era una mossa vincente, per impostazione predefinita è una partita di pareggio.

modifica: questo codice è per vincere una tavola n per n con n di fila (la tavola 3x3 richiede 3 di fila, ecc.)

modifica: aggiunto codice per controllare anti diag, non sono riuscito a capire un modo non loop per determinare se il punto era sull'anti diag, quindi è per questo che manca quel passaggio

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
Hai dimenticato di controllare l'anti-diagonale.
rampion

1
Per una scacchiera 3x3, x + y sarà sempre uguale a 2 sull'anti-diagonale, sarà sempre pari al centro e agli angoli della scacchiera e dispari altrove.
Chris Doggett

5
Non capisco la verifica del pareggio alla fine, non dovrebbe non sottrarre 1?
Inez

4
Ci sono casi in cui un giocatore vince nell'ultima (nona) mossa possibile. In quel caso verranno segnalati sia un vincitore che un pareggio ...
Marc

5
@ Roamer-1888 non si tratta di quante righe è composta la tua soluzione, si tratta di ridurre la complessità temporale dell'algoritmo per cercare un vincitore.
Shady

38

puoi usare un quadrato magico http://mathworld.wolfram.com/MagicSquare.html se una riga, una colonna o un diagramma aggiunge fino a 15, il giocatore ha vinto.


3
Come si traduce nel gioco del tris?
Paul Alexander

È un bel po 'di informazioni che non conoscevo, quindi ti ringrazio decisamente per l'informazione. Come ha detto Paul non è immediatamente chiaro in che modo ciò potrebbe aiutare a risolvere il problema in questione, ma sembra che potrebbe giocare parte di una soluzione più completa.
dreadwail

4
sovrapporlo. 1 per il bianco, 2 per il nero e moltiplicare. se qualcosa esce a 15, allora il bianco ha vinto e se è uscito a 30 allora il nero ha vinto.
adk

1
Big O-saggio, è piuttosto economico, specialmente se lo mischi con il controllo cellulare di Hardwareguy. Ogni cella può essere composta solo da 4 possibili tris: per riga, per colonna e due diagonali (barra e barra rovesciata). Quindi, una volta effettuata una mossa, devi solo fare al massimo 4 aggiunte e confronti. La risposta di Hardwareguy richiede 4 (n-1) controlli per ogni mossa, in confronto.
rampion

29
Non potremmo farlo semplicemente con 1 e -1 e sommare ogni riga / colonna / diag per vedere se è n o -n?
Nathan

24

Che ne dici di questo pseudocodice:

Dopo che un giocatore mette giù un pezzo nella posizione (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

Userei un array di char [n, n], con O, X e spazio per vuoto.

  1. semplice.
  2. Un ciclo.
  3. Cinque semplici variabili: 4 numeri interi e uno booleano.
  4. Bilancia a qualsiasi dimensione di n.
  5. Controlla solo il pezzo corrente.
  6. Nessuna magia. :)

se cell [i, n- (i + 1)] = player allora rdiag ++; - Sembra che tra parentesi sarà corretto. Ho ragione?
Pumych

@Pumych, no. Se i==1e n==3, rdiagdeve essere controllato in (1, 3)e (1, 3-1+1)è uguale alle coordinate corrette, ma (1, 3-(1+1))no.
KgOfHedgehogs

Potrebbe aver pensato che le celle fossero a indicizzazione zero.
Matias Grioni

era solo un po 'di cose fuori dalla mia testa ... deve essere risolto durante l'effettiva scrittura del codice :)
Osama Al-Maadeed

21

Questo è simile alla risposta di Osama ALASSIRY , ma scambia spazio costante e tempo lineare con spazio lineare e tempo costante. Cioè, non ci sono cicli dopo l'inizializzazione.

Inizializza una coppia (0,0)per ogni riga, ogni colonna e le due diagonali (diagonale e anti-diagonale). Queste coppie rappresentano la somma (sum,sum)dei pezzi nella riga, colonna o diagonale corrispondente, dove

Un pezzo del giocatore A ha valore (1,0)
Un pezzo del giocatore B ha valore (0,1)

Quando un giocatore piazza un pezzo, aggiorna la coppia di righe, la coppia di colonne e le coppie diagonali corrispondenti (se sulle diagonali). Se una riga, una colonna o una coppia diagonale appena aggiornata è uguale (n,0)o (0,n), rispettivamente, A o B hanno vinto.

Analisi asintotica:

O (1) volta (per mossa)
O (n) spazio (complessivo)

Per l'uso della memoria, usi 4*(n+1) numeri interi.

two_elements * n_rows + two_elements * n_columns +
due_elementi * due_diagonali = 4 * n + 4 numeri interi = 4 (n + 1) numeri interi

Esercizio: Riesci a vedere come testare un pareggio in O (1) volta per mossa? In tal caso, puoi terminare la partita in anticipo con un pareggio.


1
Penso che questo sia meglio di Osama ALASSIRY poiché il suo è più o meno il O(sqrt(n))tempo, ma deve essere fatto dopo ogni mossa, dove n è la dimensione della tavola. Quindi finisci con O(n^1.5). Per questa soluzione ottieni O(n)tempo complessivo.
Matias Grioni

bel modo di vedere questo, ha senso guardare le "soluzioni" effettive ... per 3x3, avresti solo 8 coppie di "booleani" ... Può essere ancora più efficace in termini di spazio se fosse di 2 bit ciascuna ... 16 bit necessari e puoi solo bit per bit OR 1 nel lettore corretto spostato a sinistra nella posizione corretta :)
Osama Al-Maadeed

13

Ecco la mia soluzione che ho scritto per un progetto su cui sto lavorando in javascript. Se non ti dispiace il costo della memoria di pochi array, è probabilmente la soluzione più rapida e semplice che troverai. Presume che tu conosca la posizione dell'ultima mossa.

/*
 * 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
Questo sarebbe l'approccio del grande martello, ma è davvero una soluzione praticabile, soprattutto per il sito come una delle tante soluzioni creative e funzionanti a questo problema. Inoltre è corto, elegante e molto leggibile: per una griglia 3x3 (Tx3 tradizionale) mi piace questo algoritmo.
nocarrier

Questo è fantastico !! Ho scoperto che c'è un piccolo bug nei pattern vincenti, al numero 8, i pattern dovrebbero essere [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

7

L'ho appena scritto per la mia classe di programmazione C.

Lo sto postando perché nessuno degli altri esempi qui funzionerà con una griglia rettangolare di qualsiasi dimensione e con qualsiasi numero di segni consecutivi N in una riga per vincere.

Troverai il mio algoritmo, così com'è, nella checkWinner()funzione. Non usa numeri magici o altro per cercare un vincitore, usa semplicemente quattro cicli for - Il codice è ben commentato quindi lo lascerò parlare da solo immagino.

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

Molto utile. Stavo cercando di trovare qualcosa di più efficiente, come se sapessi N = COL = ROW, potresti ridurlo a qualcosa di molto più semplice, ma non ho trovato nulla di più efficiente per dimensioni arbitrarie della scheda e N.
Hassan

6

Se il tabellone è n × n allora ci sono n righe, n colonne e 2 diagonali. Controlla ognuno di quelli per tutte le X o tutte le O per trovare un vincitore.

Se per vincere servono solo x < n quadrati consecutivi, allora è un po 'più complicato. La soluzione più ovvia è controllare ogni x × x quadrato per un vincitore. Ecco del codice che lo dimostra.

(In realtà non ho testato questo * tosse *, ma si è compilato al primo tentativo, yay me!)

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

Capisco cosa vuoi dire. Ci sarebbero (n * n * 2) risposte totali in un gioco tradizionale n = x. Tuttavia, ciò non funzionerebbe se x (il numero di consecutivi necessari per vincere) fosse inferiore a n. È una buona soluzione però, mi piace di più del tavolo per la sua estensibilità.
dreadwail

Tuttavia, non ho menzionato la possibilità di x <n nel post originale, quindi la tua risposta è ancora azzeccata.
dreadwail

4

Non conosco Java così bene, ma conosco C, quindi ho provato l'idea del quadrato magico di adk (insieme alla restrizione di ricerca di 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;
}

Si compila e verifica bene.

% gcc -o tic-tac-toe tic-tac-toe.c
% ./tic-tac-toe
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Inserisci le mosse come "" (senza virgolette, zero indicizzato)
  La mossa di X: 1 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | |
  La mossa di O: 1 2
  mossa illegale (già eseguita), riprova
  La mossa di O: 3 3
  mossa illegale (fuori dal tabellone), riprova
  La mossa di O: 2 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | | O
  La mossa di X: 1 0
     | |
  --- + --- + ---
   X | | X
  --- + --- + ---
     | | O
  La mossa di O: 1 1
     | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | O
  La mossa di X: 0 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | O
  La mossa di O: 2 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | | O
  La mossa di X: 2 1
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | O
  La mossa di O: 0 2
   X | | O
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | O
  tic-tac-toe! O vince!
% ./tic-tac-toe
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Inserisci le mosse come "" (senza virgolette, zero indicizzato)
  La mossa di X: 0 0
   X | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  La mossa di O: 0 1
   X | O |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  La mossa di X: 0 2
   X | O | X
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  La mossa di O: 1 0
   X | O | X
  --- + --- + ---
   O | |
  --- + --- + ---
     | |
  La mossa di X: 1 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
     | |
  La mossa di O: 2 0
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | |
  La mossa di X: 2 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X |
  La mossa di O: 2 2
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X | O
  La mossa di X: 1 2
   X | O | X
  --- + --- + ---
   O | X | X
  --- + --- + ---
   O | X | O
  stallo ... nessuno vince :(
%

È stato divertente, grazie!

In realtà, a pensarci bene, non serve un quadrato magico, solo un conteggio per ogni riga / colonna / diagonale. Questo è un po 'più facile che generalizzare un quadrato magico in matrici n× n, poiché devi solo contare fino a n.


3

Mi è stata posta la stessa domanda in una delle mie interviste. I miei pensieri: inizializza la matrice con 0. Mantieni 3 array 1) sum_row (dimensione n) 2) sum_column (dimensione n) 3) diagonale (dimensione 2)

Per ogni mossa di (X) decrementa il valore della casella di 1 e per ogni mossa di (0) lo incrementa di 1. In qualsiasi punto se la riga / colonna / diagonale che è stata modificata nella mossa corrente ha somma -3 o + 3 significa che qualcuno ha vinto la partita. Per un pareggio possiamo usare l'approccio sopra per mantenere la variabile moveCount.

Credi che mi manchi qualcosa?

Modifica: lo stesso può essere utilizzato per la matrice nxn. La somma dovrebbe essere pari a +3 o -3.


2

un modo non loop per determinare se il punto era sull'anti diag:

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

2

Sono in ritardo alla festa, ma volevo sottolineare un vantaggio che ho trovato nell'usare un quadrato magico , ovvero che può essere utilizzato per ottenere un riferimento al quadrato che causerebbe la vittoria o la sconfitta al turno successivo, piuttosto che utilizzato solo per calcolare quando una partita è finita.

Prendi questo quadrato magico:

4 9 2
3 5 7
8 1 6

Innanzitutto, imposta un scoresarray che viene incrementato ogni volta che viene effettuato uno spostamento. Vedi questa risposta per i dettagli. Ora, se giochiamo illegalmente X due volte di seguito a [0,0] e [0,1], l' scoresarray avrà questo aspetto:

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

E il tabellone ha questo aspetto:

X . .
X . .
. . .

Quindi, tutto ciò che dobbiamo fare per ottenere un riferimento a quale quadrato vincere / bloccare è:

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

In realtà, l'implementazione richiede alcuni trucchi aggiuntivi, come la gestione dei tasti numerati (in JavaScript), ma l'ho trovato abbastanza semplice e mi è piaciuta la matematica ricreativa.


1

Ho fatto qualche ottimizzazione nella riga, col, controlli diagonali. Viene deciso principalmente nel primo ciclo annidato se dobbiamo controllare una particolare colonna o diagonale. Quindi, evitiamo il controllo di colonne o diagonali risparmiando tempo. Ciò ha un grande impatto quando la dimensione della scheda è maggiore e un numero significativo di celle non viene riempito.

Ecco il codice Java per questo.

    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

Mi piace questo algoritmo in quanto utilizza una rappresentazione 1x9 vs 3x3 del tabellone.

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
Mi piace di più questo approccio. Sarebbe stato d'aiuto se tu avessi spiegato cosa significano "start" e "incr". (È un modo per esprimere tutte le "righe" come indice iniziale e numero di indici da saltare.)
nafg

Si prega di aggiungere ulteriori spiegazioni. Come sei arrivato a questo codice?
Farzan

0

Un'altra opzione: genera la tua tabella con il codice. Fino alla simmetria, ci sono solo tre modi per vincere: fila sul bordo, fila centrale o diagonale. Prendi quei tre e girali in ogni modo possibile:

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

Queste simmetrie possono avere più usi nel tuo codice di gioco: se arrivi a una scheda di cui hai già visto una versione ruotata, puoi semplicemente prendere il valore memorizzato nella cache o la mossa migliore memorizzata nella cache da quello (e annullarne la rotazione). Di solito è molto più veloce della valutazione della sottostruttura del gioco.

(Capovolgere a sinistra ea destra può aiutare allo stesso modo; non era necessario qui perché l'insieme di rotazioni dei modelli vincenti è simmetrico a specchio.)


0

Ecco una soluzione che ho trovato, questa memorizza i simboli come caratteri e usa il valore int del carattere per capire se X o O hanno vinto (guarda il codice dell'arbitro)

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

Anche qui ci sono i miei test unitari per convalidare che funzioni effettivamente

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

Soluzione completa: https://github.com/nashjain/tictactoe/tree/master/java


0

Che ne dici di un approccio successivo per 9 slot? Dichiara 9 variabili intere per una matrice 3x3 (a1, a2 .... a9) dove a1, a2, a3 rappresentano la riga-1 e a1, a4, a7 formerebbero la colonna-1 (hai un'idea). Usa "1" per indicare Player-1 e "2" per indicare Player-2.

Ci sono 8 possibili combinazioni di vincita: Win-1: a1 + a2 + a3 (la risposta potrebbe essere 3 o 6 in base a quale giocatore ha vinto) 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

Ora sappiamo che se il giocatore uno incrocia a1, allora dobbiamo rivalutare la somma di 3 variabili: Win-1, Win-4 e Win-7. Qualunque sia "Win-?" le variabili raggiungono 3 o 6 prima vince la partita. Se la variabile Win-1 raggiunge prima 6, vince il Player-2.

Capisco che questa soluzione non è scalabile facilmente.


0

Questo è un modo molto semplice per controllare.

    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 hai un campo di confine 5 * 5 per esempio, ho usato il metodo di controllo successivo:

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

Penso che sia più chiaro, ma probabilmente non è il modo più ottimale.


0

Ecco la mia soluzione utilizzando un array bidimensionale:

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

Soluzione a tempo costante, viene eseguita in O (8).

Memorizza lo stato della scheda come numero binario. Il bit più piccolo (2 ^ 0) è la riga in alto a sinistra del tabellone. Quindi va verso destra, poi verso il basso.

IE

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

Ogni giocatore ha il proprio numero binario per rappresentare lo stato (perché tic-tac-toe) ha 3 stati (X, O e vuoto) quindi un singolo numero binario non funzionerà per rappresentare lo stato del tabellone per più giocatori.

Ad esempio, una scheda come:

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

Si noti che i bit per il giocatore X sono disgiunti dai bit per il giocatore O, questo è ovvio perché X non può mettere un pezzo dove O ha un pezzo e viceversa.

Per verificare se un giocatore ha vinto, dobbiamo confrontare tutte le posizioni coperte da quel giocatore con una posizione che sappiamo essere una posizione vincente. In questo caso, il modo più semplice per farlo sarebbe collegando AND la posizione del giocatore e la posizione di vittoria e vedere se le due sono uguali.

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

per esempio.

X: 111001010
W: 111000000 // vince la posizione, tutti uguali sulla prima riga.
------------
&: 111000000

Nota:, X & W = Wquindi X è in uno stato vincente.

Questa è una soluzione a tempo costante, dipende solo dal numero di posizioni di vittoria, perché l'applicazione di AND-gate è un'operazione a tempo costante e il numero di posizioni di vittoria è finito.

Inoltre semplifica il compito di enumerare tutti gli stati validi della scheda, i loro solo tutti i numeri rappresentabili da 9 bit. Ma ovviamente è necessaria una condizione aggiuntiva per garantire che un numero sia uno stato della scheda valido (ad es. 0b111111111È un numero a 9 bit valido, ma non è uno stato della scheda valido perché X ha appena svolto tutti i turni).

Il numero di possibili posizioni vincenti può essere generato al volo, ma qui sono comunque.

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

Per enumerare tutte le posizioni della scheda, è possibile eseguire il seguente ciclo. Anche se lascerò determinare se un numero è uno stato del consiglio valido fino a qualcun altro.

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

Si prega di aggiungere più descrizione e modificare il codice in modo che risponda esattamente alla domanda di OP.
Farzan

0

Non sono sicuro che questo approccio sia stato ancora pubblicato. Questo dovrebbe funzionare per qualsiasi m * n board e un giocatore dovrebbe occupare la posizione consecutiva " winnerPos ". L'idea si basa sulla finestra in esecuzione.

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 volta ho sviluppato un algoritmo per questo come parte di un progetto scientifico.

Fondamentalmente dividi ricorsivamente il tabellone in un mazzo di rettangoli 2x2 sovrapposti, testando le diverse combinazioni possibili per vincere su un quadrato 2x2.

È lento, ma ha il vantaggio di lavorare su schede di qualsiasi dimensione, con requisiti di memoria abbastanza lineari.

Vorrei poter trovare la mia implementazione


La ricorsione per testare la finitura non è necessaria.
Gabriel Llamas
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.