确定Tic Tac Toe游戏的算法

Ben*_*key 89 java algorithm tic-tac-toe

我在Java中编写了一个井字游戏,我目前确定游戏结束的方法解释了游戏结束的以下可能情况:

  1. 董事会已经满员,尚未宣布获胜者:比赛是平局.
  2. 克罗斯赢了.
  3. Circle赢了.

不幸的是,为了做到这一点,它从表中读取了一组预定义的这些场景.考虑到电路板上只有9个空格,这并不一定是坏的,因此表格有点小,但有没有更好的算法来确定游戏是否结束?确定某人是否赢了是问题的关键,因为检查9个空格是否已满是微不足道的.

表方法可能是解决方案,但如果没有,那是什么?另外,如果电路板尺寸不大n=9怎么办?如果它是一个更大的板,比如n=16,n=25等,造成连续放置物品的数量取胜是x=4,x=5等?一种用于所有人的通用算法n = { 9, 16, 25, 36 ... }

Har*_*guy 125

您知道获胜的移动只能在X或O进行最近移动之后发生,因此您只能搜索包含在该移动中的可选诊断的行/列,以在尝试确定获胜板时限制搜索空间.此外,由于在最后一次移动中,如果它不是一个获胜的移动,那么在抽签的Tic-tac-toe游戏中存在固定数量的移动它默认为抽奖游戏.

