Algorithme pour déterminer la fin de la partie Tic Tac Toe

97

J'ai écrit un jeu de tic-tac-toe en Java, et ma méthode actuelle de détermination de la fin du jeu tient compte des scénarios possibles suivants pour la fin du jeu:

  1. Le plateau est plein et aucun gagnant n'a encore été déclaré: le jeu est un match nul.
  2. Cross a gagné.
  3. Circle a gagné.

Malheureusement, pour ce faire, il lit un ensemble prédéfini de ces scénarios à partir d'une table. Ce n'est pas forcément mauvais étant donné qu'il n'y a que 9 espaces sur un plateau, et donc la table est un peu petite, mais y a-t-il une meilleure façon algorithmique de déterminer si le jeu est terminé? La détermination de savoir si quelqu'un a gagné ou non est la viande du problème, car vérifier si 9 espaces sont pleins est trivial.

La méthode de la table pourrait être la solution, mais sinon, quelle est-elle? Et si la planche n'était pas de taille n=9? Et si elle était un conseil beaucoup plus, disons n=16, n=25et ainsi de suite, ce qui provoque le nombre d'éléments placés consécutivement à gagner à être x=4, x=5etc? Un algorithme général à utiliser pour tous n = { 9, 16, 25, 36 ... }?

dreadwail
la source
J'ajoute mes 2 centimes pour toutes les réponses: vous savez toujours que vous avez besoin d'au moins un certain nombre de X ou d'O sur le tableau pour gagner (dans le tableau normal 3x3, c'est 3). Ainsi, vous pouvez suivre les décomptes de chacun et ne commencer à vérifier les gains que s'ils sont plus élevés.
Yuval A.

Réponses:

133

Vous savez qu'un coup gagnant ne peut se produire qu'après que X ou O ait effectué son coup le plus récent, vous ne pouvez donc rechercher que la ligne / colonne avec un diag optionnel contenu dans ce coup pour limiter votre espace de recherche lorsque vous essayez de déterminer un tableau gagnant. De plus, comme il y a un nombre fixe de coups dans un jeu de tirage au sort tic-tac-toe une fois que le dernier coup est effectué s'il ne s'agissait pas d'un coup gagnant, il s'agit par défaut d'un jeu de tirage au sort.

edit: ce code est pour un tableau n par n avec n dans une rangée pour gagner (le tableau 3x3 nécessite 3 dans une rangée, etc.)

edit: code ajouté pour vérifier l'anti diag, je ne pouvais pas trouver un moyen sans boucle pour déterminer si le point était sur l'anti diag, c'est pourquoi cette étape est manquante

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
        }
    }
}
Hardwareguy
la source
6
Vous avez oublié de vérifier l'anti-diagonale.
rampion
1
Pour un tableau 3x3, x + y sera toujours égal à 2 sur l'anti-diagonale, sera toujours pair au centre et dans les coins du tableau, et impair ailleurs.
Chris Doggett
5
Je ne comprends pas le tirage au sort à la fin, ne devrait-il pas soustraire 1?
Inez
4
Il y a des cas où un joueur gagne dans le dernier coup possible (9e). Dans ce cas, un gagnant et un tirage au sort seront signalés ...
Marc
5
@ Roamer-1888 il ne s'agit pas du nombre de lignes de votre solution, il s'agit de réduire la complexité temporelle de l'algorithme pour rechercher un gagnant.
Shady
38

vous pouvez utiliser un carré magique http://mathworld.wolfram.com/MagicSquare.html si une ligne, une colonne ou un diag ajoute jusqu'à 15, alors un joueur a gagné.

adk
la source
3
Comment cela se traduit-il dans le jeu du tic-tac-toe?
Paul Alexander
C'est une information intéressante que je ne connaissais pas, donc je vous remercie vraiment pour l'information. Comme Paul l'a mentionné, il n'est pas immédiatement clair comment cela aiderait à résoudre le problème, mais il semble que cela pourrait jouer un rôle dans une solution plus complète.
dreadwail
4
superposez-le. 1 pour le blanc, 2 pour le noir et multiplier. si quelque chose sort à 15, alors les blancs ont gagné et s'il sort à 30, les noirs ont gagné.
adk le
1
Big O-wise, c'est assez bon marché, surtout si vous le mélangez avec la vérification cellulaire de Hardwareguy. Chaque cellule ne peut être que dans 4 tic-tac-toes possibles: ligne, colonne et deux diagonales (barre oblique et barre oblique inverse). Ainsi, une fois qu'un déménagement a été fait, vous n'avez qu'à faire au maximum 4 ajouts et comparaisons. La réponse de Hardwareguy nécessite 4 (n-1) contrôles pour chaque coup, par comparaison.
rampion
29
Ne pourrions-nous pas simplement faire cela avec 1 et -1 et additionner chaque ligne / colonne / diag pour voir si c'est n ou -n?
Nathan
24

