基于C语言 --- 自己写一个三子棋小游戏

C语言程序设计笔记---019

  • 初阶三子棋小游戏(开源)
    • 1、arr_main.c程序大纲
    • 2、arr_game1.h
    • 3、arr_game1.c
      • 3.1、 自定义初识化函数 InitBoard( ) 和 自定义显示函数 DisPlayBoard( )
      • 3.2、 自定义玩家下棋函数 PlayerMove( )
      • 3.4、 自定义电脑下棋函数 ComputerMove( )
      • 3.5、 输赢判断函数 IsWin( )
    • 4、结束语

初阶三子棋小游戏(开源)

前言:
游戏规则:
(1)、在打印的数组棋盘里,输入坐标,下棋。
(2)、坐标格式:x(空格) y
(3)、x横坐标,y竖坐标
(4)、连成三个棋子为一线则判定胜利
如图所示
在这里插入图片描述

采用模块化编写
arr_game1.c执行主要逻辑程序
arr_game1.h存放头文件或函数声明等程序
arr_main.c放主函数逻辑程序

1、arr_main.c程序大纲

首先,从以往玩游戏的经验来谈,我们需要为游戏写一个游戏开始菜单,由玩家选择是否开始游戏。
这里可以借助所学的menu( )自定义函数,设计一个简易的菜单

void menu()
{printf("****************************************\n");printf("************** 1.play game *************\n");printf("************** 0.game over *************\n");printf("****************************************\n");
}

当我们选择1,则开始游戏;当选择0,则退出游戏。
那么就得思考,如何对玩家得选择进行判定
1.利用 scanf( ) 函数获取输入值,将获取的值,借用 do while 循环语句和switch( )选择语句,进行下一步。
2.当选择1,开始游戏则执行game()自定义,游戏主逻辑执行程序
3.当选择0,switch中 进入case 0 : 的入口,执行退出游戏,并且 do while( ),判定为0,则退出程序
4.当玩家误选择非法数值,则default : 提示玩家输入错误

int input = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:printf("\n*********** 三子棋游戏开始 *************\n\n");game();//游戏执行逻辑函数break;case 0:printf("\n退出游戏\n");break;default:printf("\n选择错误请重新选择\n\n");break;}} while (input);

接下来,主要阐述game( )函数内容,游戏的执行逻辑
1.当玩家选择1,开始游戏后,会显示整个空棋盘
如何实现空棋盘的显示呢?
(1)、利用所学的数组知识可以联想到,棋盘不过是由一个个字符拼接而成,在需要下棋的地方,放置空格字符进行占位,达到肉眼所见的空棋盘。
(2)、又因为棋盘是一个平面,平面由一条条线组成,线又由一个个点组成,首先得定义和初识化一个二维数组 board[ROW][COL] ,由几行几列的方式实现一个面的显示/打印
(3)、自定义初识化函数 InitBoard( ) ,自定义显示棋盘函数 DisPlayBoard( ) ;
(4)、函数的参数,可想而知,需要数组名(board) — 首元素的地址指定需操作的数组,需要行(ROW)和列(COL)指定操作的元素或坐标或地址;

