Thuật toán xác định trò chơi Tic Tac Toe kết thúc


97

Tôi đã viết một trò chơi tic-tac-toe bằng Java và phương pháp xác định thời điểm kết thúc trò chơi hiện tại của tôi cho các trường hợp có thể xảy ra sau khi trò chơi kết thúc:

  1. Bàn cờ đã đầy và vẫn chưa có người chiến thắng nào được tuyên bố: Trò chơi hòa.
  2. Cross đã chiến thắng.
  3. Circle đã chiến thắng.

Thật không may, để làm như vậy, nó đọc qua một tập hợp được xác định trước của các tình huống này từ một bảng. Điều này không hẳn là tệ khi chỉ có 9 ô trống trên một bảng và do đó bảng hơi nhỏ, nhưng có một cách thuật toán nào tốt hơn để xác định xem trò chơi đã kết thúc không? Việc xác định xem ai đó đã thắng hay chưa là vấn đề quan trọng, vì việc kiểm tra xem 9 ô trống có đầy hay không là chuyện nhỏ.

Phương thức bảng có thể là giải pháp, nhưng nếu không, là gì? Ngoài ra, điều gì sẽ xảy ra nếu bảng không có kích thước n=9? Có gì nếu nó đã được một tàu lớn hơn nhiều, chẳng hạn n=16, n=25và như vậy, khiến cho số lượng các mặt hàng liên tục đặt để giành chiến thắng được x=4, x=5, vv? Một thuật toán chung để sử dụng cho tất cả n = { 9, 16, 25, 36 ... }?


Tôi đang thêm 2 xu của mình cho tất cả các câu trả lời: Bạn luôn biết rằng bạn cần ít nhất một số X hoặc Os trên bàn cờ để giành chiến thắng (trong bảng 3x3 thông thường là như vậy 3). Vì vậy, bạn có thể theo dõi số lượng của mỗi và chỉ bắt đầu kiểm tra các chiến thắng nếu chúng cao hơn.
Yuval A. Ngày

Câu trả lời:


133

Bạn biết rằng một nước đi chiến thắng chỉ có thể xảy ra sau khi X hoặc O đã thực hiện nước đi gần đây nhất của họ, vì vậy bạn chỉ có thể tìm kiếm hàng / cột với đường chéo tùy chọn có trong nước đi đó để giới hạn không gian tìm kiếm của bạn khi cố gắng xác định bàn thắng. Ngoài ra, vì có một số nước đi cố định trong trò chơi hòa tic-tac-toe khi nước đi cuối cùng được thực hiện nếu đó không phải là nước đi thắng thì theo mặc định là một trò chơi hòa.

chỉnh sửa: mã này dành cho bảng n x n với n liên tiếp để giành chiến thắng (bảng 3x3 yêu cầu 3 bàn liên tiếp, v.v.)

chỉnh sửa: đã thêm mã để kiểm tra đường chống, tôi không thể tìm ra cách không lặp lại để xác định xem điểm có nằm trên đường chống không, đó là lý do tại sao bước đó bị thiếu

public class TripleT {

    enum State{Blank, X, O};

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

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

