Algoritma untuk Menentukan Game Over Tic Tac Toe


97

Saya telah menulis permainan tic-tac-toe di Java, dan metode saya saat ini untuk menentukan akhir dari permainan memperhitungkan skenario yang mungkin berikut untuk permainan yang akan berakhir:

  1. Papan penuh, dan belum ada pemenang yang diumumkan: Pertandingan seri.
  2. Cross menang.
  3. Circle menang.

Sayangnya, untuk melakukannya, ia membaca sekumpulan skenario yang telah ditentukan sebelumnya dari tabel. Ini tidak selalu buruk mengingat hanya ada 9 ruang di papan, dan dengan demikian tabelnya agak kecil, tetapi adakah cara algoritmik yang lebih baik untuk menentukan apakah permainan sudah berakhir? Penentuan apakah seseorang menang atau tidak adalah inti masalahnya, karena memeriksa apakah 9 ruang penuh itu sepele.

Metode tabel mungkin bisa menjadi solusinya, tetapi jika tidak, apa? Juga, bagaimana jika papannya tidak berukuran n=9? Bagaimana jika yang papan yang jauh lebih besar, katakanlah n=16, n=25, dan sebagainya, menyebabkan jumlah item berturut-turut ditempatkan untuk menang menjadi x=4, x=5, dll? Algoritme umum yang digunakan untuk semua n = { 9, 16, 25, 36 ... }?


Saya menambahkan 2 sen saya untuk semua jawaban: Anda selalu tahu bahwa Anda membutuhkan setidaknya sejumlah X atau Os di papan untuk menang (di papan normal 3x3 itu 3). Jadi, Anda dapat melacak hitungan masing-masing, dan hanya mulai memeriksa kemenangan jika jumlahnya lebih tinggi.
Yuval A.

Jawaban:


133

Anda tahu langkah menang hanya dapat terjadi setelah X atau O melakukan langkah terbaru mereka, jadi Anda hanya dapat mencari baris / kolom dengan diag opsional yang terdapat dalam gerakan itu untuk membatasi ruang pencarian Anda saat mencoba menentukan papan pemenang. Juga karena ada sejumlah gerakan tetap dalam permainan seri tic-tac-toe setelah langkah terakhir dilakukan jika itu bukan langkah kemenangan, itu secara default adalah permainan seri.

sunting: kode ini adalah untuk papan n kali n dengan n berturut-turut untuk menang (papan 3x3 persyaratan 3 berturut-turut, dll)

edit: menambahkan kode untuk memeriksa anti diag, saya tidak dapat menemukan cara non loop untuk menentukan apakah intinya ada di anti diag jadi itulah mengapa langkah itu hilang

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
Anda lupa memeriksa anti-diagonal.
rampion

1
Untuk papan 3x3, x + y akan selalu sama dengan 2 pada anti-diagonal, akan selalu genap di tengah dan sudut papan, dan ganjil di tempat lain.
Chris Doggett

5
Saya tidak mengerti draw check di akhir, bukankah seharusnya itu mengurangi 1?
Inez

4
Ada kasus di mana seorang pemain menang dalam kemungkinan langkah terakhir (ke-9). Dalam hal kedua pemenang dan hasil imbang akan dilaporkan ...
Marc

5
@ Roamer-1888 ini bukan tentang berapa banyak baris solusi Anda, ini tentang mengurangi kerumitan waktu dari algoritme untuk memeriksa pemenang.
Teduh

38

Anda dapat menggunakan kotak ajaib http://mathworld.wolfram.com/MagicSquare.html jika ada baris, kolom, atau diag yang berjumlah 15 maka pemain telah menang.


3
Bagaimana itu bisa diterjemahkan ke dalam game tic-tac-toe?
Paul Alexander

Ini adalah sedikit informasi yang tidak saya ketahui, jadi saya sangat berterima kasih atas informasinya. Seperti yang disebutkan oleh Paulus, tidak segera jelas bagaimana hal itu akan membantu menyelesaikan masalah yang dihadapi, tetapi sepertinya itu mungkin berperan dalam solusi yang lebih lengkap.
dreadwail

4
melapisinya. 1 untuk putih, 2 untuk hitam dan kalikan. jika ada yang keluar menjadi 15, maka putih menang dan jika keluar menjadi 30 maka hitam menang.
adk

