我是C语言编程的新手,最近我挑战自己编写Tic-Tac-Toe程序。我发现我的代码由于包含大量if语句而很快变得冗长。这段代码按原样工作(我仍然希望添加更多功能)。我只是认为可能有一种更简洁/有效的方法来对此编程。有关如何修剪此效果的任何想法?

#include <stdio.h>
#include <stdlib.h>

typedef int bool;
#define true 1
#define false 0

int main()
{
    playGame();
    return 0;
}
int playGame()
{

    char scanned[3];
    printf("Do you wish to play tick-tack-toe?\n");
    scanf("%s", scanned);
    if(strcasecmp(scanned,"yes")==0)
        startGame();

    else
    {
        if (strcmp(scanned,"no")==0 || strcmp(scanned,"nah")==0 || strcmp(scanned,"naw")==0)
        {
            printf("That's too bad!/nThis program will now end.");
            return 1;
        }
        printf("Not valid input!/nThis program will now end.");
        return 1;
    }
}
int startGame()
{
    //Sets up board for values 1-9;
    char board[3][3] =
    {
        {'1','2','3'},
        {'4','5','6'},
        {'7','8','9'}
    };

    bool winner = false;
    printf("\n\nHere is your playing board. Player One is Os and Player Two is Xs\n");
    printf("Entering a number 1-9 (then pushing enter) as shown below will use\nthe current Player's turn in that location.\n");


    printf("\n\n");
    printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
    printf("-----------\n");
    printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
    printf("-----------\n");
    printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);

    int x=0;
    int j=0;
    int turn[9];
    while (j<sizeof(turn)/sizeof(turn[0]) && winner == false)
    {
        scanf("%d",&turn[j]);
        //turn[j] = x;
        if (j%2==0)
        {
            if (turn[j]==1)
            {
                board[0][0] = 'O';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==2)
            {
                board[0][1] = 'O';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==3)
            {
                board[0][2] = 'O';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==4)
            {
                board[1][0] = 'O';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==5)
            {
                board[1][1] = 'O';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==6)
            {
                board[1][2] = 'O';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==7)
            {
                board[2][0] = 'O';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==8)
            {
                board[2][1] = 'O';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
        }
        if (turn[j]==9)
        {
            board[2][2] = 'O';
            printf("\n\n");
            printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
            printf("-----------\n");
            printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
            printf("-----------\n");
            printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
        }
        if (j%2!=0)
        {
            if (turn[j]==1)
            {
                board[0][0] = 'X';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==2)
            {
                board[0][1] = 'X';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==3)
            {
                board[0][2] = 'X';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==4)
            {
                board[1][0] = 'X';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==5)
            {
                board[1][1] = 'X';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==6)
            {
                board[1][2] = 'X';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==7)
            {
                board[2][0] = 'X';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==8)
            {
                board[2][1] = 'X';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }
            if (turn[j]==9)
            {
                board[2][2] = 'X';
                printf("\n\n");
                printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
                printf("-----------\n");
                printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
            }




        }


        j++;
    }
}


#1 楼

布尔值
C99具有布尔值:#include <stdbool.h>
变量名称
int x=0;
int j=0;

xj没有描述它们的作用。 j的转数是多少?另外,除了此注释行:x,我还是看不到使用//turn[j] = x;
重复
我将设置一个变量名称来减少所有这些if。也许像这样:
char token = (j % 2 == 0) ? 'O' : 'X';

if (turn[j]==1)
{
    board[0][0] = token;
    printf("\n\n");
    printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
    printf("-----------\n");
    printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
    printf("-----------\n");
    printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
}

// ...

此外,嵌套的for循环在此处可能有助于将if的列表减少为一个值。
编辑:
为了澄清注释, ?:运算符是三元条件运算符。它的工作方式如下:

condition ? (run this statement if condition evaluated to true) : (run this statement if condition evaluated to false);


在此示例中,我将其与赋值操作结合使用。

C99是非正式术语,表示ISO / IEC 9899:1999(于1999年完成)。C的最新版本是C11,于2011年完成,并且还应支持布尔变量。

评论


\ $ \ begingroup \ $
我以前在测试某些东西时遇到了一个错误,我将删除x。
\ $ \ endgroup \ $
– SuperGoA
2015年6月27日,下午2:52

#2 楼

乍一看,此代码段似乎可以提取到其自己的参数化函数中:

    printf("\n\n");
    printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
    printf("-----------\n");
    printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
    printf("-----------\n");
    printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);


