Algorytm określania zakończenia gry w kółko i krzyżyk

97

Napisałem grę w kółko i krzyżyk w Javie i moją obecną metodę określania kont zakończenia gry dla następujących możliwych scenariuszy zakończenia gry:

  1. Plansza jest pełna i żaden zwycięzca nie został jeszcze wyłoniony: Gra kończy się remisem.
  2. Krzyż wygrał.
  3. Circle wygrał.

Niestety, aby to zrobić, czyta wstępnie zdefiniowany zestaw tych scenariuszy z tabeli. To niekoniecznie jest złe, biorąc pod uwagę, że na planszy jest tylko 9 miejsc, a zatem stół jest nieco mały, ale czy istnieje lepszy algorytmiczny sposób określania, czy gra się skończyła? Ustalenie, czy ktoś wygrał, czy nie, jest sednem problemu, ponieważ sprawdzenie, czy 9 ​​miejsc jest pełnych, jest trywialne.

Metoda tabeli może być rozwiązaniem, ale jeśli nie, to co nim jest? A co by było, gdyby tablica nie miała odpowiedniego rozmiaru n=9? Co gdyby był znacznie większy deska, powiedzmy n=16, n=25i tak dalej, powodując szereg kolejno wprowadzanych elementów do wygrania będzie x=4, x=5itp? Ogólny algorytm dla wszystkich n = { 9, 16, 25, 36 ... }?

dreadwail
źródło
Dodaję moje 2 centy do wszystkich odpowiedzi: Zawsze wiesz, że potrzebujesz co najmniej kilku X lub Os na planszy, aby wygrać (w normalnej planszy 3x3 jest to 3). Możesz więc śledzić liczbę każdego z nich i zacząć sprawdzać wygrane tylko wtedy, gdy są wyższe.
Yuval A.

Odpowiedzi:

133

Wiesz, że wygrywający ruch może nastąpić dopiero po tym, jak X lub O wykonał ostatni ruch, więc możesz przeszukiwać tylko wiersz / kolumnę z opcjonalną diagą zawartą w tym ruchu, aby ograniczyć przestrzeń poszukiwań podczas próby określenia zwycięskiej planszy. Ponieważ w grze w kółko i krzyżyk jest ustalona liczba ruchów, po wykonaniu ostatniego ruchu, jeśli nie był to ruch wygrywający, domyślnie jest to gra remisowa.

edytuj: ten kod dotyczy planszy n na n z n z rzędu, aby wygrać (3x3 wymagania na planszy 3 z rzędu itp.)

edycja: dodano kod do sprawdzania antydiag, nie mogłem wymyślić sposobu bez pętli, aby określić, czy punkt był na antydiag, dlatego brakuje tego kroku

public class TripleT {

    enum State{Blank, X, O};

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

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

        //check end conditions

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

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

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

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

        //check draw
        if(moveCount == (Math.pow(n, 2) - 1)){
            //report draw
        }
    }
}
Hardwareguy
źródło
6
Zapomniałeś sprawdzić anty-przekątną.
rampion
1
W przypadku planszy 3x3 x + y zawsze będzie równe 2 na anty-przekątnej, zawsze będzie parzyste w środku i na rogach szachownicy i nieparzyste w innych miejscach.
Chris Doggett
5
Nie rozumiem czeku na remis na końcu, czy nie powinien on odjąć 1?
Inez
4
Są przypadki, w których gracz wygrywa w ostatnim możliwym (dziewiątym) ruchu. W takim przypadku zarówno zwycięzca, jak i remis zostaną zgłoszone ...
Marc
5
@ Roamer-1888 nie chodzi o to, z ilu wierszy składa się twoje rozwiązanie, chodzi o skrócenie złożoności czasowej algorytmu sprawdzającego zwycięzcę.
Shady
38

możesz użyć magicznego kwadratu http://mathworld.wolfram.com/MagicSquare.html, jeśli którykolwiek wiersz, kolumna lub diag sumują się do 15, to gracz wygrał.