编辑:此代码用于n个n板,连续n个赢(3x3板请求连续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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 你忘了检查反对角线. (5认同)
  • 我不明白最后的抽奖检查,不应该减1吗? (4认同)
  • 在某些情况下,玩家可以在最后一次(第9次)移动中获胜.在这种情况下,将报告获胜者和平局...... (4认同)
  • @ Roamer-1888不是关于你的解决方案包含多少行,而是关于减少算法的时间复杂度以检查胜利者. (4认同)
  • 如果棋盘的尺寸大于 3x3,则此解决方案将不起作用,因为玩家可以在“if(board[x][i] != s)”评估为 true 的图块上做出获胜的举动(即使玩家获胜)对角线也可以从 x!=y 处开始 (2认同)

adk*_*adk 34

你可以使用魔方#http://mathworld.wolfram.com/MagicSquare.html如果任何行,列或诊断加起来15,那么玩家就赢了.

  • 难道我们不能用1和-1来做这个并且对每一行/ colum/diag求和以查看它是n还是-n? (24认同)
  • 覆盖它.1表示白色,2表示黑色和乘法.如果有什么东西出现在15,那么白色已经赢了,如果它出现在30,那么黑色赢了. (4认同)
  • 这如何转化为井字游戏? (3认同)
  • 大O-wise,它相当便宜,特别是如果你将它与Hardwareguy 的单元格检查混合在一起。每个单元格只能有 4 种可能的井字游戏:行行、列行和两条对角线(斜杠和反斜杠)。所以,一旦采取行动,你最多只需要进行4次添加和比较。相比之下,Hardwareguy 的答案要求每次移动进行 4(n-1) 次检查。 (2认同)
  • 你们正在关注实际问题。真正的问题是“_algorithmically **finding** a column/row/diag_”不检查其总和是否等于某个数字。一旦你这样做了,(用@Hardwareguy 的方法说)你可以很容易地检查任何(行/列/diag)的瓷砖是否全部 == "X" 或者它们都有独角兽。 (2认同)

CJ *_*net 20

这类似于Osama ALASSIRY的答案,但它为线性空间和恒定时间交换恒定空间和线性时间.也就是说,初始化后没有循环.

(0,0)为每行,每列和两个对角线(对角线和反对角线)初始化一对.这些对表示(sum,sum)相应的行,列或对角线中的片段的累积,其中

A piece from player A has value (1,0)
A piece from player B has value (0,1)

当玩家放置一块时,更新相应的行对,列对和对角线对(如果在对角线上).如果任何新更新的行,列或对角线对等于(n,0)或者(0,n)然后分别赢得A或B.

渐近分析:

O(1) time (per move)
O(n) space (overall)

对于内存使用,您使用4*(n+1)整数.

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

练习:你能看到如何在O(1)时间内进行抽签测试吗?如果是这样,你可以在平局的早期结束比赛.


Osa*_*eed 19

这个伪代码怎么样:

玩家在位置(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
Run Code Online (Sandbox Code Playgroud)

我使用char [n,n]数组,其中O,X和空格为空.

  1. 简单.
  2. 一圈.
  3. 五个简单变量:4个整数和一个布尔值.
  4. 缩放到任何大小的n.
  5. 只检查当前的一块.
  6. 没有魔法.:)


Gar*_*y C 19

超高效位板

让我们将游戏存储为二进制整数,并仅用一步来评估所有内容!

  • 我们知道X的着法占用9位:xxx xxx xxx
  • 我们知道O的着法占用9位:ooo ooo ooo

因此,棋盘位置只需 18 位即可表示:xoxoxo xoxoxo xoxoxo

但是,虽然这看起来很有效,但它并不能帮助我们确定胜利。我们需要一种更有用的位模式……它不仅可以对动作进行编码,还可以以合理的方式对行、列和对角线进行编码。

我会通过为每个棋盘位置使用一个巧妙的整数值来做到这一点。

选择更有用的表示

首先,我们需要一个董事会符号,以便我们可以讨论这个问题。因此,与国际象棋类似,让我们用字母对行进行编号,用数字对列进行编号 - 这样我们就知道我们正在谈论哪个方格

1 2 3
A a1 a2 a3
b1 b2 b3
C c1 c2 c3

让我们给每个值一个二进制值。

a1 = 100 000 000 100 000 000 100 000 ; Row A Col 1 (top left corner)
a2 = 010 000 000 000 100 000 000 000 ; Row A Col 2 (top edge)
a3 = 001 000 000 000 000 100 000 100 ; Row A Col 3 (top right corner)
b1 = 000 100 000 010 000 000 000 000 ; Row B Col 1 (left edge)
b2 = 000 010 000 000 010 000 010 010 ; Row B Col 2 (middle square)
b3 = 000 001 000 000 000 010 000 000 ; Row B Col 4 (right edge)
c1 = 000 000 100 001 000 000 000 001 ; Row C Col 1 (bottom left corner)
c2 = 000 000 010 000 001 000 000 000 ; Row C Col 2 (bottom edge)
c3 = 000 000 001 000 000 001 001 000 ; Row C Col 3 (bottom right corner)
Run Code Online (Sandbox Code Playgroud)

...其中,二进制值对位置出现的行、列和对角线进行编码。 (稍后我们将了解它是如何工作的)

我们将使用这些值构建游戏的两种表示形式,一种代表 X,一种代表 O

  • X 从一块空板开始:000 000 000 000 000 000 000 000
  • O 从一块空板开始:000 000 000 000 000 000 000 000

让我们跟随X的动作 (O也是同样的原理)

  • X 玩 A1...所以我们将值 A1 或(X 板)
  • X 扮演 A2...所以我们与值 A2 进行“或”运算
  • X 扮演 A3...所以我们与值 A3 进行“或”运算

这对 X 的董事会价值有何影响:

  1. a1 = 100 000 000 100 000 000 100 000...或与
  2. a2 = 010 000 000 000 100 000 000 000...或与
  3. a3 = 001 000 000 000 000 100 000 100... 等于 :

XB=111 000 000 100 100 100 100 100

从左到右阅读,我们看到 X 有:

  • 111(所有位置)第 1 行(\o/ 胜利,耶!)
  • 000第 2 行(无位置)
  • 000第 3 行(无位置)
  • 100(一个位置)仅第 1 列的第一个位置
  • 100(一个位置)仅第 1 列的第一个位置
  • 100(一个位置)仅第 1 列的第一个位置
  • 100(一个位置)仅对角线1的第一个位置
  • 100(一个位置)仅对角线2的第一个位置

您会注意到,每当 X(或 O)有获胜线时,他的棋盘值中也会有三个连续的位。准确地说,这三个位的位置决定了他在哪一行/哪一列/哪一条对角线上获胜。

因此,现在的技巧是找到一种方法来在单个操作中检查此(三个连续位设置)条件。

修改值以使检测更容易

为了帮助解决这个问题,让我们改变我们的位表示,以便三组之间始终有零(因为也是001 110三个连续位 - 但它们不是有效的胜利......所以,固定的零间隔符会打破这些:0 001 0 110

因此,在添加一些空格零之后,我们可以确信 X 或 O 棋盘值中的任何三个连续设置位都表示获胜!

因此,我们的新二进制值(带零填充)如下所示:

  • a1 = 100 0 000 0 000 0 100 0 000 0 000 0 100 0 000 0; 0x80080080(十六进制)
  • a2 = 010 0 000 0 000 0 000 0 100 0 000 0 000 0 000 0; 0x40008000
  • a3 = 001 0 000 0 000 0 000 0 000 0 100 0 000 0 100 0; 0x20000808
  • b1 = 000 0 100 0 000 0 010 0 000 0 000 0 000 0 000 0; 0x08040000
  • b2 = 000 0 010 0 000 0 000 0 010 0 000 0 010 0 010 0; 0x04004044
  • b3 = 000 0 001 0 000 0 000 0 000 0 010 0 000 0 000 0; 0x02000400
  • c1 = 000 0 000 0 100 0 001 0 000 0 000 0 000 0 001 0; 0x00820002
  • c2 = 000 0 000 0 010 0 000 0 001 0 000 0 000 0 000 0; 0x00402000
  • c3 = 000 0 000 0 001 0 000 0 000 0 001 0 001 0 000 0; 0x00200220

您会注意到主板的每个“winline”现在都需要 4 位。

8 个 winlines x 每条 4 位 = 32 位!是不是很方便:)))))

