三子棋想必大家都不陌生,这里就不介绍它了。直接进入正题--用c语言来实现三子棋这个游戏,完成玩家可以跟玩家对抗而且能跟电脑对抗。
三子棋一个不大不小的工程,我习惯是建造三个文件。
1.主文件test.c
2.函数文件game.c
3.头文件game.h
为什么要建造三个文件呢,完全可以只用一个文件test.c就可以完成的,何必多此一举呢?
这跟我提过很多次的编程习惯有关,现在我们的编程习惯都是慢慢形成的,可以往有利于我们日后的发展去养成习惯。现阶段可能不会有什么影响,到了后续工作,坏习惯会很影响你的发展。
打个比方,日后工作你写了一份代码卖给一个公司,它每年使用这个代码给你多少钱。不是买下这个代码,你这个时候不可能把原码直接打包发给人家吧。那它明年还会继续给你钱吗,所以这个时候我们就需要把代码的核心不让他可以看见,又不影响使用。就需要把重要的(函数具体实现过程)放在一份文件中,方便一起隐藏,所以这个时候就需要用到三个文件了,头文件单独拎出来是为其他两个文件服务。
准备工作完成之后,先写主函数
可以先暂时不管那个srand语句,这里采用do-while语句是因为可以让玩家自由选择是否继续玩下去。menu()是我的选择菜单。
菜单你可以按照你的习惯来写,这个无伤大雅,不要太难看就ok。
写完菜单,这里我想完成的是玩家跟电脑对抗和玩家和玩家对抗。先写玩家跟电脑对抗,写完这个玩家和玩家对抗就很好写了。
主函数中用了input来接收玩家的选择,用switch case语句实现选择,记得switch每条后面加break,这些工作弄完之后进入到我们游戏函数GameComputer()。
我们游戏的过程是怎么样的,我们来梳理一下,首先我们得要有棋盘,还得打印出来棋盘,然后玩家先开始下棋,下完一步我们给它打印出来,判断一下输赢,然后电脑下棋,打印出来,判断输赢。整体流程差不多就是这样。
然后把这个过程循环一下玩家下棋,电脑下棋,玩家下棋,电脑下棋.......
我们先把棋盘给准备好,这里的棋盘我用了二维数组去准备,这样好表示坐标。途中的ROW,COL是这样的,我用define语句把这两重定义为3了,这里也是为了方便如果以后要改变棋盘大小,不用再函数中一行行的寻找3去改变,直接再define那改就好了。这样可以增加代码的可维护性和灵活性。这就是我说过的好习惯。
define的操作就是在头文件中使用的,其他两个文件只需要#include“game.h”就都可以使用到在头文件game.h进行的操作,就不用每个文件都要申明一下其他的头文件。
函数在使用之前,我们在头文件中定义一下。
数组传过来我也用数组去接收,后面使用小写的row和col,为了方便区别。
简单的循环给数组赋值,没什么好说的,二维数组就用两个循环就好了。这里我赋值的是空格,不是其他是因为为了后续的棋盘的美观,空格我直接取代就好了。如果是空的,我下的棋得挤进去才行,影响后面的排列,棋盘就会变的很乱。
初始化完成之后就该打印出来了 ,这两幅图第一个是在test.c,第二个是在game.h。
打印棋盘就有点意思了,来看代码。
再来看看效果。
这个棋盘美观吧,你也可以做出不一样的棋盘。这里我讲解这个棋盘是如何来的。
我把它分为三组 | | 和---|---|---|为一组,一共三组,而第三组是没有---|---|---的,我们就设置一个if语句,当循环到第三组的时候不打印---|---|---。
先来分析第一组, | | 其实又可以分成三个部分 |和 |和 ,这里跟上面异曲同工都是在最后面的时候少了个东西,那我们就先只打印空格,用if语句判断是否要打|。(这里面给数组取空格的妙用就出来了)
搞定了第一组的第一行了,再看看第二行---|---|---,这个就跟上面没啥两样了,区别就是一个是空格一个是--。
这两行的小循环完成之后再来个大循环套住,把第二组第三组一起实现了,到第三组的时候,同样用if语句判断在第三行的时候不打印---|---|---|。
至此打印棋盘就完成了。
接下来到对抗环节。
这里面打印棋盘已经完成了,我们来看玩家下棋。
不管是玩家下棋还是电脑下棋,我们都要按照规矩来。首先你下棋得下到棋盘中,其次你不能下棋到下过的地方。按照这个逻辑,我们来写代码。
首先第一步就是先判断你下的棋子在棋盘,再判断是否重复下棋。如果下错自然是要重新下的,所以要用循环,当你成功下棋的时候才能跳出循环。这里面有个魔鬼细节:可以看到我的判断语句和if语句中二维数组的行跟列一个加1,一个减1。这是怎么回事?
我们当然知道数组下标是从0开始的,但是玩家知道吗,或者说会注意这个吗,当你一个棋盘摆在这里,没有特殊指明的话,要我玩,我也是认为第一行第一个的坐标是(1,1)。所以我们在设计这个的时候需要注意到这个细节,在设定函数的时候,把棋盘数组中的坐标手动-1,就是真正的坐标了,判断语句就是正常加1不超棋盘。
玩家下棋完,就该电脑下了。判断输赢等会再做,因为电脑还没下的时候做,不好判断是否正确。
电脑下棋我们不搞太复杂了,就简单的随机下就好了。
说到随机数自然是要用rand()函数的,用到rand()函数就不得不提srand()了,上边提过暂时不管就是srand()。
其实你不用这个也ok,但是随机数就不够随机了。这个srand()将随机数与时间戳判定前面加上unsigned int,返回一个无符号整数。
这里跟row和col求模,取到的数就是0到2了,你跟一个正整数求模,取到的数就是它-1,这里就不详细说明了。接着还是老样子判断你要下的棋的位置是没人下过的,由于是电脑,我们就不用写提示了。
电脑写完,就到我们的判断输赢了。
我们先想想赢得条件有哪些,首先一行中去相等,一列中全相等,还有就是两条对角线相等。赢得条件有了,但是我们还得判断如果赢了就游戏结束,平局也游戏结束,还有就是还没下完棋,游戏继续。
这里就是每行进行循环判断是否相等,列也是,两条对角线不用循环,因为它两特殊,就两条而且情况还不一样。这里判断的时候记得末尾加上其中任意一个不等于空格,否则你下一个棋都会结束游戏。
到这里还不算完,我函数是有返回值的,我们要利用这个返回值回到GameComputer函数里进行判断的。这里又有个魔鬼细节:我们玩家赢和电脑赢肯定是要返回不一样的值的,那这样你又得判断是电脑赢了还是玩家赢了,得分两步才能实现。但是我函数中直接返回哪三个字符一样就返回哪个字符。这样不管是玩家还是电脑赢都会返回它们自己的字符‘*’和‘#’了。然后判断平局,如果当你全部空格都被替代了,没有空格的情况还没有人赢就是平局了。所以我们使用循环判断,一旦还有空格游戏都得继续,最后如果这些条件都不满足那自然是平局了,返回‘p’。
讲到这里GameComputer()函数差不多完了。来看一下最终代码。
这里我用了ret去接收Iswin()函数的返回值,去判断函数是否继续。第二幅图才是真正判断这个游戏的结果。这里把判断输赢拆开来判断主要是为了让代码更加清爽一点,不用重复那么多东西。在while语句中只需要判断是否继续就好了,不能继续了你再跳出来看看游戏的结果,不用写两步判断游戏结果。
讲到这里与电脑的对抗就结束了,接下来到玩家和玩家的对抗了。
与玩家对抗就没什么好说的了,跟与电脑对抗大同小异。
稍微改点东西就好了。
三子棋讲到这里就已经结束了,大家如果有哪些细节不懂的欢迎问我。