我测试了几次代码的错误,但是我可能错过了一些。
我觉得我在某些地方重复了代码位置(只有很小的变化是不同的),将它们进行细化会很好。
更好地解析输入
,但是任何和所有建议都是可以接受的。如果您对查看此代码的某些不定期更新版本感兴趣,请查看存储该代码的Github存储库(随意发送fork和pull请求)。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define ROWS 9
#define COLS 9
typedef char Board[ROWS][COLS];
typedef char MetaBoard[ROWS / 3][COLS / 3];
typedef enum {VALID, NOT_A_DIGIT, NOT_IN_BOARD, SPACE_OCCUPIED, OUT_OF_BOUNDS} MoveStatus;
void fillSubBoard(Board board, int x, int y, char c)
{
for (; (x % 3) != 0; x--); // quickly set x to left bound of sub-board
for (; (y % 3) != 0; y--); // quickly set y to upper bound of sub-board
for (int rowMax = x + 2, row = x; row <= rowMax; row++)
{
for (int columnMax = y + 2, column = y; column <= columnMax; column++)
{
board[row][column] = c;
}
}
}
int getRowBound(int row)
{
switch (row)
{
case 0 ... 2:
return 0;
case 3 ... 5:
return 1;
case 6 ... 8:
return 2;
default:
return -1;
}
}
int getColumnBound(int column)
{
switch (column)
{
case 0 ... 2:
return 0;
case 3 ... 5:
return 1;
case 6 ... 8:
return 2;
default:
return -1;
}
}
void printBoard(Board board)
{
printf("\n=============||===========||=============\n");
for (int row = 0; row < ROWS; row++)
{
printf("||");
for (int column = 0; column < COLS; column++)
{
if (board[row][column] == '-') printf("%d,%d|", row, column);
else printf(" %c |", board[row][column]);
if (0 == (column+1) % 3) printf("|");
}
if ((row+1) % 3 == 0) printf("\n=============||===========||=============\n");
else printf("\n-----|---|---||---|---|---||---|---|-----\n");
}
}
static int checkMeta(MetaBoard meta)
{
const int xStart[ROWS - 1] = {0, 0, 0, 0, 1, 2, 0, 0};
const int yStart[COLS - 1] = {0, 1, 2, 0, 0, 0, 0, 2};
const int xDelta[ROWS - 1] = {1, 1, 1, 0, 0, 0, 1, 1};
const int yDelta[COLS - 1] = {0, 0, 0, 1, 1, 1, 1, 1};
static int startx, starty, deltax, deltay;
for (int trip = 0; trip < ROWS - 1; trip++)
{
startx = xStart[trip];
starty = yStart[trip];
deltax = xDelta[trip];
deltay = yDelta[trip];
// main logic to check if a subboard has a winner
if (meta[startx][starty] != '-' &&
meta[startx][starty] == meta[startx + deltax][starty + deltay] &&
meta[startx][starty] == meta[startx + deltax + deltax][starty + deltay + deltay]) return 1;
}
return 0;
}
static int checkBoard(Board board, MetaBoard meta, int player, int row, int column)
{
const int xStart[ROWS - 1] = {0, 0, 0, 0, 1, 2, 0, 0};
const int yStart[COLS - 1] = {0, 1, 2, 0, 0, 0, 0, 2};
const int xDelta[ROWS - 1] = {1, 1, 1, 0, 0, 0, 1, 1};
const int yDelta[COLS - 1] = {0, 0, 0, 1, 1, 1, 1, 1};
static int startx, starty, deltax, deltay, status = 0;
for (; (row % 3) != 0; row--); // quickly set row to left bound of sub-board
for (; (column % 3) != 0; column--); // quickly set column to upper bound of sub-board
for (int trip = 0; trip < ROWS - 1; trip++)
{
startx = row + xStart[trip];
starty = column + yStart[trip];
deltax = xDelta[trip];
deltay = yDelta[trip];
if (board[startx][starty] != '-' &&
board[startx][starty] == board[startx + deltax][starty + deltay] &&
board[startx][starty] == board[startx + deltax + deltax][starty + deltay + deltay])
{
fillSubBoard(board, row, column, (player == 1) ? 'X' : 'O');
meta[getRowBound(row)][getColumnBound(column)] = (player == 1) ? 'X' : 'O';
status = 1;
}
}
return (status + checkMeta(meta)); // always check if the game has a winner
}
MoveStatus validCoords(Board board, int row, int column, int rowBound, int columnBound)
{
if (!isdigit((char)(((int)'0') + row)) && !isdigit((char)(((int)'0') + column))) return NOT_A_DIGIT; // supplied coordinates aren't digits 1-9
else if (row > ROWS - 1 || column > COLS - 1) return NOT_IN_BOARD; // supplied coordinates aren't within the bounds of the board
else if (board[row][column] != '-') return SPACE_OCCUPIED; // supplied coordinates are occupied by another character
else if (rowBound == -1 && columnBound == -1) return VALID; // supplied coordinates can move anywhere
else if (((row > rowBound * 3 + 2 || column > columnBound * 3 + 2) ||
(row < rowBound * 3 || column < columnBound * 3)) &&
(rowBound > 0 && columnBound > 0)) return OUT_OF_BOUNDS; // coordinates aren't within the sub-board specified by the previous move
else return VALID; // didn't fail anywhere else, so coords are valid
}
int main(void)
{
int winner = 0, row = 0, column = 0, rowBound = -1, columnBound = -1, invalid = 0;
char tempRow = 'q4312079q', tempColumn = 'q4312079q';
Board board;
MetaBoard meta;
// initialize boards and fill with '-'
memset(board, '-', ROWS * COLS * sizeof(char));
memset(meta, '-', (ROWS / 3) * (COLS / 3) * sizeof(char));
// game loop
for (int turn = 0; turn < ROWS * COLS && !winner; turn++)
{
int player = (turn % 2) + 1;
printBoard(board);
printf("Player %d, enter the coordinates (x, y) to place %c: ", player, (player==1) ? 'X' : 'O');
do
{
scanf("%c, %c", &tempRow, &tempColumn);
for(; getchar() != '\n'; getchar()); // pick up superfluous input so we don't run into problems when we scan for input again
row = abs((int) tempRow - '0');
column = abs((int) tempColumn - '0');
invalid = 0;
switch (validCoords(board, row, column, rowBound, columnBound))
{
case NOT_A_DIGIT:
printf("Invalid input. Re-enter: ");
invalid = 1;
break;
case NOT_IN_BOARD:
printf("Out of board's bounds. Re-enter: ");
invalid = 2;
break;
case SPACE_OCCUPIED:
printf("There is already an %c there. Re-enter: ", board[row][column]);
invalid = 3;
break;
case OUT_OF_BOUNDS:
printf("Your move was in the wrong sub-board. Re-enter: ");
invalid = 4;
break;
default:
break;
}
} while (invalid);
board[row][column] = (player == 1) ? 'X' : 'O';
switch(checkBoard(board, meta, player, row, column))
{
case 1:
// next move can be anywhere
rowBound = -1;
columnBound = -1;
break;
case 2:
winner = player;
break;
default:
rowBound = row % 3;
columnBound = column % 3;
break;
}
}
printBoard(board);
if(!winner) printf("The game is a draw\n");
else printf("Player %d has won\n", winner);
return 0;
}
#1 楼
一些评论:[...]
for (; (x % 3) != 0; x--); // quickly set x to left bound of sub-board
for (; (y % 3) != 0; y--); // quickly set y to upper bound of sub-board
我认为我将代码四舍五入为三本身的功能。我想我会实现这样的事情:
int round3(int in) { return (in/3)*3; }
int getRowBound(int row)
{
switch (row)
{
case 0 ... 2:
return 0;
case 3 ... 5:
return 1;
case 6 ... 8:
return 2;
default:
return -1;
}
}
int getColumnBound(int column)
{
switch (column)
{
case 0 ... 2:
return 0;
case 3 ... 5:
return 1;
case 6 ... 8:
return 2;
default:
return -1;
}
}
这两个函数(
getRowBound
和getColumnBound
)是相同的-也不只是巧合,所以我想将它们合并为一个函数:int getBound(int in) {
return (unsigned)in < 9 ? in / 3 : -1;
}
[ ...]
for (; (row % 3) != 0; row--); // quickly set row to left bound of sub-board
for (; (column % 3) != 0; column--); // quickly set column to upper bound of sub-board
这些应该是前面提到的
round3
(或您喜欢的任何名称)的调用。 [...]
尽管有些不同意(在某些情况下非常强烈),我个人还是希望摆脱一些这样的条件:
fillSubBoard(board, row, column, (player == 1) ? 'X' : 'O');
meta[getRowBound(row)][getColumnBound(column)] = (player == 1) ? 'X' : 'O';
static const char marks[] = {'X', 'O'};
// ...
fillSubBoard(board, row, column, marks[player]);
meta[getBound(row)][getBound(column)] = marks[player];
[...]
MoveStatus validCoords(Board board, int row, int column, int rowBound, int columnBound)
{
if (!isdigit((char)(((int)'0') + row)) && !isdigit((char)(((int)'0') + column))) return NOT_A_DIGIT; // supplied coordinates aren't digits 1-9
传递给
isdigit
的任何用户输入(或isXXX
的其他任何ctype.h
函数/宏)都应首先转换为unsigned char
。将负数(EOF除外)传递给isXXX
将产生未定义的行为。在典型情况下,存储在char
中的任何基本US-ASCII字符集之外的任何字符(例如,任何未使用英语的字母,加上带有变音符号的任何字符)都将具有负值。[...]
int winner = 0, row = 0, column = 0, rowBound = -1, columnBound = -1, invalid = 0;
尽管有些不同意,但我认为大多数程序员都希望在单独的定义中使用每个变量。如果(出于某种原因)您不愿意这样做,则至少将每个变量的格式设置为单独的行。
for(; getchar() != '\n'; getchar()); // pick up superfluous input so we don't run into problems when we scan for input again
这看起来有问题。在这种情况下,您正在调用
getchar()
并检查返回值-但随后在循环的increment
部分中,您再次调用getchar()
而不检查返回值。我认为您可能想要更类似的东西:
while (getchar() != '\n')
;
[...]
switch (validCoords(board, row, column, rowBound, columnBound))
{
case NOT_A_DIGIT:
printf("Invalid input. Re-enter: ");
invalid = 1;
break;
case NOT_IN_BOARD:
printf("Out of board's bounds. Re-enter: ");
invalid = 2;
break;
case SPACE_OCCUPIED:
printf("There is already an %c there. Re-enter: ", board[row][column]);
invalid = 3;
break;
case OUT_OF_BOUNDS:
printf("Your move was in the wrong sub-board. Re-enter: ");
invalid = 4;
break;
在这里(再次),我认为我可能会使用返回值将其索引到数组中:
static char const *errors[] = {
"Invalid Input.",
"Out of board's bounds",
"That space is already used",
"Your move was in the wrong sub-board"
};
int error;
while (0 != (error=validCoords(...))) {
printf("%s Re-enter:", errors[error]);
getinput();
}
这确实要求您将错误列表与错误号,但收益超过了额外负担(IMO)。
[...]
board[row][column] = (player == 1) ? 'X' : 'O';
我再次使用前面提到的
marks
,所以最终会像这样:board[row][column] = marks[player];
评论
\ $ \ begingroup \ $
对无符号的强制转换在getBound函数中有什么作用?
\ $ \ endgroup \ $
–莫文
17年6月14日在12:26
\ $ \ begingroup \ $
它消除了比较。我们要为任何<0或> 8的数字返回-1。将字符强制转换为无符号可将负数转换为大正数(保证比8大得多),因此我们只将其与8进行比较,而不是0和8。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
17年6月14日在14:09
\ $ \ begingroup \ $
好的,这就是我的想法。我想至少应该对此发表评论。如果我不注意,我可能会挠头或不小心取下未签名的:p
\ $ \ endgroup \ $
–莫文
17年6月14日14:13在
#2 楼
我的一项快速观察是关于您存储可玩空间的方式。虽然9 x 9的网格是一种简单的实现方法,但这并不是最清晰的存储方式,最终需要一些复杂的寻址逻辑才能获得每个子板。您的选择处理它受C的限制,但支持嵌套类型。您可以使用它来定义采用3 x 3的SubBoard类型矩阵的MainBoard类型和采用3 x 3的可玩正方形(字符)网格的SubBoard类型。
typedef char SubBoard[ROWS][COLS];
typedef SubBoard MainBoard[ROWS][COLS];
我不确定C的语法,因为我的C语言有点生疏,但这应该可以让您使用更简单的地址。如果玩家在要玩的子板上使用空间1,3,则可以在下一个游戏中获得板子1,3。
#3 楼
在这段代码中:省略号...
是对C编程语言(称为Case Ranges)的GCC扩展。使用它会阻碍可移植性,因为并非所有编译器都支持它(对于大多数扩展)。如果要摆脱它,则应使用Jerry Coffin提出的函数getBound
:int getRowBound(int row)
{
switch (row)
{
case 0 ... 2:
return 0;
case 3 ... 5:
return 1;
case 6 ... 8:
return 2;
default:
return -1;
}
}
#4 楼
for (; (row % 3) != 0; row--); // quickly set row to left bound of sub-board
for (; (column % 3) != 0; column--); // quickly set column to upper bound of sub-board
为什么不只是
row -= row % 3;
column -= column % 3;
,实际上应该调用类似
static inline int round3(int x) {
return x - x % 3;
}
评论
我认为,如果将游戏逻辑移开,主要是更好。像while(isGameRunning())这样的游戏逻辑可以管理输入等。所有这些都带有函数。