一、采用多文件,分模块来实现
我们在之前讲到过,在我们以后完成一个项目时候,我们使用的是多文件,分模块的进行完成。那么今天我们也采用这个方法
我们创立三个文件:
test.c //测试的逻辑
game.h
game.c //游戏的实现
二、建立游戏基本框架
1.为什么是使用do while语句?
我们想要完成一个三子棋游戏,那么我们使用一个简单的do while 逻辑
在这段代码中,我们为了使我们的代码逻辑清晰,我们直接使用一个test函数,test里面的函数执行我们游戏的逻辑,而在test函数里面,我们注意到,我们使用了一个do while 循环,这是考虑到,我们完成一个游戏肯定不可能只玩了一把就自动退出去了,我们希望玩完一把后可以由我们自己来选择是否进行下一把或者结束游戏。所以我们必须使用一个循环,但是我
们又必需得先玩一把在考虑是否玩下去,所以这就是使用do while语句的原因了。
2.打印出菜单
是一个游戏肯定要有他的界面,所以我们do while进入以后直接开始打印菜单。不妨我们就将菜单设为meau函数吧,如下图所示,这块不难理解
3.实现通过选择菜单,跳转至相应界面
如上图所示,我们通过一个input,来记录我们输入的值,并通过switch语句来跳转至相应的功能。需要解释的就是while里面为什么是input就可以了?其实这句话是一个简写,我们的全写应该是input!=0,当他不等于0的时候,我们可以继续循环,如果等于0,循环结束。而这个的真值表恰好与input本身的真值表相同,因为只有0为假,非零都为真。所以这里使用input就是很合理的。
4.测试前面的逻辑
我们写代码一定要写一点测一点。而不是一股脑写到最后,代码几百个bug无从下手。
下面是截止至目前我们目前所写的代码,这些代码全部是属于test.c这个文件里面的。
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> void meau() { printf("********************************\n"); printf("******** 1.play *******\n"); printf("******** 0.exit *******\n"); printf("********************************\n"); } void test() { int input = 0; do { meau(); printf("请输入>:\n"); scanf("%d", &input); switch (input) { case 1: printf("进入三子棋游戏\n"); break; case 0: printf("退出游戏\n"); break; default: printf("选择错误,请重新选择\n"); break; } } while (input); } int main() { test(); return 0; }
运行结果为
可见我们所预想的逻辑是全部满足了。这样我们才可以放心的往下继续写下去。
三、具体游戏的逻辑实现
1.在我们的case 1下加上我们的游戏函数且定义和声明好我们的函数
我们想要实现的是按一个1,就能开始玩游戏,而不是就单单弹出一个进入三子棋游戏然后就没有下文了。我们在这块封装一共函数game,这个函数的返回类型是void,不需要传参,如下所示
2.思考游戏的棋盘是什么样子的,该如何绘制?
相信大家小时候玩过三子棋或者说井字棋小游戏。这个游戏的棋盘是一个#字,如下图所示
当然这是我们平时所画的棋盘。那么在我们c语言中我们如何制作一共形状和功能都差不多的棋盘呢。我们是这样思考的,如下图所示,三个-和一些|刚好可以组成一些正方形的格子。
3.数据如何存储,游戏该如何去玩?
根据上面的棋盘,我们其实不难得出,这个数据需要一个3*3的数组来存储。这些数据的类型我们可以使用一个char来实现,玩家打印*字符,电脑打印#字符,而我们最开始其实那些似乎没有棋子的地方,我们可以看成一个空格字符。所以最开始我们的数组都是空格字符
在玩游戏的时候,我们可以选择输入一个坐标,这是玩家下棋,然后电脑下棋。这样循环下去。直到有人赢了或者平局。
4.设计部分游戏功能函数
根据上面的分析可以得到,我们手下需要定义一个char类型的数组,这个数组的类型是3*3,并且我们需要对其初始化,这个初始化的我们可以封装成一个函数,init_board,这个函数不难想象要三个参数,分别是数组名,行和列。然后初始化完后我们可以写一个打印棋盘的函数。如下图所示
不过值得注意的是,又很多人在传这个数组的时候,传了一个board[3][3]过去,注意这是一个经典的错误标准的零分,这个传进去的是第三行第三列的元素,而且这个元素甚至还越界了。这就错上加错了。我们应该传一个数组名过去,数组名也就是首元素的地址。如下图所示就是错误的
当然我们这样设计这些函数,我们其实就把代码写死了。 未来我们想要打印一个5行5列的数组就修改比较麻烦。所以我们可以这样做,使用一个define,如下图所示,我们在game.h中将ROW,和COL都设置为3。然后我们在test.c中就可以引入game.h这个头文件。将game这个函数里面的3都可以改为行和列了。这样未来我们在想进行一些修改的时候就很简单了。
当然上图中有一处我们刚刚提到过的经典的错误标准的零分,不知道你有没有发现呢?答案就是我们此处数组传参仍然是错的,我们只需要传数组名,这下相信大家都记住了吧。
5.初始化棋盘模块的实现(init_board)
我们在game.h中声明函数,在game.c中实现
game.h如下所示
代码为
void init_board(char board[3][3], int row, int col);
game.c如下所示,注意不要忘记game.h这个头文件,因为我们声明是放在了game.h中
代码实现为
void init_board(char board[3][3], int row, int col) { int i = 0; for (i = 0; i < row; i++) { int j = 0; for (j = 0; j < col; j++) { board[i][j] = ' '; } } }
6.打印棋盘模块的实现(print_board)
(1)一些需要注意的思考
我们初始化完后,我们就想要打印出来检验一下我们初始化后的结果,所以我们现在来实现打印棋盘
game.h如下所示
代码为
void print_board(char board[3][3], int row, int col);
game.c如下所示(注意,在此处,可能有小伙伴们发现头文件里面多了个stdio.h的头文件,这样的话,game.c中虽然使用了printf但是就不需要重复添加这个头文件了,因为我们直接引用了game.h这个头文件。而且我们还可以将test.c这个头文件给删除掉了)
和上文所说的一样,写一点测一点。我们运行测试一下,我们发现,没有棋盘。所以说,我们这个打印棋盘的代码是不完整的,我们需要完善一下。
我们是这样思考这个打印的函数的。我们将下图看成三组逻辑,打印一行空格行和一个分割行是一组逻辑,最后一行也是一个分割行,只不过这个不用打印。
(2)尝试实现功能
那么代码实现如下所示