DRY-不要重复自己;-)

评论


\ $ \ begingroup \ $
然后我可以反复调用该函数吗?
\ $ \ endgroup \ $
– SuperGoA
15年6月27日在2:51

\ $ \ begingroup \ $
就是这样。写一次,用一束。
\ $ \ endgroup \ $
–user34073
15年6月27日在2:55

\ $ \ begingroup \ $
这将是学习huristics,最小/最大算法,alpha / beta修剪和某些相关主题的好时机。
\ $ \ endgroup \ $
–user3629249
15年6月27日在3:23

#3 楼

注意:


您似乎不需要将输入存储在turn数组中
您并没有主张合法的用户输入(19之间的值)
您并不是在断言用户每次输入一个新值。
由于用户输入是单个字符,您还可以扫描"%c"

您可以计算到board的索引根据用户输入(turn
您可以基于迭代次数(x)计算播放器(oj
您不会在任何地方更改winner(初始化为false)的值


实现:

int  j;
char turn;
int  index;
char players[] = {'o','x'};

for (j=0; j<9; j++)
{
    while (1)
    {
        scanf("%c",&turn);
        if (turn == '\n')
            continue; // skip the newline character
        index = turn-'1';
        if (!(0 <= index && index <= 8))
            printf("Invalid input\n");
        else if (board[index/3][index%3] == players[0] ||
                 board[index/3][index%3] == players[1])
            printf("Already chosen\n");
        else
            break;
    }

    board[index/3][index%3] = players[j%2];

    printf("\n\n");
    printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
    printf("-----------\n");
    printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
    printf("-----------\n");
    printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);

    /*
    if there is a winner
    {
        print the winner
        break;
    }
    */
}



如您所见,我把它留给您检查if there is a winner ...

评论


\ $ \ begingroup \ $
谢谢,这很有意义。该代码还没有完全完成,这就是为什么某些变量未使用的原因:P
\ $ \ endgroup \ $
– SuperGoA
15年6月27日在3:45

#4 楼

您的代码中的一些大问题没有提及:


您使用strcasecmp。此功能是非标准的。这使您的代码不可移植。

您使用

scanf("%s", scanned);


scanf会愉快地扫描无限字符并将其写入scanned。您将(并且已经)遭受缓冲区溢出的困扰。 scanned最多可容纳2个字符(NUL终止符为+1)。键入两个以上的字符会导致未定义的行为,因为其他字符将被写入无效的内存位置。到

char scanned[3];




char scanned[4]; /*Can hold a max of 3 chars (+1 for the NUL-terminator at the end)




scanf("%s", scanned);


请注意,您可能必须清除输入缓冲区以删除多余的字符(如果用户输入的字符超过3个)。

函数playGame()旨在返回int。如果用户为"yes"中的scanf输入playGame(),则if将执行并且该函数将退出,而不会显示return语句。这导致未定义的行为。通过在该函数的末尾添加return 0;来对其进行修复。

函数startGame()也旨在返回int,但您从不返回int。这再次导致未定义的行为。如果您不希望它返回值更改

scanf("%3s", scanned); /*Scan a max of 3 chars and append a NUL-terminator at the end*/




int startGame()


,这表明函数将不会返回任何值。


其他问题:



您有很多重复的代码:

void startGame()


可以放入名为printBoard()的函数中:

printf("\n\n");
printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
printf("-----------\n");
printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
printf("-----------\n");
printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);


使用循环也可以简化逻辑:

void printBoard() {

    printf("\n\n");
    printf(" %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
    printf("-----------\n");
    printf(" %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
    printf("-----------\n");
    printf(" %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
}


但这还会额外打印最后一个printf,因此请使用

void printBoard() {

    printf("\n\n");

    for(int i = 0; i < 3; i++) {
        printf(" %c | %c | %c\n", board[i][0], board[i][1], board[i][2]);
        printf("-----------\n");
    }
}



此处:

void printBoard() {

    printf("\n\n");

    for(int i = 0; i < 3; i++) {
        printf(" %c | %c | %c\n", board[i][0], board[i][1], board[i][2]);
        if(i != 2)
        {
            printf("-----------\n");
        }
    }
}


您无需检查用户是否输入了有效的号码。您还可以将输入存储在数组中。为什么?因此,这是:

scanf("%d",&turn[j]);


可以是

int turn[9];


检查scanf的返回值将显示用户是否输入数字或其他内容。如果您的scanf返回1,则表示scanf成功输入了数字,如果返回0,则表明scanf未能扫描数字。如果遇到称为-1的特殊值,它也可以返回EOFEOF)。

使用有意义的变量名代替xscanned等。我由您自己决定。

this:

int turn;


可以写为

else
{
    if (strcmp(scanned,"no")==0 || strcmp(scanned,"nah")==0 || strcmp(scanned,"naw")==0)
    {
        printf("That's too bad!/nThis program will now end.");
        return 1;
    }
    printf("Not valid input!/nThis program will now end.");
    return 1;
}


使您的代码更好。请注意,您使用了/n,而\n则表示换行符。



评论


\ $ \ begingroup \ $
您正在使用for循环执行printBoard时遇到问题。它应该在每隔一行之间打印一条破折号。现在,它在每一行之后打印该行,这意味着要多打印一行。
\ $ \ endgroup \ $
–杰瓦
15年7月9日在8:45

\ $ \ begingroup \ $
@jacwah是的。解决它。
\ $ \ endgroup \ $
– Spikatrix
15年7月11日在6:38

#5 楼

这是代码的修改后的工作版本。

它包括布尔变量的用法;处理代码重复;处理用户输入错误的情况;并决定僵局,获胜者案例。

#include <stdio.h>
#include <string.h>

void print(char board[][3])
{
    for (int i=0; i<3; i++)
    {
        printf(" %c | %c | %c\n", board[i][0], board[i][1], board[i][2]);
        if(i!=2)
            printf("-----------\n");
    }
}
bool winnerCheck(char board[][3], char check)
{
    bool winner = board[0][0]==check && board[0][1]==check && board[0][2]==check || board[1][0]==check && board[1][1]==check && board[1][2]==check || board[2][0]==check && board[2][1]==check && board[2][2]==check || board[0][0]==check && board[1][0]==check && board[2][0]==check || board[0][1]==check && board[1][1]==check && board[2][1]==check || board[0][2]==check && board[1][2]==check && board[2][2]==check || board[0][0]==check && board[1][1]==check && board[2][2]==check || board[0][2]==check && board[1][1]==check && board[2][0]==check;
    if (winner)
        printf("Player %d wins!\n", (check=='O')?1:2);
    return winner;
}
void startGame()
{
    //Sets up board for values 1-9;
    char board[3][3] =  {   {'1','2','3'},
                            {'4','5','6'},
                            {'7','8','9'}
                        };
    bool winner = false;
    printf("\nHere is your playing board. Player One is Os and Player Two is Xs\n");
    printf("Entering a number 1-9 (then pushing enter) as shown below will use\nthe current Player's turn in that location.\n\n");
    print(board);
    int j=0,cnt=9,turn;
    while (j<cnt && winner==false)
    {
        scanf("%d",&turn);
        if(board[turn/3][turn%3-1]!='O' && board[turn/3][turn%3-1]!='X')
            board[turn/3][turn%3-1] = (j%2==0)?'O':'X';
        else
        {
            printf("Illegal move\n");
            continue;
        }
        printf("\n\n");
        print(board);
        j++;
        winner = winnerCheck(board,'O') || winnerCheck(board,'X');
    }
    if(!winner)
        printf("Stalemate!\n");
}
int playGame()
{
    char scanned[10];
    printf("Do you wish to play tick-tack-toe?\n");
    scanf("%s", scanned);
    if(strcasecmp(scanned,"yes")==0)
        startGame();
    else
    {
        if (strcmp(scanned,"no")==0 || strcmp(scanned,"nah")==0 || strcmp(scanned,"naw")==0)
            printf("That's too bad!\nThis program will now end.");
        else
            printf("Not valid input!\nThis program will now end.");
        return 1;
    }
}
int main()
{
    playGame();
    return 0;
}


列出的更改是:


解决数量增加的原因之一您代码的长度是木板印刷代码的重复。这可以通过使用print(char board[][3])函数来解决。
如果获胜者之一获胜,您的代码将不会停止;在程序结束时,如果没有用户赢得,程序将终止。 winnerCheck(char board[][3], char check)接受电路板配置,并检查第一位/第二位用户的中奖条件。并且在检测到成功的胜利后,会在通知玩家后终止。
如果用户试图在已经占据的位置进行非法移动,则代码行:if(board[turn/3][turn%3-1]!='O' && board[turn/3][turn%3-1]!='X')会处理这种情况。 >如果没有获胜者,程序将检查winner变量并通知玩家。
else函数中的playgame()代码部分重复了return 1;语句。修订后的工作代码解决了该问题。
不需要转弯数组,而是使用字符转弯变量来获取用户输入。由于该数字应在[1-9]范围内,因此也可以检查用户输入的有效性。可以使用(turn/3,turn%3-1)而不是一组if语句来确定要设置的板坐标。
您可以尝试以下操作:用户输入的单词长度不得超过3个字符;因此,请使用长度超过3个字符(例如10个字符)的字符数组。但是用户可以使用11个字符的单词。因此,作为一种解决方案,可以尝试通过在终端中指定[0/1]的布尔选择来解决此问题,并在选择不正确的情况下提示用户再次输入,因为用户输入affirmativenegation字的方式可能不同语言或方言。


评论


\ $ \ begingroup \ $
谢谢,但是正如我说的那样,“我仍然希望添加功能。”我只是想要一些有关如何在已有的情况下最大程度地减少使用的行代码的想法。
\ $ \ endgroup \ $
– SuperGoA
15年6月28日在23:16

\ $ \ begingroup \ $
@SuperGoA是的,的确如此。现在是70行代码(比之前的250行代码减少了很多)。
\ $ \ endgroup \ $
–维沙尔·阿南德(Vishal Anand)
15年6月28日在23:26

\ $ \ begingroup \ $
我认为print是非常具体的功能的真正不好的命名。听起来像是标准库中非常通用的输出函数。我会称其为“ printboard”以消除歧义。
\ $ \ endgroup \ $
–杰瓦
15年7月9日在8:55

\ $ \ begingroup \ $
@jacwah我同意。
\ $ \ endgroup \ $
–维沙尔·阿南德(Vishal Anand)
19年4月4日在20:55

#6 楼

我的答案是使用“ switch / case”表达式。它是这样工作的:

switch (expression that evaluates to int) {
    case 1:
    {
        // code to be executed if the expression evaluates to 1
        break;
    }
    // add as many cases as needed
    default:
    {
         // this is the "else" case.
         break;
    }
}


这样,您将无需编写以下语句:

if (turn[j] == 1)
{
      // code
}


您可以用以下代码替换它们:

switch(turn[j]) {
    case 1:
    {
        // code
        break;
    }
    case 2:
    {
        // more code
        break;
    }
    default
    {
        // it equals neither 1 nor 2
        break;
    }
}


此外,除了明确声明“如果”的条件只能在0时运行之外,您还可以只需删除“ ==0”,然后在条件前面放置一个二进制“ not”运算符。 (0为“ false”的二进制数)

开始游戏时,可以删除应为if (!(j%2))的所有“ if”语句,并将其替换为:

board[(turn[j] - 1) / 3][(turn[j] - 1) % 3] = '0'
printf("\n\n %c | %c | %c\n-----------\n %c | %c | %c\n-----------\n %c | %c | %c\n", board[0][0], board[0][1], board[0][2], board[1][0], board[1][1], board[1][2], board[2][0], board[2][1], board[2][2]);


评论


\ $ \ begingroup \ $
如果OP将很多东西放在一行而不折断,为什么代码会更好呢?我认为这是一种不好的做法,因为这会大大降低可读性。
\ $ \ endgroup \ $
–杰瓦
15年7月9日在8:57

\ $ \ begingroup \ $
@jacwah它将行数和代码大小减少了两个字节。
\ $ \ endgroup \ $
– DDPWNAGE
15年7月9日在18:04

\ $ \ begingroup \ $
是的,但这有什么好处呢? OP可以从代码中省略所有空格,并将大小减少约0.5 KB,但该程序将令人难以理解。那是我的硬盘容量的0.00000005%。如果空间不足,请使用gzip等压缩软件。代码越容易阅读,就越容易发现其中的错误并对其进行维护。这应该是审查的目的。要编写尽可能短的代码,可以使用Code Golf。
\ $ \ endgroup \ $
–杰瓦
15年7月9日在18:29

\ $ \ begingroup \ $
@jacwah它还减少了处理器的工作量;它不必处理一大堆print(f)语句。
\ $ \ endgroup \ $
– DDPWNAGE
15年7月9日在18:30

\ $ \ begingroup \ $
这可能是对的,也是我也提到中断生产线的原因。
\ $ \ endgroup \ $
–杰瓦
15年7月9日在18:34