Алгоритм визначення гри Tic Tac Toe


97

Я написав гру на tic-tac-toe на Java, і мій поточний метод визначення кінця гри передбачає наступні можливі сценарії закінчення гри:

  1. Дошка заповнена, а переможця ще не оголошено: Гра - нічия.
  2. Хрест переміг.
  3. Коло перемогло.

На жаль, для цього він читає заздалегідь заданий набір цих сценаріїв з таблиці. Це не обов'язково погано, враховуючи, що на дошці є лише 9 пробілів, і тому таблиця дещо невелика, але чи є кращий алгоритмічний спосіб визначення, чи закінчилася гра? Визначення того, виграв хтось чи ні, - це суть проблеми, оскільки перевірка заповнення 9 пробілів є тривіальною.

Метод таблиці може бути рішенням, але якщо ні, то що таке? Крім того, що робити, якщо дошка не була розміром n=9? Що робити , якщо це було набагато більше , дошка оголошень, скажімо n=16, n=25і так далі, в результаті чого кількість послідовно розміщених елементів , щоб виграти , щоб бути x=4, x=5і т.д.? Загальний алгоритм, який потрібно використовувати для всіх n = { 9, 16, 25, 36 ... }?


Я додаю свої 2 центи за всі відповіді: Ви завжди знаєте, що вам потрібно, принаймні, кількість Xs або Os на дошці для виграшу (у звичайній дошці 3x3 це 3). Таким чином, ви можете відстежувати кількість кожного, і лише починати перевірку виграшів, якщо вони вищі.
Ювал А.

Відповіді:


133

Ви знаєте, що виграшний хід може статися лише після того, як X або O здійснили останній крок, тому ви можете шукати лише рядки / стовпці з додатковим діагнозом, які містяться в цьому ході, щоб обмежити ваші пошукові місця при спробі визначити переможну дошку. Крім того, оскільки у грі внічию тик-так-нога є фіксована кількість ходів, коли зроблений останній хід, якщо це не був виграшний хід, за замовчуванням гра-нічия.

редагувати: цей код призначений для виграшу дошки n на n з n підряд (3-х запитів на дошці 3 в ряд тощо)

редагувати: доданий код для перевірки антидіагностики, я не міг знайти спосіб, який не визначає, чи була точка на антидіагностиці, тому цей крок відсутній

public class TripleT {

    enum State{Blank, X, O};

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

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

        //check end conditions

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

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

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

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

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

6
Ви забули перевірити антидіагональ.
чемпіон

1
Для дошки 3x3 x + y завжди буде дорівнювати 2 на антидіагоналі, завжди буде навіть у центрі та кутах дошки, а в інших місцях.
Кріс Доггетт

5
Я не розумію перевірку нічиї в кінці, чи не слід це відняти 1?
Інес

4
Бувають випадки, коли гравець виграє в останньому можливому (9-му) кроці. У цьому випадку , як переможець , і нічия буде повідомлено ...
Марк

5
@ Roamer-1888 справа не в тому, з яких рядків складається ваше рішення, а в тому, щоб зменшити часову складність алгоритму для перевірки переможця.
Шади

38

ви можете використовувати чарівний квадрат http://mathworld.wolfram.com/MagicSquare.html, якщо будь-який рядок, стовпець або діагноз додає до 15, то виграв гравець.


3
Як це перекладається на гру тик-так-носок?
Пол Олександр

Це акуратний фрагмент інформації, якого я не знав, тому я безумовно дякую за інформацію. Як зазначив Пол, не відразу зрозуміло, як це допоможе вирішити існуючу проблему, але відчувається, що це може стати частиною більш повного рішення.
dreadwail

4
накладіть його. 1 для білого, 2 для чорного і множимо. якщо що-небудь виходить до 15, то білий виграв, а якщо вийшов до 30, то чорний виграв.
adk

1
Великий O-мудрий, це досить дешево, особливо якщо ви змішуєте його з осередком перевірки апаратури Hardwareguy. Кожна комірка може бути лише у чотирьох можливих тик-так-пальців: rowwise, стовпчик та дві діагоналі (косою та зворотною косою рисою). Отже, як тільки зроблено крок, вам доведеться зробити не більше 4 доповнень та порівнянь. Відповідь Hardwareguy вимагає 4 (n-1) перевірки на кожен хід, для порівняння.
чемпіон

29
Чи не могли б ми зробити це з 1 і -1 і підсумовувати кожен рядок / стовпчик / діагноз, щоб побачити, чи це n або -n?
Натан

24

Як щодо цього псевдокоду:

Після того, як гравець відкладає шматок у позиції (x, y):

col=row=diag=rdiag=0
winner=false
for i=1 to n
  if cell[x,i]=player then col++
  if cell[i,y]=player then row++
  if cell[i,i]=player then diag++
  if cell[i,n-i+1]=player then rdiag++
if row=n or col=n or diag=n or rdiag=n then winner=true

Я б використовував масив char [n, n], з O, X та пробілом для порожніх.

