C语言实现三子棋

通过一段时间的学习,我们已经能够较为熟练地使用分支语句循环语句,创建函数,创建数组,创建随机数等。之前我们做过一个扫雷游戏,今天让我们再尝试创作一个三子棋游戏吧~

一、三子棋游戏的思路

三子棋的游戏规则很简单,游戏界面是一个3*3的棋盘,玩家双方依次向棋盘中放置棋子,我的棋子用‘X’表示,对手的棋子用‘O’表示,我们需要让自己的棋连在一起,同时还要阻拦对方的棋子组成连线,当有三个相同的棋子横向相连,纵向相连,或者交叉相连,那么这个棋子的持有者就获胜了

好了,那么既然我们已经了解了三子棋的游戏思路,那让我们试着分析一下,想要用C语言制作出三子棋的游戏,大体需要分成几个步骤?

1.游戏菜单

创造出一个比较美观的游戏菜单,可以选择开始游戏和退出游戏。

2.三子棋的棋盘

创造出一个3*3的三子棋游戏棋盘

3.双方下棋

玩家输入坐标,向三子棋棋盘中放置棋子

(玩家落子需要输入坐标,电脑落子需要srand函数创造随机数)

4.判定胜负

通过对当前棋盘中棋子的计算,来判定是,或者和棋

二、三子棋游戏的游戏文件

与之前的扫雷游戏一样,还是需要分成头文件game.h源文件game.c源文件test.c三个文件。

他们的作用分别是:

  • game.h:作为头文件,它用来存放创造游戏所需要的各种类型的全局变量,并且也用来实现编写代码使所必需的函数说明
  • game.c: 存放各种实现游戏功能所需要的函数。
  • test.c:作为编译的主程序,存放主函数,编写实现游戏功能的主要思想。

(这里使用三个文件只是为了方便,并且修改代码和改进代码时会更快捷,但其实不使用三个也是可以的)

三、实现游戏的各种函数

1.创建游戏菜单

就像我们平常打一个游戏一样,我们自己做出的三子棋游戏也是需要有选择菜单的。如果没有选择菜单就无法明确如何游戏。

void GameInte()
{printf("**************************\n");printf("----- 三 子 棋 游 戏 -----\n");printf("**************************\n");printf("*******   1.play   *******\n");printf("*******   0.quit   *******\n");printf("**************************\n");printf("是否游玩?请输入>:");
}

我们需要将菜单函数GameInte在源文件game.c中进行创建和定义,然后再在头文件game.h中对函数进行声明。就像这个样子:

2.完成棋盘初始化

