一 井字棋游戏整体逻辑
1.首先打印一个 3 * 3 的空白棋盘,然后根据输入在棋盘上打印棋子,最后根据井字棋规则输出赢家。
那么怎么才能打印空白棋盘呢,看到3 * 3的棋盘,我首先想到了棋盘的样子和二维数组在我们想象中的样子一样(实际上二维数组在内存中是连续的空间,并非一排一排的),所以我们可以用二维数组代替棋盘中的格子,落子后在对应位置存储一个“棋子”,然后根据井字棋规则判断数组内对应的位置是否为同一棋子即可。
void PrintBoard(char arr[ROW][COL], int row, int col)//打印棋盘
{
for (int i = 0; i < row ; i++)
{
for (int j = 0; j < col; j++)
{
printf(" %c ", arr[i][j]);
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
for (int j = 0; j < col ; j++)
{
if (i < row - 1)
{
printf("---");
if (j < col - 1)
{
printf("|");
}
}
}
if(i < row - 1)
printf("\n");
}
}
代码如上,首先将数组与行数列数传入函数,采用两个for循环嵌套打印数组内容并同时打印棋盘分隔线,效果如下:
你可能发现,你打印的并非如上图所示,而是每格中都是 ?,因为你缺少了重要的一步:数组初始化。
2.数组初始化,顾名思义,游戏刚开始时,你必须将数组内的所有内容重置为空格,这样才能打印出上述效果,很简单,与打印相同,采用两个for循环嵌套,即可完成,如下:
void InitBoard(char arr[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
arr[i][j] = ' ';
}
}
}
3.棋盘打印好了自然就该下棋了,下棋的逻辑很简单,首先提示玩家输入下棋的坐标,由于绝大多数人会根据格子数输入,但数组的下标是0开头,因此只需要将玩家输入的player_row与player_col都减一,就能得到玩家想要落子的数组下标。得到输入的下一步必定是判断,我们需要判断的点有两个,第一个,判断这两个值是否都在棋盘内,若不在,则需要玩家重新输入,当输入合法时,就该判断第二个,判断落子点是否已经有棋子,如果有,仍需要玩家重新输入并提示玩家坐标已有棋子,大体逻辑如上,代码如下:
void PlayerMove(char arr[ROW][COL], int row, int col)
{
int player_row = -1;
int player_col = -1;
while (1)
{
printf("请输入要下的坐标:");
if (scanf("%d%d", &player_row, &player_col) == 2)
{
if (player_row - 1 >= 0 && player_row - 1 < row && player_col - 1 >= 0 && player_col - 1 < col)
{
if (arr[player_row - 1][player_col - 1] == ' ')
{
arr[player_row - 1][player_col - 1] = '*';
break;
}
else
{
printf("位置已被占用,请重新输入:\n");
}
}
else
{
printf("输入不合法,请重新输入:\n");
}
}
else
{
printf("输入不合法,请重新输入:\n");
}
}
}
玩家下棋的逻辑已经实现了,现在轮到电脑了,让还是菜鸟的我写一个AI陪我下井字棋还是太难为我了,我只能通过让电脑生成随机数的方法来完成电脑的落子,那么随机数怎么生成呢?
这里我们用rand()这个函数来生成随机数,rand可以生成0~32767(0x7fff)的随机数, 但是rand()使用起来会发现,他一旦生成随机值,在此次运行当中,就不会再发生改变了。为什么呢,因为:
rand函数调用
rand()函数每次调用前都会查询是否调用过srand(seed),是否给seed设定了一个值,如果有那么它会自动调用srand(seed)一次来初始化它的起始值
若之前没有调用srand(seed),那么系统会自动给seed赋初始值,即srand(1)自动调用它一次
————————————————
版权声明:本文为CSDN博主「TLpigff」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lvyibin890/article/details/80141412
说明用rand()函数生成随机数需要调用srand()初始化起始值,而srand()必须给一个会时刻发生改变的参数才能让rand()实现我们的需求,而时刻改变的参数,我目前能想到的只有一个:时间
电脑如何拿到时间的数据呢?time()函数,time_t time(time_t * timer),给time函数传入一个NULL空指针,再把time作为参数给srand就可以实现每次都有不同的起始值,代码如下:
srand((unsigned int)time(NULL));
//重置时间戳起点,为后续随机值生成用,只需调用一次,不用多次调用
void ComputerMove(char arr[ROW][COL], int row, int col)
{
printf("电脑的回合\n");
while (1)
{
int computer_row = rand() % row;
int computer_col = rand() % col;
if (arr[computer_row][computer_col] == ' ')
{
arr[computer_row][computer_col] = '#';
break;
}
}
}
这里用computer_row和computer_col接收随机值,因为我们只需要小于棋盘大小的值,因此用rand % 行或列,就能得到所需的合法数值,再判断目标位置是否有棋子,有棋子则重新生成数据再比较,没有棋子则落子。
4.现在就剩下最后一步了,下棋了就改判断输赢了,井字棋是只要有三个相连即可获胜,而3 * 3棋盘中只有每列 每行 每个对角线相连,这三种情况才能获胜,因此采用面向结果编程,可以得到如下代码:
char WhichWin(char arr[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
if (arr[i][0] == arr[i][1] && arr[i][1] == arr[i][2] && arr[i][0] != ' ')//判断一行是否都是'*'或'#'
return arr[i][0];
}
for (int i = 0; i < col; i++)
{
if (arr[0][i] == arr[1][i] && arr[1][i] == arr[2][i] && arr[0][i] != ' ')//判断一列是否都是'*'或'#'
return arr[0][i];
}
//判断对角线
if (arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[1][1]!= ' ')
{
return arr[0][0];
}
if (arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[1][1] != ' ')
{
return arr[0][2];
}
if (BoardFull(arr,row,col) == 1)//判断棋盘满了吗,走到这里说明还没有赢家,如果棋盘满了还没有赢家,说明平局,返回Q
return 'Q';
return 'C';//都不满足就继续
}
这里只要判断满足胜利条件,即每行、每列或者两条对角线的三个数组元素相等,就出现了胜者,那么,用函数返回这三个数组中的其中一个,就是返回了胜者的棋子,就能根据谁的棋子return了,谁就获胜了。同时,井字棋也会出现平局的情况,怎么平局呢?棋盘满了,但是没人赢,那我们只需再写一个函数用于判断棋盘是否全为空格即可,代码如下:
int BoardFull(char arr[ROW][COL],int row,int col)//判断棋盘是否满了,满了返回1,不满返回0
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (arr[i][j] == ' ')//有空格则说明棋盘还有位置,不满,返回0
return 0;
}
}
return 1;//走到这里说明:遍历一遍出了循环,说明一个空格都没有,棋盘已满,返回1
}
和初始化以及打印棋盘一样,面对二维数组,两个for循环嵌套,判断数组元素是否为空格,一旦发现空格,说明棋盘非空,立刻返回零,要是遍历完了数组,都找不到空格,说明是期盼满了,返回1,告诉判断输赢的函数棋盘已经满了。
至此,实现游戏所必需的接口函数基本已经完成,让我们写一个game函数测试一下吧
void PrintWinner(char WhichWin)// *--玩家赢 #--电脑赢 Q--平局 C--继续游戏
{
if (WhichWin == '*')
{
printf("玩家赢\n");
//return 1;
}
else if (WhichWin == '#')
{
printf("电脑赢\n");
//return 2;
}
else if (WhichWin == 'Q')
{
printf("平局\n");
//return 3;
}
}
void menu()
{
printf("----------------------------------\n");
printf("--------------1.game--------------\n");
printf("--------------0.exit--------------\n");
printf("----------------------------------\n");
printf("\n");
printf("请选择(0/1):");
}
void game()
{
srand((unsigned int)time(NULL));//重置时间戳起点,为后续随机值生成用,只需调用一次,不用多次调用
char arr[ROW][COL];
InitBoard(arr, ROW, COL);//初始化数组,初始为空格
char flag = 0;
PrintBoard(arr, ROW, COL);//打印棋盘
while (1)
{
PlayerMove(arr, ROW, COL);//玩家输入坐标下棋
PrintBoard(arr, ROW, COL);
flag = WhichWin(arr, ROW, COL);
if (flag != 'C')
{
break;
}
ComputerMove(arr, ROW, COL);
PrintBoard(arr, ROW, COL);
flag = WhichWin(arr, ROW, COL);
if (flag != 'C')
{
break;
}
}
PrintWinner(flag);
}
int main()
{
int input = 1;
do
{
menu();
if (scanf("%d", &input) == 1)
{
switch (input)
{
case 1:
game();
break;
case 0:
break;
default:
printf("输入不合法,请重新输入\n");
break;
}
}
} while (input);
return 0;
}