void game()
{//定义棋盘大小char board[ROW][COL] = { 0 };//初识化棋盘InitBoard(board,ROW,COL);//显示棋盘DisPlayBoard(board, ROW, COL);}

2.当我们完成棋盘的初识化和显示后,那么默认玩家先下棋,电脑再下棋;
所以就得写一个玩家下棋的自定义逻辑函数和电脑下棋的自定义函数,交替下棋直到游戏判定输赢或平局
(1)、玩家下棋自定义函数 PlayerMove( ) ;
(2)、电脑下棋自定义函数 ComputerMove( ) ;
(3)、判断输赢函数 IsWin( );
那么很容易思考到,交替进行下棋,就是反复的调用玩家下棋函数和电脑下棋函数,同时不停的判断每一步棋是否达到输赢的结果。所以需要一个循环来实现,这里就用while循环,实现不停的下棋。
但是,不停的下棋,始终在死循环,那么就思考利用,break跳出循环,所以 IsWin( )函数是具备返回值的,可以由IsWin( )函数的返回值进行判定跳出。 且由于棋局均有字符进行的操作,所以确定了 IsWin( )数据类型也为 char 类型。
(4)、IsWin( )跳出循环的返回值判定
a、玩家获胜 - - - - ‘ * ’
a、电脑获胜 - - - - ‘ # ’
a、平局 - - - - ‘ Q ’
a、游戏继续 - - - - ‘ C ’

//游戏执行逻辑函数
void game()
{//定义棋盘大小char board[ROW][COL] = { 0 };//初识化棋盘InitBoard(board,ROW,COL);//显示棋盘DisPlayBoard(board, ROW, COL);//根据IsWin()返回值,判断输赢char ret = 0;//下棋while (1){//玩家下棋PlayerMove(board, ROW, COL);//判断输赢ret = IsWin(board, ROW, COL);if (ret != 'C'){break;}//显示棋局DisPlayBoard(board, ROW, COL);//电脑下棋ComputerMove(board, ROW, COL);//判断输赢ret = IsWin(board, ROW, COL);if (ret != 'C'){break;}//显示棋局DisPlayBoard(board, ROW, COL);}if (ret == '*')printf("\n玩家赢\n\n");else if (ret == '#')printf("\n电脑赢\n\n");elseprintf("\n平局\n\n");//显示棋局DisPlayBoard(board, ROW, COL);
}

arr_main.c程序大纲展示

#include "arr_game1.h"//游戏菜单
void menu()
{printf("****************************************\n");printf("************** 1.play game *************\n");printf("************** 0.game over *************\n");printf("****************************************\n");
}
//游戏执行逻辑函数
void game()
{//定义棋盘大小char board[ROW][COL] = { 0 };//初识化棋盘InitBoard(board,ROW,COL);//显示棋盘DisPlayBoard(board, ROW, COL);//根据IsWin()返回值,判断输赢char ret = 0;//下棋while (1){//玩家下棋PlayerMove(board, ROW, COL);//判断输赢ret = IsWin(board, ROW, COL);if (ret != 'C'){break;}//显示棋局DisPlayBoard(board, ROW, COL);//电脑下棋ComputerMove(board, ROW, COL);//判断输赢ret = IsWin(board, ROW, COL);if (ret != 'C'){break;}//显示棋局DisPlayBoard(board, ROW, COL);}if (ret == '*')printf("\n玩家赢\n\n");else if (ret == '#')printf("\n电脑赢\n\n");elseprintf("\n平局\n\n");//显示棋局DisPlayBoard(board, ROW, COL);
}int main()
{srand((unsigned int)time(NULL));//初识化随机值生成器int input = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:printf("\n*********** 三子棋游戏开始 *************\n\n");game();//游戏执行逻辑函数break;case 0:printf("\n退出游戏\n");break;default:printf("\n选择错误请重新选择\n\n");break;}} while (input);return 0;
}

2、arr_game1.h

用于存放所自定义的函数和头文件等声明的程序
通俗易懂,就不多赘述,详见代码注释的说明。

#include <stdio.h>
#include <time.h>//调用time函数,所需声明的头文件
#include <stdlib.h>//调用srand和rand函数,所需声明的头文件//宏定义棋盘大小
#define ROW 3
#define COL 3//初识化棋盘
void InitBoard(char board[ROW][COL],int row,int col);//打印棋盘
void DisPlayBoard(char board[ROW][COL], int row, int col);//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col);//判断输赢、平局
char IsWin(char board[ROW][COL], int row, int col);

3、arr_game1.c

用于存放对 arr_main.c 程序大纲做提到的函数进行封装,实现具体的功能的程序
说明:基于arr_main.c 程序大纲逻辑对代码进行讲解
注意:这里均以3*3的棋盘为例哦

3.1、 自定义初识化函数 InitBoard( ) 和 自定义显示函数 DisPlayBoard( )

首先,根据需求我们需要一个棋盘才可以正常的下棋.
如何让棋盘初识化呢?
1.根据所学的二维数组知识,便可以知道,当我们遍历二维数组的每一个元素,使得填充为空格字符,便可以由InitBoard( ) 达到效果。

//初识化棋盘
void InitBoard(char board[ROW][COL], int row, int col)
{int i = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){board[i][j] = ' ';}}
}

如图所示
在这里插入图片描述
如图所示
在这里插入图片描述

2.但是,这样的棋盘全是空格字符,只有程序员可见字符空格,玩家并不知道,所以将对棋盘进行修饰美化,使得大众化。所以借助 DisPlayBoard( ) 达到修饰棋盘的效果。
这里使用一些分隔符,将棋盘划分出,三组,并且数组元素加分隔符为一组。