adk
źródło
3
Jak to się przekłada na grę w kółko i krzyżyk?
Paul Alexander
To zgrabna informacja, której nie znałem, więc zdecydowanie dziękuję za informację. Jak wspomniał Paul, nie jest od razu jasne, w jaki sposób pomogłoby to w rozwiązaniu problemu, ale wydaje się, że może to odegrać część pełniejszego rozwiązania.
dreadwail
4
nałóż to. 1 dla białego, 2 dla czarnego i pomnóż. jeśli cokolwiek wypadnie do 15, to białe wygrywają, a jeśli wypadnie do 30, to wygrywają czarne.
adk
1
Duży O-mądry, jest całkiem tani, zwłaszcza jeśli połączysz go z kontrolą komórkową Hardwareguy'a. Każda komórka może mieć tylko 4 możliwe krzyżyki: wiersz, kolumnę i dwie przekątne (ukośnik i lewy ukośnik). Tak więc, po wykonaniu ruchu, wystarczy wykonać maksymalnie 4 dodania i porównania. Odpowiedź Hardwareguya wymaga 4 (n-1) testów dla każdego ruchu, dla porównania.
rampion
29
Czy nie moglibyśmy po prostu zrobić tego z 1 i -1 i zsumować każdy wiersz / kolumnę / diag, aby zobaczyć, czy jest to n czy -n?
Nathan
24

A co z tym pseudokodem:

Po tym jak gracz kładzie figurę na pozycji (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

Użyłbym tablicy char [n, n], z O, X i spacją dla pustych.

  1. prosty.
  2. Jedna pętla.
  3. Pięć prostych zmiennych: 4 liczby całkowite i jedna logiczna.
  4. Wagi do dowolnego rozmiaru n.
  5. Sprawdza tylko bieżący kawałek.
  6. Żadnej magii. :)
Osama Al-Maadeed
źródło
jeśli komórka [i, n- (i + 1)] = gracz to rdiag ++; - Wygląda na to, że z nawiasami będzie poprawnie. Czy mam rację?
Pumych
@Pumych, nie. Jeśli i==1i n==3, rdiagmusi być zaznaczone na (1, 3)i (1, 3-1+1)jest równe poprawnym współrzędnym, ale (1, 3-(1+1))nie.
KgOfHedgehogs
Mógł pomyśleć, że komórki mają indeks zerowy.
Matias Grioni
to było tylko trochę z głowy ... trzeba to naprawić podczas pisania kodu :)
Osama Al-Maadeed
21

Jest to podobne do odpowiedzi Osamy ALASSIRY , ale zamienia stałą przestrzeń i czas liniowy na przestrzeń liniową i czas stały. Oznacza to, że po inicjalizacji nie ma pętli.

Zainicjuj parę (0,0)dla każdego wiersza, każdej kolumny i dwóch przekątnych (przekątnych i przeciw przekątnych). Te pary przedstawiają zgromadzone (sum,sum)elementy w odpowiednim rzędzie, kolumnie lub po przekątnej, gdzie

Bierka gracza A ma wartość (1,0)
Figura gracza B ma wartość (0,1)

Kiedy gracz umieszcza pionek, zaktualizuj odpowiednią parę rzędów, parę kolumn i pary po przekątnych (jeśli są na przekątnych). Jeśli wszystkie nowo zaktualizowanego wiersza, kolumna lub ukośnie pary albo równa (n,0)lub (0,n)następnie albo A i B wygranych, odpowiednio.

Analiza asymptotyczna:

O (1) czas (na ruch)
O (n) spacja (ogólnie)

Do wykorzystania pamięci używasz 4*(n+1)liczb całkowitych.

dwa_elementy * n_rows + two_elements * n_columns +
dwa_elementy * dwie_diagonale = 4 * n + 4 liczby całkowite = 4 (n + 1) liczby całkowite

Ćwiczenie: Czy widzisz, jak sprawdzić remis w czasie O (1) na ruch? Jeśli tak, możesz zakończyć grę wcześniej po remisie.

CJ Gaconnet
źródło
1
Myślę, że to jest lepsze niż Osama ALASSIRY, ponieważ jest to mniej więcej O(sqrt(n))czas, ale trzeba to zrobić po każdym ruchu, gdzie n jest rozmiarem planszy. Więc kończysz z O(n^1.5). W przypadku tego rozwiązania masz O(n)ogólny czas.
Matias Grioni,
ładny sposób patrzenia na to, warto spojrzeć na rzeczywiste "rozwiązania" ... dla 3x3, miałbyś po prostu 8 par "logicznych" ... Może być jeszcze bardziej efektywne przestrzennie, gdyby było 2 bity każde ... potrzeba 16 bitów i możesz po prostu bitowo LUB 1 w odpowiednim odtwarzaczu przesuniętym w lewo we właściwe miejsce :)
Osama Al-Maadeed
13