1
Big O-bijaksana, itu cukup murah, terutama jika Anda mencampurnya dengan pemeriksaan sel-bijaksana Hardwareguy. Setiap sel hanya dapat berada dalam 4 kemungkinan tic-tac-toes: berurutan, berkolom, dan dua diagonal (garis miring dan garis miring terbalik). Jadi, setelah langkah dilakukan, Anda hanya perlu melakukan paling banyak 4 penambahan dan perbandingan. Jawaban Hardwareguy membutuhkan 4 (n-1) pemeriksaan untuk setiap gerakan, sebagai perbandingan.
rampion

29
Tidak bisakah kita melakukan ini dengan 1 dan -1 dan menjumlahkan setiap baris / kolom / diag untuk melihat apakah n atau -n?
Nathan

24

Bagaimana dengan pseudocode ini:

Setelah pemain meletakkan bidak pada posisi (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

Saya akan menggunakan array char [n, n], dengan O, X dan spasi untuk kosong.

  1. sederhana.
  2. Satu putaran.
  3. Lima variabel sederhana: 4 bilangan bulat dan satu boolean.
  4. Timbangan untuk berbagai ukuran n.
  5. Hanya memeriksa bagian saat ini.
  6. Tidak ada keajaiban. :)

jika sel [i, n- (i + 1)] = pemain lalu rdiag ++; - Sepertinya dengan tanda kurung ini akan benar. Apakah saya benar?
Pumych

@Pumych, tidak. Jika i==1dan n==3, rdiagharus diperiksa pada (1, 3)dan (1, 3-1+1)sama dengan koordinat yang benar, tetapi (1, 3-(1+1))tidak.
KgOfHedgehogs

Dia mungkin berpikir bahwa sel-selnya tidak memiliki indeks nol.
Matias Grioni

itu hanya beberapa hal di luar kepala saya .... itu perlu diperbaiki selama penulisan kode yang sebenarnya :)
Osama Al-Maadeed

21

Ini mirip dengan jawaban Osama ALASSIRY , tetapi ia memperdagangkan ruang-konstan dan waktu-linier untuk ruang-linier dan waktu-konstan. Artinya, tidak ada perulangan setelah inisialisasi.

Inisialisasi pasangan (0,0)untuk setiap baris, setiap kolom, dan dua diagonal (diagonal & anti-diagonal). Pasangan-pasangan ini mewakili akumulasi(sum,sum) potongan di baris, kolom, atau diagonal yang sesuai, di mana

Sepotong dari pemain A memiliki nilai (1,0)
Bidak dari pemain B memiliki nilai (0,1)

Ketika seorang pemain menempatkan bidak, perbarui pasangan baris yang sesuai, pasangan kolom, dan pasangan diagonal (jika di diagonal). Jika ada pasangan baris, kolom, atau diagonal yang baru diperbarui sama dengan salah satu (n,0)atau (0,n)kemudian A atau B menang.

Analisis asimtotik:

O (1) waktu (per gerakan)
O (n) ruang (keseluruhan)

Untuk penggunaan memori, Anda menggunakan 4*(n+1)bilangan bulat.

dua elemen * n_ baris + dua elemen * n_kolom +
two_elements * two_diagonals = 4 * n + 4 integers = 4 (n + 1) integers

Latihan: Dapatkah Anda melihat cara menguji hasil imbang dalam O (1) kali per gerakan? Jika demikian, Anda bisa mengakhiri permainan lebih awal dengan hasil imbang.


1
Saya pikir ini lebih baik daripada Osama ALASSIRY karena ini kira-kira O(sqrt(n))waktu tetapi harus dilakukan setelah setiap gerakan, di mana n adalah ukuran papan. Jadi Anda berakhir dengan O(n^1.5). Untuk solusi ini Anda mendapatkan O(n)waktu secara keseluruhan.
Matias Grioni

cara yang bagus untuk melihat ini, masuk akal untuk melihat "solusi" yang sebenarnya ... untuk 3x3, Anda hanya akan memiliki 8 pasang "boolean" ... Ini bisa menjadi lebih efektif ruang jika masing-masing 2 bit ... Dibutuhkan 16 bit dan Anda hanya dapat melakukan bitwise ATAU 1 pemain yang benar bergeser ke kiri ke tempat yang benar :)
Osama Al-Maadeed

