อัลกอริทึมสำหรับการกำหนดเกม Tic Tac Toe Over


97

ฉันได้เขียนเกม tic-tac-toe ใน Java และวิธีการปัจจุบันของฉันในการกำหนดจุดสิ้นสุดของบัญชีเกมสำหรับสถานการณ์ที่เป็นไปได้ต่อไปนี้สำหรับการจบเกม:

  1. กระดานเต็มแล้วและยังไม่มีการประกาศผู้ชนะ: เกมเสมอกัน
  2. ครอสได้รับชัยชนะ
  3. Circle ได้รับรางวัล

น่าเสียดายที่ในการทำเช่นนั้นจะอ่านชุดของสถานการณ์เหล่านี้ที่กำหนดไว้ล่วงหน้าจากตาราง สิ่งนี้ไม่ได้แย่เสมอไปเมื่อพิจารณาว่ามีช่องว่างเพียง 9 ช่องบนกระดานดังนั้นตารางจึงค่อนข้างเล็ก แต่มีวิธีอัลกอริทึมที่ดีกว่าในการพิจารณาว่าเกมจบลงหรือไม่? การตัดสินว่าใครได้รับรางวัลหรือไม่เป็นเนื้อของปัญหาเนื่องจากการตรวจสอบว่าช่องว่าง 9 ช่องเต็มเป็นเรื่องเล็กน้อยหรือไม่

วิธีตารางอาจเป็นวิธีแก้ปัญหา แต่ถ้าไม่คืออะไร? แล้วถ้าบอร์ดไม่ได้ขนาดn=9ล่ะ? อะไรถ้ามันถูกคณะกรรมการที่มีขนาดใหญ่, การพูดn=16, n=25และอื่น ๆ ที่ก่อให้เกิดจำนวนรายการที่วางไว้อย่างต่อเนื่องที่จะชนะจะเป็นx=4, x=5etc? อัลกอริทึมทั่วไปที่จะใช้สำหรับทุกคนn = { 9, 16, 25, 36 ... }?


ฉันเพิ่ม 2 เซ็นต์ของฉันสำหรับคำตอบทั้งหมด: คุณรู้เสมอว่าคุณต้องการ Xs หรือ Os อย่างน้อยจำนวนหนึ่งบนกระดานเพื่อให้ชนะ (ในกระดาน 3x3 ปกติ3) ดังนั้นคุณสามารถติดตามจำนวนของแต่ละรายการและเริ่มตรวจสอบการชนะหากมีจำนวนสูงกว่าเท่านั้น
Yuval อ.

คำตอบ:


133

คุณทราบดีว่าการย้ายที่ชนะจะเกิดขึ้นได้หลังจากที่ X หรือ O ทำการย้ายครั้งล่าสุดเท่านั้นดังนั้นคุณสามารถค้นหาเฉพาะแถว / คอลัมน์ที่มีไดแอกเสริมที่มีอยู่ในการย้ายนั้นเพื่อ จำกัด พื้นที่การค้นหาของคุณเมื่อพยายามกำหนดกระดานที่ชนะ นอกจากนี้เนื่องจากมีจำนวนการเคลื่อนไหวที่แน่นอนในเกมเสมอ tic-tac-toe เมื่อมีการเคลื่อนไหวครั้งสุดท้ายหากไม่ใช่การเคลื่อนไหวที่ชนะจะเป็นเกมเสมอ

แก้ไข: รหัสนี้ใช้สำหรับบอร์ด n by n ที่มี n ติดต่อกันเพื่อชนะ (3x3 board requries 3 in row ฯลฯ )

แก้ไข: เพิ่มรหัสเพื่อตรวจสอบ anti diag ฉันไม่สามารถหาวิธีที่ไม่วนซ้ำเพื่อตรวจสอบว่าจุดนั้นอยู่ที่ anti diag หรือไม่นั่นจึงเป็นเหตุให้ขั้นตอนนั้นหายไป

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
คุณลืมตรวจสอบป้องกันเส้นทแยงมุม
rampion