解析

我们可以移动所有的位来寻找三个连续的位,但这将需要 32 次移位 x 2 个玩家……并且需要一个计数器来跟踪。很慢!

我们可以与 0xF 进行 AND 运算,查找值 8+4+2=14。这将允许我们一次检查 4 位。将班次减少四分之一。但同样,这很慢!

因此,让我们立即检查所有可能性......

超高效的胜利检测

想象一下我们想要评估 A3+A1+B2+C3 的情况(对角线获胜)

a1 = 100 0 000 0 000 0 100 0 000 0 000 0 100 0 000 0, OR
a3 = 001 0 000 0 000 0 000 0 000 0 100 0 000 0 100 0, OR
b2 = 000 0 010 0 000 0 000 0 010 0 000 0 010 0 010 0, OR
c3 = 000 0 000 0 001 0 000 0 000 0 001 0 001 0 000 0, =

XB = 101 0 010 0 001 0 100 0 010 0 101 0 111 0 110 0  (See the win, on Diagonal 1?)
Run Code Online (Sandbox Code Playgroud)

现在,让我们通过有效地将三位合并为一位来检查是否获胜......

只需使用:XB AND (XB << 1) AND (XB >> 1) 换句话说:XB ANDed with(XB左移)AND(XB右移)

让我们尝试一个例子......

10100100001010000100101011101100 ; whitespaces removed for easy shifting
(AND)
01001000010100001001010111011000 ; XB shifted left
(AND)
01010010000101000010010101110110 ; XB shifted left
(Equals)
00000000000000000000000001000000
Run Code Online (Sandbox Code Playgroud)

看到了吗?任何非零结果都意味着胜利!

但是,他们赢在哪里呢?

想知道他们赢在哪里吗?好吧,你可以使用第二个表:

0x40000000 = RowA
0x04000000 = RowB
0x00400000 = RowC
0x00040000 = Col1
0x00004000 = Col2
0x00000400 = Col3
0x00000040 = Diag1
0x00000004 = Diag2
Run Code Online (Sandbox Code Playgroud)

然而,我们可以比这更聪明,因为模式非常规则!

例如,在汇编中,您可以使用它BSF (Bit Scan Forward)来查找前导零的数量。然后减去 2,然后减去 /4(右移 2) - 得到 0 到 8 之间的数字...您可以将其用作索引来查找 win 字符串数组:

{"wins the top row", "takes the middle row!", ... "steals the diagonal!" }

这使得整个游戏逻辑......从移动检查,到棋盘更新,一直到赢/输检测和适当的成功消息,都适合少数 ASM 指令。