因为需要创建一个3*3的棋盘,所以我们可以创建一个char型的二维数组,board[ROW][COL],(我们把ROW和COL创建成常量,因为是三子棋直接设置成ROW和COL的值都为3就好,如此一来,如果以后像制作五子棋之类的棋盘就会更加简单。像这样

我们只需要用 -- 和 | 组成一个3*3的框架,然后再把棋子依次放入里面就可以了。而放入棋子之前需要把棋子的char值设定为‘  ’,这样才算是空,往里下入棋子后随之改变在棋盘中对应坐标的符号就好了。

char BoardCreate(char board[ROW][COL], int row, int col)
{int i = 0;int j = 0;for (i = 0; i < row; i++){for (j = 0; j < col; j++){board[i][j] = ' ';}}
}

这样就能将一个二维数组的所有制都初始化为' '啦。

3.打印棋盘

打印棋盘要注意,不能直接把二维数组的所有值都输出就算成功,因为二维数组的初始化我们把所有值都定义为‘ ’了,所以打印出来是不显示的,我们可以选择用 | 和 -- 等符号进行辅助,来打印出一个美观且成型的棋盘。

void PrintBoard(char board[ROW][COL], int row, int col)
{int i = 0;int j = 0;printf(" ----+---+----\n");for (i = 0; i < row; i++){printf(" | %c | %c | %c | \n", board[i][0], board[i][1], board[i][2]);printf(" ----+---+----\n");}
}

这样就能打印出一个3*3的棋盘啦,并且能够成功的将所有的元素都放进去。

棋盘样例:

4.玩家下棋

(因为玩家下棋是自动输入,而电脑下棋是随机数输出,所以需要放到两个函数中更加清晰易懂并且容易后期改良)

我们需要定义一个函数,实现玩家能够输入坐标,就能改变棋盘上的格局,比如玩家输入1,1,就能够让棋盘上多一个玩家的棋子。并且我们需要限定输入坐标的位置,不能超过也不能少于这个范围,同时也不能下到已经有棋子的位置。

void PlyaChess(char board[ROW][COL], int row, int col)
{int a;int b;while (1){printf("请输入下棋的坐标:>");scanf("%d %d", &a, &b);int m = a - 1;int n = b - 1;if(a<=0||a>=row+1||b<=0||b>=col+1){printf("输入错误,请重新输入:>");continue;}else if(board[m][n]!=' '){printf("你是想叠罗汉吗?\n");printf("请重新输入:>\n");continue;}board[m][n] = 'X';PrintBoard(board, ROW, COL);break;}
}

像这样设定两个分支条件语句就能分别控制,制止两种的发生。

5.电脑下棋

电脑下棋就需要用到我们之前所提到的srand()函数和rand来创造随机数,还是在主函数输入srand((unsigned int)time(NULL))这串代码来使随机数rand的种子随时间变化而变化,让它成为真正的随机数,然后使用rand()%rowrand()%col来创造出两个随机数,让电脑在棋子上下棋,遇到有棋子的地方就goto到上面重新生成随机数。

void ComputerChess(char board[ROW][COL], int row, int col)
{again:int m = rand() % row;int n = rand() % col;Sleep(1000);if (board[m][n] == ' '){board[m][n] = 'O';}else {goto again;}PrintBoard(board, ROW, COL);
}

(我们可以利用sleep函数添加一个让电脑思考的时间(只是假思考~),这样能让游戏更加带有真实性和参与的感觉。注:需要添加#include <windows.h>头文件

这就是成功的制作出让电脑下棋的步骤啦。

6.判断赢输与和棋

我们可以通过计算棋盘上的行,列,以及交叉的区域中,是否有相连的三个相同符号来确定这局三子棋的最终结果。而对于行,列的计算其实还是比较简单的,我们只需要用两个for循环的嵌套,因为两个嵌套来表示board数组的话,正好是一排一排定义的,所以我们可以第一次用for(i=0;i<row;i++),先在外层循环里定义两个初始值为0的变量分别作为'X'和'O'的计数器,内层嵌套for(j=0;j<col;j++),然后判定每一行的各个元素是否为'X'或'O',如果是'X'则对应的计数器+1,如果是'O'也同理,如果判定出有一行中都为'X'或'O'则宣布谁获胜,游戏结束。

(将两个计数器放在外层循环的目的是:每次外层循环,i变化时都会进入第二列/行的输出,此时需要重新从0开始记起)

接下来让我们再思考一下如何判定交叉区域的输赢:因为定义的是3*3的棋盘,所以只有[1][1]到[3][3]坐标和[1][3]到[3][1]这两种情况,我们仍然可以用for循环来解决。

思路有了,敲代码吧~

判断行获胜:

int WinChess(char board[ROW][COL], int row, int col)
{int i = 0;int j = 0;for (i = 0; i < row; i++)//判断行获胜{int Asum = 0;int Bsum = 0;for (j = 0; j < col; j++){if (board[i][j] == 'X'){++Asum;if (Asum == 3)return 1;}else if(board[i][j] == 'O'){++Bsum;if (Bsum == 3)return 2;}}}

判断列获胜:

for (i = 0; i < row; i++)//判断列获胜{int Asum = 0;int Bsum = 0;for (j = 0; j < col; j++){if (board[j][i] == 'X'){++Asum;if (Asum == 3)return 1;}else if (board[j][i] == 'O'){++Bsum;if (Bsum == 3)return 2;}}}

判断交叉获胜:

int Asum = 0;int Bsum = 0;for (i = 0; i < row; i++)//判断交叉获胜{if ((board[i][i] == 'X') || (board[i][row - 1 - i] == 'X')){++Asum;if (Asum == 3)return 1;}else if ((board[i][i] == 'O') || (board[i][row - 1 - i] == 'O')){++Bsum;if (Bsum == 3)return 2;}}

那么接下来我们要来判断和棋的时候是什么情况,因为判断输赢已经使这个函数变得比较复杂了,所以我们再定义一个新的函数Full,用来判断棋盘是否已经满了,如果满了则返回1,没满则返回0。

int Full(char chessBoard[ROW][COL])
{for (int row = 0; row < ROW; row++){for (int col = 0; col < COL; col++){if (chessBoard[row][col] == ' '){return 0;}}}return 1;
}

我们需要利用Full函数的返回值确定是否为满,并且在判断获胜或者失败之前,如果函数已为满,则先判断是否为和棋,这样能够防止程序因为判断不出获胜或失败则return0导致程序继续进行(而此时电脑已经没有地方可以下棋了,会导致游戏终止)

好啦~这次我们已经把所有的准备工作都完成啦,让我们来看一下主函数main是什么样子的吧~

#include"game.h"
int game()
{//棋盘初始化BoardCreate(board, ROW, COL);//打印棋盘PrintBoard(board, ROW, COL);while (1){//玩家下棋PlyaChess(board, ROW, COL);if (Full(board) == 1){if ((WinChess(board, ROW, COL)) == 1){printf("恭喜玩家获胜!!!");break;}if ((WinChess(board, ROW, COL)) == 0){printf("你和人机势均力敌,你是人机嘛");break;}}//电脑下棋ComputerChess(board, ROW, COL);if ((WinChess(board, ROW, COL)) == 1){printf("恭喜玩家获胜!!!");break;}if ((WinChess(board, ROW, COL)) == 2){printf("你被人机打败了!");break;}}
}
int main()
{srand((unsigned int)time(NULL));int a;do{//创造菜单GameInte();scanf("%d", &a);if (a == 1){game();break;}else if (a == 0){printf("退出游戏");break;}else {printf("输入错误,请重新输入:\n");}} while (a);
}

注意,此时我们在主函数中在判断胜利或失败之前加入的,判断是否为和棋的代码中,又加入了判断玩家是否胜利,因为可能玩家最后下的这一枚棋正好导致了棋子填满并且玩家获胜。加入这段判断就不会导致明明在最后一刻棋子满的时候胜利了,却被说是和棋了。

好了,这次已经解决了所有的函数了,让我们运行一下代码看看也没有什么问题~

行相连胜利:

列相连胜利:

交叉相连胜利:

和棋:

落子最后一枚获胜:

嘿嘿,游戏运行完整无误呢,给大家分享一下完整代码吧~

四、游戏完整代码

1.game.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include <time.h>
#include <windows.h>
#define ROW 3
#define COL 3
char board[ROW][COL];//创造菜单
void GameInte();//棋盘初始化
void BoardCreate(char board[ROW][COL], int row, int col);//打印棋盘
void PrintBoard(char board[ROW][COL], int row, int col);//玩家下棋
void PlyaChess(char board[ROW][COL], int row, int col);//电脑下棋
void ComputerChess(char board[ROW][COL],int row, int col);//判断棋盘是否已满
int Full(char chessBoard[ROW][COL]);//判断赢输与和棋
int WinChess(char board[ROW][COL], int row, int col);
2.game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"void GameInte()
{printf("**************************\n");printf("----- 三 子 棋 游 戏 -----\n");printf("**************************\n");printf("*******   1.play   *******\n");printf("*******   0.quit   *******\n");printf("**************************\n");printf("是否游玩?请输入>:");
}char BoardCreate(char board[ROW][COL], int row, int col)
{int i = 0;int j = 0;for (i = 0; i < row; i++){for (j = 0; j < col; j++){board[i][j] = ' ';}}
}void PrintBoard(char board[ROW][COL], int row, int col)
{int i = 0;int j = 0;printf(" ----+---+----\n");for (i = 0; i < row; i++){printf(" | %c | %c | %c | \n", board[i][0], board[i][1], board[i][2]);printf(" ----+---+----\n");}
}void PlyaChess(char board[ROW][COL], int row, int col)
{int a;int b;while (1){printf("请输入下棋的坐标:>");scanf("%d %d", &a, &b);int m = a - 1;int n = b - 1;if(a<=0||a>=row+1||b<=0||b>=col+1){printf("输入错误,请重新输入:>");continue;}else if(board[m][n]!=' '){printf("你是想叠罗汉吗?\n");printf("请重新输入:>\n");continue;}board[m][n] = 'X';PrintBoard(board, ROW, COL);break;}
}int Full(char chessBoard[ROW][COL])
{for (int row = 0; row < ROW; row++){for (int col = 0; col < COL; col++){if (chessBoard[row][col] == ' '){return 0;}}}return 1;
}void ComputerChess(char board[ROW][COL], int row,int col)
{again:int m = rand() % row;int n = rand() % col;int i = 0;int j = 0;int sum = 0;Sleep(500);if (board[m][n] == ' '){board[m][n] = 'O';}else {goto again;}PrintBoard(board, ROW, COL);
}int WinChess(char board[ROW][COL], int row, int col)
{int i = 0;int j = 0;for (i = 0; i < row; i++)//判断行获胜{int Asum = 0;int Bsum = 0;for (j = 0; j < col; j++){if (board[i][j] == 'X'){++Asum;if (Asum == 3)return 1;}else if(board[i][j] == 'O'){++Bsum;if (Bsum == 3)return 2;}}}for (i = 0; i < row; i++)//判断列获胜{int Asum = 0;int Bsum = 0;for (j = 0; j < col; j++){if (board[j][i] == 'X'){++Asum;if (Asum == 3)return 1;}else if (board[j][i] == 'O'){++Bsum;if (Bsum == 3)return 2;}}}int Asum = 0;int Bsum = 0;for (i = 0; i < row; i++)//判断交叉获胜{if ((board[i][i] == 'X') || (board[i][row - 1 - i] == 'X')){++Asum;if (Asum == 3)return 1;}else if ((board[i][i] == 'O') || (board[i][row - 1 - i] == 'O')){++Bsum;if (Bsum == 3)return 2;}}return 0;
}
3.test.c
#include"game.h"
int game()
{//棋盘初始化BoardCreate(board, ROW, COL);//打印棋盘PrintBoard(board, ROW, COL);while (1){//玩家下棋PlyaChess(board, ROW, COL);if (Full(board) == 1){if ((WinChess(board, ROW, COL)) == 1){printf("恭喜玩家获胜!!!");break;}if ((WinChess(board, ROW, COL)) == 0){printf("你和人机势均力敌,你是人机嘛");break;}}//电脑下棋ComputerChess(board, ROW, COL);if ((WinChess(board, ROW, COL)) == 1){printf("恭喜玩家获胜!!!");break;}if ((WinChess(board, ROW, COL)) == 2){printf("你被人机打败了!");break;}}
}
int main()
{srand((unsigned int)time(NULL));int a;do{//创造菜单GameInte();scanf("%d", &a);if (a == 1){game();break;}else if (a == 0){printf("退出游戏");break;}else {printf("输入错误,请重新输入:\n");}} while (a);
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI Agent调研--7种Agent框架对比!盘点国内一站式Agent搭建平台,一文说清差别!大家都在用Agent做什么?

代理&#xff08;Agent&#xff09;乃一种智能实体&#xff0c;具备自主环境感知与决策行动能力&#xff0c;旨在达成既定目标。作为个人或组织之数字化替身&#xff0c;AI代理执行特定任务与交易&#xff0c;其核心价值在于简化工作流程&#xff0c;削减繁复性&#xff0c;并有…

IoTDB 入门教程 实战篇④——C#示例(开源)

文章目录 一、前文二、新建C#项目三、NuGet安装四、示例源码五、查询数据六、参考 一、前文 IoTDB入门教程——导读 本文详细阐述了如何通过一个C#项目成功连接到IoTDB时序数据库&#xff0c;进而展示了如何向该数据库高效地写入数据以及执行精确的数据查询操作。 此示例旨在为…

Javascript前端面试基础(九)

浏览器缓存 浏览器缓存分为强缓存和协商缓存。当客户端请求某个资源时&#xff0c;获取缓存的流程如下 先根据这个资源的一些http header判断它是否命中强缓存&#xff0c;如果命中则直接从本地获取缓存资源&#xff0c;不会发请求到服务器;当强缓存没有命中时&#xff0c;客户…

【Qt开发】No matching signal for on_toolButton_clicked() 解决方案

【Qt开发】No matching signal for on_toolButton_clicked() 解决方案 文章目录 No matching signal for xxx 解决方案附录&#xff1a;C语言到C的入门知识点&#xff08;主要适用于C语言精通到Qt的C开发入门&#xff09;C语言与C的不同C中写C语言代码C语言到C的知识点Qt开发中…

企业级Linux系统防护

一、企业级Linux系统防护概述 一&#xff09;企业级Linux系统安全威胁 企业级Linux系统安全威胁列表 解决的主要安全威胁安全威胁牵涉到的人员及操作文件系统防护避免有意/无意的文件篡改、越权访问&#xff0c;根用户&#xff08;root&#xff09;权限泛滥企业内部用户误操作、…

UPLOAD-LABS靶场[超详细通关教程,通关攻略]

---------------------------------------- 靶场环境&#xff1a; 下载链接&#xff1a; https://codeload.github.com/c0ny1/upload-labs/zip/refs/heads/master 使用小皮集成环境来完成这个靶场 将文件放到WWW目录下就可以进行访问 ------------------------------------…

CTF-Web习题:[GXYCTF2019]Ping Ping Ping

题目链接&#xff1a;[GXYCTF2019]Ping Ping Ping 解题思路 访问靶机&#xff0c;得到如下页面&#xff0c;类似于URL参数 尝试用HackBar构造url传输过去看看 发现返回了ping命令的执行结果&#xff0c;可以猜测php脚本命令是ping -c 4 $ip&#xff0c;暂时不知道执行的函数…

学习Numpy的奇思妙想

学习Numpy的奇思妙想 本文主要想记录一下&#xff0c;学习 numpy 过程中的偶然的灵感&#xff0c;并记录一下知识框架。 推荐资源&#xff1a;https://numpy.org/doc/stable/user/absolute_beginners.html &#x1f4a1;灵感 为什么 numpy 数组的 shape 和 pytorch 是 tensor 是…

Ribbon负载均衡与内核原理

什么是Ribbon? 目前主流的负载方案分为两种&#xff1a; 集中式负载均衡&#xff0c;在消费者和服务提供方中间使用独立的代理方式进行负载&#xff0c;有硬件的&#xff08;比如F5&#xff09;&#xff0c;也有软件的&#xff08;Nginx&#xff09;客户端根据自己的请求做负…

FFmpeg研究

1.FFmpeg介绍 FFmpeg的全称是“Fast Forward Moving Picture Expert Group”&#xff0c;组件由命令行应用程序和函数库两部分组成。通俗概括来说&#xff0c;FFmpeg 是一个免费的开源程序库&#xff0c;一个多媒体音视频处理分析工具软件&#xff0c;且提供命令行方式调用&am…

C语言笔记38 •数据结构--队列•

数据结构--队列 1.队列的定义 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c;队列具有 先 进先出 FIFO(First In First Out). 入队列&#xff1a;进行插入操作的一端称为 队尾 出队列&#xff1a;进行删除操作的…

Jmeter混合压测(2407)

一 压测需求&#xff1a; 电商作为服务端&#xff0c;至少需要满足并发量,QPS:100/s,TPS:20/s。例如场景&#xff1a; 电商交易中&#xff0c;商品图片请求量最多&#xff0c;电商服务端需要满足并发请求查询图片信息。各家可能会并发请求同一家电商商品、订单等内容。 二 压…

基于多种机器学习算法的短信垃圾分类模型

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主导入第三方库读取数据数据预处理数据分析与可视化机器学习建模贝叶斯逻辑回归支持向量机随机森林XGBoost总结每文一语 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私…

redis集群三种模式

redis 集群 高可用 redis集群三种模式 主从复制 奇数台 3 一主两从 哨兵模式 3 一主两从 cluser 集群 6 3 3 3 9 主从复制&#xff1a;和mysql的主从复制类似&#xff0c;写入主的数据通过rdb方式把数据同步到从服务器。从不能更新到主&#xff0c;…

未来不会使用 AI 的人真的会被淘汰吗?

AI 是今年大火的一个话题&#xff0c;随着 ChatGPT 之类的一系列大模型开始流行以后&#xff0c;有不少的培训机构宣称这样的口号: “未来不会使用 AI 的人将会被淘汰”。我觉得这个观点本身并没有错&#xff0c;但是关键在于那些培训机构出于自身的利益&#xff0c;故意忽略了…

内存问题检测

内存检测方式 gcc/g 内存检测方式如下&#xff0c;添加一些编译标签&#xff1a; -fsanitizeleak 检测内存泄漏。例如添加标签&#xff1a;-fsanitizeleak -g -O0-fsanitizeaddress 检测内存越界。例如添加标签&#xff1a;-fsanitizeaddress -g -O2&#xff0c;优化级别开…

02 I/O多路复用---进程的聊天

服务器同时和很多客户端连在一起 管道的read&#xff0c;总是能读出来

前后端分离开发遵循接口规范-YAPI

目前&#xff0c;网站主流开发方式是前后端分离。因此前后端必须遵循一套统一的规范&#xff0c;才能保证前后端进行正常的数据&#xff08;JSON数据格式&#xff09;请求、影响&#xff0c;这套规范即是 YAPI. 产品经理撰写原型&#xff1b; 前端或后端撰写接口文档。 YAPI…

Android高级interview

一、Android基础知识 1、四大组件、六大布局、五大存储 四大组件&#xff1a; activity、service、content provider、broadcast六大布局&#xff08;现在是 7 大了&#xff09;: 线性布局&#xff08;LinearLayout&#xff09;相对布局&#xff08;RelativeLayout&#xf…

替换后端国外身份目录服务,宁盾身份域管接管FileNet助力国产化升级

IBM FileNet 是一款优秀的企业内容管理解决方案&#xff0c;为客户提供了领先的文档管理和流程管理集成环境&#xff0c;被大量企业所采用。FileNet 需要使用企业级的目录服务器&#xff08;LDAP&#xff09;作为其用户管理系统&#xff0c;满足其认证和授权的需求。对于 LDAP …