数组实例之三子棋的实现(C语言)

news2024/10/27 23:23:40

目录

前言

一、三子棋实现的逻辑

二、三子棋的实现

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)

可以点开这个查看。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2225012.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【论文阅读】Reliable, Adaptable, and Attributable Language Models with Retrieval

文章目录 OverviewCurrent Retrieval-Augmented LMsArchitectureTraining Limitations & Future Work Overview Parametic language models的缺点&#xff1a; 事实性错误的普遍存在验证的难度&#xff08;可溯源性差&#xff09;难以在有顾虑的情况下排除某些序列适应调整…

Unity插件-Intense TPS 讲解

目录 关于TPS 打开场景&#xff1a;WeaponTest.unity&#xff0c; 只要把这些枪点&#xff0c;打开&#xff08;默认隐藏&#xff0c;不知道为何), 一开始不能运行如何修复 总结 关于TPS 个人不是TPS&#xff0c;FPS的射击游戏爱好者&#xff0c; 不过感觉这个枪感&…

Linux系统中使用yum命令详细安装MariaDB数据库的步骤

前言 mysql和mariadb的区别 MySQL&#xff1a;最初由瑞典的MySQL AB开发&#xff0c;后被Sun Microsystems收购&#xff0c;最终被Oracle收购。MariaDB&#xff1a;由MySQL的原始开发者Michael Widenius领导的团队开发&#xff0c;目标是保持与MySQL的兼容性&#xff0c;并提…

部署前后端分离若依项目--CentOS7宝塔版

准备&#xff1a; CentOS7服务器一台 通过网盘分享的文件&#xff1a;CentOS 7 h 链接: https://pan.baidu.com/s/17DF8eRSSDuj9VeqselGa_Q 提取码: s7x4 大家有需要可以下载这个&#xff0c;密码61 若依前端编译后文件 通过网盘分享的文件&#xff1a;ruoyi-admin.jar 链…

【K8S】快速入门Kubernetes

之前企业都是使用容器化和来构建自己的服务和应用程序&#xff0c;其中容器化优点有很多&#xff1a;提升了部署效率、稳定性、提高了资源的利用率降低了成本。 但是也带来了一些新的问题&#xff1a;容器的数量变得很多&#xff0c;管理就是一个新的问题。所以Kubernetes就出…

【K8S系列】Kubernetes 中 Service IP 地址和端口不匹配问题及解决方案【已解决】

在 Kubernetes 中&#xff0c;Service 是实现 Pod 之间和 Pod 与外部之间通信的关键组件。Service 的 IP 地址和端口配置不当可能导致应用无法正常访问。本文将详细分析 Service IP 地址和端口不匹配的问题&#xff0c;常见原因及其解决方案。 一、问题描述 Service IP 地址和…

草稿1111

服务端&#xff1a; 启动nfs服务器服务和rpcbind 服务&#xff1a; 写配置文件&#xff1a; 重新导出所有当前已导出的文件系统&#xff1a; 这个命令会立即停止 firewalld 服务&#xff0c;并将其设置为在系统启动时不自动启动&#xff1a; 客户端&#xff1a; 启动nfs服务器…

练习LabVIEW第十九题

学习目标&#xff1a; 刚学了LabVIEW&#xff0c;在网上找了些题&#xff0c;练习一下LabVIEW&#xff0c;有不对不好不足的地方欢迎指正&#xff01; 第十九题&#xff1a; 创建一个程序把另外一个VI的前面板显示在Picture控件中 开始编写&#xff1a; 在前面板放置一个二…

银河麒麟V10通过tigervnc实现远程桌面和windows系统连接

1、查看系统版本:uname -a Linux localhost.localdomain 4.19.90-89.16.v2401.ky10.x86_64 #1 SMP Sat Sep 14 13:09:47 CST 2024 x86_64 x86_64 x86_64 GNU/Linux 2、查看是否具有桌面环境:yum grouplist 安装VNC需要具有桌面环境 3.、安装tigervnc: yum install tigervnc…

C++——string的模拟实现(上)

目录 引言 成员变量 1.基本框架 成员函数 1.构造函数和析构函数 2.拷贝构造函数 3.容量操作函数 3.1 有效长度和容量大小 3.2 容量操作 3.3 访问操作 (1)operator[]函数 (2)iterator迭代器 3.4 修改操作 (1)push_back()和append() (2)operator函数 引言 在 C—…

