确定Tic Tac Toe游戏的算法

时间:2009-06-29 02:18:22

标签: java algorithm tic-tac-toe

我在Java中编写了一个井字游戏,我目前确定游戏结束的方法考虑了游戏结束的以下可能情况:

  1. 董事会已经满员,尚未宣布获胜者:比赛是平局。
  2. Cross赢了。
  3. Circle赢了。
  4. 不幸的是,要这样做,它会从表中读取这些方案的预定义集合。考虑到电路板上只有9个空格,这并不一定是坏的,因此表格有点小,但有没有更好的算法来确定游戏是否结束?确定某人是否赢了是问题的关键,因为检查9个空格是否已满是微不足道的。

    表方法可能是解决方案,但如果没有,那是什么?另外,如果电路板的尺寸不是n=9怎么办?如果它是一个更大的电路板,比如说n=16n=25等,会导致连续放置的项目数量为x=4x=5等等。 ?用于所有n = { 9, 16, 25, 36 ... }的一般算法?

25 个答案:

答案 0 :(得分:119)

您知道获胜的举动只能在X或O进行最近的移动之后发生,因此您只能搜索包含在该移动中的可选诊断的行/列,以在尝试确定获胜时限制您的搜索空间板。此外,由于在最后一次移动中,如果它不是一个获胜的移动,那么在平局游戏中有一定数量的移动它默认为抽奖游戏。

编辑:此代码用于n个n板,连续n个赢(3x3板请求连续3个等)

编辑:添加代码来检查反诊断,我无法找出一个非循环方式来确定该点是否在反诊断上,这就是为什么缺少该步骤

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

答案 1 :(得分:32)

你可以使用魔方({3}},如果任何行,列或诊断加起来,那么玩家就赢了。

答案 2 :(得分:19)

这类似于Osama ALASSIRY's answer,但它为线性空间和恒定时间交换恒定空间和线性时间。也就是说,初始化后没有循环。

为每行,每列和两个对角线(对角线和反对角线)初始化一对(0,0)。这些对表示相应行,列或对角线中片段的累积(sum,sum),其中

A piece from player A has value (1,0)
A piece from player B has value (0,1)

当玩家放置一块时,更新相应的行对,列对和对角线对(如果在对角线上)。如果任何新更新的行,列或对角线对等于(n,0)(0,n),则A或B分别获胜。

渐近分析:

O(1) time (per move)
O(n) space (overall)

对于内存使用,使用4*(n+1)整数。

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

练习:你能看到如何在O(1)时间内进行抽签测试吗?如果是这样,你可以在平局的早期结束比赛。

答案 3 :(得分:18)

这个伪代码怎么样:

玩家在位置(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

我使用char [n,n]数组,其中O,X和空格为空。

  1. 简单。
  2. 一个循环。
  3. 五个简单变量:4个整数和一个布尔值。
  4. 缩放到任何大小的n。
  5. 仅检查当前作品。
  6. 没有魔力。 :)

答案 4 :(得分:11)

这是我为一个我在javascript中工作的项目写的解决方案。如果你不介意几个阵列的内存成本,它可能是你能找到的最快最简单的解决方案。它假设你知道最后一步的位置。

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

答案 5 :(得分:6)

我刚为C编程课写过这篇文章。

我发布它是因为此处的其他示例都不适用于任何大小的矩形网格,并且任何数字 N 连续标记都可以获胜

你会在checkWinner()函数中找到我的算法,例如它。它没有使用魔术数字或任何想要检查胜利者的东西,它只是使用四个for循环 - 代码得到很好的评论,所以我会让它自己说话我猜。

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

答案 6 :(得分:5)

如果电路板 n × n ,那么 n 行, n 列和2个对角线。检查所有X或全O的每一个以找到胜利者。

如果只需要 x &lt; n 连续的方块赢了,然后它有点复杂了。最明显的解决方案是检查每个 x × x 方块以获得胜利者。这里有一些代码可以证明这一点。

(我实际上没有测试过这个*咳嗽*,但在第一次尝试时编译了,伙计我!)

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