13

Inilah solusi saya yang saya tulis untuk proyek yang sedang saya kerjakan di javascript. Jika Anda tidak keberatan dengan biaya memori beberapa array, ini mungkin solusi tercepat dan paling sederhana yang akan Anda temukan. Ini mengasumsikan Anda mengetahui posisi langkah terakhir.

/*
 * 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
Ini akan menjadi pendekatan palu besar tetapi memang merupakan solusi yang layak, terutama ke situs sebagai salah satu dari banyak solusi kreatif dan bekerja untuk masalah ini. Juga pendek, elegan, dan sangat mudah dibaca - untuk kisi 3x3 (Tx3 tradisional), saya suka algoritma ini.
nocarrier

Yang ini luar biasa !! Saya menemukan ada sedikit bug pada pola kemenangan, pada penguasaan 8, polanya harus [6,7], [0,4] dan [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

Saya baru saja menulis ini untuk kelas pemrograman C.

Saya mempostingnya karena tidak ada contoh lain di sini yang akan berfungsi dengan kisi persegi panjang ukuran apa pun , dan nomor N -in-a-row yang berurutan untuk menang.

Anda akan menemukan algoritme saya, seperti itu, dalam checkWinner()fungsinya. Itu tidak menggunakan angka ajaib atau apa pun yang mewah untuk memeriksa pemenang, itu hanya menggunakan empat untuk loop - Kode dikomentari dengan baik jadi saya akan membiarkannya berbicara sendiri, saya kira.

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

Sangat membantu. Saya mencoba menemukan sesuatu yang lebih efisien, seperti jika Anda mengetahui N = COL = ROW, Anda dapat menguranginya menjadi sesuatu yang lebih sederhana, tetapi saya belum menemukan sesuatu yang lebih efisien untuk ukuran papan yang berubah-ubah dan N.
Hassan

6

Jika papannya n Ɨ n maka ada n baris, n kolom, dan 2 diagonal. Periksa masing-masing untuk semua-X atau semua-O untuk menemukan pemenang.

Jika hanya membutuhkan x < n kotak berturut-turut untuk menang, maka ini sedikit lebih rumit. Solusi paling jelas adalah memeriksa setiap x Ɨ x persegi untuk mencari pemenang. Berikut beberapa kode yang menunjukkan itu.

(Saya tidak benar-benar menguji ini * batuk *, tetapi berhasil dikompilasi pada percobaan pertama, yay me!)

public class TicTacToe
{
    public enum Square { X, O, NONE }

    /**
     * Returns the winning player, or NONE if the game has
     * finished without a winner, or null if the game is unfinished.
     */
    public Square findWinner(Square[][] board, int lengthToWin) {
        // Check each lengthToWin x lengthToWin board for a winner.    
        for (int top = 0; top <= board.length - lengthToWin; ++top) {
            int bottom = top + lengthToWin - 1;

            for (int left = 0; left <= board.length - lengthToWin; ++left) {
                int right = left + lengthToWin - 1;

                // Check each row.
                nextRow: for (int row = top; row <= bottom; ++row) {
                    if (board[row][left] == Square.NONE) {
                        continue;
                    }

                    for (int col = left; col <= right; ++col) {
                        if (board[row][col] != board[row][left]) {
                            continue nextRow;
                        }
                    }

                    return board[row][left];
                }

                // Check each column.
                nextCol: for (int col = left; col <= right; ++col) {
                    if (board[top][col] == Square.NONE) {
                        continue;
                    }

                    for (int row = top; row <= bottom; ++row) {
                        if (board[row][col] != board[top][col]) {
                            continue nextCol;
                        }
                    }

                    return board[top][col];
                }

                // Check top-left to bottom-right diagonal.
                diag1: if (board[top][left] != Square.NONE) {
                    for (int i = 1; i < lengthToWin; ++i) {
                        if (board[top+i][left+i] != board[top][left]) {
                            break diag1;
                        }
                    }

                    return board[top][left];
                }

                // Check top-right to bottom-left diagonal.
                diag2: if (board[top][right] != Square.NONE) {
                    for (int i = 1; i < lengthToWin; ++i) {
                        if (board[top+i][right-i] != board[top][right]) {
                            break diag2;
                        }
                    }

                    return board[top][right];
                }
            }
        }

        // Check for a completely full board.
        boolean isFull = true;

        full: for (int row = 0; row < board.length; ++row) {
            for (int col = 0; col < board.length; ++col) {
                if (board[row][col] == Square.NONE) {
                    isFull = false;
                    break full;
                }
            }
        }

        // The board is full.
        if (isFull) {
            return Square.NONE;
        }
        // The board is not full and we didn't find a solution.
        else {
            return null;
        }
    }
}