1
สำหรับกระดาน 3x3 x + y จะเท่ากับ 2 ในแนวป้องกันเส้นทแยงมุมเสมอจะอยู่ตรงกลางและมุมของกระดานและคี่ที่อื่น
Chris Doggett

5
ฉันไม่เข้าใจการตรวจสอบการจับฉลากในตอนท้ายไม่ควรลบ 1 หรือไม่
Inez

4
มีหลายกรณีที่ผู้เล่นชนะในการเคลื่อนที่ครั้งสุดท้าย (ครั้งที่ 9) ที่เป็นไปได้ ในกรณีนี้จะมีการรายงานทั้งผู้ชนะและเสมอ ...
มาร์ค

5
@ Roamer-1888 ไม่ได้เกี่ยวกับจำนวนบรรทัดของโซลูชันของคุณ แต่เป็นการลดความซับซ้อนของเวลาของอัลกอริทึมเพื่อตรวจสอบผู้ชนะ
ร่มรื่น

38

คุณสามารถใช้สแควร์มหัศจรรย์http://mathworld.wolfram.com/MagicSquare.htmlถ้าแถวคอลัมน์หรือทแยงใด ๆ รวมกันได้ถึง 15 แสดงว่าผู้เล่นชนะ


4
นั่นแปลเป็นเกม tic-tac-toe ได้อย่างไร?
Paul Alexander

เป็นข้อมูลเล็กน้อยที่ฉันไม่รู้ดังนั้นฉันขอขอบคุณสำหรับข้อมูลนี้อย่างแน่นอน ดังที่ Paul กล่าวถึงยังไม่ชัดเจนในทันทีว่าจะช่วยแก้ปัญหาได้อย่างไร แต่รู้สึกว่าอาจเป็นส่วนหนึ่งของวิธีแก้ปัญหาที่สมบูรณ์ยิ่งขึ้น
dreadwail

4
ซ้อนทับ 1 สำหรับสีขาว 2 สำหรับสีดำและคูณ ถ้ามีอะไรออกมาเป็น 15 สีขาวจะชนะและถ้าออกมาเป็น 30 สีดำจะชนะ
แอด

1
Big O-wise มันค่อนข้างถูกโดยเฉพาะอย่างยิ่งถ้าคุณผสมกับการตรวจสอบเซลล์ฉลาดของ Hardwareguy แต่ละเซลล์สามารถอยู่ในนิ้วเท้า tic-tac-toes ที่เป็นไปได้ 4 แบบเท่านั้น: rowwise, columnwise และ 2 diagonals (slash และ backslash) ดังนั้นเมื่อทำการย้ายแล้วคุณจะต้องทำการเพิ่มและเปรียบเทียบไม่เกิน 4 ครั้งเท่านั้น คำตอบของ Hardwareguy ต้องการการตรวจสอบ 4 (n-1) สำหรับแต่ละการเคลื่อนไหวโดยเปรียบเทียบ
rampion

29
เราไม่สามารถทำได้ด้วย 1 และ -1 และรวมแต่ละแถว / colum / diag เพื่อดูว่าเป็น n หรือ -n หรือไม่?
นาธาน

26

แล้วรหัสเทียมนี้เป็นอย่างไร:

หลังจากผู้เล่นวางหมากที่ตำแหน่ง (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

@Pumych เลขที่ ถ้าi==1และn==3, rdiagจะต้องตรวจสอบที่(1, 3)และ(1, 3-1+1)มีค่าเท่ากับพิกัดที่ถูกต้อง แต่(1, 3-(1+1))ไม่มี
KgOfHedgehogs

เขาอาจจะคิดว่าเซลล์ไม่มีดัชนี
Matias Grioni

มันเป็นเพียงบางสิ่งบางอย่างที่อยู่ด้านบนของหัวของฉัน .... จำเป็นต้องได้รับการแก้ไขในระหว่างการเขียนโค้ดจริง :)
Osama Al-Maadeed