答案 7 :(得分:3)

我不太了解Java,但我知道C,所以我尝试了adk's magic square idea(以及Hardwareguy's search restriction)。

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

它编译和测试很好。

% gcc -o tic-tac-toe tic-tac-toe.c
% ./tic-tac-toe
     |   |
  ---+---+---
     |   |
  ---+---+---
     |   |
  Enter moves as " " (no quotes, zero indexed)
  X's move: 1 2
     |   |
  ---+---+---
     |   | X
  ---+---+---
     |   |
  O's move: 1 2
  illegal move (already taken), try again
  O's move: 3 3
  illegal move (off the board), try again
  O's move: 2 2
     |   |
  ---+---+---
     |   | X
  ---+---+---
     |   | O
  X's move: 1 0
     |   |
  ---+---+---
   X |   | X
  ---+---+---
     |   | O
  O's move: 1 1
     |   |
  ---+---+---
   X | O | X
  ---+---+---
     |   | O
  X's move: 0 0
   X |   |
  ---+---+---
   X | O | X
  ---+---+---
     |   | O
  O's move: 2 0
   X |   |
  ---+---+---
   X | O | X
  ---+---+---
   O |   | O
  X's move: 2 1
   X |   |
  ---+---+---
   X | O | X
  ---+---+---
   O | X | O
  O's move: 0 2
   X |   | O
  ---+---+---
   X | O | X
  ---+---+---
   O | X | O
  tic-tac-toe! O wins!
% ./tic-tac-toe
     |   |
  ---+---+---
     |   |
  ---+---+---
     |   |
  Enter moves as " " (no quotes, zero indexed)
  X's move: 0 0
   X |   |
  ---+---+---
     |   |
  ---+---+---
     |   |
  O's move: 0 1
   X | O |
  ---+---+---
     |   |
  ---+---+---
     |   |
  X's move: 0 2
   X | O | X
  ---+---+---
     |   |
  ---+---+---
     |   |
  O's move: 1 0
   X | O | X
  ---+---+---
   O |   |
  ---+---+---
     |   |
  X's move: 1 1
   X | O | X
  ---+---+---
   O | X |
  ---+---+---
     |   |
  O's move: 2 0
   X | O | X
  ---+---+---
   O | X |
  ---+---+---
   O |   |
  X's move: 2 1
   X | O | X
  ---+---+---
   O | X |
  ---+---+---
   O | X |
  O's move: 2 2
   X | O | X
  ---+---+---
   O | X |
  ---+---+---
   O | X | O
  X's move: 1 2
   X | O | X
  ---+---+---
   O | X | X
  ---+---+---
   O | X | O
  stalemate... nobody wins :(
%

这很有趣,谢谢!

实际上,考虑到它,你不需要一个魔方,只需要每行/每列/对角线的计数。这比将{#1}}×n矩阵概括为方形要容易一些,因为您只需要计算到n

答案 8 :(得分:3)

在我的一次采访中,我被问到了同样的问题。 我的想法: 用0初始化矩阵。 保留3个阵列 1)sum_row(大小n) 2)sum_column(大小n) 3)对角线(尺寸2)

对于每次移动,通过(X)将框值减1,对于每次移动,按(0)将其增加1。 在任何时候,如果在当前移动中被修改的行/列/对角线具有-3或+3的总和意味着有人赢得了游戏。 对于平局,我们可以使用上述方法来保持moveCount变量。

你认为我错过了什么吗?

编辑:相同可用于nxn矩阵。总和应该是+3或-3。

答案 9 :(得分:2)

一种非循环方式来确定该点是否在抗诊断上:

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

答案 10 :(得分:1)

我在行,对角线,对角线检查中进行了一些优化。如果我们需要检查特定的列或对角线,它主要在第一个嵌套循环中决定。因此,我们避免检查列或对角线,以节省时间。当电路板尺寸更大且未填充大量电池时,这会产生很大的影响。

这是java代码。

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

答案 11 :(得分:1)