Que diriez-vous de ce pseudocode:

Après qu'un joueur pose une pièce à la position (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

J'utiliserais un tableau de char [n, n], avec O, X et espace pour vide.

  1. Facile.
  2. Une boucle.
  3. Cinq variables simples: 4 entiers et un booléen.
  4. Échelle à n'importe quelle taille de n.
  5. Vérifie uniquement la pièce actuelle.
  6. Pas de magie. :)
Oussama Al-Maadeed
la source
si cellule [i, n- (i + 1)] = joueur alors rdiag ++; - On dirait qu'avec des parenthèses, ce sera correctement. Ai-je raison?
Pumych
@Pumych, non. Si i==1et n==3, rdiagdoit être vérifié à (1, 3)et (1, 3-1+1)est égal aux coordonnées correctes, mais (1, 3-(1+1))non.
KgOfHedgehogs
Il pensait peut-être que les cellules étaient indexées à zéro.
Matias Grioni
c'était juste des trucs qui me
sortaient
21

Ceci est similaire à la réponse d'Oussama ALASSIRY , mais il échange un espace constant et un temps linéaire contre un espace linéaire et un temps constant. Autrement dit, il n'y a pas de boucle après l'initialisation.

Initialisez une paire (0,0)pour chaque ligne, chaque colonne et les deux diagonales (diagonale et anti-diagonale). Ces paires représentent l'accumulation (sum,sum)des pièces dans la ligne, la colonne ou la diagonale correspondante, où

Une pièce du joueur A a une valeur (1,0)
Une pièce du joueur B a une valeur (0,1)

Lorsqu'un joueur place une pièce, mettez à jour la paire de lignes, la paire de colonnes et les paires diagonales correspondantes (si sur les diagonales). Si une ligne, une colonne ou une paire diagonale nouvellement mise à jour est égale à (n,0)ou (0,n)alors A ou B a gagné, respectivement.

Analyse asymptotique:

O (1) fois (par coup)
Espace O (n) (global)

Pour l'utilisation de la mémoire, vous utilisez des 4*(n+1)entiers.

two_elements * n_rows + two_elements * n_columns +
two_elements * two_diagonals = 4 * n + 4 entiers = 4 (n + 1) entiers

Exercice: Pouvez-vous voir comment tester un match nul en O (1) fois par coup? Si tel est le cas, vous pouvez terminer le jeu tôt sur un match nul.

CJ Gaconnet
la source
1
Je pense que c'est mieux que celui d'Oussama ALASSIRY car le sien est à peu près le O(sqrt(n))temps, mais doit être fait après chaque mouvement, où n est la taille du plateau. Alors vous vous retrouvez avec O(n^1.5). Pour cette solution, vous obtenez du O(n)temps global.
Matias Grioni du
belle façon de voir cela, il est logique de regarder les "solutions" réelles ... pour 3x3, vous auriez juste 8 paires de "booléens" ... Cela peut être encore plus efficace si c'était 2 bits chacun ... 16 bits nécessaires et vous pouvez juste au niveau du bit OU 1 dans le bon joueur décalé à gauche au bon endroit :)
Osama Al-Maadeed
13

Voici ma solution que j'ai écrite pour un projet sur lequel je travaille en javascript. Si le coût de la mémoire de quelques baies ne vous dérange pas, c'est probablement la solution la plus rapide et la plus simple que vous trouverez. Cela suppose que vous connaissez la position du dernier coup.