...它小巧、高效且超快!

检查一个动作是否可以进行

显然,“X 的棋盘”与“O 的棋盘”进行 OR 运算 = 所有位置

因此,您可以很容易地检查移动是否有效。如果用户选择UpperLeft,则该位置具有整数值。只需用 (XB OR OB) 检查该值的“AND”...

...如果结果非零,则该位置已被使用。

结论

如果您正在寻找处理板的有效方法,请不要从板对象开始。尝试发现一些有用的抽象。

查看状态是否适合整数,并考虑要处理的“简单”位掩码是什么样的。通过巧妙地选择整数来表示动作、位置或棋盘……您可能会发现整个游戏可以非常有效地进行、评估和评分 - 使用简单的按位逻辑。

最后致歉

顺便说一句,我不是 StackOverflow 的常客,所以我希望这篇文章不会太混乱而难以理解。另外,请友善......“人类”是我的第二语言,我还不太流利;)

无论如何,我希望这对某人有帮助。

  • 被低估的惊人答案 (3认同)
  • 神圣的无聊,但仍然是我最喜欢的答案! (3认同)
  • 爱“‘人类’是我的第二语言,但我还不太流利”! (2认同)

Jac*_*lan 11

以下是我为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;
}
Run Code Online (Sandbox Code Playgroud)

  • 这将是一个大锤子的方法,但它确实是一个可行的解决方案,尤其是作为解决这个问题的众多创造性和工作解决方案之一的站点。此外,它简短、优雅且非常易读——对于 3x3 网格(传统 Tx3),我喜欢这个算法。 (2认同)
  • @David Ruiz谢谢!我修复了这个bug (2认同)

ale*_*alo 8

Constant time solution, runs in O(8).

Store the state of the board as a binary number. The smallest bit (2^0) is the top left row of the board. Then it goes rightwards, then downwards.

I.E.

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

Each player has their own binary number to represent the state (because tic-tac-toe) has 3 states (X, O & blank) so a single binary number won't work to represent the state of the board for multiple players.

For example, a board like:

+-----------+
| 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;
}
Run Code Online (Sandbox Code Playgroud)

例如。

电话:111001010
W: 111000000 // 获胜位置,第一行都相同。
------------
&:111000000

注:X & W = W,所以X处于胜利状态。

这是一个恒定时间解决方案,它仅取决于获胜位置的数量,因为应用与门是恒定时间操作并且获胜位置的数量是有限的。

它还简化了枚举所有有效板状态的任务,它们只是由 9 位表示的所有数字。但当然,您需要一个额外的条件来保证一个数字是有效的棋盘状态(例如,0b111111111是一个有效的 9 位数字,但它不是有效的棋盘状态,因为 X 刚刚完成了所有回合)。

可能的获胜位置的数量可以即时生成,但无论如何它们都在这里。

short[] winCombinations = new short[] {
  // each row
  0b000000111,
  0b000111000,
  0b111000000,
  // each column
  0b100100100,
  0b010010010,
  0b001001001,
  // each diagonal
  0b100010001,
  0b001010100
};
Run Code Online (Sandbox Code Playgroud)

要枚举所有板位置,您可以运行以下循环。尽管我将把确定一个数字是否是有效的棋盘状态留给其他人。

注意:(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));
Run Code Online (Sandbox Code Playgroud)

  • 当棋盘尺寸固定时,大O就没有意义了。O(8) = O(1)。 (3认同)

小智 7

我刚刚为我的C编程类编写了这个.

我发布它是因为这里没有任何其他示例可以使用任何大小的矩形网格,并且任何数量的N -in-a-row连续标记都可以获胜.

你会在checkWinner()函数中找到我的算法,例如它.它不使用魔术数字或任何想要检查胜利者的东西,它只是使用四个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.
Run Code Online (Sandbox Code Playgroud)


Joh*_*ica 5

如果电路板是n × n,那么有n行,n列和2个对角线.检查所有X或全O的每一个以找到胜利者.

如果它只需要x < n个连续的正方形来赢,那么它就会复杂一些.最明显的解决方案是检查每个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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)