我喜欢这个算法,因为它使用了1x9对3x3的电路板表示。

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

答案 12 :(得分:1)

我迟到了,但是我想指出我发现使用magic square的一个好处,即它可以用来获得引起胜负的广场的引用。下一回合,而不仅仅是用于计算游戏何时结束。

采取这个神奇的方块:

4 9 2
3 5 7
8 1 6

首先,设置一个scores数组,每次移动时该数组都会递增。有关详细信息,请参阅this answer。现在如果我们在[0,0]和[0,1]连续两次非法播放X,那么scores数组看起来像这样:

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

董事会看起来像这样:

X . .
X . .
. . .

然后,我们要做的就是获得对哪个方块获胜/阻止的引用:

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

实际上,实现需要一些额外的技巧,比如处理数字键(在JavaScript中),但我发现它非常简单并且喜欢休闲数学。

答案 13 :(得分:1)

超高效的位板

让我们将游戏存储在一个二进制整数中,然后只需一步即可评估所有内容!

  • 我们知道 X 的移动占用了 9 位:xxx xxx xxx
  • 我们知道 O 的移动占用了 9 位:ooo ooo ooo

因此,棋盘位置可以仅用 18 位表示:xoxoxo xoxoxo xoxoxo

但是,虽然这看起来很有效,但它并不能帮助我们确定胜利。我们需要一种更有用的位模式……一种不仅可以对移动进行编码,而且还可以以合理的方式对行、列和对角线进行编码的位模式。

我会通过为每个棋盘位置使用一个巧妙的整数值来做到这一点。

选择更有用的表示

首先,我们需要一个棋盘符号,以便我们可以讨论这个问题。所以,类似于国际象棋,让我们用字母对行进行编号,用数字对列进行编号 - 这样我们就知道我们在谈论哪个方块

<头>
1 2 3
A a1 a2 a3
B b1 b2 b3
C c1 c2 c3

让我们给每一个二进制值。

a1 = 100 000 000 100 000 000 100 000 ; Row A Col 1 (top left corner)
a2 = 010 000 000 000 100 000 000 000 ; Row A Col 2 (top edge)
a3 = 001 000 000 000 000 100 000 100 ; Row A Col 3 (top right corner)
b1 = 000 100 000 010 000 000 000 000 ; Row B Col 1 (left edge)
b2 = 000 010 000 000 010 000 010 010 ; Row B Col 2 (middle square)
b3 = 000 001 000 000 000 010 000 000 ; Row B Col 4 (right edge)
c1 = 000 000 100 001 000 000 000 001 ; Row C Col 1 (bottom left corner)
c2 = 000 000 010 000 001 000 000 000 ; Row C Col 2 (bottom edge)
c3 = 000 000 001 000 000 001 001 000 ; Row C Col 3 (bottom right corner)

...其中,二进制值对位置出现在哪些行、列和对角线上进行编码。(稍后我们将看看这是如何工作的)

我们将使用这些值来构建游戏的两种表示形式,一种用于 X,一种用于 O

  • X 以一个空板开始:000 000 000 000 000 000 000 000
  • O 以一块空板开始:000 000 000 000 000 000 000 000

我们跟着X走 (O应该是一样的原理)

  • X 玩 A1...所以我们或(X 板)与值 A1
  • X 玩 A2...所以我们用值 A2 进行 OR
  • X 玩 A3...所以我们用值 A3 进行 OR

这对 X 的董事会价值有何影响:

  1. a1 = 100 000 000 100 000 000 100 000 ... ORed with
  2. a2 = 010 000 000 000 100 000 000 000 ... ORed with
  3. a3 = 001 000 000 000 000 100 000 100 ... 等于:

XB = 111 000 000 100 100 100 100 100

从左到右阅读我们看到 X 有:

  • 111(所有位置)在第 1 行 (\o/ 赢了,耶!)
  • 000(没有位置)在第 2 行
  • 000(没有位置)在第 3 行
  • 100(一个位置)仅第 1 列的第一个位置
  • 100(一个位置)仅第 1 列的第一个位置
  • 100(一个位置)仅第 1 列的第一个位置
  • 100 (One position) 仅对角线 1 的第一个位置
  • 100 (One position) 仅对角线 2 的第一个位置

