数组实例之三子棋的实现(C语言)
目录
前言
一、三子棋实现的逻辑
二、三子棋的实现
2.1文件的创建添加
2.2 test文件基本逻辑
2.2.1菜单的实现
2.2.2菜单的选择
2.2.3game函数棋盘的实现
2.3game.c文件的编写
2.3.1初始化函数的模块
2.3.2棋盘打印的模块
2.3.3实现棋盘界面的打印
2.3.4实现玩家下棋模块
2.3.5实现电脑下棋模块
2.3.6实现判断输赢(三个相连为例)
判断谁赢逻辑
is_win函数实现
总结
前言
我们理解并知道了数组等一些知识的使用后就可以适当进行自己的一些创作,本篇文章基于数组学习之后的实例练习,教你如何写一个三子棋的简单小游戏。
环境依然是基于VS2022的集成开发环境,项目创建可以参考之前的文章—创建空项目。
一、三子棋实现的逻辑
我们通过test.c文件来编写逻辑的实现,玩家怎么下棋,电脑怎么下棋,都在这里面写,之后game.h和game.c来编写游戏的实现,通过测试的逻辑来调用游戏的实现。
二、三子棋的实现
2.1文件的创建添加
我们要先分别创建这三个文件,方便后期的代码的编写。这里就不给出怎么创建了,就是添加文件,,h放进头文件里面,.c放进源文件里面进行实现。
由于VS2022对于一些函数有一些安全问题,会导致写出来的代码与在其它编译器上的不一样,所以会在开始define一下这个问题,这样在其它编译器上也可以使用这些代码。接下来我们开始编写代码。
2.2 test文件基本逻辑
当我们要玩游戏的时候,肯定是需要游戏一直进行的,所以游戏逻辑是当达到某个条件后游戏继续或者停止(代表你输了或者赢了),这里使用的是do-while循环,如下图。
这里先写出一个大致框架,通过主函数来调用test函数(游戏实现的逻辑)。
2.2.1菜单的实现
我们知道,一个游戏必须有一个菜单,来实现游戏的开始和退出,就像之前的猜数字小程序一样,这里用比较简陋的菜单来实现(可以自己设计自己想要的样式)。
通过一个函数menu来实现菜单的创建,而这个函数在test函数里的do-while循环里面完成。
菜单里面说明了1.play(游戏开始),0.eixt游戏结束,玩家通过选择这两个来实现游戏的游玩和节数。通过定义一个input临时变量来接收选择的是几。
2.2.2菜单的选择
当我们的input接收了值后,要进行判断游戏的执行和结束,所以在这里用上switch-case语句来完成。
当我们选择 1 的时候,就会进入game函数里面,实现游戏,选择 0 就会退出游戏,其它数字就会重新选择。而我们要保证do-while循环的运行和结束,这里我们通过while里面的条件进行限制,里面传入input就可以实现这一功能,当input传入0的时候,就判断为假,循环就结束,当传入为非0的数字的时候,程序循环不会结束。
这里面case 1中的打印三子棋来代替game函数的实现(因为game函数没有编写和定义声明,先注释掉),先运行一下是否可以得到想要的效果,如下图所示:
选择1:
选择0:
选择其它数字:
这样测试一遍发现没有错误,这样基本的test逻辑就实现了。
2.2.3game函数棋盘的实现
我们知道三子棋的棋盘就是一个井字,总共3*3个格子:
要储存数据,就需要一个3*3的数组,这时候就可以利用学过的二维数组来实现这一棋盘 ,棋盘内每个格子的位置都是空格,因为是空数据,所以当棋盘(二维数组)创建后需要初始化一下棋盘,初始化后还需要打印出棋盘。
所以这里封装俩函数(尽管没有实现)来实现棋盘的初始化和打印,参数分别是棋盘的地址和行和列。但这样写有一点不足,就是以后如果我们想要扩大棋盘,那么这里就都要改,所以推荐一种方法,就是在game.h文件中规定行和列,这里大写:
当想用3的时候使用ROW和COL就可以了,由于这是头文件,所以需要在test文件上面包含一下这个头文件就可以使用这两个了。
这时候行列就可以写成这样了,直接调用这两个就可以,以后想改成大一些的棋盘只需要修改一下game.h里的数字就可以了。
2.3game.c文件的编写
2.3.1初始化函数的模块
我们要实现init_board这个函数,这个函数是属于游戏模块内的,所以对于这个函数的声明和实现分别在game.h和game.c中编写,函数先声明这个函数,声明的时候要告诉函数参数是什么,返回参数是什么,函数名是什么:
由于初始化函数传入了一个棋盘数组,还有行和列,所以传入的三个参数入上图所示,其中行和列传入的是形参,所以用小写表示,不要和ROW和COL冲突。
这样初始化函数的声明就完事了,接下来编写这个函数的实现代码,在game.c里编写代码的实现。
首先包含一下game.h的头文件,因为头文件中我们有很多东西是需要用的,包括ROW,COL等等。
通过遍历来实现行列都给上空格,因为有形参row,col,所以这里遍历的条件就是这俩,把每一个格子的数据全变成空格。这就是棋盘的初始化。
2.3.2棋盘打印的模块
这里又是和之前一样,首先先在头文件中声明函数,包括参数类型,参数和返回类型。
接下来开始在game.c文件中写函数的实现:
一行打印完后换行就可以实现每一行都打印出来。要记住printf需要头文件stdio.h才能使用,所以需要在game.c里面包含一下stdio.h头文件才可以。
这里有一个小技巧,因为game.c和test.c里都需要game.h,所以只要把stdio.h在game.h里包含就可以了。
2.3.3实现棋盘界面的打印
我们假如要实现下面的棋盘样式(这里拿3*3举例)
我们可以给它拆分成一个个这样的:
把这个打印三行三列,但是最后一组这个分割行不用打印,即可满足这个棋盘的样式。
这时候之前写的输出二维数组的行和列就没有用了,把这部分改成打印棋盘。
我们把打印棋盘这个模块里面的代码改成下面:
打印三组数据,每组数据打印两行,第一行打印数据和竖线,第二行打印横杠和竖杠,将这两行看为一组,这样就可以打印出来如图所示的内容,但是我们发现最后一行出现了横杠,与我们画的理想图形不怎么符合,所以最后一行让它不输出就可以,这时候就可以加上一个限制条件:
当i < row-1 的时候,才打印横杠,所以就输出了两行就不用输出了。所以输出就会与理想的一样非常的美观哈。我们可以通过改变以下初始化的数组里面放的数据来看一下是否正确。
上面有一个缺陷,就是当我们的行数和列数改变的时候,就会发现它打印的行数会发生变化,但是列数由于我们给出了打印的内容,所以是会出现三列,例如我们把ROW和COL变为10,来看看棋盘输出的数据是什么样:
这并不是我们想要的结果,我们想要的是一个10*10的棋盘,所以我们将代码改成下面这样的:
这样还是一组内输出两行,但是这两行都是用循环来进行输出,第一个数据先输出空格+字符+空格然后紧接着是竖杠,由于最后一个竖杠不要,所以这里有个限制条件就是竖杠只输出col-1个。
同理,第二行横线和竖杠一样。
注意每行都有一个换行符,要不然打印出来的就是一个连一起的东西。
这样这两行看为一组,因为这一组在一个大的循环里面,这一粗输出row行,就打印出来棋盘了。执行结果如下图(10*10):
是不是很美观哈哈。
2.3.4实现玩家下棋模块
我们将玩家下棋的这个模块命名为player_move( );我们知道玩家下棋还是下到这个棋盘里,而且还要知道下棋的坐标,通过坐标来进行下到哪里。
在game模块的里面写下这个模块函数。
要调用这个模块需要声明和定义,所以跟之前的操作一样,在头文件里面声明,在game.c文件里进行定义和逻辑的编写。
声明的时候还是要把相关内容都写上,返回类型,参数类型,还有形参。
这时候开始进行玩家下棋的逻辑实现,在game.c文件里面进行编写:
我们知道下棋的话需要知道坐标,根据坐标来进行下棋,所以这时候定义x和y的临时变量,来接收横纵坐标, 再确定坐标之前,还需要判断坐标的合法度,也就是是否在这个范围之内,通过一个if和与连接起来。同时希望选择坐标错误的时候这个过程可以重新进行,所以套用了一个while循环来实现这一功能。
这里有一个拐弯的地方,就是 玩家和我们对于坐标的看法是不同的,我们认为这是个二维数组,第一个元素的坐标是(0,0),而玩家认为是(1,1),所以这里需要注意。
所以玩家输入的坐标横纵都减去1就是这个数据对应的二维数组的下标,这里判断如果里面为空格(也就是初始化成功了)就把这个坐标的值变为*(相当于下棋了),当不符合这个条件的时候,说明这个坐标已经被占用了,就重新进行输入,只有下棋成功后才会跳出循环,这样就实现了玩家下棋的功能。
我们希望下完棋后程序还会打印出下完后的棋盘。所以在玩家下棋的模块后再调用一下打印棋盘的模块,这样就好了:
我们来测试一下:
这就没有任何问题了,这里由于是只是下一次棋,所以只会出现一次。如果想让玩家一直下棋,就可以把玩家下棋的两个函数外套入一个while循环就可以实现:
游戏中玩家是不能一直下棋的,所以玩家下完棋后是电脑下棋。
2.3.5实现电脑下棋模块
将这个模块命名为computer_move,电脑下棋还是下到数组中,行列都一样,这一系列操作和之前的一样,声明定义(这里省略了声明)。
直接从定义开始编写:
这里电脑下棋是随机生成坐标,只要坐标没有被占用就下棋,这里就用到了随机数的概念,我们知道随机数生成用rand函数。
rand( )函数
在调用rand()函数之前,可以使用srand()函数设置随机数种子,如果没有设置随机数种子,rand()函数在调用时,自动设计随机数种子为1。随机种子相同,即rand()函数进入的入口相同,则每次产生的随机数也会相同。所以srand种子可使用传入时间戳的方式来确定入口。这样就可以保证随机性,因为时间在一直变化。
在C语言中,rand()函数用于生成伪随机数,这些随机数的范围在0到RAND_MAX之间,其中RAND_MAX通常被定义为32767
这里随机生成一个数,这个数对行和列取模,那么就能保证它的坐标在范围之内。我们想要用rand( )函数,所以要先在test.c中调用srand(),这个调用一次就可以,参数写成time()函数,将time函数内设为空指针,因为时间在不断改变,这个time函数返回的值传入srand( ),这样就保证了随机数的值的随机性。
如果要使用time和rand函数,则需要包含一下头文件,这两个文件都用到了game.h,所以直接在这个里面定义就行:
接下来实现电脑下棋的逻辑:
首先先提示一下电脑下棋,随机生成x,y的坐标,如果这个坐标没有被占用,那么就输入#,代表电脑下的棋,如果被占用了,就用一个循环来重新随机生成一个坐标,重复操作,当真正的下棋成功后循环就结束。
现在就可以测试一下,运行:
这就可以实现这个玩家下一次,之后电脑再下一次的功能。
2.3.6实现判断输赢(三个相连为例)
判断谁赢逻辑
因为三子棋需要三个相连就赢了,所以需要再写一个赢了的逻辑,要不然程序一直运行,下满了还停不下来而且有了三个相连的棋子也不会宣布谁赢。所以就在电脑下棋和玩家下棋的循环里面判断一下谁赢了。由于玩家下完了玩家有可能会赢,电脑下完了电脑有可能会赢,所以分别在他们两个之后判断一下就可以了。
判断输赢的代码要告诉我们:电脑赢 ?玩家赢?平局?谁都没赢游戏继续?
我们命名这个模块为is_win( ),里面的参数还是这个棋盘还有行和列;
这里定义一个字符变量ret,用来接收谁赢了。玩家赢就返回 * ,电脑赢就返回#,平局就返回C,谁都没赢,游戏继续就返回D。
所以写成这样,如果等于D,游戏就继续进行,如果不等于D了就跳出循环,与其它的字符进行比对,来找出对应的字母来打印谁赢了。
is_win函数实现
我们要通过is_win来获取返回的字符,才能对比出谁赢,所以首先还是先在game.h中进行声明,在game.c里进行编写。
我们知道判断三个连续的相等就是要么是横着的三个、要么是竖着的三个、要么是对角线三个:
这里先判断前三行,要是同时满足第一个元素等于第二个和第三个,并且里面不是空格,就返回第一个的元素,因为之前定义的是玩家赢是 * ,电脑赢是 # ,而我们在下棋的时候,往棋盘里面下的就是这俩字符,所以就直接可以返回。
接下来判断3列,同样的原理,列是固定的,但行时不同的:
接下来判断对角线:
接下来还剩最后一种情况,就是是否这个数组都满了,所以这里用一个函数来判断一下,我们将这个函数命名为is_full,这里面的参数还是这个棋盘和行和列,如果慢了就返回C,就是平局。
我们来写这个函数:
直接写在上面的位置就可以,根据遍历来判断棋盘中是否还有空格,如果没有,就一直循环到结束,之后返回1,这就代表已经满了。
之后就可以用来判断,如果为1,返回值相等,就返回C这个字符,也就是平局,如果都没有,就返回D,游戏继续。
这里面is_full这个函数只是为了支持is_win函数的,只是在is_win函数内部使用。所以就没必要去在头文件中声明。也可以在is_full函数前面加上static,这样这个函数只能在这个文件查看和使用,外部就看不到了。
这样运行一下,分别测试这几个功能好不好使:
玩家赢:
电脑赢:
平局:
这样就完成了。
总结
要保证之前知识的连贯性,这样学着才比较轻松,只要把逻辑想明白,实现就很好实现了。
三子棋的源码我放进仓库中了三子棋: C语言实现三子棋 (gitee.com)
可以点开这个查看。
相关文章:

数组实例之三子棋的实现(C语言)
目录 前言 一、三子棋实现的逻辑 二、三子棋的实现 2.1文件的创建添加 2.2 test文件基本逻辑 2.2.1菜单的实现 2.2.2菜单的选择 2.2.3game函数棋盘的实现 2.3game.c文件的编写 2.3.1初始化函数的模块 2.3.2棋盘打印的模块 2.3.3实现棋盘界面的打印 2.3.4实现玩家下…...

【Linux驱动开发】设备树节点驱动开发入门
【Linux驱动开发】设备树节点驱动开发入门 文章目录 设备树文件设备树文件驱动开发附录:嵌入式Linux驱动开发基本步骤开发环境驱动文件编译驱动安装驱动自动创建设备节点文件 驱动开发驱动设备号地址映射,虚拟内存和硬件内存地址字符驱动旧字符驱动新字…...

C++——string的模拟实现(下)
目录 成员函数 3.4 修改操作 (3)insert()函数 (4)pop_back()函数 (5)erase()函数 (6)swap()函数 3.5 查找操作 (1)find()函数 (2)substr()函数 3.6 重载函数 (1)operator赋值函数 (2)其他比较函数 (3)流插入和流提取 完整代码 结束语 第一篇链接:C——…...
面试 Java 基础八股文十问十答第二十九期
面试 Java 基础八股文十问十答第二十九期 作者:程序员小白条,个人博客 相信看了本文后,对你的面试是有一定帮助的!关注专栏后就能收到持续更新! ⭐点赞⭐收藏⭐不迷路!⭐ 1)类加载过程 类加载…...
454.四数相加||
题目: 454. 四数相加 II - 力扣(LeetCode) 思路: 考虑到时间复杂度问题,本题最重要的是要将四个数组划分成两个部分,每个部分(n^2)的时间复杂度,选取数据结构时,考虑到既要存储元素(key),又要有元素次数…...

