这是我对UTTT代码挑战的尝试(响应“周末挑战”重启)。这是我想批评的内容:


我测试了几次代码的错误,但是我可能错过了一些。
我觉得我在某些地方重复了代码位置(只有很小的变化是不同的),将它们进行细化会很好。
更好地解析输入

,但是任何和所有建议都是可以接受的。如果您对查看此代码的某些不定期更新版本感兴趣,请查看存储该代码的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;
}
 


评论

我认为,如果将游戏逻辑移开,主要是更好。像while(isGameRunning())这样的游戏逻辑可以管理输入等。所有这些都带有函数。

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


这两个函数(getRowBoundgetColumnBound)是相同的-也不只是巧合,所以我想将它们合并为一个函数:

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