Oto moje rozwiązanie, które napisałem dla projektu, nad którym pracuję, w javascript. Jeśli nie przeszkadza Ci koszt pamięci kilku tablic, jest to prawdopodobnie najszybsze i najprostsze rozwiązanie, jakie znajdziesz. Zakłada, że ​​znasz pozycję ostatniego ruchu.

/*
 * Determines if the last move resulted in a win for either player
 * board: is an array representing the board
 * lastMove: is the boardIndex of the last (most recent) move
 *  these are the boardIndexes:
 *
 *   0 | 1 | 2
 *  ---+---+---
 *   3 | 4 | 5
 *  ---+---+---
 *   6 | 7 | 8
 * 
 * returns true if there was a win
 */
var winLines = [
    [[1, 2], [4, 8], [3, 6]],
    [[0, 2], [4, 7]],
    [[0, 1], [4, 6], [5, 8]],
    [[4, 5], [0, 6]],
    [[3, 5], [0, 8], [2, 6], [1, 7]],
    [[3, 4], [2, 8]],
    [[7, 8], [2, 4], [0, 3]],
    [[6, 8], [1, 4]],
    [[6, 7], [0, 4], [2, 5]]
];
function isWinningMove(board, lastMove) {
    var player = board[lastMove];
    for (var i = 0; i < winLines[lastMove].length; i++) {
        var line = winLines[lastMove][i];
        if(player === board[line[0]] && player === board[line[1]]) {
            return true;
        }
    }
    return false;
}
Jack Allan
źródło
2
Byłoby to podejście typu „wielki młot”, ale rzeczywiście jest to realne rozwiązanie, zwłaszcza w przypadku witryny jako jednego z wielu kreatywnych i działających rozwiązań tego problemu. Również jest krótki, elegancki i bardzo czytelny - jak na siatkę 3x3 (tradycyjny Tx3) podoba mi się ten algorytm.
nocarrier
Ten jest niesamowity !! Odkryłem, że jest mały błąd w wygrywających wzorcach, na pozycji 8 wzorce powinny być [6,7], [0,4] i [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

Właśnie napisałem to dla mojej klasy programowania w C.

Publikuję to, ponieważ żaden z innych przykładów tutaj nie będzie działał z żadną prostokątną siatką i każdą liczbą N w rzędzie kolejnych znaków, aby wygrać.

Znajdziesz mój algorytm, taki jaki jest, w checkWinner()funkcji. Nie używa magicznych liczb ani niczego wymyślnego do sprawdzenia zwycięzcy, po prostu używa czterech dla pętli - Kod jest dobrze skomentowany, więc myślę, że będę mówił sam za siebie.

// This program will work with any whole number sized rectangular gameBoard.
// It checks for N marks in straight lines (rows, columns, and diagonals).
// It is prettiest when ROWS and COLS are single digit numbers.
// Try altering the constants for ROWS, COLS, and N for great fun!    

// PPDs come first

    #include <stdio.h>
    #define ROWS 9              // The number of rows our gameBoard array will have
    #define COLS 9              // The number of columns of the same - Single digit numbers will be prettier!
    #define N 3                 // This is the number of contiguous marks a player must have to win
    #define INITCHAR ' '        // This changes the character displayed (a ' ' here probably looks the best)
    #define PLAYER1CHAR 'X'     // Some marks are more aesthetically pleasing than others
    #define PLAYER2CHAR 'O'     // Change these lines if you care to experiment with them


// Function prototypes are next

    int playGame    (char gameBoard[ROWS][COLS]);               // This function allows the game to be replayed easily, as desired
    void initBoard  (char gameBoard[ROWS][COLS]);               // Fills the ROWSxCOLS character array with the INITCHAR character
    void printBoard (char gameBoard[ROWS][COLS]);               // Prints out the current board, now with pretty formatting and #s!
    void makeMove   (char gameBoard[ROWS][COLS], int player);   // Prompts for (and validates!) a move and stores it into the array
    int checkWinner (char gameBoard[ROWS][COLS], int player);   // Checks the current state of the board to see if anyone has won

// The starting line
int main (void)
{
    // Inits
    char gameBoard[ROWS][COLS];     // Our gameBoard is declared as a character array, ROWS x COLS in size
    int winner = 0;
    char replay;

    //Code
    do                              // This loop plays through the game until the user elects not to
    {
        winner = playGame(gameBoard);
        printf("\nWould you like to play again? Y for yes, anything else exits: ");

        scanf("%c",&replay);        // I have to use both a scanf() and a getchar() in
        replay = getchar();         // order to clear the input buffer of a newline char
                                    // (http://cboard.cprogramming.com/c-programming/121190-problem-do-while-loop-char.html)

    } while ( replay == 'y' || replay == 'Y' );

    // Housekeeping
    printf("\n");
    return winner;
}


int playGame(char gameBoard[ROWS][COLS])
{
    int turn = 0, player = 0, winner = 0, i = 0;

    initBoard(gameBoard);

    do
    {
        turn++;                                 // Every time this loop executes, a unique turn is about to be made
        player = (turn+1)%2+1;                  // This mod function alternates the player variable between 1 & 2 each turn
        makeMove(gameBoard,player);
        printBoard(gameBoard);
        winner = checkWinner(gameBoard,player);

        if (winner != 0)
        {
            printBoard(gameBoard);

            for (i=0;i<19-2*ROWS;i++)           // Formatting - works with the default shell height on my machine
                printf("\n");                   // Hopefully I can replace these with something that clears the screen for me

            printf("\n\nCongratulations Player %i, you've won with %i in a row!\n\n",winner,N);
            return winner;
        }

    } while ( turn < ROWS*COLS );                           // Once ROWS*COLS turns have elapsed

    printf("\n\nGame Over!\n\nThere was no Winner :-(\n");  // The board is full and the game is over
    return winner;
}


void initBoard (char gameBoard[ROWS][COLS])
{
    int row = 0, col = 0;

    for (row=0;row<ROWS;row++)
    {
        for (col=0;col<COLS;col++)
        {
            gameBoard[row][col] = INITCHAR;     // Fill the gameBoard with INITCHAR characters
        }
    }

    printBoard(gameBoard);                      // Having this here prints out the board before
    return;                             // the playGame function asks for the first move
}


void printBoard (char gameBoard[ROWS][COLS])    // There is a ton of formatting in here
{                                               // That I don't feel like commenting :P
    int row = 0, col = 0, i=0;                  // It took a while to fine tune
                                                // But now the output is something like:
    printf("\n");                               // 
                                                //    1   2   3
    for (row=0;row<ROWS;row++)                  // 1    |   |
    {                                           //   -----------
        if (row == 0)                           // 2    |   |
        {                                       //   -----------
            printf("  ");                       // 3    |   |

            for (i=0;i<COLS;i++)
            {
                printf(" %i  ",i+1);
            }

            printf("\n\n");
        }

        for (col=0;col<COLS;col++)
        {
            if (col==0)
                printf("%i ",row+1);

            printf(" %c ",gameBoard[row][col]);

            if (col<COLS-1)
                printf("|");
        }

        printf("\n");

        if (row < ROWS-1)
        {
            for(i=0;i<COLS-1;i++)
            {
                if(i==0)
                    printf("  ----");
                else
                    printf("----");
            }

            printf("---\n");
        }
    }

    return;
}


void makeMove (char gameBoard[ROWS][COLS],int player)
{
    int row = 0, col = 0, i=0;
    char currentChar;

    if (player == 1)                    // This gets the correct player's mark
        currentChar = PLAYER1CHAR;
    else
        currentChar = PLAYER2CHAR;

    for (i=0;i<21-2*ROWS;i++)           // Newline formatting again :-(
        printf("\n");

    printf("\nPlayer %i, please enter the column of your move: ",player);
    scanf("%i",&col);
    printf("Please enter the row of your move: ");
    scanf("%i",&row);

    row--;                              // These lines translate the user's rows and columns numbering
    col--;                              // (starting with 1) to the computer's (starting with 0)

    while(gameBoard[row][col] != INITCHAR || row > ROWS-1 || col > COLS-1)  // We are not using a do... while because
    {                                                                       // I wanted the prompt to change
        printBoard(gameBoard);
        for (i=0;i<20-2*ROWS;i++)
            printf("\n");
        printf("\nPlayer %i, please enter a valid move! Column first, then row.\n",player);
        scanf("%i %i",&col,&row);

        row--;                          // See above ^^^
        col--;
    }

    gameBoard[row][col] = currentChar;  // Finally, we store the correct mark into the given location
    return;                             // And pop back out of this function
}


int checkWinner(char gameBoard[ROWS][COLS], int player)     // I've commented the last (and the hardest, for me anyway)
{                                                           // check, which checks for backwards diagonal runs below >>>
    int row = 0, col = 0, i = 0;
    char currentChar;

    if (player == 1)
        currentChar = PLAYER1CHAR;
    else
        currentChar = PLAYER2CHAR;

    for ( row = 0; row < ROWS; row++)                       // This first for loop checks every row
    {
        for ( col = 0; col < (COLS-(N-1)); col++)           // And all columns until N away from the end
        {
            while (gameBoard[row][col] == currentChar)      // For consecutive rows of the current player's mark
            {
                col++;
                i++;
                if (i == N)
                {
                    return player;
                }
            }
            i = 0;
        }
    }

    for ( col = 0; col < COLS; col++)                       // This one checks for columns of consecutive marks
    {
        for ( row = 0; row < (ROWS-(N-1)); row++)
        {
            while (gameBoard[row][col] == currentChar)
            {
                row++;
                i++;
                if (i == N)
                {
                    return player;
                }
            }
            i = 0;
        }
    }

    for ( col = 0; col < (COLS - (N-1)); col++)             // This one checks for "forwards" diagonal runs
    {
        for ( row = 0; row < (ROWS-(N-1)); row++)
        {
            while (gameBoard[row][col] == currentChar)
            {
                row++;
                col++;
                i++;
                if (i == N)
                {
                    return player;
                }
            }
            i = 0;
        }
    }
                                                        // Finally, the backwards diagonals:
    for ( col = COLS-1; col > 0+(N-2); col--)           // Start from the last column and go until N columns from the first
    {                                                   // The math seems strange here but the numbers work out when you trace them
        for ( row = 0; row < (ROWS-(N-1)); row++)       // Start from the first row and go until N rows from the last
        {
            while (gameBoard[row][col] == currentChar)  // If the current player's character is there
            {
                row++;                                  // Go down a row
                col--;                                  // And back a column
                i++;                                    // The i variable tracks how many consecutive marks have been found
                if (i == N)                             // Once i == N
                {
                    return player;                      // Return the current player number to the
                }                                       // winnner variable in the playGame function
            }                                           // If it breaks out of the while loop, there weren't N consecutive marks
            i = 0;                                      // So make i = 0 again
        }                                               // And go back into the for loop, incrementing the row to check from
    }

    return 0;                                           // If we got to here, no winner has been detected,
}                                                       // so we pop back up into the playGame function

// The end!

// Well, almost.

// Eventually I hope to get this thing going
// with a dynamically sized array. I'll make
// the CONSTANTS into variables in an initGame
// function and allow the user to define them.
mattR
źródło
Bardzo pomocne. Próbowałem znaleźć coś bardziej wydajnego, na przykład jeśli znasz N = COL = ROW, możesz to zredukować do czegoś znacznie prostszego, ale nie znalazłem nic bardziej wydajnego dla dowolnego rozmiaru płyty i N.
Hassan
6

Jeśli plansza jest n × n, to jest n wierszy, n kolumn i 2 przekątne. Sprawdź każdy z nich dla wszystkich X lub all-O, aby znaleźć zwycięzcę.

Jeśli do wygrania potrzeba tylko x < n następujących po sobie kwadratów, to jest to trochę bardziej skomplikowane. Najbardziej oczywistym rozwiązaniem jest sprawdzenie, czy każdy kwadrat x × x jest zwycięzcą. Oto kod, który to potwierdza.

(I faktycznie nie przetestować ten * Kaszel *, ale nie kompilacji przy pierwszej próbie, yay mnie!)

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

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

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

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

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

                    return board[row][left];
                }

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

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

                    return board[top][col];
                }

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

                    return board[top][left];
                }

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

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

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

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

        // The board is full.
        if (isFull) {
            return Square.NONE;
        }
        // The board is not full and we didn't find a solution.
        else {
            return null;
        }
    }
}
John Kugelman
źródło
Rozumiem, co masz na myśli. W tradycyjnej grze n = x byłoby (n * n * 2) wszystkich odpowiedzi. To nie zadziałałoby, gdyby x (liczba kolejnych zwycięstw potrzebnych do wygrania) było mniejsze niż n. To jednak dobre rozwiązanie, bardziej mi się podoba niż stół ze względu na jego rozciągliwość.
dreadwail
Nie wspomniałem jednak o możliwości x <n w oryginalnym poście, więc Twoja odpowiedź jest nadal trafna.
dreadwail
4