21

สิ่งนี้คล้ายกับคำตอบของ Osama ALASSIRYแต่จะแลกเปลี่ยนค่าคงที่ของปริภูมิและเวลาเชิงเส้นสำหรับพื้นที่เชิงเส้นและเวลาคงที่ นั่นคือไม่มีการวนซ้ำหลังจากเริ่มต้น

เริ่มต้นคู่(0,0)สำหรับแต่ละแถวแต่ละคอลัมน์และเส้นทแยงมุมสองเส้น (เส้นทแยงมุม & ต่อต้านเส้นทแยงมุม) คู่เหล่านี้แสดงถึงการสะสม(sum,sum)ของชิ้นส่วนในแถวคอลัมน์หรือเส้นทแยงมุมที่สอดคล้องกันโดยที่

ชิ้นส่วนจากผู้เล่น A มีค่า (1,0)
ชิ้นส่วนจากผู้เล่น B มีค่า (0,1)

เมื่อผู้เล่นวางชิ้นส่วนให้อัปเดตคู่แถวคู่คอลัมน์และคู่ทแยงมุมที่เกี่ยวข้อง (หากอยู่บนเส้นทแยงมุม) หากคู่แถวคอลัมน์หรือเส้นทแยงมุมที่อัปเดตใหม่มีค่าเท่ากัน(n,0)หรือ(0,n)จากนั้น A หรือ B จะชนะตามลำดับ

การวิเคราะห์ Asymptotic:

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) ครั้งต่อการเคลื่อนไหวได้หรือไม่? หากเป็นเช่นนั้นคุณสามารถจบเกมก่อนกำหนดโดยเสมอกัน


1
ฉันคิดว่านี่ดีกว่าของ Osama ALASSIRY เนื่องจากเขาเป็นO(sqrt(n))เวลาที่สั้น แต่ต้องทำทุกครั้งโดยที่ n คือขนาดของกระดาน O(n^1.5)ดังนั้นคุณจึงจบลงด้วย สำหรับวิธีนี้คุณจะได้รับO(n)เวลาโดยรวม
Matias Grioni

วิธีที่ดีในการดูสิ่งนี้มันสมเหตุสมผลแล้วที่จะดู "โซลูชัน" ที่แท้จริง ... สำหรับ 3x3 คุณจะมี "บูลีน" 8 คู่ ... มันจะมีพื้นที่มากขึ้นหากมีขนาด 2 บิต ... ต้องใช้ 16 บิตและคุณสามารถบิตหรือ 1 ในเครื่องเล่นที่ถูกต้องเลื่อนไปทางซ้ายไปยังตำแหน่งที่ถูกต้อง :)
Osama Al-Maadeed

13

นี่เป็นวิธีแก้ปัญหาของฉันที่ฉันเขียนสำหรับโครงการที่ฉันกำลังทำในจาวาสคริปต์ หากคุณไม่คำนึงถึงต้นทุนหน่วยความจำของอาร์เรย์ไม่กี่อาร์เรย์อาจเป็นวิธีที่เร็วและง่ายที่สุดที่คุณจะพบ ถือว่าคุณรู้ตำแหน่งของการเคลื่อนไหวล่าสุด