  1. просто.
  2. Одна петля.
  3. П'ять простих змінних: 4 цілих числа і одна булева.
  4. Ваги до будь-якого розміру n.
  5. Перевіряє лише поточну деталь.
  6. Ніякої магії. :)

якщо комірка [i, n- (i + 1)] = гравець, то rdiag ++; - Схоже, з дужками це буде правильно. Маю рацію?
Пумич

@Pumych, ні. Якщо i==1і n==3, rdiagпотрібно перевірити на рівні (1, 3)та (1, 3-1+1)дорівнює правильним координатам, але (1, 3-(1+1))ні.
KgOfHedgehogs

Він, можливо, думав, що клітини індексують нуль.
Matias Grioni

це були лише якісь речі в моїй голові .... це потрібно виправити під час фактичного написання коду :)
Осама Аль-Мадейд

21

Це схоже на відповідь Осама АЛАССІРІ , але він торгує постійним простором та лінійним часом для лінійного простору та постійного часу. Тобто, після ініціалізації немає циклу.

Ініціалізуйте пару (0,0)для кожного рядка, кожного стовпця та двох діагоналей (діагоналі та антидіагоналі). Ці пари представляють накопичені (sum,sum)частини у відповідному рядку, стовпці чи діагоналі, де

Шматок гравця A має значення (1,0)
Шматок гравця B має значення (0,1)

Коли гравець розміщує фрагмент, оновіть відповідну пару рядків, пару стовпців та діагональні пари (якщо на діагоналях). Якщо пара оновлених рядків, стовпців або діагоналей дорівнює (n,0)або(0,n) те або А або Б виграв, відповідно.

Асимптотичний аналіз:

O (1) час (за хід)
O (n) простір (загальний)

Для використання пам'яті ви використовуєте 4*(n+1) цілі числа.

two_elements * n_rows + two_elements * n_ Column +
two_elements * two_diagonals = 4 * n + 4 цілих числа = 4 (n + 1) цілих чисел

Вправа: Чи можете ви бачити, як тестувати на нічию в O (1) час за рух? Якщо це так, ви можете закінчити гру рано внічию.


1
Я думаю, що це краще, ніж у Осама АЛАССІРІ, оскільки його приблизно O(sqrt(n))час, але це потрібно робити після кожного ходу, де n - розмір дошки. Так ви закінчите O(n^1.5). На це рішення ви отримуєте O(n)загальний час.
Matias Grioni

приємний спосіб бачити це, має сенс подивитися на фактичні "рішення" ... для 3x3, у вас було б просто 8 пар "булевих" ... Це може бути ще більшим простором, якби було 2 біти кожен ... Потрібно 16 біт, і ви можете просто побіжно АБО 1 у правильному гравці змістили ліворуч у
потрібне

13

Ось моє рішення, яке я написав для проекту, над яким працюю в JavaScript. Якщо ви не заперечуєте проти вартості пам'яті кількох масивів, це, мабуть, найшвидше і найпростіше рішення, яке ви знайдете. Це передбачає, що ви знаєте положення останнього ходу.

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

