目录
写在前面
实现三子棋小游戏需要具备哪些知识呢?
游戏设计
1.main函数设计
2.menu函数的设计
3.game函数的设计
3.1InitBoard函数
3.2DisplayBoard函数
3.3player_move函数
3.4computer_move函数
3.4.1随机坐标的生成
3.5is_win函数
3.5.1is_is_full
完整源码
前言
大家是否曾经和同桌玩过三子棋
小游戏呢?
也许大家都玩过这个游戏,但是对它的称呼却并不相同,那首先带大家回忆一下玩法和规则
看到这些九宫格,有没有感觉到死去的记忆突然攻击你呢?
今天我们就试着用c语言来实现这个小游戏。
正文
实现三子棋小游戏需要具备哪些知识呢?
1.分支与循环语句
2.函数
3.数组
没错,当我们能简单的使用上述知识,就可以来试着写一个三子棋小游戏了!
如果觉得上述知识没有简单掌握的话,请参照我之前的博客,里面有庖丁解牛式讲解。
游戏设计
游戏设计我们采用多文件的形式:
1.test.c //只包含main()、menu()、game()
2.game.c //用于定义游戏需要的各个函数
3.game.h //用于包含各种头文件和函数声明
1.main函数设计
首先,我们需要向玩家展示一份游戏菜单,如下图:
玩家此时可以选择
1.开始游戏
0.退出游戏
而且每结束一局游戏游戏后,都会再次打印菜单,所以此处需要一个循环。
那么三种循环语句我们该如何选择呢?
为了确保程序一运行之后,就能打印菜单,我们选择do......while循环
do { menu();//为了代码的整洁,我们设计一个单独的menu函数来打印菜单 }while(...)
之后我们还需要定义一个变量input来接收玩家的选择
int input = 0; printf("请选择:>"); scanf("%d", &input);
那么还有个问题,我们do......while该何时停止呢?
此时有个巧妙的设计,当玩家输入“0”来选择退出游戏是,循环就应该停止,那不妨就用input来控制循环,如下:
int main() { int input = 0; do { menu(); printf("请选择:>"); scanf("%d", &input); ......... } while (input); return 0; }
接下来我们根据玩家输入的选择来实现菜单中的功能
switch (input) { case 1: game();//进入游戏,我们用另外一个函数game()单独实现 break; case 0: printf("退出游戏\n"); break; default: printf("选择错误,请重新输入\n");//对于选项以外的输入,给出提示 break; }
但这里我们main函数的主体已经实现完毕。
来看看完整代码:
int main() { srand((unsigned int)time(NULL)); int input = 0; do { menu(); printf("请选择:>"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("退出游戏\n"); break; default: printf("选择错误,请重新输入\n"); break; } } while (input); return 0; }
2.menu函数的设计
menu()函数,我们只需要用它将菜单的内容打印到屏幕上即可,所以它的实现很简单:
void menu() { printf("******三子棋*******\n"); printf("*******************\n"); printf("******1.start******\n"); printf("******0.exit ******\n"); printf("*******************\n"); printf("*******************\n"); }
3.game函数的设计
如图,玩家和电脑每走一步,都会将棋盘打印出来,因此,我们需要用一个3*3的二维数组来存放棋子。
char board[ROW][COL];//ROW为行数,COL为列数
由于在后面的设计中,我们会多次用到行数和列数,我们干脆在game.h中用define定义ROW与COL。今后如果想设计四子棋,五子棋,十子棋等等,我们只需改变ROW和COL即可。
#define ROW 3 #define COL 3
仔细观察棋盘,当某个位置没有落子时,它是空白的。所以我们首先得初始化一下二维数组board,使其内容都为空格。这里我们用一个函数InitBoard来实现数组的初始化。
3.1InitBoard函数
数组的初始化也很简单,通过for循环的嵌套遍历整个数组即可实现
//初始化棋盘 void InitBoard(char board[ROW][COL], const int row, const int col) { int i = 0; for (i = 0; i < row; i++) { int j = 0; for (j = 0; j < col; j++) { board[i][j] = ' ' ; } } }
InitBoard是设计游戏内容的函数,所以我们将它在game.h中声明,并在game.c中定义。
接着,玩家游戏开始前,我们最好先将棋盘打印一下,方便玩家选择坐标进行落子,
所以我们需要设计一个打印棋盘的函数。
3.2DisplayBoard函数
计算机里没有提供棋盘的图案,所以这需要我们手动来设计。
为了简便我们用' | '和' - '来“拼凑”一个棋盘大概长这个样子:
那么我们简单分析一下这个棋盘的组成
注意红色圈圈里的其实是空格,而最中间的红色圈圈就是将来要落子的位置。
因此棋盘就是数组元素与' | '和' - '组成的,其中2、4为分割线。那我们直接打印就好:
void DisplayBoard(char board[ROW][COL], const int row, const int col) { for (int i = 0; i < row; i++) { printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]); if(i<row-1) printf("---|---|---\n"); } }
这样我们就可以成功的打印3*3的棋盘了。但有一点小小的瑕疵就是,如果将来我们修改ROW和
COL的值,想要打印10*10的棋盘,显然此刻的 DisplayBoard 不能完成。我们可以试着改造一个
动态的、大小可变的棋盘打印函数,如下:
void DisplayBoard(char board[ROW][COL], const int row, const int col) { /*for (int i = 0; i < row; i++) { printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]); if(i<row-1) printf("---|---|---\n"); }*/ int i = 0; for (i = 0; i < row; i++) { //打印棋子和' | ' int j = 0; for (j = 0; j < col; j++) { printf(" %c ", board[i][j]); if (j < col - 1)//防止最后一列再次打印' | ' printf("|"); } printf("\n"); //打印分割线 if (i < row - 1) { for (j = 0; j < col; j++) { printf("---"); if (j < col - 1)//防止最后一行打印分割线 printf("|"); } } printf("\n"); } }
DisplayBoard是设计游戏内容的函数,所以我们将它在game.h中声明,并在game.c中定义。
到目前为止,我们的game()函数已经完善到这步:
void game() { char board[ROW][COL]; InitBoard(board, ROW, COL);//初始化棋盘 DisplayBoard(board, ROW, COL);//打印棋盘 //下棋 ...... }
接下来就是最重要的环节,下棋的部分了。
我们默认让玩家先手,这是我们设计一个函数来实现玩家下棋。
3.3player_move函数
玩家下棋时,首先我们给出提示玩家该下棋了:
下棋其实本质就是,将棋子的坐标保存到数组中,在通过DisplayBoard打印。
当然,在落子之前,我们必须得判断棋子的坐标是否合理。比如:
1.该坐标是不是已经被其他棋子占用
2.该坐标是否超出了棋盘的范围
当玩家不小心下到了不合理的位置,我们要给出提示,并让他重新选择下棋的坐标,直到下在正确的位置,所以,此处应该运用到循环。
注意:大部分玩家并非程序员,所以他们可能并不知道数组的下标是从0开始的。所以我们需要将
玩家输入的坐标减一之后再使用。
void player_move(char board[ROW][COL], const int row, const int col) { int x = 0; int y = 0; printf("玩家下棋:>\n"); while (1) { printf("请输入坐标:>"); scanf("%d %d", &x,&y); //判断下棋坐标是否超出棋盘范围 //如果合理,break结束循环 if (x >= 1 && x <= row && y >= 1 && y <= col) { //判断该坐标是否已有棋子 if (board[x - 1][y - 1] == ' ') { board[x - 1][y - 1] = '*'; break; } else printf("该坐标已被占用,请重新输入\n"); } else { printf("超出棋盘范围,请重新输入\n"); } } }
3.4computer_move函数
玩家落一子后轮到电脑下棋了,电脑是不会自主思考的,这里我们就不要求AI的方式下棋了。
我们让电脑在一个随机的坐标下棋即可。
电脑下棋时,我们照样得判断它随机生成的坐标是否合法。但是却不用提醒它“你下错了”,我们就
让它不停的尝试,直到找到正确的位置。此处依旧需要一个while循环。
void computer_move(char board[ROW][COL], const int row, const int col) { printf("电脑下棋:>\n"); while (1) { int x = ...//该怎么赋值? int y = ... if (board[x][y] == ' ') { board[x][y] = '#'; break; } } }
但这里我们怎么让电脑生成随机坐标的?
3.4.1随机坐标的生成
这里我们将认识一个函数 rand ,它的作用就是生成一个随机数。
我们这里不详细介绍它的原理,只是简单介绍一下使用方法:
使用rand函数之前,我们得包含stdlib.h的头文件
最重要的一步,我们要在main()函数内部加上这句代码:
1. srand((unsigned int)time(NULL)); 2. 3.srand((unsigned int)time(NULL)); //别忘记包含time()函数所在的头文件 time.h
这样做之后,我们就可以使用rand函数了。
既然是随机数,我们就通过对随机数模3的操作,让它的值落在[0,3)的区间内。
int x = rand() % row; int y = rand() % col;
然后仿照玩家下棋的逻辑,我们就顺利完成电脑下棋的函数。
void computer_move(char board[ROW][COL], const int row, const int col) { printf("电脑下棋:>\n"); while (1) { int x = rand() % row; int y = rand() % col; if (board[x][y] == ' ') { board[x][y] = '#'; break; } } }
至此,玩家和电脑都会下棋了,我们只需要通过while循环让他俩一人一子即可,每落一子后将棋盘再展示一遍。
void game() { char ret = 0; char board[ROW][COL]; InitBoard(board, ROW, COL); DisplayBoard(board, ROW, COL); //下棋 while (1) { player_move(board, ROW, COL);//玩家下棋 DisplayBoard(board, ROW, COL); computer_move(board, ROW, COL);//电脑下棋 DisplayBoard(board, ROW, COL); } }
但到这里还没有结束,因为我们会发现一会儿程序就会进入死循环,因为棋盘下满后,电脑一直在
生成随机数妄图找到正确的位置,却找不到。
按照正常的逻辑,当一方获胜后,游戏将不在进行,并宣布某一方获胜。
当棋盘下满后,无任何一方获胜,就宣布平局。
那我们该怎么实现这样的逻辑呢?