Nie znam tak dobrze Javy, ale znam C, więc wypróbowałem pomysł magicznego kwadratu adk (wraz z ograniczeniami wyszukiwania Hardwareguy'a ).

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

Dobrze się kompiluje i testuje.

% gcc -o kółko i krzyżyk tic-tac-toe.c
% ./kółko i krzyżyk
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Wpisz ruchy jako „” (bez cudzysłowów, indeksowane zero)
  Ruch X: 1 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | |
  Ruch O: 1 2
  nielegalny ruch (już wykonany), spróbuj ponownie
  Ruch O: 3 3
  nielegalny ruch (poza szachownicą), spróbuj ponownie
  Ruch O: 2 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | | O
  Ruch X: 1 0
     | |
  --- + --- + ---
   X | | X
  --- + --- + ---
     | | O
  Ruch O: 1 1
     | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | O
  Ruch X: 0 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | O
  Ruch O: 2 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | | O
  Ruch X: 2 1
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | O
  Ruch O: 0 2
   X | | O
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | O
  kółko i krzyżyk! O wygrywa!
% ./kółko i krzyżyk
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Wpisz ruchy jako „” (bez cudzysłowów, indeksowane zero)
  Ruch X: 0 0
   X | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Ruch O: 0 1
   X | O |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Ruch X: 0 2
   X | O | X
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Ruch O: 1 0
   X | O | X
  --- + --- + ---
   O | |
  --- + --- + ---
     | |
  Ruch X: 1 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
     | |
  Ruch O: 2 0
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | |
  Ruch X: 2 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X |
  Ruch O: 2 2
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X | O
  Ruch X: 1 2
   X | O | X
  --- + --- + ---
   O | X | X
  --- + --- + ---
   O | X | O
  pat ... nikt nie wygrywa :(
%

To było fajne, dzięki!

Właściwie, myśląc o tym, nie potrzebujesz magicznego kwadratu, wystarczy policzyć dla każdego wiersza / kolumny / przekątnej. Jest to trochę łatwiejsze niż uogólnienie magicznego kwadratu na macierze n× n, ponieważ wystarczy policzyć do n.

rampion
źródło
3

W jednym z wywiadów zadano mi to samo pytanie. Moje przemyślenia: Zainicjuj macierz wartością 0. Zachowaj 3 tablice 1) sum_row (rozmiar n) 2) sum_column (rozmiar n) 3) przekątna (rozmiar 2)