//打印棋盘
void DisPlayBoard(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++){printf(" %c ",board[i][j]);printf("|");}printf("\n");//每行打印完换行}printf("\n");
}

如图所示:
在这里插入图片描述

但是目前的效果达不到理想的棋盘效果,导致最后一组的分隔符,也被打印了出来。
所以思考可知:由 if (j < col - 1) 进行判断让最后一行的分割符被约束不打印出来。
然后打印完一行,换行一次
同理,每一列打印完,使用 if (i < row - 1) 使得最后一列的分隔符不被打印出来
所以优化后得到以下代码:

//打印棋盘
void DisPlayBoard(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++){printf(" %c ",board[i][j]);if(j < col-1)printf("|");}printf("\n");//每行打印完换行if (i < row - 1){int j = 0;for (j = 0; j < col; j++){printf("---");if (j < col - 1)printf("|");}printf("\n");//每行打印完换行}}printf("\n");
}

如图所示
在这里插入图片描述
棋盘显示最终效果如图所示
在这里插入图片描述

3.2、 自定义玩家下棋函数 PlayerMove( )

1.首先,我们下棋,需要给棋子一个坐标,并且需要以作为玩家的角度,思考坐标的取值范围。因为玩家不一定知道数组的下标从0开始,所以我们定义 x,y 坐标变量,使得 if (x >= 1 && x <= row && y >= 1 && y <= col )//符合玩家下棋坐标的逻辑
2.当我们坐标符合了范围,还得判定玩家输入错误坐标超出范围时,给予提示信息,超出范围,重新输入。
3.当玩家输入坐标范围正确,但是此时的坐标处已经被上一步棋子占用时,也得提示重新输入。

//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col)
{//定义下棋的坐标int x = 0;int y = 0;while (1){printf("玩家下,请输入棋子的坐标:>");scanf("%d %d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col )//符合玩家下棋坐标逻辑{//判断下棋的坐标是否被占用if (board[x - 1][y - 1] == ' '){board[x - 1][y - 1] = '*';break;}else{printf("\n坐标被占用,请重新输入坐标\n");}}else {printf("\n坐标超出棋盘范围,请重新输入棋子,坐标\n");}}
}

如图所示
在这里插入图片描述

3.4、 自定义电脑下棋函数 ComputerMove( )

1.下棋的逻辑与玩家如出一辙,不多赘述。
2.这里强调一下,电脑如何实现产生随机的坐标下棋?
首先,在之前的篇章链接: link 里提到的srand函数与rand函数的应用原理相同。通过初识化srand随机值生成器函数,提供变化的随机值范围,给rand随机值函数,将rand()%row 和 rand()%col,就可以得到符合范围的数组元素坐标。
然后根据srand的参数要求,放一个时刻变化的数据,这里就用到了time时间转换函数,相互作用即可。

查阅资料文档的具体参数:
void srand( unsigned int seed ); 头文件 <stdlib.h>
int rand(void ); 头文件 <stdlib.h>
time_t time( time_t *timer ); 头文件 <time.h>

所以得到:

srand((unsigned int)time(NULL));
x = rand() % row;
y = rand() % col;

注意:srand只需要整个工程初识化一次即可

//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{//定义棋子坐标int x = 0;int y = 0;printf("电脑下棋:>\n");while (1){x = rand() % row;y = rand() % col;if (board[x][y] == ' '){board[x][y] = '#';break;}}
}

如图所示
在这里插入图片描述

3.5、 输赢判断函数 IsWin( )

由arr_main.c程序大纲提到的思路可知:
如何判定输赢:
玩家赢 — ‘*’
电脑赢 — ‘#’
平局 ---- ‘Q’
继续游戏 — ‘C’
所以可以明确 IsWin( ) 的返回值类型为char字符型

根据三子棋的游戏规则,三个棋子连城一线即可判定获胜,所以有:
(1)、一行三个棋子
(2)、一列三个棋子
(3)、对角线三个棋子
那么如何判定平局呢?
顾名思义,当我们的棋盘被占用完,还是没有连成一线,则判定为平局。
这里利用 int IsFull( )函数,遍历判断数组每个元素是否已用完即可,当返回0,则仍然有空位,继续游戏;当返回1,则所有元素均被占用且未连成一线,判定为平局。


