前言
无论是游戏还是软件的开发,短短几十行代码肯定是无法实现的,就如我们今天马上要学习的三子棋小游戏,都需要几百行代码,才能实现从菜单打印,棋盘初始化,玩家下棋,电脑下棋,判断输赢等的逻辑关系联通,这对于刚刚接触C语言的初学者而言,肯定是编程路上的一道大坎,但是我相信,当你能熟练地书写出三子棋,扫雷这些游戏时,那种成就感一定是无与伦比的,那么从现在开始,让我们一起进入三子棋游戏代码的学习当中吧。
![](https://i-blog.csdnimg.cn/blog_migrate/6741004e81e3290795e5c7e74e29d019.gif)
文件的划分
为了书写和测试方便,我们将文件分为三个部分:
1,test.c------测试游戏逻辑
/游戏的模块/
2,game.c-------函数的实现
3, game.h-------函数的声明
为什么要这样区分呢?我们知道,无论是什么游戏的开发,都少不了测试员,测试,顾名思义,就是为了测试游戏的某些模块,区域是否能正常运行,会不会存在bug等,而且你想测试哪部分,只需将其头文件包含过来就可以了,是不是非常方便。
打印菜单
为了测试方便,我们还是把整个的主逻辑放到test.c里面去,
我们先写一个打印菜单的函数,如图1
![](https://i-blog.csdnimg.cn/blog_migrate/0ef7a3e45138c48484ec5f04bb97980c.png)
图1
这时就轮到玩家进行选择,这里就会出现第一个选择语句分支,玩家的选择情况有3种,分别为“0”,“1”,以及除1和0以外的数字,由于输入的内容都为整型,我们可以考虑用switch语句较为简单,如图2
![](https://i-blog.csdnimg.cn/blog_migrate/7eb69e5c820468c5c115ed6a6222f379.png)
图2
但是玩家肯定不会只玩一次,所以我们不妨加一个循环,实现多次玩耍,如图3
![](https://i-blog.csdnimg.cn/blog_migrate/daea843285325242dd7abc84fdae72a7.png)
图3
进行到这里,读者可以进行一次测试了,检查一下到目前为止有没有出现书写或者逻辑的错误。
初始化棋盘
在我们还没有下棋的时候,棋盘实际已经存在了,既然存在,就需要有东西存放在里面,我们不妨将空格放进里面,这样在下棋的时候只需要将要下的东西把空格替换掉就可以了,为了完成这个步骤,我们定义一个函数InitBoard,这个函数首先需要这个棋盘(即一个二维数组),然后还需要将行和列传递过去,如图4
![](https://i-blog.csdnimg.cn/blog_migrate/4ec33678f26c1654c14bc85e87036a0b.png)
图4
但是这个代码有一个明显的缺陷,那就是行和列都固定死了,如果我们想改变其行和列的长度,我们可以用宏来实现,如图5
![](https://i-blog.csdnimg.cn/blog_migrate/c1362627673927b0dc8b02548b03c423.png)
![](https://i-blog.csdnimg.cn/blog_migrate/683a511e3f0652ebbc70c96fb53c254a.png)
图5
为了方便,我们把定义宏放到game.h的头文件里,然后在test.c里调用头文件即可
接下来我们书写InitBoard函数的内容:(在game.c中书写哦,这是游戏里的函数了)
一个很简单的for循环就可以实现,如图6
![](https://i-blog.csdnimg.cn/blog_migrate/ffc229a896db06d66a6de585d3956228.png)
图6
写完这些函数后,不要忘了把函数的声明放在头文件中哦。
显示棋盘
正常的棋盘都会有横竖分界线,但先前我们初始化棋盘只是打印了一片空格,什么也看不到,所以接下来我们要想办法把棋盘给显示出来,我们定义一个函数DisplayBoard,我们先看我们最终希望打印出来的图形是什么样的,如图7
![](https://i-blog.csdnimg.cn/blog_migrate/de8dfac33182acedd3682da62f8298d5.png)
图7
同样,我们也需要一个for循环来完成打印,经过观察我们发现,我们把一个数据和竖行当成一组 (%c |),假设最右面还有一排竖行,这样一共就有三组,再往下有一排分割线(---|---|---),然后再往下又是重复上面这种情况。如图8
![](https://i-blog.csdnimg.cn/blog_migrate/99315bd731e1001bbbfad3700a2c47e3.png)
图8
其中最后一列分割线可以用一个if语句进行判断,在进行到第三列时不满足条件跳过即可。
因为每个数据(即我们要在键盘上输入要下的东西)只和一道横杠-长度相同,为了美观,我们在每次打印数据的时候这样书写:printf(“ %c ”,board【】【】)(%c两边各再打一个空格),代码如图9
![](https://i-blog.csdnimg.cn/blog_migrate/27cc517ee33701211ae1800d08fe3b23.png)
图9
写完函数后同样把声明放到头文件中
打印完棋盘以后,就要开始下棋了,我们很容易想到每轮都是玩家下棋,然后电脑下棋,然后进行输赢的判断,这样一个流程为一次循环,我们不妨将这个流程放入while循环中。下面开始详细讲解各步骤的函数如何实现。
玩家下棋
我们定义一个函数PlayerMove,同样将棋盘,行,列传递过来,先给出提示“玩家下棋”,“请输入下棋的坐标”,我们玩家就会根据提示输入一个值(行 列),但是注意,普通玩家思想中并不会认为第一行下标是0,第一列下标是0,而是根据常识就认为是第几行第几列,所以我们程序员在书写代码是也要考虑到这一点,例如把board【x】【y】改为board【x-1】【y-1】(x,y是玩家输入的坐标)
这时也会出现三种情况:
1,正确输入了一个坐标
2,输入的坐标已经被占用
3,输入的坐标不合理(超出了限制范围)
所以我们首先要判断玩家输入的坐标处是不是空格,如果是,则用“#”(假设玩家输入#)替换,如果不是且在限制范围内,则打印“坐标被占用,请重新输入“,而如果超出了这个限制范围,则打印”坐标非法,请重新输入“,除了第一种情况,其余两种情况都是需要重新输入的,所以我们考虑将其放入一个循环当中,只有当满足了第一种情况才会跳出循环(break语句在循环当中的应用),代码如图10。
![](https://i-blog.csdnimg.cn/blog_migrate/ab46896aaa08e04e16d227a4dccb41bb.png)
图10
电脑下棋
我们根据玩家输入的函数对应写一个电脑输入的函数ComputerMove电脑下棋除了要保证随机性外就要比玩家下棋简单许多,只需要对输入的坐标判断是否合理即可,合理就跳出循环,不合理就一直循环到合理为止,这里由于要随机性这个性质,我们能想到的就是不断变化的时间,这里就要用到时间戳这个概念,需要引用头文件 #include <stdlib.h>,并初始化如图11
![](https://i-blog.csdnimg.cn/blog_migrate/f183331d65465b75237b97118a24d878.png)
图11
目前为止,我们只需要照猫画虎即可,知道它会产生一个1到几十万的随机数即可,然而我们要给坐标初始化用的x,y并不需要这么大,所以我们考虑用mod(即求余数),格式如图12
![](https://i-blog.csdnimg.cn/blog_migrate/05386c0d25ab63c9576872cffbfc0799.png)
图12
这样一来,我们得到的x,y就不可能超过row,col,进而完成后续步骤,整个函数代码如图13
![](https://i-blog.csdnimg.cn/blog_migrate/07c6b260397a9cee160510dac4cd07cf.png)
图13
同样把函数的声明放到game.h中去
接下来就是所有环节中最关键,也是最有难度的地方:判断输赢。完成了这个环节,意味着整个游戏就初步完成了,相信你已经走到这一步了,接下来的困难也难不倒你,加油!
![](https://i-blog.csdnimg.cn/blog_migrate/e56c8438d0c7627027716782309b8d0b.png)
判断输赢
首先我们还是先判断有几种情况
1,玩家赢
2,电脑赢
3,平局
4,继续
我们不妨这样思考,我们设计一个函数,让他在分别满足这四种情况下返回不同的值,然后我们在main函数中进行判断,如果为真,则打印对应的结果。所以我们不妨设玩家赢就用‘*’,电脑赢就用‘#’,平局就用‘Q',继续则用‘C’。有些读者可能会问了,为什么玩家和电脑返回值和输入值一样,不能是其他的东西吗,这是为了方便书写,有现成的东西为什么不用现成的呢,我输入在某个坐标上的东西我直接就返回那个坐标的内容不就可以了吗,清晰,一目了然。
接下来我们就每种情况再仔细分析
我们还是定义一个函数IsWin
首先是玩家(电脑)赢的情况:三子棋获胜规则是三颗子连城线(横竖斜都可以),这里不过多赘述,
我们先来看简单的:
三颗子在同一行:我们只需对board【i】【0】,board【i】【1】,board【i】【2】进行相等判断即可(注意不能连等 要灵活运用逻辑与&&),若为真则返回board【i】【0】(这里直接用各自的棋子做返回值的优势就体现出来了)
三颗子在同一列:和行的判断方法类似,只需对board【0】【i】,board【1】【i】,board【2】【i】进行相等判断即可,同样若为真则返回board【0】【i】。
三颗子在一条斜线:这里就又有两种情况,如图14(情况一定要考虑完整)
![](https://i-blog.csdnimg.cn/blog_migrate/98cbd198a6c6701b40fa07e0dbf3e9cd.png)
图14
因为只有这两种情况,我们可以直接对这几个位置进行相等判断不用再写循环。
特别注意:三个’ ‘也算相等,如果我们不排除掉三个’ ‘的情况就很容易出现bug!
如果上述判断全部完成后仍然没有跳出循环,那么接着判断平局的情况,我们在设计一个函数IsFull ,这里我们要思考一下:怎么写一个函数来实现平局的判断呢?好像从下的子上不好判断,因为平局情况有很多种而且没有规律,所以我们换一个方向,从剩余的空格上判断,当棋盘上没有空格时就平局了。函数如图15
![](https://i-blog.csdnimg.cn/blog_migrate/b5cb7233bed6857090a32780ea0c6ea7.png)
图15
函数返回1或0,再用一个if语句,如果返回为0,则结果为假,跳出语句,如果为1,则为真,进入语句,逻辑实现如图16
![](https://i-blog.csdnimg.cn/blog_migrate/ff5104c5aca57bbfd39329462b84af02.png)
图16
当然这里的判断以及书写方法肯定不止这一种,只要能正常运行就好。
最后,如果上述所有情况都不符合,那么游戏就会继续,由于每次下一个回合都会进行判断,所以我们把代码放到一个循环中去。如图17
![](https://i-blog.csdnimg.cn/blog_migrate/eae7190217029cb011bc9752e40a5d5c.png)
图17
然后我们只需要在main函数中对返回值进行一个判断,再根据判断结果打印不同的内容即可。如图18
![](https://i-blog.csdnimg.cn/blog_migrate/ca4f56541a0d72aad2febf3edbdb5cf0.png)
注意这里继续的判断要写在游戏循环内,只有当游戏不继续了才跳出循环进行输赢判断。
还需要注意的是,每次玩家或电脑下棋后要将当前的棋盘打印一个出来,不然什么也看不到无法进行下一步的判断哦。
完整代码链接:三子棋 · f1de440 · 张思诚/明解c语言练习 - Gitee.com
到这里三子棋代码的探讨学习和书写就基本完成了,如有出入,欢迎各位大佬指正