Dla każdego ruchu o (X) zmniejsz wartość pola o 1, a dla każdego ruchu o (0) zwiększaj ją o 1. W dowolnym momencie, jeśli wiersz / kolumna / przekątna, który został zmodyfikowany w bieżącym ruchu, ma sumę -3 lub + 3 oznacza, że ​​ktoś wygrał grę. W przypadku remisu możemy zastosować powyższe podejście, aby zachować zmienną moveCount.

Myślisz, że czegoś mi brakuje?

Edycja: to samo można zastosować dla macierzy nxn. Suma powinna wynosić nawet +3 lub -3.

Piyush Beli
źródło
2

niepętlowy sposób określenia, czy punkt znajdował się na antydiag:

`if (x + y == n - 1)`
Jeff
źródło
2

Spóźniłem się na przyjęcie, ale chciałem wskazać jedną korzyść, jaką znalazłem przy użyciu magicznego kwadratu , a mianowicie, że można go użyć do uzyskania odniesienia do kwadratu, który spowodowałby wygraną lub przegraną w następnej turze, zamiast po prostu używane do obliczania zakończenia gry.

Weź ten magiczny kwadrat:

4 9 2
3 5 7
8 1 6

Najpierw skonfiguruj scorestablicę, która jest zwiększana za każdym razem, gdy wykonywany jest ruch. Zobacz tę odpowiedź, aby uzyskać szczegółowe informacje. Teraz, jeśli nielegalnie zagramy X dwa razy z rzędu w [0,0] i [0,1], to scorestablica wygląda tak:

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