        //check end conditions

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

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

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

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

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

6
Bạn quên kiểm tra đường chống chéo.
rapter

1
Đối với bảng 3x3, x + y sẽ luôn bằng 2 trên đường chống chéo, sẽ luôn ở tâm và các góc của bảng, và lẻ ở những nơi khác.
Chris Doggett

5
Tôi không hiểu kiểm tra rút ra ở cuối, nó không nên trừ đi 1?
Inez

4
Có những trường hợp người chơi thắng ở nước đi cuối cùng có thể (thứ 9). Trong trường hợp đó, cả người chiến thắng và trận hòa sẽ được báo cáo ...
Marc

5
@ Roamer-1888, vấn đề không phải là giải pháp của bạn bao gồm bao nhiêu dòng, mà là giảm độ phức tạp về thời gian của thuật toán để kiểm tra người chiến thắng.
Shady

38

bạn có thể sử dụng một hình vuông ma thuật http://mathworld.wolfram.com/MagicSquare.html nếu bất kỳ hàng, cột hoặc đường chéo nào cộng tối đa 15 thì người chơi đã thắng.


3
Điều đó chuyển sang trò chơi tic-tac-toe như thế nào?
Paul Alexander

Đó là một chút thông tin mà tôi không biết, vì vậy tôi chắc chắn cảm ơn bạn về thông tin. Như Paul đã đề cập, không rõ ngay lập tức điều đó sẽ giúp giải quyết vấn đề hiện tại như thế nào, nhưng có vẻ như nó có thể đóng vai trò là một phần của một giải pháp hoàn thiện hơn.
dreadwail

4
chồng lên nó. 1 cho màu trắng, 2 cho màu đen và nhân. nếu bất kỳ thứ gì xuất hiện đến 15, thì màu trắng đã thắng và nếu nó ra đến 30 thì màu đen đã thắng.
adk

1
Big O-khôn ngoan, nó khá rẻ, đặc biệt nếu bạn kết hợp nó với việc kiểm tra di động của Hardwareguy. Mỗi ô chỉ có thể nằm trong 4 dấu gạch chéo ngược có thể có: đường chéo, theo chiều cột và hai đường chéo (gạch chéo và gạch chéo ngược). Vì vậy, khi đã thực hiện một bước di chuyển, bạn chỉ phải thực hiện nhiều nhất 4 phép cộng và so sánh. Câu trả lời của Hardwareguy yêu cầu 4 (n-1) lần kiểm tra cho mỗi bước đi, bằng cách so sánh.
rapter

29
Chúng ta không thể làm điều này với 1 và -1 và tính tổng từng hàng / cột / đường chéo để xem nó là n hay -n?
Nathan

24

Làm thế nào về mã giả này:

Sau khi người chơi đặt quân cờ xuống vị trí (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

Tôi sẽ sử dụng một mảng char [n, n], với O, X và không gian trống.

  1. đơn giản.
  2. Một vòng lặp.
  3. Năm biến đơn giản: 4 số nguyên và một boolean.
  4. Cân với kích thước bất kỳ của n.
  5. Chỉ kiểm tra mảnh hiện tại.
  6. Không có phép thuật. :)

if cell [i, n- (i + 1)] = player then rdiag ++; - Có vẻ như với dấu ngoặc đơn thì sẽ đúng. Tôi nói đúng chứ?
Pumych

@Pumych, không. Nếu i==1n==3, rdiagphải được kiểm tra tại (1, 3)(1, 3-1+1)bằng với tọa độ chính xác, nhưng (1, 3-(1+1))không.
KgOfHedgehogs

Anh ta có thể nghĩ rằng các ô không được lập chỉ mục.
Matias Grioni

nó chỉ là một số thứ ngoài đầu của tôi .... nó cần được sửa trong quá trình viết mã thực sự :)
Osama Al-Maadeed

21

Điều này tương tự như câu trả lời của Osama ALASSIRY , nhưng nó giao dịch không gian và thời gian tuyến tính không đổi cho không gian tuyến tính và thời gian không đổi. Đó là, không có vòng lặp sau khi khởi tạo.

Khởi tạo một cặp (0,0)cho mỗi hàng, mỗi cột và hai đường chéo (chéo & chống chéo). Các cặp này đại diện cho sự tích lũy (sum,sum)của các phần trong hàng, cột hoặc đường chéo tương ứng, trong đó

Một mảnh của người chơi A có giá trị (1,0)
Một mảnh của người chơi B có giá trị (0,1)

Khi người chơi đặt một quân cờ, hãy cập nhật cặp hàng, cặp cột và cặp đường chéo tương ứng (nếu nằm trên đường chéo). Nếu bất kỳ cặp hàng, cột hoặc đường chéo nào mới được cập nhật bằng một trong hai (n,0)hoặc (0,n)thì A hoặc B tương ứng sẽ thắng.

Phân tích tiệm cận:

O (1) lần (mỗi lần di chuyển)
O (n) không gian (tổng thể)

Đối với việc sử dụng bộ nhớ, bạn sử dụng 4*(n+1)số nguyên.

two_elements * n_rows + two_elements * n_columns +
two_elements * two_diricals = 4 * n + 4 integer = 4 (n + 1) integer

Bài tập: Bạn có thể xem cách kiểm tra tỷ lệ hòa trong O (1) lần mỗi nước đi không? Nếu vậy, bạn có thể kết thúc trò chơi sớm với tỷ số hòa.