2
Це було б великим підходом до молота, але це дійсно життєздатне рішення, особливо для розміщення сайту як одного з безлічі творчих та робочих рішень цієї проблеми. Також його короткий, елегантний і дуже читабельний - для сітки 3х3 (традиційний Tx3) мені подобається цей алгоритм.
nocarrier

Цей дивовижний !! Я виявив, що у виграшних моделях є невелика помилка, за можливості 8, шаблони повинні бути [6,7], [0,4] та [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]]];
Девід Руїс

7

Я щойно написав це для свого класу програмування на С.

Я розміщую це повідомлення, тому що жоден з інших прикладів тут не працюватиме з будь-якою розміром прямокутної сітки та будь-яким числом послідовних знаків N -у-рядку, щоб виграти.

Ви знайдете мій алгоритм, такий як він, у checkWinner()функції. Він не використовує магічні цифри чи щось фантазійне, щоб перевірити переможця, він просто використовує чотири для циклу. Код добре коментується, тому я дозволяю йому говорити сам, напевно.

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

Дуже корисний. Я намагався знайти щось більш ефективне, наприклад, якщо ви знаєте N = COL = ROW, ви можете звести це до чогось набагато простішого, але я не знайшов нічого більш ефективного для довільного розміру дошки і Н.
Хассан

6

Якщо дошка n × n, то n рядків, n стовпців та 2 діагоналі. Перевірте кожен із них для всіх-X чи all-O, щоб знайти переможця.

Якщо для вигравання знадобиться лише х < n квадратиків, то це трохи складніше. Найбільш очевидне рішення - перевірити кожен х × х квадрат на переможця. Ось якийсь код, який це демонструє.

(Я фактично не перевірити це * кашель *, але він зробив компіляції з першої спроби, яй мене!)

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

Я бачу, що ти маєш на увазі. У традиційній грі n = x було б (n * n * 2) загальних відповідей. Це не вийде, якби х (кількість послідовних перемог, необхідних для перемоги) було менше n, однак. Хоча це хороше рішення, мені це подобається краще, ніж таблиця за його розширюваність.
dreadwail

Я не згадував про можливість x <n у первісному дописі, тому ваша відповідь все-таки помічена.
dreadwail

4

Я не знаю Java так добре, але я знаю C, тому я спробував ідею магічного квадрата Adk (разом із обмеженням пошуку Hardwareguy ).

// tic-tac-toe.c
// to compile:
//  % gcc -o tic-tac-toe tic-tac-toe.c
// to run:
//  % ./tic-tac-toe
#include <stdio.h>

// the two types of marks available
typedef enum { Empty=2, X=0, O=1, NumMarks=2 } Mark;
char const MarkToChar[] = "XO ";

// a structure to hold the sums of each kind of mark
typedef struct { unsigned char of[NumMarks]; } Sum;

// a cell in the board, which has a particular value
#define MAGIC_NUMBER 15
typedef struct {
  Mark mark;
  unsigned char const value;
  size_t const num_sums;
  Sum * const sums[4];
} Cell;

#define NUM_ROWS 3
#define NUM_COLS 3

// create a sum for each possible tic-tac-toe
Sum row[NUM_ROWS] = {0};
Sum col[NUM_COLS] = {0};
Sum nw_diag = {0};
Sum ne_diag = {0};

// initialize the board values so any row, column, or diagonal adds to
// MAGIC_NUMBER, and so they each record their sums in the proper rows, columns,
// and diagonals
Cell board[NUM_ROWS][NUM_COLS] = { 
  { 
    { Empty, 8, 3, { &row[0], &col[0], &nw_diag } },
    { Empty, 1, 2, { &row[0], &col[1] } },
    { Empty, 6, 3, { &row[0], &col[2], &ne_diag } },
  },
  { 
    { Empty, 3, 2, { &row[1], &col[0] } },
    { Empty, 5, 4, { &row[1], &col[1], &nw_diag, &ne_diag } },
    { Empty, 7, 2, { &row[1], &col[2] } },
  },
  { 
    { Empty, 4, 3, { &row[2], &col[0], &ne_diag } },
    { Empty, 9, 2, { &row[2], &col[1] } },
    { Empty, 2, 3, { &row[2], &col[2], &nw_diag } },
  }
};

