当前位置:网站首页>用C语言来实现五子棋小游戏
用C语言来实现五子棋小游戏
2022-08-03 05:11:00 【一个隐藏的大佬】
目录
一、五子棋的准备工作
我们在写一个项目的时候,首先第一步就是应该规划一个项目应该有哪些文件,然而五子棋的代码逻辑比较简单,这里我就分了三个文件来写:
- game.h(这个文件用来存储头文件和函数的声明),
- game.c(这个文件用来存储具体的实现代码),
- test.c(这个文件用来测试我们的代码)
第二步就是规划一个项目应该有哪些功能或者说有哪些机制,比如说五子棋,他可能就有人机对战,玩家下一步,电脑下一步,判断棋盘有没有一片区域是连成一块的,这些都是我们要考虑到的,并且把他们一个个封装成函数,整个逻辑就可以运行起来,下面是具体要实现的一些功能的函数并且还有相应的头文件,我都放到game.h里面了
#pragma once #include <stdio.h> #include <stdlib.h> #include <time.h> //棋盘的长度和宽度 #define ROW 5 #define COL 5 //初始化 void board_init(char arr[ROW][COL], int row, int col); //打印 void board_display(char arr[ROW][COL], int row, int col); //玩家下棋 void player_move(char arr[ROW][COL], int row, int col); //电脑下棋 computer_move(char arr[ROW][COL], int row, int col); //判断是否赢了 char is_win(char arr[ROW][COL], int row, int col);
二、五子棋的具体实现
1.棋盘
下五子棋,第一步就是要有一个棋盘,我们常见可能有3*3的棋盘或5*5的棋盘,用来下井字棋最合适,我本来以为我写的棋盘可能会和下面这些图一样。
但是没想到自己写的棋盘长这样,其实也还能看。
下面就是具体实现过程:
我们会发现棋盘的每一个格子是由上下两部分组成的,首先是上面部分是由两个空格加上中间的数据区域加上右边的竖杠(|),然后是下面部分是由三个减号(-)加上右边的竖杠(|),但是我们会发现我们数据区域好像一开始也是空格,那是因为在实现打印棋盘的函数之前,我先给所有区域都初始化为空格,为什么要初始化空格呢?首先初始化空格可以帮助我们占一个位置,如果你不初始化,很可能就是随机值,到时候打印出来就会很奇怪,那为什么一定要初始化空格呢?首先空格可以帮助我们占一个位置,就是把右边空格顶过去,如果你初始化为0,就不会帮你占位置,右边的空格就和左边的空格挨在一起,就会很难看,下面是初始化的代码:
//初始化 void board_init(char arr[ROW][COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { arr[i][j] = ' '; } } }
这样我们很快就分析出,棋盘第一行是数据区域,第二行是分隔线,然后以此类推,每一行都是这样的,那我们就想先用for循环遍历第一行把数据的那一部分先打印出来,然后用第二个for循环把分割线也打印出来,但是这样就会有一个问题,我们会发现图中右边和下边多出了一行分割线。
所以我们还要加一个限制条件,当j<col-1时,我就不再打印竖杠(|),当i<row-1时,我就不再打印减号(-),具体打印棋盘实现代码如下:
//打印 void board_display(char arr[ROW][COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { printf(" %c ", arr[i][j]); if (j < col - 1) printf("|"); } printf("\n"); for (j = 0; j < col; j++) { if (i < row - 1) { printf("---"); if (j < col - 1) printf("|"); } } if (i < row - 1) printf("\n"); } }
2.人机对战
棋盘已经有了,下一步就是人机对战,大致逻辑就是玩家走一步,电脑走一步,然后每次判断棋盘有没有已经连成一片的区域,如果有,则返回连成那一片区域里的棋子,返回*(星号)玩家胜利,返回#(井号)电脑胜利,返回q代表游戏平局,就是所有棋盘都下满了,结果还没有连成一块,就平局,如果上面三条都不符合,则返回c(continue)表示游戏继续,这就是五子棋里面的所有逻辑。
2.1 玩家下棋
玩家要怎么下棋呢?我们这个棋盘有可能是3*3或5*5,最好的方式就是利用坐标来下棋,每次让玩家输入坐标,输入一个合理的坐标,我们就在棋盘相应的位置放置一颗*(星号),当然我们必须去检查玩家输入的坐标,玩家可能好奇心上来,比如输入一个(10 10),很明显超出了棋盘范围,这时就要友好的提示一下,说玩家“输入坐标不合法,请重新输入”,很明显这里应该设置一个循环,直到玩家输入了正确的坐标,我才跳出这个循环,那我们分析完之后,这里代码就好写了,首先定义两个变量x,y,然后让玩家输入,但是玩家不知道我们这个棋盘是从0开始的,可能一开始以为是从1开始的,其实问题也不大,我们只需要一开始设置条件x>=1且x<=row且y>=1且y<=col,玩家输入的范围就只能在1到棋盘最大界限之间,然后后面给玩家输入的数字-1就行,比如玩家输入了两个数(1 1),我们程序自动为这两个数-1,并且在相应位置上放上*(星号),这样玩家就能看到具体效果了。
下面是具体实现代码:
//玩家下棋 void player_move(char arr[ROW][COL], int row, int col) { int x = 0; int y = 0; printf("玩家下棋\n"); printf("请输入坐标:>"); scanf("%d%d", &x, &y); while (1) { if (x >= 1 && x <= row && y >= 1 && y <= col) { if (arr[x - 1][y - 1] == ' ') { arr[x - 1][y - 1] = '*'; break; } else { printf("已有旗子,请重新输入\n"); } } else { printf("输入坐标不合法,请重新输入\n"); } printf("玩家下棋\n"); printf("请输入坐标:>"); scanf("%d%d", &x, &y); } }
2.2 电脑下棋
电脑怎么下棋呢?其实就更简单了,比玩家下的逻辑还简单一点,电脑下棋就随机下,哪里有位置,就往哪里下,首先定义x,y两个变量,并给这两个变量赋值成随机值,这个随机值范围是0到棋盘最大界限之间,这里就可以调用rand()函数,但是rand()函数的随机值的范围是0-32767,我们肯定要控制一下,看了文档之后,才知道这个函数是可以控制的,其实只需要用这个函数随机出来的值去%(取模)棋盘的最大界限值,假设棋盘的最大界限值是3,然后随机值对3取模,那得出来的结果一定是0-3之间的数,但是不包括3,这样就拿到我们想要的结果了,但是好像还是有问题,每次随机的结果竟让都是一样的。
这不是同一张图,而是我运行了两次程序,却发现每次结果都是一样的,然后我又去看了文档,之后才明白,在用这个函数之前,还要再调用一个srand()函数,才能使rand()函数,每次出来的值都是不一样的,但是srand()函数,也要传一个随机值,那这不就完了,我现在想要的就是随机值,结果你告诉我,还得传个随机值给你,其实文档解释的很清楚,只要是一直动的数字就行了,什么东西会一直动呢?那就是时间,具体来说是时间戳,时间戳就是指(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。那问题现在就简单多了,得到时间戳传给你不就完了,时间戳也有个函数叫做time(),这个函数就可以返回一个时间戳,没有参数就在括号里面写NULL,但是这个函数返回值类型是time_t,好家伙我都没见过这个类型,转到定义才发现是typedef重新定义的一个名字,但是typedef写的又是另一个类型__time64_t,好家伙更没见过了,再次转到定义,才发现就是一个int64位类型,但是srand函数的参数要一个unsigned int的类型,这就更简单了,老师讲过强制类型转换,我们只需要转换一下,这样我们的问题就都解决了。
这样每次结果都不一样,这才是我想要的,剩下就简单了,判断棋盘的格子里面是不是空格,如果是空格,则就可以落子了,不是空格,就说明这个格子被占用了,那我们就重新获取随机数,直到可以落子,然而我们发现这又是一个循环,内容都分析差不多了,代码就很容易实现了,代码如下:
//电脑下棋 computer_move(char arr[ROW][COL], int row, int col) { printf("电脑下棋\n"); while (1) { int x = rand() % row; int y = rand() % col; if (arr[x][y] == ' ') { arr[x][y] = '#'; break; } } }
2.3 判断输赢
玩家下棋,电脑下棋,我们都说完了,只剩下判断输赢了,如果返回*(星号),则玩家赢,如果返回#(井号),则电脑赢,如果返回q,则平局,以上情况都不满足,则返回c(continue),游戏继续。判断输赢其实很简单,五子棋,棋盘为5*5,看的就是谁能连成5个棋子,那他的情况也就那么几种,先看每一行有没有连成的棋子,再看每一列有没有连成的棋子,最后再看两条对角线,如果都没有连成5个棋子,判断棋盘是否已满,如果满了,则平局,没满则继续下棋,棋盘5*5就这么些情况,如果棋盘再大一点的话,判断东西可能就复杂了,我们先实现简单的,后面有能力再去实现复杂的。然后具体操作逻辑就是对每一行或每一列遍历,首先我会先把第一行或第一列的第一个字符先保存下来,这个字符可能是#(井号)也可能是*(星号),还可能是空格,然后我再一个一个的跟后面的字符比较,如果全部相同,则返回当前的字符,当然在比较之前一定要加条件,首先这个字符一定不能是空格,不是空格我才接着和后面的字符比较,全部相同则返回当前字符,比较之中如果有一个不相同,赶紧break,没有必要进行下一次比较了。然后后面的逻辑都是这样处理的,包括两条对角线也是这样处理的。还有个平局的逻辑没讲到,其实也很简单,创建一个函数is_full(),这个函数专门用来看棋盘满没满,如果上面四种情况都不满足,就来看棋盘有没有满,其实棋盘有没有满也很好判断,就是遍历一遍棋盘看还有没有多余的空格,如果一个空格都没有了,说明平局了,这时候我们就会返回q,游戏就平局了。如果以上五种情况都没有发生,说明游戏还没有结束,则返回c(continue),游戏继续,一直到两方有一方赢了,或者平局,游戏才会结束。整体逻辑就是这样,分析完了,就开始写代码了,代码如下:
//判断是否满了 static int is_full(char arr[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 (arr[i][j] == ' ') { return 1; } } } return 0; } //判断是否赢了 char is_win(char arr[ROW][COL], int row, int col) { char tmp = 0; int i = 0; int j = 0; //判断是否有一行连成一片 for (i = 0; i < row; i++) { tmp = arr[i][0]; for (j = 1; j < col; j++) { if (tmp == arr[i][j] && tmp != ' ') { if (j == col - 1) { return tmp; } } else { break; } } } //判断是否有一列连成一片 for (j = 0; j < col; j++) { tmp = arr[0][j]; for (i = 1; i < row; i++) { if (tmp == arr[i][j] && tmp != ' ') { if (i == row - 1) { return tmp; } } else { break; } } } //判断左对角线是否有连成一片 tmp = arr[0][0]; for (i = 1; i < row; i++) { if (tmp == arr[i][i] && tmp != ' ') { if (i == row - 1) { return tmp; } } else { break; } } //判断右对角线是否有连成一片 tmp = arr[0][col - 1]; for (i = 0, j = col - 1; i < row; i++, j--) { if (tmp == arr[i][j] && tmp != ' ') { if (i == row - 1) { return tmp; } } else { break; } } //判断是否满了 int ret = is_full(arr, row, col); if (ret == 1) return 'c'; else { return 'q'; } }
全部逻辑已经写完了,剩下就是测试代码了
游戏测试完成之后,逻辑和自己想到的差不多,基本功能也实现了,基本没有什么太大的问题,可能以后有时间会再优化一下代码,或者写一些更好玩的游戏。
三、总结
其实感觉写代码挺爽的,特别是用自己学会的东西,来实现一个比较简单的小程序,这个游戏可能有200多行代码吧!我觉得挺开心的,因为可以把老师在课堂上教的东西,学以致用,这更能证明你平时有没有努力的练习,就和我讲的一样,想成功一定要多努力,努力不一定能成功,但不努力一定不会成功。上面所有代码我都会放在我的码云上,如果有兴趣可以去玩一下哟!希望大家能多多支持哟!
链接:https://gitee.com/smnhaaa/c-project.git
边栏推荐
猜你喜欢
PotPlayer实现上班摸鱼电视自由
HarmonyOS应用开发第一次培训
flask 面试题 问题
Install IIS services (Internet Information Services (Internet Information Services, abbreviated IIS, Internet Information Services)
3. 无重复字符的最长子串
第四次培训
Coordinate knowledge in digital twin campus scenarios
[Harmony OS] [ARK UI] ETS context basic operations
第三次HarmonyOS培训
Build your own web page on the Raspberry Pi (2)
随机推荐
1094 谷歌的招聘 (20 分)
【打印菱形】
在树莓派上搭建属于自己的网页(2)
Install PostgreSQL on Windows
Presto installation and deployment tutorial
[Harmony OS] [ARK UI] ETS context basic operations
Common lipophilic cell membrane dyes DiO, Dil, DiR, Did spectrograms and experimental procedures
typescript39-class类的可见修饰符
Install IIS services (Internet Information Services (Internet Information Services, abbreviated IIS, Internet Information Services)
Exception(异常) 和 Error(错误)区别解析
详解Nurbs曲线
Benchmark 第一篇 了解Benchmark
mysql 存储过程 动态参数 查询执行结果
【Biotin Azide|cas:908007-17-0】Price_Manufacturer
【Harmony OS】【ARK UI】ets use startAbility or startAbilityForResult to invoke Ability
Newifi路由器第三方固件玩机教程,这个路由比你想的更强大以及智能_Newifi y1刷机_smzdm
高可用 两地三中心
js实现一个 bind 函数
数据分析 第一篇
Redis6学习笔记