Saya mengerti apa yang kamu maksud. Akan ada (n * n * 2) jawaban total dalam permainan n = x tradisional. Ini tidak akan berhasil jika x (jumlah berturut-turut yang dibutuhkan untuk menang) kurang dari n. Ini adalah solusi yang bagus, saya lebih menyukainya daripada tabel karena ekstensibilitasnya.
dreadwail

Saya tidak menyebutkan kemungkinan x <n di posting asli, jadi jawaban Anda masih tepat.
dreadwail

4

Saya tidak tahu Java dengan baik, tapi saya tahu C, jadi saya mencoba ide kotak ajaib adk (bersama dengan pembatasan pencarian 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;
}

Ini mengkompilasi dan menguji dengan baik.

% gcc -o tic-tac-toe tic-tac-toe.c
% ./tic-tac-toe
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Masukkan gerakan sebagai "" (tanpa tanda kutip, indeks nol)
  Langkah X: 1 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | |
  Gerakan O: 1 2
  pindah ilegal (sudah diambil), coba lagi
  Gerakan O: 3 3
  tindakan ilegal (keluar dari papan), coba lagi
  Gerakan O: 2 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | | HAI
  Gerakan X: 1 0
     | |
  --- + --- + ---
   X | | X
  --- + --- + ---
     | | HAI
  Gerakan O: 1 1
     | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | HAI
  Gerakan X: 0 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | HAI
  Gerakan O: 2 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | | HAI
  Langkah X: 2 1
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | HAI
  Gerakan O: 0 2
   X | | HAI
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | HAI
  tic-tac-toe! O menang!
% ./tic-tac-toe
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Masukkan gerakan sebagai "" (tanpa tanda kutip, indeks nol)
  Gerakan X: 0 0
   X | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Gerakan O: 0 1
   X | O |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Langkah X: 0 2
   X | O | X
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Gerakan O: 1 0
   X | O | X
  --- + --- + ---
   O | |
  --- + --- + ---
     | |
  Langkah X: 1 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
     | |
  Gerakan O: 2 0
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | |
  Langkah X: 2 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X |
  Gerakan O: 2 2
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X | HAI
  Langkah X: 1 2
   X | O | X
  --- + --- + ---
   O | X | X
  --- + --- + ---
   O | X | HAI
  jalan buntu ... tidak ada yang menang :(
%

Itu menyenangkan, terima kasih!

Sebenarnya, jika dipikir-pikir, Anda tidak membutuhkan bujur sangkar ajaib, cukup hitungan untuk setiap baris / kolom / diagonal. Ini sedikit lebih mudah daripada menggeneralisasi persegi ajaib menjadi matriks nƗ n, karena Anda hanya perlu menghitungnya n.


3

Saya ditanyai pertanyaan yang sama dalam salah satu wawancara saya. Pikiran saya: Inisialisasi matriks dengan 0. Simpan 3 array 1) sum_row (ukuran n) 2) sum_column (ukuran n) 3) diagonal (ukuran 2)

Untuk setiap gerakan dengan (X) turunkan nilai kotak dengan 1 dan untuk setiap gerakan dengan (0) tambahkan 1. Pada titik mana pun jika baris / kolom / diagonal yang telah dimodifikasi dalam gerakan saat ini berjumlah -3 atau + 3 berarti seseorang telah memenangkan permainan. Untuk menggambar kita bisa menggunakan pendekatan di atas untuk mempertahankan variabel moveCount.

Apakah Anda pikir saya melewatkan sesuatu?

