三子棋的问题小结
在最近的编程中,我本人遇到了一些小问题,所以在此来对这些问题进行一次小结
1.最常见的是链接型错误
当我在写三子棋的时候,写到末尾的时候,总是容易将函数名的大小写弄错,一开始自己也没有注意到这些问题,当程序运行报错的时候才发现了函数写错了,要么是大小写写错,要么是写的过快导致函数名中间有几个反过来的,或是漏掉字母
就像这样:
或是这样:
还有就是这样子:
这一开始没注意到就导致一直卡住,后面通过查阅资料知道了:当下面报错类型是LNK时,那就属于链接型错误,而链接型错误无非是没有对函数声明,或是将函数名写错了
2.第二种就是运行时错误
这种错误令我感到十分的头疼,每看到电脑的棋下的位置总跟自己预想的不一样时,就得调试一次,而且可能这种错误不是一下就能找到
1.就像下面这串电脑运行逻辑的代码:
void Compuetrmove(char board[ROW][COL], int row, int col)
{
int x = rand() % row;
int y = rand() % col;
int i = 0,j = 0;
int a, b;
while (1)
{
int count1 = 0;
int count2 = 0;
printf("Computermove:>\n");
if (board[1][1] == ' ')
{
printf("2 2\n");
board[1][1] = '*';
goto end;
}
else
{
for (i = 0, j = 0, count1 = 0, count2 = 0; i < row, j < col; i++, j++)//判断斜下线
{
if (board[i][j] == '#')
{
count1++;
}
else if (board[i][j] == '*')
{
count2++;
}
else
{
//当board[i][j]==' '时,直接将i,j赋值给a,b,然后输出
a = j;
b = i;
}
if ((count1 == 2 || count2 == 2) && count1 + count2 != row)
{
//当count1==2时,说明玩家的子已经连成2个,此时拦截
//当count2==2时,说明电脑的子已经连成2个,此时下这直接获胜
//count1+count2!=3,保证此时斜下这条线有空位
board[a][b] = '*';
goto end;
}
}
for (i = 0, j = col, count1 = 0, count2 = 0; i < row, j > 0; i++, j--)//判断斜上线
{
if (board[i][j] == '#')
{
count1++;
}
else if (board[i][j] == '*')
{
count2++;
}
else
{
//当board[i][j]==' '时,直接将i,j赋值给a,b,然后输出
a = j;
b = i;
}
if ((count1 == 2 || count2 == 2) && count1 + count2 != row)
{
//当count1==2时,说明玩家的子已经连成2个,此时拦截
//当count2==2时,说明电脑的子已经连成2个,此时下这直接获胜
//count1+count2!=3,保证此时斜上这条线有空位
board[a][b] = '*';
goto end;
}
}
for (i = 0, count1 = 0, count2 = 0; i < row;i++)//判断列
{
for (j = 0; j < col; j++)
{
if (board[j][i] == '#')
{
count1++;
}
else if (board[j][i] == '*')
{
count2++;
}
else
{
//当board[i][j]==' '时,直接将i,j赋值给a,b,然后输出
a = i;
b = j;
}
if ((count1 == 2 || count2 == 2) && count1 + count2 != row - 1)
{
//当count1==2时,说明玩家的子已经连成2个,此时拦截
//当count2==2时,说明电脑的子已经连成2个,此时下这直接获胜
//count1+count2!=3,保证此时列这条线有空位
board[b][a] = '*';
goto end;
}
}
}
for (i = 0, count1 = 0, count2 = 0; i < row; i++)//判断行
{
for (j = 0; j < col; j++)
{
if (board[i][j] == '#')
{
count1++;
}
else if (board[i][j] == '*')
{
count2++;
}
else
{
//当board[i][j]==' '时,直接将i,j赋值给a,b,然后输出
a = i;
b = j;
}
if ((count1 == 2 || count2 == 2) && count1 + count2 != row - 1)
{
//当count1==2时,说明玩家的子已经连成2个,此时拦截
//当count2==2时,说明电脑的子已经连成2个,此时下这直接获胜
//count1+count2!=3,保证此时行这条线有空位
board[i][j] = '*';
goto end;
}
}
}
if (board[x][y] == ' ')
{
board[x][y] = '*';
}
end:
break;
}
}
这段代码中藏了好多个错误,下面一个一个讲
1.a,b的赋值错误
最开始的判断斜下和斜上的a,b的值被赋反了,这导致了有时电脑会重复落子,落到原本已经有子的位置上
而正确的方法是a=i,b=j
2.初始化只进行了一次
在后面的判断行与列的代码中,count1和count2的初始化只进行了一次,这也导致了重复落子的问题
而正确的方法是将这个初始化放到下面,这样每判断一次行或列的时候,会重新计数,这样就可以达到原本的拦截或取胜目的
3.随机值只取一次
最开始,我们为x,y进行取随机值,这目的是为了当前面的方法都不适用的时候,可以进行随机落点,但若board[x][y] != ' ' ,这时候就应该重新取一次随机值,但经过测试,就算没有用到goto的语法,只要不对代码进行干涉,那么代码最终还是会自动运行goto后面的语句块,
所以,这时候正确的做法是:
if(board[x][y]==' ')
{
board[x][y]='*';
end:
break;
}
这样就可以做到当我们的board[x][y]即使不是空的,也可以重新进行一次循环
2.下面这串代码也是
int main()
{
int choice = 0;
int choice2 = 0;
srand((unsigned int)time(NULL));//创建随机值
do
{
menu();//游戏菜单
scanf("%d", &choice);
switch (choice)
{
while(1)
{
menudiff();
scanf("%d",&choice2);
if(choice2!=1||choice2!=2||choice2!=3)
{
printf("输入错误,请重新输入\n");
}
else
{
break;
}
}
game(choice2);
continue;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
}
} while (choice);
return 0;
}
这上面这串代码存在一个巨大的问题,就是逻辑非:只要一个一个是真,那么就会执行该语句块,其不能代表既的意思
所以这段代码有严重的错误:
所以这段代码应该这样改:
do
{
menudiff();
scanf("%d", &choice2);
switch (choice2)
{
case 1:
case 2:
case 3:
goto out;
default:
printf("无此选项,请重新选择\n");
}
} while (1);
out:
game(choice2);
continue;
这样就能够达到我们的目的
3.还有这也是
char Iswin(char Board[ROW][COL], int row, int col)
{
for (int i = 0, j = 0, count = 0; i < COL - 1, j < ROW - 1; i++, j++)//判断斜下
{
if (Board[i][j] == Board[i + 1][j + 1] && Board[i][j] != ' ')
{
count++;
}
if (count == 2)
{
if (Board[i][j] == '*')
return '*';
else
return '#';
}
}
for (int i = COL - 1, j = 0, count = 0; i >= 1, j < ROW - 1; i--, j++)//判断斜上
{
if (Board[i][j] == Board[i - 1][j + 1] && Board[i][j] != ' ')
{
count++;
}
if (count == 2)
{
if (Board[i][j] == '*')
return '*';
else
return '#';
}
}
for (int i = 0,count = 0; i < COL; i++)//判断行
{
for (int j = 0; j < ROW - 1; j++)
{
if (Board[i][j] == Board[i][j + 1]&&Board[i][j]!=' ')
{
count++;
}
if (count == 2)
{
if (Board[i][j] == '*')
return '*';
else
return '#';
}
}
for (int i = 0,count = 0; i < COL; i++)//判断列
{
for (int j = 0; j < ROW - 1; j++)
{
if (Board[j][i] == Board[j + 1][i]&&Board[j][i]!=' ')
{
count++;
}
if (count == 2)
{
if (Board[j][i] == '*')
return '*';
else
return '#';
}
}
}
int count1 = 0;
for (int i = 0; i < COL; i++)//判断棋盘是否满了,满了则平局
{
for (int j = 0; j < ROW; j++)
{
if (Board[i][j] != ' ')
{
count1++;
}
else
//当Board[i][j]==' '时,说明棋盘至少有一个空位,可以直接跳出循环
break;
if (count1 == (ROW) * (COL))
{
return 'p';
}
}
}
//当以上一次都没有完成return,说明游戏还没有结束,所以继续
return 'c';
}
}
这段代码中这段也存在初始化只进行一次的问题
通过上面的代码,我们现在能很好的看到这里的count只初始化了一次
所以更正后应该是这样的:
for (int i = 0; i < COL; i++)//判断行
{
int count=0;
for (int j = 0; j < ROW - 1; j++)
{
if (Board[i][j] == Board[i][j + 1]&&Board[i][j]!=' ')
{
count++;
}
if (count == 2)
{
if (Board[i][j] == '*')
return '*';
else
return '#';
}
}
for (int i = 0; i < COL; i++)//判断列
{
int count=0;
for (int j = 0; j < ROW - 1; j++)
{
if (Board[j][i] == Board[j + 1][i]&&Board[j][i]!=' ')
{
count++;
}
if (count == 2)
{
if (Board[j][i] == '*')
return '*';
else
return '#';
}
}
}
总结
这些大概就是我在三子棋中遇见的一些错误,这些问题第一次碰见的时候真的花费了我大量时间去寻找,一直在慢慢的反复的调试,最终才把这些问题揪了出来