1
Tôi nghĩ điều này tốt hơn Osama ALASSIRY vì O(sqrt(n))thời gian của anh ấy gần như là thời gian nhưng phải được thực hiện sau mỗi lần di chuyển, trong đó n là kích thước của bảng. Vì vậy, bạn kết thúc với O(n^1.5). Đối với giải pháp này, bạn có được O(n)thời gian tổng thể.
Matias Grioni

cách tốt để nhìn thấy điều này, thật hợp lý khi nhìn vào "giải pháp" thực tế ... đối với 3x3, bạn chỉ có 8 cặp "boolean" ... Nó có thể còn hiệu quả hơn nếu nó là 2 bit mỗi cặp ... 16 bit cần thiết và bạn có thể chỉ cần bitwise HOẶC 1 trong trình phát đúng dịch chuyển sang trái đến đúng vị trí :)
Osama Al-Maadeed

13

Đây là giải pháp của tôi mà tôi đã viết cho một dự án mà tôi đang thực hiện bằng javascript. Nếu bạn không bận tâm về chi phí bộ nhớ của một vài mảng, đây có thể là giải pháp nhanh nhất và đơn giản nhất mà bạn sẽ tìm thấy. Nó giả sử bạn biết vị trí của nước đi cuối cùng.

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

2
Đây sẽ là cách tiếp cận búa lớn nhưng nó thực sự là một giải pháp khả thi, đặc biệt là trang web là một trong vô số các giải pháp sáng tạo và hiệu quả cho vấn đề này. Ngoài ra, nó ngắn gọn, thanh lịch và rất dễ đọc - đối với lưới 3x3 (Tx3 truyền thống), tôi thích thuật toán này.
nocarrier

Cái này thật tuyệt vời !! Tôi thấy rằng có một chút lỗi trong các mẫu chiến thắng, ở sở hữu 8, các mẫu phải là [6,7], [0,4] và [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

Tôi vừa viết cái này cho lớp lập trình C của tôi.

Tôi đăng nó vì không có ví dụ nào khác ở đây sẽ hoạt động với bất kỳ lưới hình chữ nhật có kích thước nào và bất kỳ số N -in-a-hàng nào đánh dấu liên tiếp để giành chiến thắng.

Bạn sẽ tìm thấy thuật toán của tôi, chẳng hạn như nó, trong checkWinner()hàm. Nó không sử dụng những con số ma thuật hay bất cứ thứ gì lạ mắt để kiểm tra người chiến thắng, nó chỉ đơn giản sử dụng bốn vòng lặp for - Mã được nhận xét tốt nên tôi sẽ để nó tự nói theo ý mình.

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

Rất hữu ích. Tôi đang cố gắng tìm một thứ gì đó hiệu quả hơn, chẳng hạn như nếu bạn biết N = COL = ROW, bạn có thể giảm điều này thành một cái gì đó đơn giản hơn nhiều, nhưng tôi chưa tìm thấy thứ gì hiệu quả hơn cho kích thước bảng tùy ý và N.
Hassan

6

Nếu bảng là n × n thì có n hàng, n cột và 2 đường chéo. Kiểm tra từng cái đó cho tất cả X hoặc tất cả O để tìm người chiến thắng.

Nếu chỉ cần x < n ô vuông liên tiếp để thắng thì phức tạp hơn một chút. Các giải pháp rõ ràng nhất là để kiểm tra mỗi x × x vuông cho một người chiến thắng. Đây là một số mã chứng minh điều đó.

(Tôi đã không thực sự kiểm tra điều này * ho *, nhưng nó đã biên dịch trong lần thử đầu tiên, đúng là tôi!)

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

Tôi hiểu ý bạn là gì. Sẽ có (n * n * 2) tổng số câu trả lời trong trò chơi n = x truyền thống. Tuy nhiên, điều này sẽ không thành công nếu x (số liên quân cần thiết để thắng) nhỏ hơn n. Tuy nhiên, đó là một giải pháp tốt, tôi thích nó hơn bảng vì khả năng mở rộng của nó.
dreadwail

Mặc dù vậy, tôi đã không đề cập đến khả năng x <n trong bài viết gốc, vì vậy câu trả lời của bạn vẫn còn nguyên.
dreadwail

4

Tôi không biết rõ về Java, nhưng tôi biết C, vì vậy tôi đã thử ý tưởng hình vuông kỳ diệu của adk (cùng với hạn chế tìm kiếm của 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;
}

Nó biên dịch và kiểm tra tốt.

% gcc -o tic-tac-toe tic-tac-toe.c
% ./TIC Tac Toe
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Nhập nước đi dưới dạng "" (không có dấu ngoặc kép, không được lập chỉ mục)
  Nước đi của X: 1 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | |
  Nước đi của O: 1 2
  di chuyển bất hợp pháp (đã được thực hiện), hãy thử lại
  Nước đi của O: 3 3
  di chuyển bất hợp pháp (ra khỏi bảng), hãy thử lại
  Nước đi của O: 2 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | | O
  Nước đi của X: 1 0
     | |
  --- + --- + ---
   X | | X
  --- + --- + ---
     | | O
  Nước đi của O: 1 1
     | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | O
  Nước đi của X: 0 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | O
  Nước đi của O: 2 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | | O
  Nước đi của X: 2 1
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | O
  Nước đi của O: 0 2
   X | | O
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | O
  TIC Tac Toe! O chiến thắng!
% ./TIC Tac Toe
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Nhập nước đi dưới dạng "" (không có dấu ngoặc kép, không được lập chỉ mục)
  Nước đi của X: 0 0
   X | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Nước đi của O: 0 1
   X | O |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Nước đi của X: 0 2
   X | O | X
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Nước đi của O: 1 0
   X | O | X
  --- + --- + ---
   O | |
  --- + --- + ---
     | |
  Nước đi của X: 1 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
     | |
  Nước đi của O: 2 0
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | |
  Nước đi của X: 2 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X |
  Nước đi của O: 2 2
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X | O
  Nước đi của X: 1 2
   X | O | X
  --- + --- + ---
   O | X | X
  --- + --- + ---
   O | X | O
  bế tắc ... không ai thắng :(
%

Thật là vui, cảm ơn!

Thực ra, nghĩ về nó, bạn không cần một hình vuông ma thuật, chỉ cần một số đếm cho mỗi hàng / cột / đường chéo. Điều này dễ dàng hơn một chút so với việc tổng quát một ma trận vuông thành n× nma trận, vì bạn chỉ cần đếm đến n.


3

Tôi đã được hỏi câu hỏi tương tự trong một cuộc phỏng vấn của tôi. Suy nghĩ của tôi: Khởi tạo ma trận bằng 0. Giữ 3 mảng 1) sum_row (kích thước n) 2) sum_column (kích thước n) 3) đường chéo (kích thước 2)