// print the board
void show_board(void)
{
  size_t r, c;
  for (r = 0; r < NUM_ROWS; r++) 
  {
    if (r > 0) { printf("---+---+---\n"); }
    for (c = 0; c < NUM_COLS; c++) 
    {
      if (c > 0) { printf("|"); }
      printf(" %c ", MarkToChar[board[r][c].mark]);
    }
    printf("\n");
  }
}


// run the game, asking the player for inputs for each side
int main(int argc, char * argv[])
{
  size_t m;
  show_board();
  printf("Enter moves as \"<row> <col>\" (no quotes, zero indexed)\n");
  for( m = 0; m < NUM_ROWS * NUM_COLS; m++ )
  {
    Mark const mark = (Mark) (m % NumMarks);
    size_t c, r;

    // read the player's move
    do
    {
      printf("%c's move: ", MarkToChar[mark]);
      fflush(stdout);
      scanf("%d %d", &r, &c);
      if (r >= NUM_ROWS || c >= NUM_COLS)
      {
        printf("illegal move (off the board), try again\n");
      }
      else if (board[r][c].mark != Empty)
      {
        printf("illegal move (already taken), try again\n");
      }
      else
      {
        break;
      }
    }
    while (1);

    {
      Cell * const cell = &(board[r][c]);
      size_t s;

      // update the board state
      cell->mark = mark;
      show_board();

      // check for tic-tac-toe
      for (s = 0; s < cell->num_sums; s++)
      {
        cell->sums[s]->of[mark] += cell->value;
        if (cell->sums[s]->of[mark] == MAGIC_NUMBER)
        {
          printf("tic-tac-toe! %c wins!\n", MarkToChar[mark]);
          goto done;
        }
      }
    }
  }
  printf("stalemate... nobody wins :(\n");
done:
  return 0;
}

Він добре компілює та тестує.

% gcc -o tic-tac-toe tic-tac-toe.c
% ./хрестики-нулики
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Введіть ходи як "" (без лапок, нульового індексу)
  Хід руху: 1 2
     | |
  --- + --- + ---
     | | Х
  --- + --- + ---
     | |
  Хід руху: 1 2
  незаконний переїзд (вже зроблено), спробуйте ще раз
  Хід руху: 3 3
  незаконне переміщення (поза дошкою), спробуйте ще раз
  Хід руху: 2 2
     | |
  --- + --- + ---
     | | Х
  --- + --- + ---
     | | О
  Хід руху: 1 0
     | |
  --- + --- + ---
   X | | Х
  --- + --- + ---
     | | О
  Хід руху: 1 1
     | |
  --- + --- + ---
   X | O | Х
  --- + --- + ---
     | | О
  Хід руху: 0 0
   X | |
  --- + --- + ---
   X | O | Х
  --- + --- + ---
     | | О
  Хід руху: 2 0
   X | |
  --- + --- + ---
   X | O | Х
  --- + --- + ---
   O | | О
  Хід руху: 2 1
   X | |
  --- + --- + ---
   X | O | Х
  --- + --- + ---
   O | X | О
  Хід руху: 0 2
   X | | О
  --- + --- + ---
   X | O | Х
  --- + --- + ---
   O | X | О
  хрестики-нулики! О виграш!