您会注意到,每当 X(或 O)有获胜线时,他的棋盘值中也会有三个连续的位。正是这三个位的位置,决定了他赢得了哪一行/列/对角线。

所以,现在的技巧是找到一种方法来在单个操作中检查此(设置了三个连续位)条件。

修改值使检测更容易

为了帮助解决这个问题,让我们改变我们的位表示,以便三组之间总是有零(因为 001 110 也是三个连续的位 - 但它们不是有效的胜利......所以,一个固定的零间隔会破坏这些:0 001 0 110)

因此,在添加一些间距零之后,我们可以确信 X 或 O 的棋盘值中的任何三个连续设置位都表示获胜!

因此,我们的新二进制值(带零填充)如下所示:

  • a1 = 100 0 000 0 000 0 100 0 000 0 000 0 100 0 000 0 ; 0x80080080(十六进制)
  • a2 = 010 0 000 0 000 0 000 0 100 0 000 0 000 0 000 0 ; 0x40008000
  • a3 = 001 0 000 0 000 0 000 0 000 0 100 0 000 0 100 0 ; 0x20000808
  • b1 = 000 0 100 0 000 0 010 0 000 0 000 0 000 0 000 0 ; 0x08040000
  • b2 = 000 0 010 0 000 0 000 0 010 0 000 0 010 0 010 0 ; 0x04004044
  • b3 = 000 0 001 0 000 0 000 0 000 0 010 0 000 0 000 0 ; 0x02000400
  • c1 = 000 0 000 0 100 0 001 0 000 0 000 0 000 0 001 0 ; 0x00820002
  • c2 = 000 0 000 0 010 0 000 0 001 0 000 0 000 0 000 0 ; 0x00402000
  • c3 = 000 0 000 0 001 0 000 0 000 0 001 0 001 0 000 0 ; 0x00200220

您会注意到板子的每个“赢线”现在需要 4 位。

8 个 winlines x 每个 4 位 = 32 位!是不是很方便:))))))

解析

我们可以移动所有位以寻找三个连续的位,但这需要 32 次移动 x 2 个玩家......以及一个计数器来跟踪。很慢!

我们可以与 0xF 进行 AND 运算,寻找值 8+4+2=14。这将允许我们一次检查 4 位。将轮班次数减少四分之一。但同样,这很慢!

所以,相反,让我们一次检查所有的可能性......

超高效的胜利检测

假设我们想要评估 A3+A1+B2+C3 的情况(在对角线上获胜)

a1 = 100 0 000 0 000 0 100 0 000 0 000 0 100 0 000 0, OR
a3 = 001 0 000 0 000 0 000 0 000 0 100 0 000 0 100 0, OR
b2 = 000 0 010 0 000 0 000 0 010 0 000 0 010 0 010 0, OR
c3 = 000 0 000 0 001 0 000 0 000 0 001 0 001 0 000 0, =

XB = 101 0 010 0 001 0 100 0 010 0 101 0 111 0 110 0  (See the win, on Diagonal 1?)

现在,让我们通过有效地将三位合并为一位来检查它是否获胜......

只需使用:XB AND (XB << 1) AND (XB >> 1) 换句话说:XB与(XB左移)AND(XB右移)

让我们尝试一个例子...

10100100001010000100101011101100 ; whitespaces removed for easy shifting
(AND)
01001000010100001001010111011000 ; XB shifted left
(AND)
01010010000101000010010101110110 ; XB shifted left
(Equals)
00000000000000000000000001000000

看到了吗?任何非零结果都意味着胜利!

但是,他们在哪里赢了

想知道他们在哪里获胜?好吧,你可以使用第二个表:

0x40000000 = RowA
0x04000000 = RowB
0x00400000 = RowC
0x00040000 = Col1
0x00004000 = Col2
0x00000400 = Col3
0x00000040 = Diag1
0x00000004 = Diag2