/*
 * 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;
}
Jack Allan
la source
2
Ce serait l'approche du gros marteau, mais c'est en effet une solution viable, en particulier pour le site comme l'une d'une multitude de solutions créatives et fonctionnelles à ce problème. Aussi son court, élégant et très lisible - pour une grille 3x3 (Tx3 traditionnel) j'aime cet algorithme.
nocarrier
Celui-ci est génial !! J'ai trouvé qu'il y avait un petit bug dans les patterns gagnants, à la possibilité 8, les patterns devraient être [6,7], [0,4] et [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

Je viens d'écrire ceci pour ma classe de programmation C.

Je le publie car aucun des autres exemples ici ne fonctionnera avec une grille rectangulaire de toute taille , et n'importe quel nombre de marques consécutives N -in-a-row pour gagner.

Vous trouverez mon algorithme, tel quel, dans la checkWinner()fonction. Il n'utilise pas de nombres magiques ou quoi que ce soit de fantaisie pour rechercher un gagnant, il utilise simplement quatre boucles for - Le code est bien commenté donc je vais le laisser parler de lui-même, je suppose.

// 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.
mattR
la source
Très utile. J'essayais de trouver quelque chose de plus efficace, comme si vous connaissez N = COL = ROW, vous pourriez réduire cela à quelque chose de beaucoup plus simple, mais je n'ai rien trouvé de plus efficace pour une taille de carte arbitraire et N.
Hassan
6

Si la carte est n × n alors il y a n lignes, n colonnes et 2 diagonales. Vérifiez chacun de ceux-ci pour tous les X ou tous les O pour trouver un gagnant.

S'il ne faut que x < n carrés consécutifs pour gagner, c'est un peu plus compliqué. La solution la plus évidente est de vérifier chaque x × x carré pour un gagnant. Voici un code qui le démontre.

(Je n'ai pas réellement testé cette * toux *, mais elle s'est compilée du premier coup, yay moi!)

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;
        }
    }
}
John Kugelman
la source
Je vois ce que tu veux dire. Il y aurait (n * n * 2) réponses totales dans un jeu traditionnel n = x. Cela ne fonctionnerait pas si x (le nombre de consécutifs nécessaires pour gagner) était inférieur à n. C'est une bonne solution cependant, je l'aime mieux que la table pour son extensibilité.
dreadwail
Je n'ai pas mentionné la possibilité de x <n dans le message d'origine, donc votre réponse est toujours juste.
dreadwail
4

Je ne connais pas très bien Java, mais je connais C, alors j'ai essayé l'idée de carré magique d'adk (avec la restriction de recherche 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;
}

Il compile et teste bien.

% gcc -o tic-tac-toe tic-tac-toe.c
% ./tic-tac-toe
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Entrez les mouvements comme "" (pas de guillemets, zéro indexé)
  Coup de X: 1 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | |
  Coup d'O: 1 2
  déplacement illégal (déjà effectué), réessayez
  Coup d'O: 3 3
  déplacement illégal (hors du plateau), réessayez
  Coup d'O: 2 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | | O
  Coup de X: 1 0
     | |
  --- + --- + ---
   X | | X
  --- + --- + ---
     | | O
  Coup d'O: 1 1
     | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | O
  Coup de X: 0 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | O
  Coup d'O: 2 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | | O
  Coup de X: 2 1
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | O
  Coup de O: 0 2
   X | | O
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | O
  tic-tac-toe! O gagne!
% ./tic-tac-toe
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Entrez les mouvements comme "" (pas de guillemets, zéro indexé)
  Coup de X: 0 0
   X | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Coup de O: 0 1
   X | O |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Coup de X: 0 2
   X | O | X
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Coup de O: 1 0
   X | O | X
  --- + --- + ---
   O | |
  --- + --- + ---
     | |
  Coup de X: 1 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
     | |
  Coup d'O: 2 0
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | |
  Coup de X: 2 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X |
  Coup d'O: 2 2
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X | O
  Coup de X: 1 2
   X | O | X
  --- + --- + ---
   O | X | X
  --- + --- + ---
   O | X | O
  impasse ... personne ne gagne :(
%

C'était amusant, merci!

En fait, en y réfléchissant, vous n'avez pas besoin d'un carré magique, juste un décompte pour chaque ligne / colonne / diagonale. C'est un peu plus facile que de généraliser un carré magique aux matrices n× n, car il suffit de compter jusqu'à n.

rampion
la source
3

On m'a posé la même question lors d'une de mes interviews. Mes pensées: Initialisez la matrice avec 0. Gardez 3 tableaux 1) sum_row (taille n) 2) sum_column (taille n) 3) diagonale (taille 2)

Pour chaque mouvement de (X), décrémentez la valeur de la boîte de 1 et pour chaque mouvement de (0), incrémentez-la de 1. À tout moment si la ligne / colonne / diagonale qui a été modifiée dans le mouvement actuel a une somme de -3 ou + 3 signifie que quelqu'un a gagné la partie. Pour un tirage au sort, nous pouvons utiliser l'approche ci-dessus pour conserver la variable moveCount.

Pensez-vous que je manque quelque chose?

Edit: la même chose peut être utilisée pour la matrice nxn. La somme doit être paire +3 ou -3.

Piyush Beli
la source
2

un moyen sans boucle pour déterminer si le point était sur l'anti-diag:

`if (x + y == n - 1)`
Jeff
la source
2

Je suis en retard à la fête, mais je voulais souligner un avantage que j'ai trouvé à utiliser un carré magique , à savoir qu'il peut être utilisé pour obtenir une référence au carré qui causerait la victoire ou la perte au tour suivant, plutôt que juste utilisé pour calculer quand une partie est terminée.

Prenez ce carré magique:

4 9 2
3 5 7
8 1 6

Tout d'abord, configurez un scorestableau qui est incrémenté à chaque fois qu'un déplacement est effectué. Voir cette réponse pour plus de détails. Maintenant, si nous lisons illégalement X deux fois de suite à [0,0] et [0,1], alors le scorestableau ressemble à ceci:

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

Et le tableau ressemble à ceci:

X . .
X . .
. . .

Ensuite, tout ce que nous avons à faire pour obtenir une référence sur la case à gagner / bloquer est:

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 réalité, la mise en œuvre nécessite quelques astuces supplémentaires, comme la gestion des touches numérotées (en JavaScript), mais je l'ai trouvée assez simple et j'ai apprécié les mathématiques récréatives.

gwg
la source
1

J'ai fait quelques optimisations dans les contrôles de ligne, de col et de diagonale. C'est principalement décidé dans la première boucle imbriquée si nous devons vérifier une colonne ou une diagonale particulière. On évite ainsi de vérifier les colonnes ou les diagonales pour gagner du temps. Cela a un impact important lorsque la taille de la carte est supérieure et qu'un nombre important de cellules ne sont pas remplies.

Voici le code java pour cela.

    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;
}
sanjiv
la source
1

J'aime cet algorithme car il utilise une représentation 1x9 vs 3x3 de la carte.

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;
}
Scott Duchin
la source
1
J'aime le plus cette approche. Cela aurait aidé si vous aviez expliqué ce que signifient «start» et «incr». (C'est une façon d'exprimer toutes les "lignes" comme un index de départ et le nombre d'index à sauter.)
nafg
Veuillez ajouter plus d'explications. Comment avez-vous élaboré ce code?
Farzan
0

Autre option: générer votre table avec du code. Jusqu'à la symétrie, il n'y a que trois façons de gagner: la rangée de bord, la rangée du milieu ou la diagonale. Prenez ces trois et faites-les tourner de toutes les manières possibles:

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

Ces symétries peuvent avoir plus d'utilisations dans votre code de jeu: si vous arrivez sur un tableau dont vous avez déjà vu une version pivotée, vous pouvez simplement prendre la valeur mise en cache ou le meilleur mouvement mis en cache à partir de celle-ci (et la annuler). C'est généralement beaucoup plus rapide que d'évaluer le sous-arbre du jeu.

(Le basculement à gauche et à droite peut aider de la même manière; ce n'était pas nécessaire ici car l'ensemble des rotations des motifs gagnants est symétrique en miroir.)

Darius Bacon
la source
0

Voici une solution que j'ai trouvée, cela stocke les symboles sous forme de caractères et utilise la valeur int du char pour déterminer si X ou O a gagné (regardez le code de l'arbitre)

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

Voici également mes tests unitaires pour valider que cela fonctionne réellement

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

Solution complète: https://github.com/nashjain/tictactoe/tree/master/java

Naresh Jain
la source
0

Que diriez-vous d'une approche suivante pour 9 emplacements? Déclarez 9 variables entières pour une matrice 3x3 (a1, a2 .... a9) où a1, a2, a3 représentent la ligne 1 et a1, a4, a7 formeraient la colonne 1 (vous voyez l'idée). Utilisez «1» pour indiquer le joueur 1 et «2» pour indiquer le joueur 2.

Il y a 8 combinaisons de gains possibles: Win-1: a1 + a2 + a3 (la réponse peut être 3 ou 6 selon le joueur qui a gagné) 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

Maintenant, nous savons que si le joueur 1 traverse a1, nous devons réévaluer la somme de 3 variables: Win-1, Win-4 et Win-7. Quel que soit «Win-?» variables atteint 3 ou 6 remporte la première partie. Si la variable Win-1 atteint d'abord 6, le joueur 2 gagne.

Je comprends que cette solution n'est pas évolutive facilement.

utilisateur3071398
la source
0

C'est un moyen très simple de vérifier.

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

}

lusion
la source
0

Si vous avez le champ pensionnaire 5 * 5 pour exemple, j'ai utilisé la méthode suivante de vérification:

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

Je pense que c'est plus clair, mais ce n'est probablement pas le moyen le plus optimal.

Aleksei Moshkov
la source
0

Voici ma solution en utilisant un tableau à 2 dimensions:

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;
}
user3743369
la source
0

Solution à temps constant, s'exécute en O (8).

Stockez l'état de la carte sous forme de nombre binaire. Le plus petit bit (2 ^ 0) est la rangée supérieure gauche de la carte. Ensuite, il va vers la droite, puis vers le bas.

C'EST À DIRE

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

Chaque joueur a son propre nombre binaire pour représenter l'état (car tic-tac-toe) a 3 états (X, O et vide) donc un seul nombre binaire ne fonctionnera pas pour représenter l'état du plateau pour plusieurs joueurs.

Par exemple, un tableau comme:

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

Notez que les bits pour le joueur X sont disjoints des bits pour le joueur O, c'est évident car X ne peut pas mettre un morceau où O a un morceau et vice versa.

Pour vérifier si un joueur a gagné, nous devons comparer toutes les positions couvertes par ce joueur à une position que nous savons être une position gagnante. Dans ce cas, le moyen le plus simple de le faire serait de passer ET de la position du joueur et de la position gagnante et de voir si les deux sont égaux.

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

par exemple.

X: 111001010
W: 111000000 // position gagnante, tous identiques sur la première ligne.
------------
&: 111000000

Remarque: X & W = W donc X est dans un état gagnant.

C'est une solution à temps constant, cela ne dépend que du nombre de positions gagnantes, car l'application de la porte ET est une opération à temps constant et le nombre de positions gagnantes est fini.

Cela simplifie également la tâche d'énumération de tous les états valides de la carte, leur juste tous les nombres représentables par 9 bits. Mais bien sûr, vous avez besoin d'une condition supplémentaire pour garantir qu'un numéro est un état de carte valide (par exemple.0b111111111 est un nombre valide de 9 bits, mais ce n'est pas un état de carte valide car X vient de prendre tous les tours).

Le nombre de positions gagnantes possibles peut être généré à la volée, mais les voici quand même.

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

Pour énumérer toutes les positions de la carte, vous pouvez exécuter la boucle suivante. Bien que je laisse à quelqu'un d'autre déterminer si un numéro est un état de carte valide.

REMARQUE: (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));
Alexsalo
la source
Veuillez ajouter plus de description et changer le code pour qu'il réponde exactement à la question d'OP.
Farzan
0

Je ne sais pas si cette approche est encore publiée. Cela devrait fonctionner pour n'importe quel tableau m * n et un joueur est censé occuper une position consécutive " winnerPos ". L'idée est basée sur la fenêtre en cours d'exécution.

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;
}
Aigle
la source
-2

J'ai développé un algorithme pour cela dans le cadre d'un projet scientifique une fois.

Vous divisez le plateau de manière récursive en un tas de rects 2x2 qui se chevauchent, en testant les différentes combinaisons possibles pour gagner sur un carré 2x2.

Il est lent, mais il a l'avantage de fonctionner sur n'importe quelle carte de taille, avec des besoins en mémoire assez linéaires.

J'aimerais pouvoir trouver ma mise en œuvre

bgw
la source
La récursivité pour tester la finition n'est pas nécessaire.
Gabriel Llamas