/*
 * 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
นี่อาจเป็นวิธีการค้อนขนาดใหญ่ แต่เป็นวิธีการแก้ปัญหาที่ทำงานได้จริงโดยเฉพาะอย่างยิ่งสำหรับไซต์เป็นหนึ่งในโซลูชันที่สร้างสรรค์และใช้งานได้มากมายสำหรับปัญหานี้ นอกจากนี้ยังสั้นสง่างามและอ่านง่ายมากสำหรับตาราง 3x3 (Tx3 แบบดั้งเดิม) ฉันชอบอัลกอริทึมนี้
nocarrier

อันนี้สุดยอด !! ฉันพบว่ามีจุดบกพร่องเล็กน้อยในรูปแบบการชนะโดยที่ Possition 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]]];
David Ruiz

7

ฉันเพิ่งเขียนสิ่งนี้สำหรับคลาสการเขียนโปรแกรม C ของฉัน

ฉันกำลังโพสต์ข้อความนี้เนื่องจากไม่มีตัวอย่างอื่นใดในที่นี้ที่จะใช้ได้กับตารางสี่เหลี่ยมขนาดใด ๆ และหมายเลขใด ๆต่อเนื่องกันN -in-a-row เพื่อชนะ

คุณจะพบอัลกอริทึมของฉันเช่นที่เป็นอยู่ใน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 คุณสามารถลดสิ่งนี้ให้เป็นสิ่งที่ง่ายกว่านี้ได้มาก แต่ฉันไม่พบสิ่งใดที่มีประสิทธิภาพมากขึ้นสำหรับขนาดบอร์ดโดยพลการและ N
Hassan

6

ถ้าบอร์ดเป็นn × n แสดงว่ามีnแถวnคอลัมน์และ 2 เส้นทแยงมุม ตรวจสอบแต่ละรายการสำหรับ all-X หรือทั้งหมด -O เพื่อหาผู้ชนะ

หากใช้เพียงx < nกำลังสองติดต่อกันเพื่อชนะก็จะซับซ้อนขึ้นเล็กน้อย วิธีแก้ปัญหาที่ชัดเจนที่สุดคือตรวจสอบแต่ละx × xสี่เหลี่ยมเพื่อหาผู้ชนะ นี่คือรหัสบางส่วนที่แสดงให้เห็นว่า

(ผมไม่ได้จริงทดสอบไอ * * แต่มันไม่รวบรวมในครั้งแรกลอง, yay ฉัน!)

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

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

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

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

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

                    return board[row][left];
                }

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

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

                    return board[top][col];
                }

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

                    return board[top][left];
                }

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

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

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

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

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

ฉันเข้าใจว่าคุณหมายถึงอะไร จะมีคำตอบทั้งหมด (n * n * 2) ในเกม n = x แบบดั้งเดิม สิ่งนี้จะไม่ได้ผลถ้า x (จำนวนผู้บริหารที่ต้องชนะ) น้อยกว่า 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
% ./tic-tac-toe
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  ป้อนการเคลื่อนไหวเป็น "" (ไม่มีเครื่องหมายคำพูดไม่มีการจัดทำดัชนีเป็นศูนย์)
  การเคลื่อนไหวของ X: 1 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | |
  การเคลื่อนไหวของ O: 1 2
  การเคลื่อนไหวที่ผิดกฎหมาย (ดำเนินการไปแล้ว) โปรดลองอีกครั้ง
  การเคลื่อนไหวของ O: 3 3
  การเคลื่อนไหวที่ผิดกฎหมาย (นอกกระดาน) โปรดลองอีกครั้ง
  การเคลื่อนไหวของ O: 2 2
     | |
  --- + --- + ---
     | | X
  --- + --- + ---
     | | โอ
  การเคลื่อนไหวของ X: 1 0
     | |
  --- + --- + ---
   X | | X
  --- + --- + ---
     | | โอ
  การเคลื่อนไหวของ O: 1 1
     | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | โอ
  การเคลื่อนไหวของ X: 0 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
     | | โอ
  การเคลื่อนไหวของ O: 2 0
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | | โอ
  การเคลื่อนไหวของ X: 2 1
   X | |
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | โอ
  การเคลื่อนไหวของ O: 0 2
   X | | โอ
  --- + --- + ---
   X | O | X
  --- + --- + ---
   O | X | โอ
  ทิคแทคโทส! โอชนะ!
% ./tic-tac-toe
     | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  ป้อนการเคลื่อนไหวเป็น "" (ไม่มีเครื่องหมายคำพูดไม่มีการจัดทำดัชนีเป็นศูนย์)
  การเคลื่อนไหวของ X: 0 0
   X | |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  การเคลื่อนไหวของ O: 0 1
   X | O |
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  การเคลื่อนไหวของ X: 0 2
   X | O | X
  --- + --- + ---
     | |
  --- + --- + ---
     | |
  การเคลื่อนไหวของ O: 1 0
   X | O | X
  --- + --- + ---
   O | |
  --- + --- + ---
     | |
  การเคลื่อนไหวของ X: 1 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
     | |
  การเคลื่อนไหวของ O: 2 0
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | |
  การเคลื่อนไหวของ X: 2 1
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X |
  การเคลื่อนไหวของ O: 2 2
   X | O | X
  --- + --- + ---
   O | X |
  --- + --- + ---
   O | X | โอ
  การเคลื่อนไหวของ X: 1 2
   X | O | X
  --- + --- + ---
   O | X | X
  --- + --- + ---
   O | X | โอ
  ทางตัน ... ไม่มีใครชนะ :(
%

สนุกมากขอบคุณ!

ที่จริงลองคิดดูว่าคุณไม่จำเป็นต้องมีสี่เหลี่ยมวิเศษเพียงแค่การนับสำหรับแต่ละแถว / คอลัมน์ / เส้นทแยงมุม นี้เป็นเพียงเล็กน้อยง่ายกว่า generalizing ตารางมายากลเพื่อn× เมทริกซ์ตั้งแต่คุณเพียงแค่ต้องนับถึงnn


3

ฉันถูกถามคำถามเดียวกันในการสัมภาษณ์ครั้งหนึ่งของฉัน ความคิดของฉัน: เริ่มต้นเมทริกซ์ด้วย 0 เก็บ 3 อาร์เรย์ 1) sum_row (ขนาด n) 2) sum_column (ขนาด n) 3) เส้นทแยงมุม (ขนาด 2)

สำหรับการเคลื่อนไหวแต่ละครั้งโดย (X) ให้ลดค่ากล่องลง 1 และสำหรับการเคลื่อนไหวแต่ละครั้งโดย (0) เพิ่มขึ้นทีละ 1 ณ จุดใดก็ได้หากแถว / คอลัมน์ / เส้นทแยงมุมที่ได้รับการแก้ไขในการย้ายปัจจุบันมีผลรวม -3 หรือ + 3 หมายถึงมีคนชนะเกม สำหรับการจับฉลากเราสามารถใช้แนวทางข้างต้นเพื่อเก็บตัวแปร moveCount

คุณคิดว่าฉันขาดอะไรไปหรือเปล่า?

แก้ไข: สามารถใช้เมทริกซ์ nxn ได้เช่นเดียวกัน ผลรวมควรเป็น +3 หรือ -3



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

ฉันทำการปรับให้เหมาะสมในแถว, col, การตรวจสอบเส้นทแยงมุม ส่วนใหญ่จะตัดสินใจในลูปที่ซ้อนกันครั้งแรกหากเราต้องการตรวจสอบคอลัมน์ใดคอลัมน์หนึ่งหรือแนวทแยง ดังนั้นเราจึงหลีกเลี่ยงการตรวจสอบคอลัมน์หรือแนวทแยงมุมเพื่อประหยัดเวลา สิ่งนี้ทำให้เกิดผลกระทบอย่างมากเมื่อบอร์ดมีขนาดใหญ่ขึ้นและไม่ได้เติมเซลล์จำนวนมาก

นี่คือรหัส 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
ฉันชอบแนวทางนี้มากที่สุด มันจะช่วยได้ถ้าคุณอธิบายว่า "start" และ "incr" หมายถึงอะไร (เป็นวิธีการแสดง "เส้น" ทั้งหมดเป็นดัชนีเริ่มต้นและจำนวนดัชนีที่จะข้าม)
nafg

กรุณาอธิบายเพิ่มเติม คุณคิดรหัสนี้ได้อย่างไร?
Farzan

1

โซลูชันเวลาคงที่ทำงานใน 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
O: 0 1 0 1 0 0 0 1 0

สังเกตว่าบิตสำหรับผู้เล่น X ไม่ปะติดปะต่อจากบิตสำหรับผู้เล่น O ซึ่งเห็นได้ชัดเนื่องจาก X ไม่สามารถใส่ชิ้นส่วนที่ O มีชิ้นส่วนได้และในทางกลับกัน

ในการตรวจสอบว่าผู้เล่นชนะหรือไม่เราจำเป็นต้องเปรียบเทียบตำแหน่งทั้งหมดที่ผู้เล่นคนนั้นครอบคลุมกับตำแหน่งที่เรารู้ว่าเป็นตำแหน่งที่ชนะ ในกรณีนี้วิธีที่ง่ายที่สุดในการทำเช่นนั้นคือ AND-gating ตำแหน่งผู้เล่นและตำแหน่งชนะและดูว่าทั้งสองเท่ากันหรือไม่

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-gate เป็นการดำเนินการเวลาคงที่และจำนวนตำแหน่งที่ชนะนั้น จำกัด

นอกจากนี้ยังช่วยลดความยุ่งยากในการระบุสถานะบอร์ดที่ถูกต้องทั้งหมดซึ่งเป็นตัวเลขทั้งหมดที่แสดงได้ด้วย 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));

กรุณาใส่คำอธิบายเพิ่มเติมและเปลี่ยนรหัสเพื่อให้ตอบคำถามของ OP ได้อย่างตรงประเด็น
Farzan

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 แสดงถึง row-1 และ a1, a4, a7 จะสร้างคอลัมน์ -1 (คุณจะได้รับแนวคิด) ใช้ '1' เพื่อระบุผู้เล่น -1 และ '2' เพื่อระบุผู้เล่น -2

มีชุดค่าผสมการชนะ 8 แบบ: Win-1: a1 + a2 + a3 (คำตอบอาจเป็น 3 หรือ 6 ขึ้นอยู่กับผู้เล่นที่ชนะ) Win-2: a4 + a5 + a6 Win-3: a7 + a8 + a9 Win-4 : a1 + a4 + a7 .... วิน -7: a1 + a5 + a9 วิน -8: a3 + a5 + a7

ตอนนี้เรารู้แล้วว่าหากผู้เล่นคนใดคนหนึ่งข้าม a1 เราจำเป็นต้องประเมินผลรวมของตัวแปร 3 ตัวใหม่: Win-1, Win-4 และ Win-7 แล้วแต่ว่า 'ชนะ -?' ตัวแปรถึง 3 หรือ 6 คนแรกชนะเกม หากตัวแปร Win-1 ถึง 6 ก่อนผู้เล่น 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

นี่คือวิธีแก้ปัญหาของฉันโดยใช้อาร์เรย์ 2 มิติ:

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

ไม่แน่ใจว่ามีการเผยแพร่แนวทางนี้หรือยัง สิ่งนี้ควรใช้ได้กับบอร์ด 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

ฉันได้พัฒนาอัลกอริทึมสำหรับสิ่งนี้โดยเป็นส่วนหนึ่งของโครงงานวิทยาศาสตร์ครั้งหนึ่ง

โดยพื้นฐานแล้วคุณจะแบ่งกระดานออกเป็นกลุ่มของสี่เหลี่ยม 2x2 ที่ทับซ้อนกันโดยทดสอบชุดค่าผสมต่างๆที่เป็นไปได้สำหรับการชนะบนสี่เหลี่ยม 2x2

ทำงานได้ช้า แต่มีข้อได้เปรียบในการทำงานบนบอร์ดทุกขนาดโดยมีข้อกำหนดหน่วยความจำเชิงเส้นค่อนข้างมาก

ฉันหวังว่าฉันจะพบการใช้งานของฉัน


ไม่จำเป็นต้องเรียกซ้ำเพื่อทดสอบการเสร็จสิ้น
Gabriel Llamas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.