GoFly快速开发框架已集成了RTSP流媒体服务器(直播、录播)代码插件-开发者集成流媒体服务器更加方便

温馨提示&#xff1a;我们分享的文章是给需要的人&#xff0c;不需要的人请绕过&#xff0c;文明浏览&#xff0c;误恶语伤人&#xff01; 说明 本代码包是音视频流媒体服务器&#xff0c;支持RTSP推流到服务器、服务端录像与回放。在视频直播、安防及车辆监控视频上云、无人…

解读AVL树:平衡二叉搜索树的奥秘

✨✨小新课堂开课了&#xff0c;欢迎欢迎~✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C&#xff1a;由浅入深篇 小新的主页&#xff1a;编程版小新-CSDN博客 前言&#xff1a; 前面我们已经介绍了二叉搜…

编译却出现Error: C9555E: Failed to check out a license.问题解决

学stm32的时候&#xff0c;照着操作突然发现编译报错。 去网上搜索答案的时候发现了这篇文章&#xff1a;参考博客:KEI5许可证没到期&#xff0c;编译却出现Error: C9555E: Failed to check out a license.问题解决 根据网上找的资料发现应该是版本问题导致的。在上一篇博客…

Prompt提示词设计:如何让你的AI对话更智能?

Prompt设计&#xff1a;如何让你的AI对话更智能&#xff1f; 在人工智能的世界里&#xff0c;Prompt&#xff08;提示词&#xff09;就像是一把钥匙&#xff0c;能够解锁AI的潜力&#xff0c;让它更好地理解和响应你的需求。今天&#xff0c;我们就来聊聊如何通过精心设计的Pr…

【纯血鸿蒙】安装hdc工具

这里我先写Mac版的,Windows的在下面 首先要知道你的SDK安装在哪里了,不知道的话,可以打开DevEco Studio,打开设置页面里的HarmonyOS SDK,这个我们之前配置环境变量的时候用过。 其实主要是用到这里toolchains下的hdc命令。 所以我们需要配置环境变量。 1、打开Mac下的…

关于OpenFeign中@GetMapping不支持Java对象传递

原因&#xff1a;在使用openfeign远程调用的时候&#xff0c;使用get请求时候发现&#xff0c;如果feign里面传的是实体&#xff0c;当然接口里面也是需要传实体&#xff0c;但是发现出错了&#xff0c;后来去官网找了一下&#xff0c;发现get请求不支持传入Java实体对象&#…

网络原理之 TCP解释超详细!!!

TCP 有连接的 可靠传输 面向字节流 全双工 其中最核心的是可靠传输 那么 TCP 如何使用可靠传输的 ??? 我们尽可能传过去, 如果传不过去,发送方至少知道自己没传过去, 所以在于接收方, 收到或者没有收到, 都会有应答的操作 1. 确认应答 实现可靠性最核心的机制!!! 引出 …

一种解决Leaflet中Divicon城市气温标注空间重叠的办法

目录 前言 一、一些解决办法 1、marker的聚类 2、使用leaflet-canvas-label 3、使用Zoom和样式控制 二、基于rbush和Leaflet.LayerGroup.Collision的解决办法 1、关于rbush 2、Leaflet.LayerGroup.Collision.js 三、解决标签重叠的具体实现 1、添加元数据 2、添加到…

理解OAuth2与用户账户与授权UAA的关系

目录 关于OAuth2OAuth2的核心组件授权流程授权模式使用场景优点与缺点 关于UAA技术解释 UAA与OAuth2的关系 关于OAuth2 ‌‌OAuth2&#xff08;开放授权2.0&#xff09;是一个开放标准&#xff0c;用于授权第三方应用程序访问用户资源&#xff0c;而无需共享用户的用户名和密码…

c语言中值调用(call by value)方式和引用调用(call by reference)

在C语言中参数传递主要有两种方式&#xff1a;通过值调用&#xff08;call by value&#xff09;和通过引用调用&#xff08;call by reference&#xff09;。 通过值调用&#xff08;Call by Value&#xff09; 说明&#xff1a;当使用值调用时&#xff0c;函数接收到的是参数…