Edit: Sama dapat digunakan untuk matriks nxn. Jumlahnya harus genap +3 atau -3.


2

cara non-loop untuk menentukan apakah titik itu ada di anti diag:

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

2

Saya terlambat ke pesta, tetapi saya ingin menunjukkan satu keuntungan yang saya temukan dengan menggunakan kotak ajaib , yaitu dapat digunakan untuk mendapatkan referensi ke kotak yang akan menyebabkan menang atau kalah pada giliran berikutnya, daripada hanya digunakan untuk menghitung saat permainan selesai.

Ambil kotak ajaib ini:

4 9 2
3 5 7
8 1 6

Pertama, siapkan scoreslarik yang bertambah setiap kali perpindahan dilakukan. Lihat jawaban ini untuk detailnya. Sekarang jika kita secara ilegal memainkan X dua kali berturut-turut pada [0,0] dan [0,1], maka scoresarraynya akan terlihat seperti ini:

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

Dan papannya terlihat seperti ini:

X . .
X . .
. . .

Kemudian, yang harus kita lakukan untuk mendapatkan referensi ke kotak mana yang akan dimenangkan / diblokir adalah:

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

Pada kenyataannya, implementasinya membutuhkan beberapa trik tambahan, seperti menangani kunci bernomor (dalam JavaScript), tetapi saya merasa cukup mudah dan menikmati matematika rekreasional.


1

Saya membuat beberapa pengoptimalan di baris, kolom, pemeriksaan diagonal. Ini terutama diputuskan dalam loop bersarang pertama jika kita perlu memeriksa kolom atau diagonal tertentu. Jadi, kami menghindari pengecekan kolom atau diagonal untuk menghemat waktu. Ini membuat dampak besar ketika ukuran papan lebih banyak dan sejumlah besar sel tidak terisi.

Berikut adalah kode java untuk itu.

    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

Saya suka algoritma ini karena menggunakan representasi papan 1x9 vs 3x3.

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

1
Saya paling suka pendekatan ini. Akan sangat membantu jika Anda menjelaskan apa arti "mulai" dan "incr". (Ini adalah cara untuk mengekspresikan semua "baris" sebagai indeks awal dan jumlah indeks yang dilewati.)
nafg

Silakan tambahkan penjelasan lebih lanjut. Bagaimana Anda mendapatkan kode ini?
Farzan

0

Opsi lain: buat tabel Anda dengan kode. Hingga simetri, hanya ada tiga cara untuk menang: baris tepi, baris tengah, atau diagonal. Ambil ketiganya dan putar dengan segala cara yang memungkinkan:

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

Simetri ini dapat memiliki lebih banyak kegunaan dalam kode permainan-permainan Anda: jika Anda sampai ke papan yang telah Anda lihat versi rotasinya, Anda bisa mengambil nilai yang di-cache atau langkah terbaik yang di-cache dari yang itu (dan membatalkannya kembali). Ini biasanya jauh lebih cepat daripada mengevaluasi subtree game.

(Membalik ke kiri dan kanan dapat membantu dengan cara yang sama; ini tidak diperlukan di sini karena rangkaian rotasi pola kemenangan adalah simetris cermin.)


0

Berikut adalah solusi yang saya buat, ini menyimpan simbol sebagai karakter dan menggunakan nilai int char untuk mengetahui apakah X atau O telah menang (lihat kode Wasit)

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

Juga di sini adalah tes unit saya untuk memvalidasinya benar-benar berfungsi

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

Solusi lengkap: https://github.com/nashjain/tictactoe/tree/master/java


0

Bagaimana dengan pendekatan berikut untuk 9 slot? Deklarasikan 9 variabel integer untuk matriks 3x3 (a1, a2 .... a9) di mana a1, a2, a3 mewakili baris-1 dan a1, a4, a7 akan membentuk kolom-1 (Anda mengerti). Gunakan '1' untuk menunjukkan Player-1 dan '2' untuk menunjukkan Player-2.

Ada 8 kemungkinan kombinasi kemenangan: Menang-1: a1 + a2 + a3 (jawaban bisa 3 atau 6 berdasarkan pemain mana yang menang) Menang-2: a4 + a5 + a6 Menang-3: a7 + a8 + a9 Menang-4 : a1 + a4 + a7 .... Menang-7: a1 + a5 + a9 Menang-8: a3 + a5 + a7

