Tic Tac Toe Oyun Bittiğini Belirleme Algoritması


97

Java'da bir tic-tac-toe oyunu yazdım ve oyunun sonunu belirlemeye yönelik mevcut yöntemim, oyunun sona ermesiyle ilgili aşağıdaki olası senaryolar için hesaplar:

  1. Tahta dolu ve henüz bir kazanan açıklanmadı: Oyun berabere.
  2. Cross kazandı.
  3. Circle kazandı.

Ne yazık ki, bunu yapmak için, bu senaryoların önceden tanımlanmış bir kümesini bir tablodan okur. Bir tahtada yalnızca 9 boşluk olduğu ve bu nedenle masanın biraz küçük olduğu düşünüldüğünde, bu mutlaka kötü değildir, ancak oyunun bitip bitmediğini belirlemenin daha iyi bir algoritmik yolu var mı? Birisinin kazanıp kazanmadığının belirlenmesi sorunun temelidir, çünkü 9 boşluğun dolu olup olmadığını kontrol etmek önemsizdir.

Tablo yöntemi çözüm olabilir, ancak değilse nedir? Ayrıca, pano boyutu değilse ne olur n=9? Ya çok daha büyük bir pano olsaydı n=16,n=25 ve böylece arka arkaya yerleştirilen öğelerin sayısını neden olmak kazanmak için x=4, x=5vb? Herkes için kullanılacak genel bir algoritma n = { 9, 16, 25, 36 ... }?


Tüm cevaplar için 2 sentimi ekliyorum: Her zaman bir galibiyet için tahtada en az birkaç X veya O'ya ihtiyacınız olduğunu biliyorsunuz (normal 3x3 tahtada bu 3). Böylece her birinin sayısını takip edebilir ve yalnızca daha yüksekse kazançları kontrol etmeye başlayabilirsiniz.
Yuval A.

Yanıtlar:


133

Kazanan bir hareketin ancak X veya O en son hamlesini yaptıktan sonra gerçekleşebileceğini biliyorsunuz, bu nedenle kazanan bir tahta belirlemeye çalışırken arama alanınızı sınırlamak için yalnızca o harekette bulunan isteğe bağlı tanılama ile satır / sütun arayabilirsiniz. Ayrıca bir tic-tac-toe oyununda son hamle yapıldığında sabit sayıda hamle olduğundan, kazanan bir hamle değilse, varsayılan olarak bir berabere oyunudur.

düzenleme: bu kod, bir satırda n bulunan n'ye n'lik bir pano içindir (3x3 tahta, arka arkaya 3'ü gerektirir, vb.)

düzenleme: anti diag'i kontrol etmek için kod eklendi, noktanın anti diag üzerinde olup olmadığını belirlemek için döngü dışı bir yol bulamadım, bu yüzden bu adımın neden eksik olduğunu

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
Çapraz köşegeni kontrol etmeyi unuttunuz.
rampion

1
3x3'lük bir tahta için, x + y her zaman çapraz köşegende 2'ye eşit olacak, her zaman tahtanın merkezinde ve köşelerinde çift olacak ve başka yerlerde tuhaf olacaktır.
Chris Doggett

5
Sonunda çek çek anlamıyorum, 1 çıkarmaması gerekmez mi?
Inez

4
Bir oyuncunun mümkün olan son (9.) hamlede kazandığı durumlar vardır. Bu durumda hem bir kazanan hem de bir beraberlik bildirilecektir ...
Marc

5
@ Roamer-1888 Çözümünüzün kaç satırdan oluştuğu değil, bir kazananı kontrol etmek için algoritmanın zaman karmaşıklığını azaltmakla ilgilidir.
Shady

38

sihirli bir kare kullanabilirsiniz http://mathworld.wolfram.com/MagicSquare.html herhangi bir satır, sütun veya diyag 15'e kadar eklerse, bir oyuncu kazanır.


3
Bu, tic-tac-toe oyununa nasıl çevrilir?
Paul Alexander

Bu bilmediğim güzel bir bilgi, bu yüzden bilgi için kesinlikle teşekkür ederim. Paul'ün bahsettiği gibi, bunun eldeki sorunu çözmeye nasıl yardımcı olacağı hemen belli değil, ancak daha eksiksiz bir çözümün parçası olarak oynayabileceğini düşünüyor.
dreadwail

4
üst üste bindirin. Beyaz için 1, siyah için 2 ve çarpın. 15'e bir şey gelirse, beyaz kazanır ve 30'a gelirse siyah kazanır.
adk

1
Big O-wise, oldukça ucuz, özellikle de Hardwareguy'un cep telefonu kontrolüyle karıştırırsanız. Her hücre yalnızca 4 olası tic-tac-toes olabilir: satır, sütun ve iki köşegen (eğik çizgi ve ters eğik çizgi). Yani, bir hareket yapıldıktan sonra, yalnızca en fazla 4 ekleme ve karşılaştırma yapmanız gerekir. Hardwareguy'un cevabı, karşılaştırmalı olarak her hareket için 4 (n-1) kontrol gerektirir.
rampion

29
Bunu 1 ve -1 ile yapıp n veya -n olup olmadığını görmek için her satırı / sütunu / diag'ı toplayamaz mıyız?
Nathan 1812

24

Bu sözde koda ne dersiniz:

Bir oyuncu taşı (x, y) konumuna koyduktan sonra:

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

Boş için O, X ve boşluk içeren bir char [n, n] dizisi kullanırdım.

  1. basit.
  2. Bir döngü.
  3. Beş basit değişken: 4 tam sayı ve bir boole.
  4. N'nin her boyutuna ölçeklenir.
  5. Yalnızca mevcut parçayı kontrol eder.
  6. Sihir yok. :)