禅道源码部署
文章目录 禅道部署1.环境部署安装httpd和mariadb安装php 2.安装禅道首先进行httpd服务的配置安装禅道 禅道部署 1.环境部署 安装lamp环境 组件版本httpdyum安装mariadbyum安装phpphp-7.4.33 选择一个php版本就行,我们这里选择的是7.4.33 安装httpd和mariadb [r…...

️ Vulnhuntr:利用大型语言模型(LLM)进行零样本漏洞发现的工具
在网络安全领域,漏洞的发现和修复是保护系统安全的关键。今天,我要向大家介绍一款创新的工具——Vulnhuntr,这是一款利用大型语言模型(LLM)进行零样本漏洞发现的工具,能够自动分析代码,检测远程…...

【Android】多渠道打包配置
目录 简介打包配置签名配置渠道配置配置打包出来的App名称正式包与测试包配置 打包方式开发工具打包命令行打包 优缺点 简介 多渠道打包 是指在打包一个 Android 应用时,一次编译生成多个 APK 文件,每个 APK 文件针对一个特定的渠道。不同的渠道可能代表…...

Spring Boot Configuration和AutoConfiguration加载逻辑和加载顺序调整
在spring中, AutoConfiguration也是一个种Configuration,只是AutoConfiguration是不能使用proxy的。 而且spring对于两者的加载顺序也不是一视同仁,是有顺序的。spring会先加载@SpringBootApplication可达的且标注了@Configuration的类,这个过程会将@AutoConfiguration标注…...

点餐系统需求分析说明书(软件工程分析报告JAVA)
目录 1 引言 4 1.1 编写目的 4 1.2 项目背景 4 1.3 定义 4 1.4 预期的读者 5 1.5 参考资料 5 2 任务概述 5 2.1 目标 5 2.2 运行环境 5 2.3 条件与限制 6 3 数据描述 6 3.1 静态数据 6 3.2 动态数据 6 3.3 数据库介绍 6 3.4 对象模型 6 3.5 数据采集 7 4 动态模型 7 4.1 脚本 …...

Python条形图 | 指标(特征)重要性图的绘制
在数据科学和机器学习的工作流程中,特征选择是一个关键步骤。通过评估每个特征对模型预测能力的影响,我们可以选择最有意义的特征(指标),从而提高模型的性能并减少过拟合。本文将介绍如何使用 Python 的 Seaborn 和 Ma…...

危险物品图像分割系统:一键训练
危险物品图像分割系统源码&数据集分享 [yolov8-seg-GFPN&yolov8-seg-CSwinTransformer等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI Global…...

城市景色视频素材下载好去处
在制作短视频、Vlog 或商业宣传片时,城市景色视频素材能为作品增添现代感与活力。繁华都市、流光溢彩的夜景、清晨街道等都是展现城市魅力的好素材。那么城市景色视频素材去哪里下载呢? 蛙学网 是专为短视频创作者打造的素材平台,城市景色素材…...

基于SSM美容院管理系统的设计
管理员账户功能包括:系统首页,个人中心,用户管理,套餐类型管理,美容预约管理,生日提醒管理,管理员管理,系统管理 员工账号功能包括:系统首页,个人中心&#…...
Threejs 实现3D 地图(04)3d 地图的柱状图和文字显示
3d 地图的数据展示 代码仓库: King/threejs-3d-map 核心代码: function createText(feature, level, font) {if (feature.properties.name) {const [x_XYZ, y_XYZ] handleProject(feature.properties.center)// 缺点:首次渲染很慢 无法使…...
Oracle 第2章:安装与配置Oracle
安装与配置Oracle数据库是一项复杂但有序的过程。以下是对Oracle数据库安装与配置的概述,包括系统需求与硬件推荐,以及详细的安装步骤。 系统需求与硬件推荐 系统需求 在安装Oracle数据库之前,需要确保目标系统满足Oracle官方规定的最低要…...

动态规划 —— 斐波那契数列模型-解码方法
1. 解码方法 题目链接: 91. 解码方法 - 力扣(LeetCode)https://leetcode.cn/problems/decode-ways/description/ 2. 题目解析 1. 对字母A - Z进行编码1-26 2. 11106可以解码为1-1-10-6或者11-10-6, 但是11-1-06不能解码 3. 0n不能解码 4. …...

PPT / Powerpoint中利用LaTeX输入公式
PPT / Powerpoint中利用LaTeX输入公式_ppt插入latex公式-CSDN博客文章浏览阅读2.8w次,点赞42次,收藏75次。新版的Word(Office 2016后?)是支持LaTeX公式输入的,但是Powerpoint并不支持。下面介绍如何利用。_…...
C++ 模板专题 - 类型擦除
一:概述 C 中的类型擦除(Type Erasure)是一种技术,允许你在不暴露具体类型信息的情况下,通过统一的接口处理不同的类型。这种技术常用于实现泛型编程,特别是在需要支持多种不同类型的情况下,如容…...

RuoYi-Vue项目 重点代码讲解
1. RuoYi-Vue项目 常规说明: ruoyi-admin:后台接口开发(主要存放控制层相关代码)ruoyi-common:通用工具ruoyi-framework:框架核心ruoyi-generator:代码生成(可以移除)r…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
Kafka主题运维全指南:从基础配置到故障处理
#作者:张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1:主题删除失败。常见错误2:__consumer_offsets占用太多的磁盘。 主题日常管理 …...
小木的算法日记-多叉树的递归/层序遍历
🌲 从二叉树到森林:一文彻底搞懂多叉树遍历的艺术 🚀 引言 你好,未来的算法大神! 在数据结构的世界里,“树”无疑是最核心、最迷人的概念之一。我们中的大多数人都是从 二叉树 开始入门的,它…...
二维FDTD算法仿真
二维FDTD算法仿真,并带完全匹配层,输入波形为高斯波、平面波 FDTD_二维/FDTD.zip , 6075 FDTD_二维/FDTD_31.m , 1029 FDTD_二维/FDTD_32.m , 2806 FDTD_二维/FDTD_33.m , 3782 FDTD_二维/FDTD_34.m , 4182 FDTD_二维/FDTD_35.m , 4793...
大数据治理的常见方式
大数据治理的常见方式 大数据治理是确保数据质量、安全性和可用性的系统性方法,以下是几种常见的治理方式: 1. 数据质量管理 核心方法: 数据校验:建立数据校验规则(格式、范围、一致性等)数据清洗&…...