Sekarang kita tahu bahwa jika pemain satu melewati a1 maka kita perlu mengevaluasi kembali jumlah 3 variabel: Menang-1, Menang-4 dan Menang-7. Mana 'Menang-?' variabel mencapai 3 atau 6 pertama memenangkan permainan. Jika variabel Win-1 mencapai 6 terlebih dahulu, maka Player-2 menang.

Saya mengerti bahwa solusi ini tidak dapat diskalakan dengan mudah.


0

Ini adalah cara yang sangat sederhana untuk memeriksanya.

    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

Jika Anda memiliki bidang asrama 5 * 5 sebagai contoh, saya menggunakan metode pemeriksaan selanjutnya:

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

Saya pikir, ini lebih jelas, tetapi mungkin bukan cara yang paling optimal.


0

Inilah solusi saya menggunakan array 2 dimensi:

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

Solusi waktu konstan, berjalan di O (8).

Simpan status papan sebagai bilangan biner. Bit terkecil (2 ^ 0) adalah baris kiri atas papan. Lalu ke kanan, lalu ke bawah.

YAITU

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

Setiap pemain memiliki bilangan biner sendiri untuk mewakili keadaan (karena tic-tac-toe) memiliki 3 status (X, O & blank) sehingga satu bilangan biner tidak akan berfungsi untuk mewakili status papan untuk beberapa pemain.

Misalnya, papan seperti:

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

Perhatikan bahwa bit untuk pemain X terpisah dari bit untuk pemain O, ini jelas karena X tidak dapat meletakkan bidak di mana O memiliki bidak dan sebaliknya.

Untuk memeriksa apakah seorang pemain telah menang, kita perlu membandingkan semua posisi yang dicakup oleh pemain itu dengan posisi yang kita tahu adalah posisi menang. Dalam hal ini, cara termudah untuk melakukannya adalah dengan melakukan AND-gating pada posisi pemain dan posisi menang dan melihat apakah keduanya sama.

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

misalnya.

X: 111001010
W: 111000000 // posisi menang, semua sama di baris pertama.
------------
&: 111000000

Catatan:, X & W = Wjadi X dalam keadaan menang.

Ini adalah solusi waktu yang konstan, ini hanya bergantung pada jumlah posisi-menang, karena menerapkan gerbang-AND adalah operasi waktu yang konstan dan jumlah posisi-menang terbatas.

Ini juga menyederhanakan tugas untuk menghitung semua status papan yang valid, hanya semua angka yang diwakili oleh 9 bit. Tetapi tentu saja Anda memerlukan kondisi tambahan untuk menjamin suatu nomor adalah status papan yang valid (mis. 0b111111111Adalah nomor 9-bit yang valid, tetapi ini bukan status papan yang valid karena X baru saja mengambil semua giliran).

Jumlah kemungkinan posisi menang dapat dihasilkan dengan cepat, tetapi di sinilah tempatnya.

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

Untuk menghitung semua posisi papan, Anda dapat menjalankan loop berikut. Meskipun saya akan meninggalkan menentukan apakah suatu nomor adalah status papan yang valid hingga orang lain.

CATATAN: (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));

Tambahkan lebih banyak deskripsi dan ubah kodenya sehingga itu akan menjawab pertanyaan OP dengan tepat.
Farzan

0

Tidak yakin apakah pendekatan ini belum dipublikasikan. Ini harus bekerja untuk setiap papan m * n dan pemain harus mengisi posisi berturut-turut " winnerPos ". Ide ini didasarkan pada jendela yang sedang berjalan.

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

Saya pernah mengembangkan algoritme untuk ini sebagai bagian dari proyek sains.

Anda pada dasarnya membagi papan secara rekursif menjadi sekelompok bagian 2x2 yang tumpang tindih, menguji berbagai kemungkinan kombinasi untuk menang di kotak 2x2.

Ini lambat, tetapi memiliki keuntungan untuk bekerja pada papan ukuran apa pun, dengan persyaratan memori yang cukup linier.

Saya berharap saya dapat menemukan implementasi saya


Rekursi untuk menguji hasil akhir tidak diperlukan.
Gabriel Llamas
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.