hücre [i, n- (i + 1)] = oynatıcı ise rdiag ++; - Parantez içinde doğru şekilde olacak gibi görünüyor. Haklı mıyım
Pumych

@Pumych, hayır. Eğer i==1ve n==3, rdiagkontrol edilmelidir (1, 3)ve (1, 3-1+1)doğru koordinatlara eşittir, ancak (1, 3-(1+1))hayır.
KgOfHedgehogs

Hücrelerin sıfır endeksli olduğunu düşünüyor olabilir.
Matias Grioni

sadece kafamın üstünden bir şeydi .... kodun gerçek yazımı sırasında düzeltilmesi gerekiyor :)
Usame Al-Maadeed

21

Bu, Usame ALASSIRY'nin cevabına benzer , ancak sabit-uzay ve doğrusal-zamanı doğrusal-uzay ve sabit-zaman ile değiştirir. Yani, başlatmadan sonra döngü yoktur.

(0,0)Her satır, her sütun ve iki köşegen (diyagonal ve anti-diyagonal) için bir çift oluşturun. Bu çiftler (sum,sum), karşılık gelen satır, sütun veya köşegendeki parçaların birikimini temsil eder.

A oyuncusunun bir taşının değeri (1,0)
B oyuncusunun taşı değeri (0,1)

Bir oyuncu bir taş yerleştirdiğinde, karşılık gelen satır çiftini, sütun çiftini ve çapraz çiftleri (eğer köşegenlerde ise) güncelleyin. Yeni güncellenen herhangi bir satır, sütun veya köşegen çifti bunlardan birine eşitse (n,0)veya (0,n)daha sonra sırasıyla A veya B kazanır.

Asimptotik analiz:

O (1) süre (hareket başına)
O (n) boşluk (genel)

Hafıza kullanımı için 4*(n+1)tamsayılar kullanırsınız .

two_elements * n_rows + two_elements * n_columns +
two_elements * two_diagonals = 4 * n + 4 tam sayı = 4 (n + 1) tamsayı

Alıştırma: Hareket başına 0 (1) seferde beraberliği nasıl test edeceğinizi görebiliyor musunuz? Öyleyse oyunu berabere bitirdikten sonra erken bitirebilirsiniz.


1
Bunun Usame ALASSIRY'ninkinden daha iyi olduğunu düşünüyorum, çünkü onun kabaca O(sqrt(n))zamanı geldi ama her hareketten sonra yapılması gerekiyor, burada n, tahtanın boyutu. Yani sonuçta sen varsın O(n^1.5). Bu çözüm için O(n)genel olarak zaman kazanırsınız .
Matias Grioni