Tablica wygląda tak:

X . .
X . .
. . .

Następnie wszystko, co musimy zrobić, aby uzyskać odniesienie do pola, na którym wygrać / zablokować, to:

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

W rzeczywistości implementacja wymaga kilku dodatkowych sztuczek, takich jak obsługa numerowanych klawiszy (w JavaScript), ale wydało mi się to całkiem proste i podobała mi się rekreacyjna matematyka.

gwg
źródło
1

Dokonałem optymalizacji w wierszach, kole, przekątnych. O tym, czy musimy sprawdzić konkretną kolumnę lub przekątną, decyduje głównie w pierwszej zagnieżdżonej pętli. W ten sposób unikamy sprawdzania kolumn lub przekątnych, oszczędzając czas. Ma to duży wpływ, gdy rozmiar płytki jest większy, a znaczna liczba komórek nie jest wypełniona.

Oto kod java do tego.

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


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


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


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

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

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

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

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

    if (allFilled)
        return DRAW;

    return INPROGRESS;
}
sanjiv
źródło
1

Podoba mi się ten algorytm, ponieważ wykorzystuje on reprezentację planszy 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;
}
Scott Duchin
źródło
1
Najbardziej podoba mi się to podejście. Pomogłoby to, gdybyś wyjaśnił, co oznacza „start” i „wzrost”. (Jest to sposób na wyrażenie wszystkich „linii” jako indeksu początkowego i liczby indeksów do pominięcia.)
nafg
Dodaj więcej wyjaśnień. Jak wymyśliłeś ten kod?
Farzan
0