% ./хрестики-нулики
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Введіть ходи як "" (без лапок, нульового індексу)
  Хід руху: 0 0
   X | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Хід руху: 0 1
   X | O |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Хід руху: 0 2
   X | O | Х
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  Хід руху: 1 0
   X | O | Х
  --- + --- + ---
   O | |
  --- + --- + ---
     | |
  Хід Х: 1 1
   X | O | Х
  --- + --- + ---
   O | X |
  --- + --- + ---
     | |
  Хід руху: 2 0
   X | O | Х
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | |
  Хід руху: 2 1
   X | O | Х
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X |
  Хід руху: 2 2
   X | O | Х
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X | О
  Хід руху: 1 2
   X | O | Х
  --- + --- + ---
   O | X | Х
  --- + --- + ---
   O | X | О
  тупик ... ніхто не виграє :(
%

Це було весело, дякую!

Насправді, думаючи про це, вам не потрібен чарівний квадрат, а лише кількість для кожного рядка / стовпця / діагоналі. Це трохи простіше, ніж узагальнення магічного квадрата до n× nматриць, оскільки вам просто потрібно порахувати n.


3

Мені було задано те саме питання в одному з моїх інтерв'ю. Мої думки: Ініціалізуйте матрицю з 0. Тримайте 3 масиви 1) sum_row (розмір n) 2) sum_column (розмір n) 3) діагональ (розмір 2)

Для кожного переміщення за допомогою (X) значення поля на 1, а для кожного переміщення на (0) збільшення його на 1. У будь-якій точці, якщо рядок / стовпчик / діагональ, який був змінений у поточному ході, має значення або -3, або + 3 означає, що хтось виграв гру. Для розіграшу ми можемо використати вище підхід, щоб зберегти змінну moveCount.

Ви думаєте, я щось пропускаю?

Редагувати: те ж саме можна використовувати для матриці nxn. Сума повинна бути навіть +3 або -3.


2

непізнавальний спосіб визначити, чи була точка на антидіагностиці:

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

2

Я запізнююсь на вечірку, але хотів зазначити одну перевагу, яку я виявив за допомогою магічного квадрата , а саме те, що з її допомогою можна отримати посилання на квадрат, який би спричинив виграш чи програш на наступному кроці, а не просто використовується для розрахунку, коли гра закінчена.

Візьміть цей чарівний квадрат:

4 9 2
3 5 7
8 1 6

По-перше, встановіть scoresмасив, який збільшується кожного разу, коли буде зроблено рух. Детальну інформацію див. У цій відповіді . Тепер, якщо ми незаконно відтворюємо X двічі поспіль у [0,0] та [0,1], то scoresмасив виглядає так:

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

І рада виглядає так:

X . .
X . .
. . .

Тоді, все, що ми повинні зробити, щоб отримати посилання на те, на якому квадраті виграти / заблокувати, це:

get_winning_move = function() {
  for (var i = 0, i < scores.length; i++) {
    // keep track of the number of times pieces were added to the row
    // subtract when the opposite team adds a piece
    if (scores[i].inc === 2) {
      return 15 - state[i].val; // 8
    }
  }
}

Насправді реалізація потребує декількох додаткових хитрощів, як, наприклад, обробка пронумерованих клавіш (у JavaScript), але я вважав це досить простим і насолоджувався математикою для відпочинку.


1

Я зробив деяку оптимізацію в рядках, колах, діагональних перевірках. Її головним чином вирішили в першому вкладеному циклі, якщо нам потрібно перевірити певний стовпчик або діагональ. Тож ми уникаємо перевірки стовпців чи діагоналей, економлячи час. Це робить великий вплив, коли розмір плати більше і значна кількість комірок не заповнена.

Ось код Java для цього.

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


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


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


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

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

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

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

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

    if (allFilled)
        return DRAW;

    return INPROGRESS;
}

1

Мені подобається цей алгоритм, оскільки він використовує 1x9 проти 3x3 представлення дошки.

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

1
Мені найбільше подобається такий підхід. Це допомогло б, якби ви пояснили, що означає "старт" і "інк". (Це спосіб виразити всі "рядки" як початковий індекс, а кількість індексів для пропуску.)
nafg

Будь ласка, додайте більше пояснень. Як ви придумали цей код?
Фарзан

0

Ще один варіант: генеруйте свою таблицю з кодом. До симетрії є лише три способи виграти: крайній ряд, середній ряд або діагональ. Візьміть ці три і обертайте їх усіляко:

def spin(g): return set([g, turn(g), turn(turn(g)), turn(turn(turn(g)))])
def turn(g): return tuple(tuple(g[y][x] for y in (0,1,2)) for x in (2,1,0))

X,s = 'X.'
XXX = X, X, X
sss = s, s, s

ways_to_win = (  spin((XXX, sss, sss))
               | spin((sss, XXX, sss))
               | spin(((X,s,s),
                       (s,X,s),
                       (s,s,X))))

Ці симетрії можуть мати більше застосувань у вашому ігровому коді: якщо ви потрапили на дошку, за якою ви вже бачили поворотну версію, ви можете просто взяти кешоване значення або кеш найкраще перемістити з цього (і зняти його назад). Зазвичай це набагато швидше, ніж оцінка ігрового піддерева.

(Перегортання ліворуч і праворуч може допомогти однаково; це було не потрібно тут, оскільки набір обертів виграшних візерунків є дзеркально-симетричним.)


0

Ось рішення, яке я придумав, він зберігає символи у вигляді символів та використовує значення int значення char, щоб визначити, чи переміг X або O (подивіться код рефері)

public class TicTacToe {
    public static final char BLANK = '\u0000';
    private final char[][] board;
    private int moveCount;
    private Referee referee;

    public TicTacToe(int gridSize) {
        if (gridSize < 3)
            throw new IllegalArgumentException("TicTacToe board size has to be minimum 3x3 grid");
        board = new char[gridSize][gridSize];
        referee = new Referee(gridSize);
    }

    public char[][] displayBoard() {
        return board.clone();
    }

    public String move(int x, int y) {
        if (board[x][y] != BLANK)
            return "(" + x + "," + y + ") is already occupied";
        board[x][y] = whoseTurn();
        return referee.isGameOver(x, y, board[x][y], ++moveCount);
    }

    private char whoseTurn() {
        return moveCount % 2 == 0 ? 'X' : 'O';
    }

    private class Referee {
        private static final int NO_OF_DIAGONALS = 2;
        private static final int MINOR = 1;
        private static final int PRINCIPAL = 0;
        private final int gridSize;
        private final int[] rowTotal;
        private final int[] colTotal;
        private final int[] diagonalTotal;

        private Referee(int size) {
            gridSize = size;
            rowTotal = new int[size];
            colTotal = new int[size];
            diagonalTotal = new int[NO_OF_DIAGONALS];
        }

        private String isGameOver(int x, int y, char symbol, int moveCount) {
            if (isWinningMove(x, y, symbol))
                return symbol + " won the game!";
            if (isBoardCompletelyFilled(moveCount))
                return "Its a Draw!";
            return "continue";
        }

        private boolean isBoardCompletelyFilled(int moveCount) {
            return moveCount == gridSize * gridSize;
        }

        private boolean isWinningMove(int x, int y, char symbol) {
            if (isPrincipalDiagonal(x, y) && allSymbolsMatch(symbol, diagonalTotal, PRINCIPAL))
                return true;
            if (isMinorDiagonal(x, y) && allSymbolsMatch(symbol, diagonalTotal, MINOR))
                return true;
            return allSymbolsMatch(symbol, rowTotal, x) || allSymbolsMatch(symbol, colTotal, y);
        }

        private boolean allSymbolsMatch(char symbol, int[] total, int index) {
            total[index] += symbol;
            return total[index] / gridSize == symbol;
        }

        private boolean isPrincipalDiagonal(int x, int y) {
            return x == y;
        }

        private boolean isMinorDiagonal(int x, int y) {
            return x + y == gridSize - 1;
        }
    }
}

Також ось мої одиничні тести, щоб перевірити, чи справді це працює

