[java] Tic Tac Toe 게임 오버 결정을위한 알고리즘

저는 Java로 tic-tac-toe 게임을 작성했으며 현재 게임의 끝을 결정하는 방법은 게임이 끝날 때 다음과 같은 가능한 시나리오를 설명합니다.

  1. 보드가 꽉 찼고 아직 승자가 선언되지 않았습니다. 게임은 무승부입니다.
  2. 크로스가 이겼습니다.
  3. 서클이 이겼습니다.

안타깝게도이를 위해 테이블에서 이러한 시나리오의 미리 정의 된 집합을 읽습니다. 보드에 9 개의 공간 만 있고 따라서 테이블이 다소 작다는 점을 고려하면 이것이 반드시 나쁘지는 않지만 게임이 끝났는지 확인하는 더 나은 알고리즘 방식이 있습니까? 누군가가 이겼는지 여부를 결정하는 것은 문제의 핵심입니다. 9 칸이 꽉 찼는 지 확인하는 것은 사소하기 때문입니다.

테이블 방법이 해결책이 될 수 있지만 그렇지 않은 경우 무엇입니까? 또한 보드가 크기가 아니라면 n=9어떻게 될까요? 그것은 무엇 말하자면 훨씬 더 큰 보드된다면 n=16, n=25등 연속적으로 배치 항목의 수를 원인에로 승리 x=4, x=5등? 모두를 위해 사용하는 일반적인 알고리즘 n = { 9, 16, 25, 36 ... }?



답변

이기는 이동은 X 또는 O가 가장 최근에 이동 한 후에 만 ​​발생할 수 있으므로 해당 이동에 포함 된 선택적 진단을 사용하여 행 / 열만 검색하여 승리 한 보드를 결정하려고 할 때 검색 공간을 제한 할 수 있습니다. 또한 무승부 틱택 토 게임에는 고정 된 수의 무브가 있기 때문에 마지막 무브가 이루어지면이기는 무브가 아니라면 기본적으로 무승부 게임입니다.

편집 :이 코드는 n x n 보드를위한 것입니다 (3×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
        }
    }
}


답변

행, 열 또는 diag의 합이 15가되고 플레이어가이긴 경우 매직 스퀘어 http://mathworld.wolfram.com/MagicSquare.html을 사용할 수 있습니다 .


답변

이 의사 코드는 어떻습니까?

플레이어가 (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

나는 문자 [n, n]의 배열을 사용하고, O, X와 빈 공간을 사용합니다.

  1. 단순한.
  2. 하나의 루프.
  3. 5 개의 간단한 변수 : 4 개의 정수와 1 개의 부울.
  4. n의 모든 크기로 확장됩니다.
  5. 현재 조각 만 확인합니다.
  6. 마법이 아닙니다. 🙂

답변

이것은 Osama ALASSIRY의 답변 과 유사 하지만 선형 공간 및 상수 시간에 대해 상수 공간 및 선형 시간을 교환합니다. 즉, 초기화 후 루핑이 없습니다.

(0,0)각 행, 각 열 및 두 개의 대각선 (대각선 및 대각선)에 대한 쌍 을 초기화합니다 . 이 쌍은 (sum,sum)해당 행, 열 또는 대각선에있는 조각 의 누적 을 나타냅니다 .

플레이어 A의 조각은 (1,0) 값을가집니다.
플레이어 B의 조각은 (0,1) 값을가집니다.

플레이어가 말을 놓을 때 해당 행 쌍, 열 쌍 및 대각선 쌍을 업데이트합니다 (대각선에있는 경우). 새로 업데이트 된 행, 열, 대각선 쌍 중 하나에 해당하는 경우 (n,0)또는 (0,n)다음 A 또는 B 중 각각 받았다.

점근 분석 :

O (1) 시간 (이동 당)
O (n) 공간 (전체)

메모리 사용을 위해 4*(n+1)정수 를 사용 합니다.

two_elements * n_rows + two_elements * n_columns +
two_elements * two_diagonals = 4 * n + 4 정수 = 4 (n + 1) 정수

운동 : 이동 당 O (1) 시간으로 무승부를 테스트하는 방법을 알 수 있습니까? 그렇다면 무승부에서 게임을 일찍 끝낼 수 있습니다.


답변

내가 자바 스크립트에서 작업중 인 프로젝트를 위해 작성한 솔루션입니다. 몇 가지 어레이의 메모리 비용에 신경 쓰지 않는다면 아마도 가장 빠르고 간단한 솔루션 일 것입니다. 마지막 움직임의 위치를 ​​알고 있다고 가정합니다.

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


답변

방금 C 프로그래밍 수업을 위해 작성했습니다.

여기에있는 다른 예는 어떤 크기의 직사각형 그리드 에서도 작동하지 않기 때문에 게시하고 있으며 , N -in-a-row 연속 표시를 이길 수 있습니다.

checkWinner()함수 에서 내 알고리즘을 찾을 수 있습니다. 승자를 확인하기 위해 매직 넘버 나 멋진 것을 사용하지 않고, 단순히 4 개의 for 루프를 사용합니다. 코드는 잘 주석 처리되어 있으므로 스스로 말하도록하겠습니다.

// 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 × n 이면 n 개의 행, n 개의 열, 2 개의 대각선이 있습니다. 승자를 찾기 위해 각각의 모든 X 또는 모두 O를 확인하십시오.

승리하는 데 x < n 연속 사각형 만 필요 하면 조금 더 복잡합니다. 가장 확실한 해결책은 각 x x x 사각형을 확인하는 것입니다. 이를 보여주는 코드가 있습니다.

(사실이 * 기침 *을 테스트하지 못했지만, 그것은 않았다 첫 번째 시도에서 컴파일, 나를 야호!)

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