Bunu görmenin güzel bir yolu, gerçek "çözümlere" bakmak mantıklı ... 3x3 için, sadece 8 çift "boole" elde edersiniz ... Her biri 2 bit olsaydı daha fazla alan etkili olabilirdi ... 16 bit gerekli ve sadece bit şeklinde VEYA 1 doğru oyuncudan sola doğru yere kaydırılabilir :)
Usame Al-Maadeed

13

Javascript üzerinde çalıştığım bir proje için yazdığım çözüm bu. Birkaç dizinin bellek maliyetini önemsemiyorsanız, bulacağınız muhtemelen en hızlı ve en basit çözümdür. Son hamlenin konumunu bildiğinizi varsayar.

/*
 * 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
Bu, büyük çekiç yaklaşımı olacaktır, ancak gerçekten de uygulanabilir bir çözümdür, özellikle de bu soruna çok sayıda yaratıcı ve çalışan çözümden biri olarak sahada. Ayrıca kısa, zarif ve çok okunabilir - 3x3 ızgara (geleneksel Tx3) için bu algoritmayı beğendim.
nocarrier

Bu harika !! Kazanan modellerde küçük bir hata olduğunu buldum, 8 numaralı olasılıkta desenler [6,7], [0,4] ve [2,5] olmalıdır: 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

Bunu C programlama sınıfım için yazdım.

Bunu gönderiyorum çünkü buradaki diğer örneklerin hiçbiri herhangi bir boyuttaki dikdörtgen ızgara ile çalışmayacak ve herhangi bir sayı N -art arda arka arkaya puanlar kazanacak.

Algoritmam, olduğu gibi, checkWinner()işlevde bulacaksınız . Bir kazananı kontrol etmek için sihirli sayılar veya herhangi bir şey kullanmaz, sadece döngüler için dört kullanır - Kod iyi yorumlanmış, bu yüzden sanırım kendi adına konuşmasına izin vereceğim.

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

Çok yararlı. Daha verimli bir şey bulmaya çalışıyordum, örneğin N = COL = ROW biliyorsanız, bunu çok daha basit bir şeye indirgeyebilirsiniz, ancak keyfi tahta boyutu ve N için daha verimli bir şey bulamadım.
Hassan

6

Kart n × n ise, o zaman n sıra, n sütun ve 2 köşegen vardır. Bir kazanan bulmak için bunların her birini tüm X'ler veya hepsi O'lar için kontrol edin.

Kazanmak için sadece x < n ardışık kare alıyorsa , o zaman biraz daha karmaşıktır. En bariz çözüm, her x × x kareyi bir kazanan için kontrol etmektir . İşte bunu gösteren bazı kodlar.

(Aslında bu * öksürük * test etmedi ama did ilk denemede derleme, beni yay!)

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

Ne demek istediğini anlıyorum. Geleneksel bir n = x oyununda (n * n * 2) toplam cevap olacaktır. Ancak x (kazanmak için gereken ardışık sayı) n'den az olsaydı bu işe yaramazdı. Yine de iyi bir çözüm, genişletilebilirliği açısından tablodan daha çok seviyorum.
dreadwail

Orijinal gönderide x <n olasılığından bahsetmedim, bu yüzden cevabınız hala yerinde.
dreadwail

4

Java'yı o kadar iyi bilmiyorum ama C'yi biliyorum, bu yüzden adk'ın sihirli kare fikrini denedim ( Hardwareguy'un arama kısıtlamasıyla birlikte ).

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

İyi derler ve test eder.

% gcc -o tic-tac-toe tic-tac-toe.c
% ./tic-tac-toe
     | |
  - + - + -
     | |
  - + - + -
     | |
  Hareketleri "" olarak girin (tırnak işareti yok, endeksli sıfır)
  X'in hareketi: 1 2
     | |
  - + - + -
     | | X
  - + - + -
     | |
  O'nun hamlesi: 1 2
  yasa dışı hareket (zaten alınmış), tekrar deneyin
  O'nun hamlesi: 3 3
  kural dışı hareket (oyun alanı dışında), tekrar deneyin
  O'nun hamlesi: 2 2
     | |
  - + - + -
     | | X
  - + - + -
     | | Ö
  X'in hareketi: 1 0
     | |
  - + - + -
   X | | X
  - + - + -
     | | Ö
  O'nun hareketi: 11
     | |
  - + - + -
   X | O | X
  - + - + -
     | | Ö
  X'in hareketi: 0 0
   X | |
  - + - + -
   X | O | X
  - + - + -
     | | Ö
  O'nun hamlesi: 2 0
   X | |
  - + - + -
   X | O | X
  - + - + -
   O | | Ö
  X'in hareketi: 2 1
   X | |
  - + - + -
   X | O | X
  - + - + -
   O | X | Ö
  O'nun hamlesi: 0 2
   X | | Ö
  - + - + -
   X | O | X
  - + - + -
   O | X | Ö
  tic-tac-toe! O kazanır!
% ./tic-tac-toe
     | |
  - + - + -
     | |
  - + - + -
     | |
  Hareketleri "" olarak girin (tırnak işareti yok, endeksli sıfır)
  X'in hareketi: 0 0
   X | |
  - + - + -
     | |
  - + - + -
     | |
  O'nun hareketi: 0 1
   X | O |
  - + - + -
     | |
  - + - + -
     | |
  X'in hareketi: 0 2
   X | O | X
  - + - + -
     | |
  - + - + -
     | |
  O'nun hareketi: 1 0
   X | O | X
  - + - + -
   O | |
  - + - + -
     | |
  X'in hareketi: 1 1
   X | O | X
  - + - + -
   O | X |
  - + - + -
     | |
  O'nun hamlesi: 2 0
   X | O | X
  - + - + -
   O | X |
  - + - + -
   O | |
  X'in hareketi: 2 1
   X | O | X
  - + - + -
   O | X |
  - + - + -
   O | X |
  O'nun hamlesi: 2 2
   X | O | X
  - + - + -
   O | X |
  - + - + -
   O | X | Ö
  X'in hareketi: 1 2
   X | O | X
  - + - + -
   O | X | X
  - + - + -
   O | X | Ö
  çıkmaz ... kimse kazanmaz :(
%

Eğlenceliydi, teşekkürler!

Aslında, onu düşününce, sihirli bir kareye ihtiyacınız yok, sadece her satır / sütun / köşegen için bir sayı. Bu, sihirli kareyi n× nmatrislerine genellemekten biraz daha kolaydır , çünkü sadece saymanız gerekir n.


3

Bir röportajımda da bana aynı soru soruldu. Düşüncelerim: Matrisi 0 ile başlatın. 3 dizi tutun 1) sum_row (size n) 2) sum_column (boyut n) 3) diyagonal (boyut 2)

Her hareket için (X) kutu değerini 1 azaltın ve her hareket için (0) 1 artırın. Geçerli harekette değiştirilen satır / sütun / köşegenin toplamı -3 veya + ise herhangi bir noktada 3, birinin oyunu kazandığı anlamına gelir. Bir çekiliş için, moveCount değişkenini korumak için yukarıdaki yaklaşımı kullanabiliriz.

Bir şeyi kaçırdığımı mı düşünüyorsun?

Düzenleme: Aynı nxn matrisi için de kullanılabilir. Toplam, +3 veya -3 olmalıdır.


2

noktanın anti diyag üzerinde olup olmadığını belirlemenin döngü dışı bir yolu:

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

2

Partiye geç kaldım, ancak sihirli bir kare kullanmakta bulduğum bir faydayı belirtmek istedim , yani bir sonraki dönüşte kazanmaya veya kaybetmeye neden olacak kareye referans almak için kullanılabileceğini değil, sadece bir oyunun ne zaman bittiğini hesaplamak için kullanılıyor.

Bu sihirli kareyi al:

4 9 2
3 5 7
8 1 6

İlk olarak, scoresher hareket yapıldığında artan bir dizi ayarlayın . Ayrıntılar için bu yanıta bakın. Şimdi, X'i [0,0] ve [0,1] 'de art arda iki kez yasa dışı olarak oynarsak, scoresdizi şöyle görünür:

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

Ve yönetim kurulu şuna benzer:

X . .
X . .
. . .

Ardından, hangi karede kazanılacağına / bloke edileceğine dair bir referans almak için tek yapmamız gereken:

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

Gerçekte, uygulama, numaralandırılmış anahtarları (JavaScript'te) kullanmak gibi birkaç ek numara gerektirir, ancak bunu oldukça basit buldum ve eğlence matematiğinden keyif aldım.


1

Satır, sütun, çapraz kontrollerde bazı optimizasyonlar yaptım. Belirli bir sütunu veya köşegeni kontrol etmemiz gerekiyorsa, esas olarak ilk iç içe döngüde karar verilir. Bu nedenle, zaman kazandıran sütunların veya köşegenlerin kontrol edilmesinden kaçınıyoruz. Bu, tahta boyutu daha fazla olduğunda ve hücrelerin önemli bir kısmı doldurulmadığında büyük etki yaratır.

İşte bunun için java kodu.

    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

Bu algoritmayı, kartın 1x9'a karşı 3x3 gösterimini kullandığı için seviyorum.

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
Bu yaklaşımı en çok seviyorum. "Başlangıç" ve "incr" nin ne anlama geldiğini açıklasaydınız yardımcı olurdu. (Tüm "satırları" bir başlangıç ​​dizini ve atlanacak dizin sayısı olarak ifade etmenin bir yolu.)
nafg

Lütfen daha fazla açıklama ekleyin. Bu kodu nasıl oluşturdunuz?
Farzan

0

Başka bir seçenek: tablonuzu kodla oluşturun. Simetriye kadar, kazanmanın yalnızca üç yolu vardır: kenar sıra, orta sıra veya çapraz. Bu üçünü alın ve mümkün olan her şekilde çevirin:

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

Bu simetrilerin oyun oynama kodunuzda daha fazla kullanımı olabilir: Eğer zaten döndürülmüş bir versiyonunu gördüğünüz bir tahtaya gelirseniz, sadece önbelleğe alınmış değeri veya önbelleğe alınmış en iyi hamleyi ondan alabilir (ve geri döndürebilirsiniz). Bu genellikle oyun alt ağacını değerlendirmekten çok daha hızlıdır.

(Sola ve sağa çevirmek de aynı şekilde yardımcı olabilir; burada gerekli değildi çünkü kazanan modellerin dönüşleri ayna simetriktir.)


0

İşte bulduğum bir çözüm, bu, sembolleri karakter olarak saklar ve X veya O'nun kazanıp kazanmadığını anlamak için char'ın int değerini kullanır (Hakemin koduna bakın)

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

Ayrıca, gerçekten çalıştığını doğrulamak için birim testlerim de burada

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

Tam çözüm: https://github.com/nashjain/tictactoe/tree/master/java


0

9 yuva için aşağıdaki yaklaşıma ne dersiniz? 3x3 matris (a1, a2 .... a9) için 9 tamsayı değişken bildirin; burada a1, a2, a3 satır-1'i temsil eder ve a1, a4, a7 sütun-1'i oluşturur (fikri anladınız). Oyuncu-1'i belirtmek için '1'i ve Oyuncu-2'yi belirtmek için' 2'yi kullanın.

8 olası kazanma kombinasyonu vardır: Kazan-1: a1 + a2 + a3 (hangi oyuncunun kazandığına bağlı olarak cevap 3 veya 6 olabilir) Kazan-2: a4 + a5 + a6 Kazan-3: a7 + a8 + a9 Kazan-4 : a1 + a4 + a7 .... Kazan-7: a1 + a5 + a9 Kazan-8: a3 + a5 + a7

Şimdi, eğer birinci oyuncu a1'i geçerse, 3 değişkenin toplamını yeniden değerlendirmemiz gerektiğini biliyoruz: Win-1, Win-4 ve Win-7. Hangisi 'Kazanır-?' değişkenler 3 veya 6'ya ulaştığında oyunu kazanır. İlk olarak Win-1 değişkeni 6'ya ulaşırsa Oyuncu-2 kazanır.

Bu çözümün kolay ölçeklenebilir olmadığını anlıyorum.


0

Bu, kontrol etmenin gerçekten basit bir yoludur.

    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

Sınav için yatılı 5 * 5 alanınız varsa, sonraki kontrol yöntemini kullandım:

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

Bence daha açık, ama muhtemelen en uygun yol değil.


0

İşte 2 boyutlu bir dizi kullanan çözümüm:

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

Sabit zamanlı çözüm, O (8) 'de çalışır.

Kartın durumunu ikili sayı olarak kaydedin. En küçük bit (2 ^ 0), panonun sol üst satırıdır. Sonra sağa, sonra aşağıya doğru gider.

IE

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

Her oyuncunun durumu temsil etmek için kendi ikili numarası vardır (çünkü tic-tac-toe) 3 duruma (X, O ve boş) sahiptir, bu nedenle tek bir ikili sayı, birden fazla oyuncu için panonun durumunu temsil etmek için çalışmayacaktır.

Örneğin, aşağıdaki gibi bir tahta:

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

X oyuncusu için bitlerin, oyuncu O için olan bitlerden ayrık olduğuna dikkat edin, bu açıktır çünkü X, O'nun bir taşı olduğu bir taşı koyamaz ve bunun tersi de geçerlidir.

Bir oyuncunun kazanıp kazanmadığını kontrol etmek için, o oyuncunun kapladığı tüm pozisyonları kazan-pozisyon olduğunu bildiğimiz bir pozisyonla karşılaştırmamız gerekir. Bu durumda, bunu yapmanın en kolay yolu, oyuncu konumunu ve kazanma konumunu AND-geçitlendirmek ve ikisinin eşit olup olmadığını görmek olacaktır.

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

Örneğin.

X: 111001010
W: 111000000 // pozisyon kazanın, hepsi ilk satırda aynı.
------------
&: 111000000

Not:, X & W = Wdolayısıyla X bir kazanma durumundadır.

Bu sabit zamanlı bir çözümdür, yalnızca kazanma pozisyonlarının sayısına bağlıdır, çünkü AND geçidi uygulamak sabit zamanlı bir işlemdir ve kazanma pozisyonlarının sayısı sonludur.

Ayrıca, tüm geçerli kart durumlarını, yalnızca 9 bit ile temsil edilebilen tüm sayıları listeleme görevini basitleştirir. Ancak elbette, bir numaranın geçerli bir pano durumu olduğunu garanti etmek için fazladan bir koşula ihtiyacınız vardır (örneğin 0b111111111, geçerli bir 9 bitlik sayıdır, ancak bu geçerli bir pano durumu değildir, çünkü X tüm dönüşleri henüz almıştır).

Olası kazanma pozisyonlarının sayısı anında oluşturulabilir, ancak yine de buradalar.

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

Tüm pano pozisyonlarını numaralandırmak için aşağıdaki döngüyü çalıştırabilirsiniz. Yine de bir numaranın geçerli bir yönetim kurulu durumu olup olmadığını belirlemeyi başkasına bırakacağım.

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

Lütfen daha fazla açıklama ekleyin ve kodu OP'nin sorusunu tam olarak yanıtlayacak şekilde değiştirin.
Farzan

0

Bu yaklaşımın henüz yayınlanıp yayınlanmadığından emin değilim. Bu, herhangi bir m * n tahtası için işe yaramalı ve bir oyuncunun ardışık " kazananPos " pozisyonunu doldurması beklenir . Fikir, çalışan pencereye dayanmaktadır.

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

Bunun için bir kez bir bilim projesinin parçası olarak bir algoritma geliştirdim.

Temel olarak tahtayı, 2x2 karede kazanmak için farklı olası kombinasyonları test ederek, üst üste binen 2x2 reklama bölersiniz.

Yavaştır, ancak oldukça doğrusal bellek gereksinimleri ile herhangi bir boyuttaki kart üzerinde çalışma avantajına sahiptir.

Keşke uygulamamı bulabilseydim


Bitişi test etmek için yineleme gerekli değildir.
Gabriel Llamas
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.