//判断输赢
//玩家赢 --- '*'
//电脑赢 --- '#'
//平局 ---- 'Q'
//继续游戏 --- 'C'
int Isfull(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++){if (board[i][j] == ' '){return 0;//棋盘没满}}}return 1;//满了
}char IsWin(char board[ROW][COL], int row, int col)
{int i = 0;//判断三行,均相同for (i = 0; i < row; i++){if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' '){return board[i][1];//*/#}}//判断三列,均相同for (i = 0; i < col; i++){if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' '){return board[1][i];//#/*}}//判断对角线,均相同(三子棋)//主对角if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' '){return board[1][1];//*/#}//副对角if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' '){return board[1][1];//*/#}//判断平局int ret = Isfull(board, ROW, COL);if (ret == 1)//棋盘无空位,平局{return 'Q';}//以上所有情况不满足时,继续游戏return 'C';
}

输赢结果如图所示
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4、结束语

相信通过这样一个三子棋的小游戏,更具掌握了对数组的操作以及对自定义函数的深刻认识;
如果觉着文章对您有所帮助,请不要吝啬的一赞三连哦,谢谢阅读,不足之处还请多多指教。

三子棋源码获取链接: 三子棋

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

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

相关文章

反射简述

什么是反射反射在java中起到什么样的作用获取class对象的三种方式反射的优缺点图 什么是反射 JAVA反射机制是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象&#xff0c;都能够调用它的任意一个方法和属性&…

自然语言处理学习笔记(一)————概论

目录 1.自然语言处理概念 2.自然语言与编程语言的比较 &#xff08;1&#xff09;词汇量&#xff1a; &#xff08;2&#xff09;结构化&#xff1a; &#xff08;3&#xff09;歧义性&#xff1a; &#xff08;4&#xff09;容错性&#xff1a; &#xff08;5&#xff0…

LabVIEW FPGA开发实时滑动摩擦系统

LabVIEW FPGA开发实时滑动摩擦系统 由于非线性摩擦效应的建模和补偿的固有困难&#xff0c;摩擦系统的运动控制已被广泛研究。最近&#xff0c;人们更加关注滑动动力学和滑动定位&#xff0c;作为传统机器人定位的低成本和更灵活的驱动替代方案。摩擦控制器设计和适当选择基础…

【机器学习】Overfitting and Regularization

Overfitting and Regularization 1. 过拟合添加正则化2. 具有正则化的损失函数2.1 正则化线性回归的损失函数2.2 正则化逻辑回归的损失函数 3. 具有正则化的梯度下降3.1 使用正则化计算梯度&#xff08;线性回归 / 逻辑回归&#xff09;3.2 正则化线性回归的梯度函数3.3 正则化…

解决python-opencv:(-215:Assertion failed) _img.empty() in function ‘cv::imwrite‘在将视频分成帧图片,写入时出现的问题

最近在搞视频检测问题&#xff0c;在用到将视频分帧保存为图片时&#xff0c;图片可以保存&#xff0c;但是会出现(-215:Assertion failed) !_img.empty() in function cv::imwrite问题而不能正常运行&#xff0c;在检查代码、检查路径等措施均无果后&#xff0c;了解了视频分帧…

linux实现运行java分包部署

1.打好包之后 找到bin文件夹下的 startup.sh文件 2.cd 进入bin文件夹下 3.执行 sh startup.sh 运行命令 4.如果出现此错误 是Windows和Linux的.sh脚本文件格式不同&#xff0c;如果在脚本中有空行&#xff0c;脚本是在Windows下进行编辑之后上传到linux上去执行的话&#xff0c…

JVM系统优化实践(23):GC生产环境案例(6)

您好&#xff0c;这里是「码农镖局」CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e; 在互联网大厂中&#xff0c;对每天亿级流量的日志进行清洗、整理是非常常见的工作。在某个系统中&#xff0c;需要对用户的访问日志做脱敏处理&#xff0c;也就是清洗掉姓名…

【Spring Cloud】Gateway的配置与使用

文章目录 前言第一步&#xff0c;创建一个springboot工程第二步&#xff0c;添加依赖第三步&#xff0c;编写yml文件第四步&#xff0c;启动主启动类总结 前言 Gateway其实是springcloud 原生的东西&#xff0c;但是我还是想放在这里讲&#xff0c;因为我们使用nacos时&#x…

IPsec VPN小实验

IPSec 是什么&#xff1a; IPSec是一个框架&#xff0c;它不是具体指某个协议&#xff0c;而是定义了一个框架&#xff0c;由各种协议组和协商而成。该框架涉及到的主要有加密算法、验证算法、封装协议、封装模式、密钥有效期等等。 IPSecVPN建立的前提&#xff1a;要想在两个…

【Lua学习笔记】Lua进阶——垃圾回收

按照唐老师的课程本来要讲自带库的&#xff0c;但是想想这东西能看文档&#xff0c;ctrl左键还能看注解&#xff0c;并且最重要的许多自带库的方法基本大部分语言都有&#xff0c;其实看看就能懂了。所以还是重点讲讲垃圾回收 文章目录 GC辅助垃圾回收collectgarbage增量模式分…

Elasticsearch Query DSL

Elasticsearch Query DSL 这里使用的 Elasticsearch 的版本为 7.12.1。 1、基本概念 1.1 文档(Document) ElasticSearch 是面向文档的&#xff0c;文档是所有可搜索数据的最小单位&#xff0c;例如 MySQL 的一条数据记录。 文档会被序列化成为 json 格式&#xff0c;保存在…

iPhone 安装 iOS 17公测版(Public Beta)

文章目录 步骤1. 备份iPhone资料步骤2. 申请iOS 17 公测Beta 资格步骤3. 下载iOS 16 Beta 公测描述档步骤4. 选择iOS 17 Beta 公测描述档更新项目步骤5. 升级iOS 17 Public Beta 公开测试版 苹果已经开始向大众释出首个iOS 17 公开测试版/ 公测版( iOS 17 Public Beta)&#xf…

测试|Selenium之WebDriver常见API使用

测试|Selenium之WebDriver常见API使用 文章目录 测试|Selenium之WebDriver常见API使用1.定位对象&#xff08;findElement&#xff09;css定位xpath定位css选择器语法&#xff1a;xpath语法:校验结果 2.操作对象鼠标点击对象在对象上模拟按键输入clear清除对象输入的文本内容su…

【Python】Web学习笔记_flask(1)——getpost

flask提供的request请求对象可以实现获取url或表单中的字段值 GET请求 从URL中获取name、age两个参数 from flask import Flask,url_for,redirect,requestappFlask(__name__)app.route(/) def index():namerequest.args.get(name)agerequest.args.get(age)messagef姓名:{nam…

[css]margin-top不起作用问题(外边距合并)

在初学css时&#xff0c;会遇到突然间margin-top不起作用的情况。如下面&#xff1a; 情况一&#xff1a; 代码&#xff1a; <html> <head><style type"text/css"> * {margin:0;padding:0;border:0; }#outer {width:300px;height:300px;backgroun…

Elasticsearch Java客户端和Spring data elasticsearch

文章目录 官网版本组件版本说明实现代码地址es Spring Data Repositories例子&#xff1a;ElasticsearchRepository分析 es Spring Data Repositories 关键字es Spring Data Repositories client 加载rest风格客户端直接执行dsl例子响应式客户端-ReactiveElasticsearchClientpo…

数据结构:顺序表(C实现)

个人主页 水月梦镜花 个人专栏 C语言 &#xff0c;数据结构 文章目录 一、顺序表二、实现思路1.存储结构2.初始化顺序表(SeqListInit)3.销毁顺序表(SeqListDestroty)4.打印顺序表(SeqListPrint)5.顺序表尾插(SeqListPushBack)and检查容量(SeqListCheckCapacity)6.顺序表头插(Se…

K8S群集调度

目录 一、调度约束二、Pod 启动典型创建过程三、K8S的调度过程3.1 Predicate&#xff08;预选策略&#xff09; 常见的算法使用3.2 常见优先级选项3.3 指定调度节点3.3.1 nodeName指定3.3.2 nodeSelector指定3.3.3 Pod亲和性与反亲和1.节点亲和硬策略示例2.节点亲和软策略示例3…

linux -网络编程-多线程并发服务器

目录 1.三次握手和四次挥手 2 滑动窗口 3 函数封装思想 4 高并发服务器 学习目标&#xff1a; 掌握三次握手建立连接过程掌握四次握手关闭连接的过程掌握滑动窗口的概念掌握错误处理函数封装实现多进程并发服务器实现多线程并发服务器 1.三次握手和四次挥手 思考: 为什么…

【javaSE】 面向对象程序三大特性之继承

目录 为什么需要继承 继承的概念 继承的语法 注意事项 父类成员访问 子类中访问父类的成员变量 子类和父类不存在同名成员变量 子类和父类成员变量同名 访问原则 子类中访问父类的成员方法 成员方法名字不同 总结&#xff1a; 成员方法名字相同 总结&#xff1a; …