Inna opcja: wygeneruj tabelę z kodem. Jeśli chodzi o symetrię, są tylko trzy sposoby na wygraną: rząd skrajny, rząd środkowy lub przekątna. Weź te trzy i obracaj nimi w każdy możliwy sposób:

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

Te symetrie mogą mieć więcej zastosowań w twoim kodzie do gry: jeśli dojdziesz do planszy, na której już widziałeś obróconą wersję, możesz po prostu wziąć wartość z pamięci podręcznej lub buforowany najlepszy ruch z tego (i cofnąć zwrot z powrotem). Zwykle jest to znacznie szybsze niż ocena poddrzewa gry.

(Przerzucanie w lewo i w prawo może pomóc w ten sam sposób; nie było to potrzebne, ponieważ zestaw obrotów zwycięskich wzorów jest lustrzano-symetryczny).

Darius Bacon
źródło
0

Oto rozwiązanie, które wymyśliłem, zapisuje symbole jako znaki i używa wartości int znaku, aby dowiedzieć się, czy wygrał X lub O (spójrz na kod sędziego)

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

Tutaj są również moje testy jednostkowe, aby sprawdzić, czy faktycznie działa

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

Pełne rozwiązanie: https://github.com/nashjain/tictactoe/tree/master/java

Naresh Jain
źródło
0

Co powiesz na następujące podejście do 9 slotów? Zadeklaruj 9 zmiennych całkowitych dla macierzy 3x3 (a1, a2 .... a9), gdzie a1, a2, a3 reprezentują wiersz-1, a a1, a4, a7 utworzą kolumnę-1 (masz pomysł). Użyj „1”, aby wskazać gracza-1, a „2”, aby wskazać gracza-2.

Istnieje 8 możliwych kombinacji wygranych: Win-1: a1 + a2 + a3 (odpowiedź może wynosić 3 lub 6 w zależności od tego, który gracz wygrał) Win-2: a4 + a5 + a6 Win-3: a7 + a8 + a9 Win-4 : a1 + a4 + a7 .... Win-7: a1 + a5 + a9 Win-8: a3 + a5 + a7

Teraz wiemy, że jeśli gracz jeden przekroczy a1, musimy ponownie oszacować sumę 3 zmiennych: Win-1, Win-4 i Win-7. Które z „wygranych”? zmienna osiągnie 3 lub 6 jako pierwsza wygrywa grę. Jeśli zmienna Win-1 osiągnie 6 jako pierwsza, wtedy Gracz-2 wygrywa.