然而,我们可以比这更聪明,因为模式非常规则!

例如,在汇编中,您可以使用 BSF (Bit Scan Forward) 来查找前导零的数量。然后减去 2,然后减去 /4(右移 2) - 得到一个介于 0 和 8 之间的数字......您可以将其用作索引来查找获胜字符串数组:

{"wins the top row", "takes the middle row!", ... "steals the diagonal!" }

这使得整个游戏逻辑......从移动检查到棋盘更新,再到赢/输检测和适当的成功消息,所有这些都包含在少数 ASM 指令中。

...它小巧、高效且超快!

检查移动是否可玩

显然,“X's board”与“O's board”的 ORing = ALL POSITIONS

因此,您可以很容易地检查移动是否有效。如果用户选择 UpperLeft,则此位置为整数值。只需使用(XB OR OB)检查此值的“与”...

...如果结果非零,则该位置已在使用中。

结论

如果您正在寻找处理板的有效方法,请不要从板对象开始。尝试发现一些有用的抽象。

看看状态是否适合一个整数,并考虑一个“简单”的位掩码来处理会是什么样子。通过一些巧妙的整数选择来表示移动、位置或棋盘……您可能会发现可以非常有效地玩、评估和评分整个游戏 - 使用简单的按位逻辑。

致歉

顺便说一句,我不是 StackOverflow 上的常客,所以我希望这篇文章不会太混乱而无法遵循。另外,请善待......“人类”是我的第二语言,我还不太流利;)

无论如何,我希望这对某人有所帮助。

答案 14 :(得分:0)

不确定是否已发布此方法。这应该适用于任何m * n板,并且玩家应该连续填补“ winnerPos ”位置。这个想法是基于运行窗口的。

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

答案 15 :(得分:0)

我只想分享我在Javascript中所做的事情。我的想法是要找到搜索方向;在网格中,它可能是8个方向,但搜索应该是双向的,因此8/2 = 4个方向。当玩家移动时,搜索将从该位置开始。它会搜索4个不同的双向,直到其值与玩家的石头(O或X)不同为止。

每次进行双向搜索时,可以添加两个值,但由于起点重复,因此需要减去一个值。

getWin(x,y,value,searchvector) {
if (arguments.length==2) {
  var checkTurn = this.state.squares[y][x];
  var searchdirections = [[-1,-1],[0,-1],[1,-1],[-1,0]];
  return searchdirections.reduce((maxinrow,searchdirection)=>Math.max(this.getWin(x,y,checkTurn,searchdirection)+this.getWin(x,y,checkTurn,[-searchdirection[0],-searchdirection[1]]),maxinrow),0);
} else {
  if (this.state.squares[y][x]===value) {
    var result = 1;
    if (
      x+searchvector[0] >= 0 && x+searchvector[0] < 3 && 
      y+searchvector[1] >= 0 && y+searchvector[1] < 3
      ) result += this.getWin(x+searchvector[0],y+searchvector[1],value,searchvector);
    return result;
  } else {
    return 0;
  }
}

}

此函数可以与两个参数(x,y)一起使用,它们是最后一个移动的坐标。在初始执行中,它使用4个参数递归调用四个双向搜索。所有结果均以长度形式返回,该函数最终在4个搜索双向中选择最大长度。

class Square extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value:null};
  }
  render() {
    return (
      <button className="square" onClick={() => this.props.onClick()}>
        {this.props.value}
      </button>
    );
  }
}

class Board extends React.Component {
  renderSquare(x,y) {
    return <Square value={this.state.squares[y][x]} onClick={() => this.handleClick(x,y)} />;
  }
  handleClick(x,y) {
    const squares = JSON.parse(JSON.stringify(this.state.squares));
    if (!squares[y][x] && !this.state.winner) {
      squares[y][x] = this.setTurn();
      this.setState({squares: squares},()=>{
        console.log(`Max in a row made by last move(${squares[y][x]}): ${this.getWin(x,y)-1}`);
        if (this.getWin(x,y)==4) this.setState({winner:squares[y][x]});
      });
    }
  }
  setTurn() {
    var prevTurn = this.state.turn;
    this.setState({turn:prevTurn == 'X' ? 'O':'X'});
    return prevTurn;
  }
  