import static com.agilefaqs.tdd.demo.TicTacToe.BLANK;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class TicTacToeTest {
    private TicTacToe game = new TicTacToe(3);

    @Test
    public void allCellsAreEmptyInANewGame() {
        assertBoardIs(new char[][] { { BLANK, BLANK, BLANK },
                { BLANK, BLANK, BLANK },
                { BLANK, BLANK, BLANK } });
    }

    @Test(expected = IllegalArgumentException.class)
    public void boardHasToBeMinimum3x3Grid() {
        new TicTacToe(2);
    }

    @Test
    public void firstPlayersMoveMarks_X_OnTheBoard() {
        assertEquals("continue", game.move(1, 1));
        assertBoardIs(new char[][] { { BLANK, BLANK, BLANK },
                { BLANK, 'X', BLANK },
                { BLANK, BLANK, BLANK } });
    }

    @Test
    public void secondPlayersMoveMarks_O_OnTheBoard() {
        game.move(1, 1);
        assertEquals("continue", game.move(2, 2));
        assertBoardIs(new char[][] { { BLANK, BLANK, BLANK },
                { BLANK, 'X', BLANK },
                { BLANK, BLANK, 'O' } });
    }

    @Test
    public void playerCanOnlyMoveToAnEmptyCell() {
        game.move(1, 1);
        assertEquals("(1,1) is already occupied", game.move(1, 1));
    }

    @Test
    public void firstPlayerWithAllSymbolsInOneRowWins() {
        game.move(0, 0);
        game.move(1, 0);
        game.move(0, 1);
        game.move(2, 1);
        assertEquals("X won the game!", game.move(0, 2));
    }

    @Test
    public void firstPlayerWithAllSymbolsInOneColumnWins() {
        game.move(1, 1);
        game.move(0, 0);
        game.move(2, 1);
        game.move(1, 0);
        game.move(2, 2);
        assertEquals("O won the game!", game.move(2, 0));
    }

    @Test
    public void firstPlayerWithAllSymbolsInPrincipalDiagonalWins() {
        game.move(0, 0);
        game.move(1, 0);
        game.move(1, 1);
        game.move(2, 1);
        assertEquals("X won the game!", game.move(2, 2));
    }

    @Test
    public void firstPlayerWithAllSymbolsInMinorDiagonalWins() {
        game.move(0, 2);
        game.move(1, 0);
        game.move(1, 1);
        game.move(2, 1);
        assertEquals("X won the game!", game.move(2, 0));
    }

    @Test
    public void whenAllCellsAreFilledTheGameIsADraw() {
        game.move(0, 2);
        game.move(1, 1);
        game.move(1, 0);
        game.move(2, 1);
        game.move(2, 2);
        game.move(0, 0);
        game.move(0, 1);
        game.move(1, 2);
        assertEquals("Its a Draw!", game.move(2, 0));
    }

    private void assertBoardIs(char[][] expectedBoard) {
        assertArrayEquals(expectedBoard, game.displayBoard());
    }
}

Повне рішення: https://github.com/nashjain/tictactoe/tree/master/java


0

Як щодо наступного підходу для 9 слотів? Оголосіть 9 цілочисельних змінних для матриці 3x3 (a1, a2 .... a9), де a1, a2, a3 являють собою рядок-1 і a1, a4, a7 утворюють стовпчик-1 (ви отримаєте ідею). Використовуйте "1", щоб вказати Player-1, і "2", щоб вказати Player-2.

Існує 8 можливих комбінацій виграшів: Win-1: a1 + a2 + a3 (відповідь може бути 3 або 6 залежно від того, який гравець виграв) Win-2: a4 + a5 + a6 Win-3: a7 + a8 + a9 Win-4 : a1 + a4 + a7 .... Win-7: a1 + a5 + a9 Win-8: a3 + a5 + a7

Тепер ми знаємо, що якщо гравець перетинає a1, то нам потрібно переоцінити суму 3 змінних: Win-1, Win-4 і Win-7. Що б "Win-?" змінні досягають 3 або 6 перших виграє гру. Якщо змінна Win-1 досягає перших 6, тоді Player-2 виграє.

Я розумію, що це рішення не можна легко масштабувати.


0