Đối với mỗi lần di chuyển bằng (X), giảm giá trị hộp đi 1 và đối với mỗi lần di chuyển bằng (0) tăng nó lên 1. Tại bất kỳ thời điểm nào nếu hàng / cột / đường chéo đã được sửa đổi trong nước đi hiện tại có tổng là -3 hoặc + 3 có nghĩa là ai đó đã thắng trò chơi. Để có kết quả hòa, chúng ta có thể sử dụng phương pháp trên để giữ biến moveCount.

Bạn có nghĩ rằng tôi đang thiếu một cái gì đó?

Chỉnh sửa: Tương tự có thể được sử dụng cho ma trận nxn. Tổng phải là số chẵn +3 hoặc -3.


2

một cách không lặp lại để xác định xem điểm có nằm trên đường chéo không:

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

2

Tôi đến muộn trong nhóm, nhưng tôi muốn chỉ ra một lợi ích mà tôi tìm thấy khi sử dụng hình vuông ma thuật , đó là nó có thể được sử dụng để tham chiếu đến hình vuông sẽ gây ra thắng hoặc thua ở lượt tiếp theo, thay vì chỉ được sử dụng để tính toán khi một trò chơi kết thúc.

Lấy hình vuông kỳ diệu này:

4 9 2
3 5 7
8 1 6

Đầu tiên, hãy thiết lập một scoresmảng được tăng lên mỗi khi thực hiện một bước di chuyển. Xem câu trả lời này để biết chi tiết. Bây giờ nếu chúng ta chơi bất hợp pháp X hai lần liên tiếp tại [0,0] và [0,1], thì scoresmảng sẽ giống như sau:

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

Và bảng trông như thế này:

X . .
X . .
. . .

Sau đó, tất cả những gì chúng ta phải làm để tham chiếu đến ô vuông nào sẽ thắng / chặn là:

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