Rozumiem, że tego rozwiązania nie da się łatwo skalować.

user3071398
źródło
0

To naprawdę prosty sposób na sprawdzenie.

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

}

luzja
źródło
0

Jeśli masz na przykład pole granicy 5 * 5, zastosowałem następną metodę sprawdzenia:

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

Myślę, że jest to bardziej przejrzyste, ale prawdopodobnie nie jest to najbardziej optymalny sposób.

Aleksei Moshkov
źródło
0

Oto moje rozwiązanie wykorzystujące dwuwymiarową tablicę:

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

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

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

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

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

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

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

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

Rozwiązanie ze stałym czasem, działa w O (8).

Zapisz stan płytki jako liczbę binarną. Najmniejszy bit (2 ^ 0) znajduje się w lewym górnym rzędzie planszy. Następnie idzie w prawo, a potem w dół.

TO ZNACZY

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

Każdy gracz ma swój własny numer binarny reprezentujący stan (ponieważ kółko i krzyżyk) ma 3 stany (X, O i puste), więc pojedyncza liczba binarna nie będzie działać, aby reprezentować stan planszy dla wielu graczy.

Na przykład tablica taka jak:

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

Zauważ, że bity gracza X są rozłączne od bitów gracza O, jest to oczywiste, ponieważ X nie może umieścić figury, w której O ma bierkę i odwrotnie.

Aby sprawdzić, czy gracz wygrał, musimy porównać wszystkie pozycje objęte przez tego gracza z pozycją, o której wiemy, że jest wygrana. W tym przypadku najłatwiejszym sposobem byłoby bramkowanie ORAZ pozycji gracza i zwycięskiej pozycji oraz sprawdzenie, czy te dwie pozycje są równe.

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

na przykład.

X: 111001010
W: 111000000 // wygrana pozycja, wszystko to samo w pierwszym rzędzie.
------------
&: 111000000

Uwaga: X & W = Wwięc X jest w stanie wygranej.

Jest to rozwiązanie oparte na stałym czasie, zależy tylko od liczby zwycięskich pozycji, ponieważ zastosowanie bramki AND jest operacją o stałym czasie, a liczba zwycięskich pozycji jest skończona.

Upraszcza również zadanie wyliczenia wszystkich prawidłowych stanów tablicy, tylko ich wszystkich liczb reprezentowanych przez 9 bitów. Ale oczywiście potrzebujesz dodatkowego warunku, aby zagwarantować, że liczba jest prawidłowym stanem tablicy (np. 0b111111111Jest prawidłową liczbą 9-bitową, ale nie jest to prawidłowy stan tablicy, ponieważ X właśnie wykonał wszystkie tury).

Liczbę możliwych zwycięskich pozycji można wygenerować w locie, ale tutaj i tak są.

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

Aby wyliczyć wszystkie pozycje tablicy, możesz uruchomić następującą pętlę. Chociaż pozostawię ustalenie, czy liczba jest prawidłowym stanem tablicy do kogoś innego.

UWAGA: (2 ** 9 - 1) = (2 ** 8) + (2 ** 7) + (2 ** 6) + ... (2 ** 1) + (2 ** 0)

for (short X = 0; X < (Math.pow(2,9) - 1); X++)
   System.out.println(isWinner(X));
alexsalo
źródło
Dodaj więcej opisu i zmień kod tak, aby dokładnie odpowiadał na pytanie OP.
Farzan
0

Nie jestem pewien, czy to podejście zostało jeszcze opublikowane. Powinno to zadziałać dla każdej planszy m * n, a gracz powinien zajmować kolejne pozycje „ zwycięzcaPos ”. Pomysł opiera się na uruchomionym oknie.

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;
}
orzeł
źródło
-2

Opracowałem kiedyś algorytm do tego w ramach projektu naukowego.

Zasadniczo rekurencyjnie dzielisz planszę na kilka nakładających się prostokątów 2x2, testując różne możliwe kombinacje wygranej na kwadracie 2x2.

Jest powolny, ale ma tę zaletę, że działa na płytach o dowolnej wielkości, z dość liniowymi wymaganiami dotyczącymi pamięci.

Żałuję, że nie mogę znaleźć mojej realizacji

bgw
źródło
Rekurencja do testowania wykończenia nie jest konieczna.
Gabriel Llamas