  getWin(x,y,value,searchvector) {
    if (arguments.length==2) {
      var checkTurn = this.state.squares[y][x];
      var searchdirections = [[-1,-1],[0,-1],[1,-1],[-1,0]];
      return searchdirections.reduce((maxinrow,searchdirection)=>Math.max(this.getWin(x,y,checkTurn,searchdirection)+this.getWin(x,y,checkTurn,[-searchdirection[0],-searchdirection[1]]),maxinrow),0);
    } else {
      if (this.state.squares[y][x]===value) {
        var result = 1;
        if (
          x+searchvector[0] >= 0 && x+searchvector[0] < 3 && 
          y+searchvector[1] >= 0 && y+searchvector[1] < 3
          ) result += this.getWin(x+searchvector[0],y+searchvector[1],value,searchvector);
        return result;
      } else {
        return 0;
      }
    }
  }
  
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(3).fill(Array(3).fill(null)),
      turn: 'X',
      winner: null
    };
  }
  render() {
    const status = !this.state.winner?`Next player: ${this.state.turn}`:`${this.state.winner} won!`;

    return (
      <div>
        <div className="status">{status}</div>
        <div className="board-row">
          {this.renderSquare(0,0)}
          {this.renderSquare(0,1)}
          {this.renderSquare(0,2)}
        </div>
        <div className="board-row">
          {this.renderSquare(1,0)}
          {this.renderSquare(1,1)}
          {this.renderSquare(1,2)}
        </div>
        <div className="board-row">
          {this.renderSquare(2,0)}
          {this.renderSquare(2,1)}
          {this.renderSquare(2,2)}
        </div>
      </div>
    );
  }
}

class Game extends React.Component {
  render() {
    return (
      <div className="game">
        <div className="game-board">
          <Board />
        </div>
        <div className="game-info">
          <div>{/* status */}</div>
          <ol>{/* TODO */}</ol>
        </div>
      </div>
    );
  }
}

// ========================================

ReactDOM.render(
  <Game />,
  document.getElementById('root')
);
body {
  font: 14px "Century Gothic", Futura, sans-serif;
  margin: 20px;
}

ol, ul {
  padding-left: 30px;
}

.board-row:after {
  clear: both;
  content: "";
  display: table;
}

.status {
  margin-bottom: 10px;
}