Trong thực tế, việc triển khai yêu cầu một vài thủ thuật bổ sung, như xử lý các khóa được đánh số (trong JavaScript), nhưng tôi thấy nó khá đơn giản và rất thích phép toán giải trí.


1

Tôi đã thực hiện một số tối ưu hóa trong kiểm tra hàng, cột, đường chéo. Nó chủ yếu được quyết định trong vòng lặp lồng nhau đầu tiên nếu chúng ta cần kiểm tra một cột hoặc đường chéo cụ thể. Vì vậy, chúng tôi tránh kiểm tra cột hoặc đường chéo tiết kiệm thời gian. Điều này gây ra tác động lớn khi kích thước bảng lớn hơn và một số lượng đáng kể các ô không được lấp đầy.

Đây là mã java cho điều đó.

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


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


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


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

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

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

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

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

    if (allFilled)
        return DRAW;

    return INPROGRESS;
}

1

Tôi thích thuật toán này vì nó sử dụng biểu diễn 1x9 so với 3x3 của bảng.

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

1
Tôi thích cách tiếp cận này nhất. Sẽ rất hữu ích nếu bạn giải thích "start" và "incr" nghĩa là gì. (Đó là một cách thể hiện tất cả các "dòng" dưới dạng chỉ mục bắt đầu và số chỉ mục cần bỏ qua.)
nafg 30/11/16

Vui lòng giải thích thêm. Làm thế nào bạn nghĩ ra mã này?
Farzan

0

Một tùy chọn khác: tạo bảng của bạn bằng mã. Theo tính đối xứng, chỉ có ba cách để giành chiến thắng: hàng cạnh, hàng giữa hoặc đường chéo. Lấy ba thứ đó và xoay chúng theo mọi cách có thể:

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

Những đối xứng này có thể có nhiều công dụng hơn trong mã chơi trò chơi của bạn: nếu bạn đến một bảng mà bạn đã thấy phiên bản xoay của nó, bạn chỉ có thể lấy giá trị đã lưu trong bộ nhớ cache hoặc di chuyển tốt nhất được lưu trong bộ nhớ cache từ bảng đó (và tháo nó trở lại). Điều này thường nhanh hơn nhiều so với đánh giá cây con của trò chơi.

(Lật trái và phải có thể giúp ích theo cách tương tự; ở đây không cần thiết vì tập hợp các phép quay của các mẫu chiến thắng là đối xứng gương.)


0

Đây là một giải pháp mà tôi đã đưa ra, điều này lưu trữ các ký hiệu dưới dạng ký tự và sử dụng giá trị int của char để tìm xem X hoặc O có thắng hay không (xem mã của Trọng tài)

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

Ngoài ra, đây là các bài kiểm tra đơn vị của tôi để xác nhận nó thực sự hoạt động

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

Giải pháp đầy đủ: https://github.com/nashjain/tictactoe/tree/master/java


0

Làm thế nào về một cách tiếp cận sau cho 9 vị trí? Khai báo 9 biến số nguyên cho ma trận 3x3 (a1, a2 .... a9) trong đó a1, a2, a3 đại diện cho hàng-1 và a1, a4, a7 sẽ tạo thành cột-1 (bạn có thể hiểu được). Sử dụng '1' để chỉ Người chơi-1 và "2" để chỉ Người chơi-2.

Có 8 kết hợp chiến thắng có thể xảy ra: Thắng-1: a1 + a2 + a3 (câu trả lời có thể là 3 hoặc 6 dựa trên người chơi thắng) Thắng-2: a4 + a5 + a6 Thắng-3: a7 + a8 + a9 Thắng-4 : a1 + a4 + a7 .... Thắng-7: a1 + a5 + a9 Thắng-8: a3 + a5 + a7

Bây giờ chúng ta biết rằng nếu một người chơi vượt qua a1 thì chúng ta cần phải đánh giá lại tổng của 3 biến: Win-1, Win-4 và Win-7. Cái nào 'Thắng-?' biến đạt 3 hoặc 6 đầu tiên sẽ thắng trò chơi. Nếu biến Win-1 đạt 6 đầu tiên thì Player-2 sẽ thắng.

Tôi hiểu rằng giải pháp này không thể mở rộng một cách dễ dàng.


0