Це дійсно простий спосіб перевірити.

    public class Game() { 

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

    char piece;

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

public void checkWin(Game player) {

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

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

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

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

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

}


0

Якщо у вас є поле для борту 5 * 5 для іспиту, я використовував наступний метод перевірки:

public static boolean checkWin(char symb) {
  int SIZE = 5;

        for (int i = 0; i < SIZE-1; i++) {
            for (int j = 0; j <SIZE-1 ; j++) {
                //vertical checking
            if (map[0][j] == symb && map[1][j] == symb && map[2][j] == symb && map[3][j] == symb && map[4][j] == symb) return true;      // j=0
            }
            //horisontal checking
            if(map[i][0] == symb && map[i][1] == symb && map[i][2] == symb && map[i][3] == symb && map[i][4] == symb) return true;  // i=0
        }
        //diagonal checking (5*5)
        if (map[0][0] == symb && map[1][1] == symb && map[2][2] == symb && map[3][3] == symb && map[4][4] == symb) return true;
        if (map[4][0] == symb && map[3][1] == symb && map[2][2] == symb && map[1][3] == symb && map[0][4] == symb) return true;

        return false; 
        }

Я думаю, це більш зрозуміло, але, мабуть, це не найоптимальніший спосіб.


0

Ось моє рішення з використанням двовимірного масиву:

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

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

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

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

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

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

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

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

0

Розчин постійного часу, працює в O (8).

Зберігайте стан дошки як двійкове число. Найменший шматочок (2 ^ 0) - це верхній лівий ряд дошки. Потім йде вправо, потім вниз.

IE

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

У кожного гравця є свій двійковий номер для представлення стану (оскільки tic-tac-toe) має 3 стани (X, O & blank), тому одне двійкове число не працюватиме, щоб представити стан дошки для кількох гравців.

Наприклад, дошка типу:

+ ----------- +
| 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
О: 0 1 0 1 0 0 0 1 0

Зауважте, що біти для гравця X від'єднуються від бітів для гравця O, це очевидно, оскільки X не може поставити шматок, де O має шматок, і навпаки.

Щоб перевірити, чи виграв гравець, нам потрібно порівняти всі позиції, які охоплює той гравець, на позицію, яку ми знаємо, це виграшна позиція. У цьому випадку, найпростіший спосіб зробити це було б, зібравши позицію гравця та виграшну позицію та побачивши, чи є два рівні.

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

напр.

X: 111001010
W: 111000000 // виграти позицію, все те саме в першому ряду.
------------
&: 111000000

Примітка: X & W = Wзначить, X перебуває у виграшному стані.

Це рішення постійного часу, воно залежить тільки від кількості виграшних позицій, оскільки застосування AND-ворота - це постійна операція в часі, а кількість виграшних позицій - кінцева.

Це також спрощує завдання перерахувати всі дійсні стани плати, їх справедливі всі числа, представлені на 9 біт. Але, звичайно, вам потрібна додаткова умова, щоб гарантувати, що номер - це дійсне стан плати (наприклад 0b111111111, це дійсне 9-бітове число, але це не є дійсним станом плати, оскільки X щойно пройшов усі повороти).

Кількість можливих виграшних позицій можна генерувати на льоту, але тут вони все одно є.

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

Для перерахування всіх позицій плати можна виконати наступний цикл. Хоча я залишу визначати, чи є дійсним число дошки для когось іншого.

ПРИМІТКА: (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));

Будь ласка, додайте більше опису та змініть код, щоб він точно відповідав на питання ОП.
Фарзан

0

Не впевнений, чи опублікований такий підхід ще. Це повинно працювати на будь-якій дошці m * n, і гравець повинен заповнити послідовну позицію " winnerPos ". Ідея заснована на запущеному вікні.

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

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

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

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

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

-2

Я розробив алгоритм цього в рамках наукового проекту одного разу.

Ви в основному рекурсивно розділяєте дошку на купу перекриваються 2х2 ректи, випробовуючи різні можливі комбінації для виграшу на площі 2х2.

Це повільно, але має перевагу працювати на будь-якій дошці розміру, з досить лінійними вимогами до пам'яті.

Я б хотів, щоб я міг знайти свою реалізацію


Рекурсія для тестування фінішу не потрібна.
Габріель Лламас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.