.square {
  background: #fff;
  border: 1px solid #999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}

.square:focus {
  outline: none;
}

.kbd-navigation .square:focus {
  background: #ddd;
}

.game {
  display: flex;
  flex-direction: row;
}

.game-info {
  margin-left: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="errors" style="
  background: #c00;
  color: #fff;
  display: none;
  margin: -20px -20px 20px;
  padding: 20px;
  white-space: pre-wrap;
"></div>
<div id="root"></div>
<script>
window.addEventListener('mousedown', function(e) {
  document.body.classList.add('mouse-navigation');
  document.body.classList.remove('kbd-navigation');
});
window.addEventListener('keydown', function(e) {
  if (e.keyCode === 9) {
    document.body.classList.add('kbd-navigation');
    document.body.classList.remove('mouse-navigation');
  }
});
window.addEventListener('click', function(e) {
  if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
    e.preventDefault();
  }
});
window.onerror = function(message, source, line, col, error) {
  var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
  errors.textContent += text + '\n';
  errors.style.display = '';
};
console.error = (function(old) {
  return function error() {
    errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
    errors.style.display = '';
    old.apply(this, arguments);
  }
})(console.error);
</script>

答案 16 :(得分:0)

这是 React 中的示例实现:CodeSandbox Demo

算法非常简单:

For every move:
   checkDiagonals()
   checkVerticals()
   checkHorizontals()

为“O”输入分配 0,为“X”输入分配 1。检查函数的结果可以是0,或1,或2(平局)。

这是实现:

    const checkDiagonals = () => {
        if ((state[0][0] === val && state[1][1] === val && state[2][2] === val) ||
            (state[0][2] === val && state[1][1] === val && state[2][0] === val)) {
            return val;
        }
        return -1;
    }

    const checkVerticals = () => {
        for (let i = 0; i <= 2; i++) {
            if (state[0][i] === val && state[1][i] === val && state[2][i] === val) {
                return val;
            }
        }
        return -1;
    }

    const checkHorizontals = () => {
        for (let i = 0; i <= 2; i++) {
            if (state[i][0] === val && state[i][1] === val && state[i][2] === val) {
                return val;
            }
        }
        return -1;
    }

剩下的就是有一个函数可以在每个用户输入时触发:

const updateWinningPlayer = () => {

    const diagonals = checkDiagonals();
    const verticals = checkVerticals();
    const horizontals = checkHorizontals();

    if (diagonals !== -1) {
        setWinner(diagonals)
        return;
    }

    if (verticals !== -1) {
        setWinner(verticals);
        return;
    }

    if (horizontals !== -1) {
        setWinner(horizontals);
        return;
    }

    if (isDraw()) {
        setWinner(2);
    }
}

这就给你了!

TicTacToe image

GitHub 存储库链接:https://github.com/mkotsollaris/tic-tac-toe

答案 17 :(得分:0)

这是我使用二维数组的解决方案:

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

答案 18 :(得分:0)

如果你有考试的寄宿家庭5 * 5,我使用下一种检查方法:

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

我认为,它更清楚,但可能不是最佳方式。

答案 19 :(得分:0)

这是一种非常简单的检查方法。

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

}

答案 20 :(得分:0)

9个插槽的后续方法怎么样?为3x3矩阵(a1,a2 ...... a9)声明9个整数变量,其中a1,a2,a3表示第1行,a1,a4,a7将形成第1列(你明白了)。使用“1”表示Player-1,使用“2”表示Player-2。

有8种可能的胜利组合:     Win-1:a1 + a2 + a3(根据哪位玩家获胜,答案可能是3或6)     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

现在我们知道,如果玩家1越过a1,那么我们需要重新评估3个变量的总和:Win-1,Win-4和Win-7。无论哪个'赢 - ?'变量达到3或6首先赢得比赛。如果Win-1变量首先达到6,则Player-2获胜。

我明白这个解决方案不易扩展。

答案 21 :(得分:0)

这是我提出的解决方案,它将符号存储为字符并使用char的int值来确定X或O是否已赢(查看裁判的代码)

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

此外,我的单元测试验证它确实有效

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

完整解决方案:https://github.com/nashjain/tictactoe/tree/master/java

答案 22 :(得分:0)

另一种选择:使用代码生成表格。达到对称性,只有三种获胜方式:边缘行,中间行或对角线。拿这三个并尽可能地旋转它们:

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

这些对称性可以在你的游戏代码中有更多的用途:如果你已经看到了旋转版本的板子,你可以从缓存中获取缓存值或缓存最佳移动(并取消旋转)背部)。这通常比评估游戏子树要快得多。

(向左和向右翻转可以帮助相同的方式;这里不需要它,因为获胜模式的旋转集是镜像对称的。)

答案 23 :(得分:-1)

恒定时间O(8),平均4个短AND。玩家=短号码。需要额外的检查以确保移动有效。

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

short[] winCombinations = new short[]{
  7, 7 << 3, 7 << 6, // horizontal
  73, 73 << 1, 73 << 2, // vertical
  273, // diagonal
  84   // anti-diagonal
};

for (short X = 0; X < 511; X++)
   System.out.println(isWinner(X));

答案 24 :(得分:-2)

我为此开发了一种算法,作为科学项目的一部分。

你基本上递归地将棋盘划分为一堆重叠的2x2 rects,测试不同的可能组合,以便在2x2的方格上获胜。

它很慢,但它具有在任何尺寸的电路板上工作的优势,具有相当线性的存储器要求。

我希望我能找到我的实施