Đây là một cách thực sự đơn giản để kiểm tra.

    public class Game() { 

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

    char piece;

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

public void checkWin(Game player) {

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

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

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

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

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

}


0

Nếu bạn có trường nội trú 5 * 5 cho kỳ thi, tôi đã sử dụng phương pháp kiểm tra tiếp theo:

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

Tôi nghĩ, nó rõ ràng hơn, nhưng có lẽ không phải là cách tối ưu nhất.


0

Đây là giải pháp của tôi bằng cách sử dụng mảng 2 chiều:

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

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

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

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

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

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

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

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

0

Dung dịch thời gian không đổi, chạy trong O (8).

Lưu trữ trạng thái của bảng dưới dạng số nhị phân. Bit nhỏ nhất (2 ^ 0) là hàng trên cùng bên trái của bảng. Sau đó, nó đi xuống bên phải, rồi đi xuống.

I E

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

Mỗi người chơi có số nhị phân của riêng họ để đại diện cho trạng thái (vì tic-tac-toe) có 3 trạng thái (X, O & trống) nên một số nhị phân duy nhất sẽ không hoạt động để đại diện cho trạng thái của bàn cờ cho nhiều người chơi.

Ví dụ, một bảng như:

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

Lưu ý rằng các bit cho người chơi X không tách rời các bit cho người chơi O, điều này là hiển nhiên vì X không thể đặt một quân cờ mà O có một quân cờ và ngược lại.

Để kiểm tra xem một người chơi có thắng hay không, chúng ta cần so sánh tất cả các vị trí được bao phủ bởi người chơi đó với vị trí mà chúng tôi biết là vị trí thắng. Trong trường hợp này, cách dễ nhất để làm điều đó là AND-gating vị trí người chơi và vị trí thắng và xem liệu hai người có bằng nhau hay không.

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

ví dụ.

X: 111001010
W: 111000000 // vị trí giành chiến thắng, tất cả đều giống nhau trên hàng đầu tiên.
------------
&: 111000000

Lưu ý: X & W = WVì vậy, X ở trạng thái thắng.

Đây là một giải pháp thời gian không đổi, nó chỉ phụ thuộc vào số lượng vị trí giành chiến thắng, bởi vì áp dụng cổng AND là một hoạt động thời gian không đổi và số lượng vị trí chiến thắng là hữu hạn.

Nó cũng đơn giản hóa nhiệm vụ liệt kê tất cả các trạng thái bảng hợp lệ, chỉ là tất cả các số có thể biểu diễn bằng 9 bit. Nhưng tất nhiên bạn cần một điều kiện bổ sung để đảm bảo một số là trạng thái bảng hợp lệ (ví dụ: 0b111111111là một số 9 bit hợp lệ, nhưng nó không phải là trạng thái bảng hợp lệ vì X vừa thực hiện tất cả các lượt).

Số lượng các vị trí chiến thắng khả thi có thể được tạo ra ngay lập tức, nhưng dù sao đi nữa, chúng vẫn là như vậy.

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

Để liệt kê tất cả các vị trí bảng, bạn có thể chạy vòng lặp sau. Mặc dù tôi sẽ để xác định xem một số có phải là trạng thái hội đồng hợp lệ hay không đối với người khác.

LƯU Ý: (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));

Vui lòng thêm mô tả và thay đổi mã để nó trả lời chính xác câu hỏi của OP.
Farzan

0

Không chắc liệu phương pháp này có được công bố hay không. Điều này sẽ hoạt động đối với bất kỳ bảng m * n nào và một người chơi phải điền vào vị trí liên tiếp của " winnerPos ". Ý tưởng dựa trên cửa sổ đang chạy.

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

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

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

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

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

-2

Tôi đã phát triển một thuật toán cho điều này như một phần của dự án khoa học một lần.

Về cơ bản, bạn chia bảng một cách đệ quy thành một loạt các lệnh 2x2 chồng lên nhau, thử nghiệm các kết hợp có thể khác nhau để giành chiến thắng trên một hình vuông 2x2.

Nó chậm, nhưng nó có lợi thế là hoạt động trên bất kỳ bảng có kích thước nào, với yêu cầu bộ nhớ khá tuyến tính.

Tôi ước tôi có thể tìm thấy cách triển khai của mình


Đệ quy để kiểm tra kết thúc là không cần thiết.
Gabriel Llamas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.