玩家下棋
我们让玩家输入坐标,根据坐标来判断在哪个地方落子。
但是我们要注意两个细节,
第一,玩家不是程序员,不能用下标作为坐标
第二,我们要考虑到玩家输入的坐标是否合法,即是不是在坐标范围内,和是不是已经被落子了。
接下来我们给出代码。
void player(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; while (1) { printf("请输入你要下的坐标"); scanf("%d %d", &x, &y); //判断坐标合法性 if (1 <= x && x <= ROW && 1 <= y && y <= COL) { if (board[x - 1][y - 1] == ' ') { board[x - 1][y - 1] = '*'; break; } else { printf("坐标被占用请重新输入\n"); } } else { printf("输入错误请重新输入\n"); } } }
说明:我们既然要让玩家下棋就一定要有变量来接收,
我们就用x,y 来接收,再判断xy是不是在棋盘范围之内。
但是即使在棋盘之内也不能就直接落子,还有判断该位置是否已经被落子。落子的下标即坐标x-1,y-1。
电脑下棋
我们之前在学习猜数字游戏的时候了解过rand srand time函数的用法,这次我们同样也要用到这几个函数。
电脑下棋思路与玩家下棋差不了太多这里就直接给出代码
void computer(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; while (1) { x = rand() % row; y = rand() % col; if (board[x][y] == ' ') { board[x][y] = '#'; break; } } }
首先还是用x,y来接收,然后x = rand % row , y = rand % col ,这样做就是让生成的x,y永远也不会超出二维数组,当然我们也需要判断该区域是否被落子,如果落子就重新生成坐标,所以就是用循环来解决。
我们还要注意一点就是srand和time函数的位置
一定是要在do-while之前就调用,如果在do-while里面定义就不符合随机的概念了。
判断输赢
判断输赢是三子棋最为重要的一环,我们一定要好好理解。
我们知道游戏只有4种状态:玩家赢,电脑赢,平局和继续游戏,也就是说其实无论是电脑还是玩家下一次棋我们都需要判断是当中的哪种状态。我们需要在test.c中写出满足这个条件的代码,下面给出一个模板。
我们需要写出一个判断输赢的函数,下面是这个函数的返回值。
玩家赢 ———*
电脑赢 ———#
打平 ———e
继续 ———c三子棋中输赢是什么样的?(3x3棋盘)
- 行
- 列
- 对角线
只要我们判断这3个方向是否有赢就行,有赢就会返回3子成线中的任意一个就行,当然没有人赢就返回c就好,如果棋盘满了就返回e。大家可以动手实践一下。
下面是参考代码。
char judge(char board[ROW][COL], int row, int col) { int i = 0; //行 for (i = 0; i < row; i++) { if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1]!=' ') { return board[i][0]; } } int j = 0; //列 for (j = 0; j < col; j++) { if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[0][j] != ' ') { return board[0][j]; } } //对角线 //3x3版本 if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] !=' ') { return board[0][0]; } if (board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[1][1]!= ' ') { return board[1][1]; } if (1 == is_full(board, row, col)) { return 'e'; //满了 //平局 } return 'c'; } int is_full(char board[ROW][COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { if (board[i][j] == ' ') { return 0 ; //没有满 } } } return 1; //满了 }
相信大家应该能理解这个代码,就是不要忘记在判断行列对角线时,忽略有空格的情况,另外我们为了实现判断输赢这个函数自己定义了一个判断棋盘是否已经满的函数is_full,这个函数并不需要在头文件添加声明,因为它是我们只是为了实现judge这个函数而生成的,并不属于我们思路的中的一类。
当然我们只实现这个函数是不行的,接下来我们来看看test.c中是如何来实现,人下一次判断一次,电脑下一次判断一次的。
void game() { //存储数据的二维数组 char board[ROW][COL]; //初始化为space intiboard(board, ROW, COL); //打印棋盘 printboard(board,ROW,COL); char ret = 0; while (1) { //玩家下棋 player(board, ROW, COL); printboard(board, ROW, COL); ret = judge(board, ROW, COL); if (ret != 'c') { break; } //电脑下棋 computer(board, ROW, COL); printboard(board, ROW, COL); ret = judge(board, ROW, COL); if (ret != 'c') { break; } } if ('*' == ret) //单引号里面别加空格 { printf("玩家赢了\n"); } else if ('#' == ret) { printf("电脑赢了\n"); } else { printf("平局\n"); } }
我们看到我们需要在do-while循环外定义一个变量ret来接收judge的返回值。注意一个要在do_while外面定义,因为我们用if语句来判断时,是在do-while之外。
还有一个细节是我因为打代码时,因为个人习惯导致的bug我也提一下。注意在if判断的时候’'单引号里面不要加space,我因为手误调试浪费了时间,这是要注意的。
推广到nxn🍊
当然判断输赢还不仅仅只有这些,我的标题写的是推广到n x n,我们可以想一下如果推广到n x n ,我们判断输赢的代码哪里需要改变呢?我们以5x5来举例
对没错就是对角线的判断,我们不能再仅仅只判断对角线,我画个图来给大家看看。
可以看到()左斜连成3子,(/)右斜连成3子的情况变多了,这下就不是很好讨论了。
不过不要着急我们慢慢来。
我们知道三子棋只要三子连在一起就能获胜,我们就需要判断一个棋子的四面八方,也就是上下左右,和 西北到东南和 东北到西南。但是我们的方法是以一个坐标为基准,用下标不断加减的方法来判断是否三子连线,这与我们三子棋的方法是一致的。如果大家不是很懂我可以用一张图来说明。
假设我们以红色为基准,用二维数组表示假设是board[ i ] [ j ],那么紫色的就是board[i+1][j+1]和board[i+2][j+2].
同理可得,蓝色也为board[ i ][ j ],绿色的就是board[i-1][j-1]和board[i-2][j-2]。
大家明白我的意思吗?
我的意思其实就是我们在这个棋盘上,想让以 \ 的方式连成三子其实是有限制的,同理以 / 的方式连成三子也是有限制的。
总结:我们只能在有限的区域连成三子。
大家可以试试写出代码。
下面是我的代码,可以作为参考。
//5x5版本 / 与 \ for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { if (i < row - 2 && j < col - 2) { if (board[i][j] == board[i + 1][j + 1] && board[i + 1][j + 1] == board[i + 2][j + 2] && board[i][j] != ' ') return board[i][j]; } if (j > 1 && i < row - 2) { if (board[i][j] == board[i - 1][j - 1] && board[i - 1][j - 1] == board[i - 2][j - 2] && board[i][j] != ' ') return board[i][j]; } } }
如果大家不懂得话请看下面的图。
讲解:为什么是要这样画图呢?还记得我们说得我们是以下标加或者减的形式来判断三子连线吗?我们实际上能选做基准的元素只有那些空白区域,如果你不相信可以试一试,只要你在画x的区域选基准来实现左斜或者右斜都是不行的。
这也是这篇博客最有价值的地方。