【C语言】三子棋小游戏的思路及实现(内附代码)

news2024/12/25 23:16:12

简单不先于复杂,而是在复杂之后。

89efcc89ac61428db4d5b6639b2bd948.jpeg

目录

1. 分文件实现

2.分步骤实现 

2.1 游戏菜单 

2.2 创建棋盘 

2.3 初始化棋盘 

2.4 打印棋盘 

2.5 玩家下棋 

2.6 电脑下棋 

2.7 判断输赢 

3. 附完整代码

3.1 test.c 

3.2 game.h 

3.2 game.c 


 

1. 分文件实现

当我们实现一个有较多功能的三子棋小游戏时,我们要用到许多库函数,需要包含头文件,还有符号的定义,函数的声明等等,还要写很多“高内聚、低耦合”的函数来实现具体的一些功能,所以代码量会非常庞大。

我们就需要在一个工程里创建多个文件,赋予其不同的分工,来使我们的程序可读性提高,清晰易懂。

我在vs2019中创建了3个文件来实现三子棋小游戏:

test.c  //测试游戏的逻辑

game.c  //游戏代码的实现
game.h  //游戏代码的声明(函数声明,符号定义)

 

2.分步骤实现 

在写一个完整的大型的程序之前,我们需要先梳理好实现程序的逻辑:

1. 我们需要实现一个游戏菜单,来让用户选择游戏或者退出游戏,打印在屏幕上。

2. 既然要下棋,我们需要提供一个棋盘来承载棋子,所以下一步我们要创建一个棋盘。

3. 创建了棋盘之后,我们需要将棋盘初始化,将所有的格子都初始化为空格。

4. 上一步仅仅初始化了棋盘,并没有打印到屏幕上,我们需要让用户看到棋盘才可以正常下棋,所以要打印棋盘。

5. 棋盘已就位,我们要开始下棋,由我们来和电脑对弈,所以这部分要实现的功能是玩家在棋盘上下棋。我们还需要在其中实现一个检验合法性的功能,来检验棋盘是否已经下满了,如果已经下满了,当然不能继续在棋盘上下棋。

6. 玩家下棋之后轮到电脑下棋。

7.当双方达到一定条件之后,一方或输或赢或双方平局,我们需要实现判断输赢的功能。 

1. 游戏菜单

2. 创建棋盘

3. 初始化棋盘

4. 显示棋盘

5. 玩家下棋

6. 电脑下棋

7. 判断输赢

2.1 游戏菜单 

我们在 game.c 和 test.c 里面均要用到库函数以及函数,为了更加便捷,我们把所有库函数头文件的包含、宏定义以及函数声明都放到 game.h 中,然后在  game.c 和 test.c 中包含上game.h。

首先在 main 函数中实现一个菜单,我们选择用do while循环来实现,因为无论游戏如何进行,都必须先让用户看到菜单界面选择是否进入游戏。do while 循环的特点就是先去做,再去判断条件,至少执行一次。

我们用 printf 函数和 scanf 函数来提醒用户进行选择并且输入选择。

用户选择之后我们需要对用户的选择做出相应的反馈,选择进入游戏,就进入游戏逻辑的函数,或者选择退出,选错了就要重新选择。

 接下来测试一下功能:

2.2 创建棋盘 

接下来创建一个行列都是3的棋盘

此时我们用到符号定义,用 ROW 和 COL 分别代表行和列的值,这样做的好处是我们在之后维护或者迭代代码的时候,不必修改所有行列的值,直接在头文件里更改就可以·,同时也增加了代码的可读性。

2.3 初始化棋盘 

接下来我们需要写一个函数来初始化棋盘,并且将每一个格子都设置为空格。

在 test.c 文件中调用函数,game.h 中声明函数,game.c中定义函数。

函数调用: 

函数声明:

 函数定义:

2.4 打印棋盘 

接下来我们需要写一个打印棋盘函数,用来打印当前棋盘的状态。

函数调用:

 函数声明:

函数定义:

测试一下:

但是如果这么写的话不便于维护,如果我们后期要迭代成五子棋,需要打印5*5的棋盘,当ROW 和 COL 的值更改了之后,打印的行没有问题,但是列还是不变,会变成这个样子:

为了适应更多情况,更改代码如下:

 

行列改为5的情况也适用:

 

2.5 玩家下棋 

接下来我们要实现让玩家能够输入坐标并在棋盘上下棋。

函数调用:

玩家下棋实际上还是对表示棋盘的数组进行操作,而且要检验下棋位置是否合法,所以函数传参的时候需要把数组和行列都传过去。

而且玩家下棋和电脑下棋是一个不断交互的过程,双方轮次下棋,直到一方获胜或者平局为止。

我们暂时不考虑游戏终止的条件,用一个 while 循环里面置玩家和电脑下棋的函数。

注:验证两点是否合法

1. 落子范围是否合法

2. 落子位置是否已被占用

函数声明:

 

函数定义:

2.6 电脑下棋 

要实现电脑下棋,我们可以用生成随机数的方法,然后通过对生成随机数的限制控制随机数生成的范围,随机数代表 board 这个二维数组的坐标。

然后通过合法性验证:

如果随机数生成的坐标处没有下棋,则电脑落子。

如果已经下棋,则继续循环生成随机数,直至电脑落子后跳出循环。

函数调用:

 

函数声明:

 

函数定义:

 

包含头文件:

srand 函数需要包含<stdlib.h>

time 函数需要包含<time.h>

 

设置随机数生成起点:

 

2.7 判断输赢 

我们要实现的这个函数需要判断四种状态:

1. 玩家赢 - ’*‘

2. 电脑赢 - ’#‘

3. 平局 - 'Q'

4. 继续 - 'C'

我们可以约定不同状态返回不同的符号如上

在每一次下棋后都要判断一次输赢,且判断输赢之后显示最终的棋盘。

 

函数声明:

 

函数定义:

 

3. 附完整代码

3.1 test.c 

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

void game()
{
	char ret = 0;
	char board[ROW][COL] = { 0 };//创建棋盘

	InitBoard(board, ROW,	COL);//初始化棋盘
	DisplayBoard(board, ROW, COL);//打印棋盘
	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");
	}
	else if (ret == '#')
	{
		printf("\n电脑获胜!!!!!!!!!!!!!!!!\n");
	}
	else
	{
		printf("\n平局\n");
	}
	DisplayBoard(board, ROW, COL);//打印棋盘

}

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

int main()
{
	srand((unsigned int) time(NULL));//设置随机数生成的起点

	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("\n已退出");
			break;
		default:
			printf("\n选错了,请重新选择");
			break;
		}
		
	} while (input);

	return 0;
}#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

void game()
{
	char ret = 0;
	char board[ROW][COL] = { 0 };//创建棋盘

	InitBoard(board, ROW,	COL);//初始化棋盘
	DisplayBoard(board, ROW, COL);//打印棋盘
	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");
	}
	else if (ret == '#')
	{
		printf("\n电脑获胜!!!!!!!!!!!!!!!!\n");
	}
	else
	{
		printf("\n平局\n");
	}
	DisplayBoard(board, ROW, COL);//打印棋盘

}

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

int main()
{
	srand((unsigned int) time(NULL));//设置随机数生成的起点

	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("\n已退出");
			break;
		default:
			printf("\n选错了,请重新选择");
			break;
		}
		
	} while (input);

	return 0;
}

 

3.2 game.h 

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#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);

//判断输赢
//玩家赢 - '*'
//电脑赢 - '#'
//平局 - 'Q'
//继续 - 'C'

char IsWin(char board[ROW][COL], int row, int col);

 

3.2 game.c 

 

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

void InitBoard(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 DisplayBoard(char board[ROW][COL], int row, int col)
//{
//	int i = 0;
//	for (i = 0; i < row; i++)
//	{
//		//打印数据
//		printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//		//打印分割行
//		if(i<row-1)
//		{
//			printf("---|---|---\n");
//
//		}
//	}
//}

void DisplayBoard(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++)
		{
			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");
		}
	}
}

void PlayerMove(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;

	printf("\n玩家下棋:\n");

	while (1)
	{
		printf("\n请输入坐标:\n");
		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");
		}
	}
}

void ComputerMove(char board[ROW][COL], int row, int col)
{
	printf("\n电脑下棋:\n");

	int x = 0;
	int y = 0;

	while (1)
	{
		x = rand() % row;//0~2
		y = rand() % col;//0~2
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}

//棋盘被下满了返回1
//棋盘没下满返回0
int IsFull(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++)
		{
			if (board[i][j] != '0')
			{
				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];
		}
	}

	//列
	int j = 0;
	for (j = 0; j < col; j++)
	{
		if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[1][j] != ' ')
		{
			return board[1][j];
		}
	}

	//对角线
	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];
	}

	//没有人赢,平局
	if (IsFull(board, row, col))
	{
		return 'Q';
	}

	//游戏继续
	return 'C';

}

 

 

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

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

相关文章

对称加密、非对称加密、数字签名、消息摘要的简单学习

对称加密、非对称加密、数字签名、消息摘要的简单学习 前言对称加密算法DES特点&#xff1a;为什么不使用&#xff1a; 3DES&#xff08;Triple DES 或者 DESede&#xff09;特点&#xff1a;使用场景&#xff1a;为什么不用&#xff1a; AES&#xff08;Advanced Encryption S…

聊一聊模板方法模式

统一抽取&#xff0c;制定规范&#xff1b; 一、概述 模板方法模式&#xff0c;又叫模板模式&#xff0c;属于23种设计模式中的行为型模式。在抽象类中公开定义了执行的方法&#xff0c;子类可以按需重写其方法&#xff0c;但是要以抽象类中定义的方式调用方法。总结起来就是&…

c语言实现栈(顺序栈,链栈)

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f349;本篇简介:>:讲解用c语言实现:“数据结构之"栈”,分别从"顺序栈…

区间预测 | MATLAB实现QRCNN-BiGRU卷积双向门控循环单元分位数回归时间序列区间预测

区间预测 | MATLAB实现QRCNN-BiGRU卷积双向门控循环单元分位数回归时间序列区间预测 目录 区间预测 | MATLAB实现QRCNN-BiGRU卷积双向门控循环单元分位数回归时间序列区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 1.Matlab实现基于QRCNN-BiGRU分位数回…

MySQL视图与联集

一、VIEW&#xff08;视图&#xff09; 1、 概念 可以被当作是虚拟表或存储查询 视图跟表格的不同是&#xff0c;表格中有实际储存资料&#xff0c;而视图是建立在表格之上的一个架构&#xff0c;它本身并不实际储存资料。 临时表在用户退出或同数据库的连接断开后就自动消…

DIY技巧:微星B760主板13600K降压教程 CPU温度暴降25℃

前段时间微星B600/700系主板更新了最新的BIOS&#xff0c;最新的BIOS更新&#xff1b;额105微码&#xff0c;让用户能直接在BIOS中对13代带K处理器进行降压&#xff0c;十分方便&#xff0c;今就带大家体验一下微星B760迫击炮主板的降压流程&#xff0c;其他微星B600/700系主板…

43岁,年薪200万的高管,被裁了!这4条职场潜规则,你越早知道越好

作者| Mr.K 编辑| Emma 来源| 技术领导力(ID&#xff1a;jishulingdaoli) 我的一位老朋友S总&#xff0c;是某世界500强外企中国区运营总监&#xff0c;光年薪就200万&#xff0c;还不包括福利、股票的部分&#xff0c;他比我略长一两岁&#xff0c;我们人生经历相似&#xf…

一文搞懂Go错误链

0. Go错误处理简要回顾 Go是一种非常强调错误处理的编程语言。在Go中&#xff0c;错误被表示为实现了error接口的类型的值&#xff0c;error接口只有一个方法&#xff1a; type error interface {Error() string } 这个接口的引入使得Go程序可以以一致和符合惯用法的方式进行错…

Python实现哈里斯鹰优化算法(HHO)优化LightGBM回归模型(LGBMRegressor算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 2019年Heidari等人提出哈里斯鹰优化算法(Harris Hawk Optimization, HHO)&#xff0c;该算法有较强的全…

有主题的图文内容创作 | AIGC实践

话说&#xff0c;昨天我发布了第一篇&#xff0c;内容由ChatGPT和Midjourney协助完成的文章&#xff1a;胡同与侏罗纪公园的时空交错 | 胡同幻想 在这篇文章中&#xff0c;大约70%图文内容由ChatGPT和Midjourney输出。我个人参与的部分&#xff0c;主要是提出指令&#xff08;P…

Monaco Editor编辑器教程(三一):在编辑器中实现模拟调试的交互

前言 最近有小伙伴咨询如何在编辑中实现 像vscode调试代码那样,可以打断点,可以高亮当前运行的一行。这样的需求并不多见,如果要做,那肯定是对编辑器做一个深层次的定制。一般很少很少会实现这种在浏览器中调试。 目前我还没见过,如果有遇到过的朋友可以指点一下。我去学…

Cesium AI GPT 文档 源码 ChatGPT问答

我用Cesium104.0的 源码 | 文档 | 3DTiles标准 作为上下文语料定制了一个智能Cesium专家问答助手 语料: 3D Tiles Specificationhttps://cesium.com/downloads/cesiumjs/releases/1.104/Build/CesiumUnminified/Cesium.jshttps://github1s.com/CesiumGS/cesium/blob/HEAD/Doc…

redis中的管道

Redis 管道 文章目录 1. 前言2. Redis 管道3. 小总结 1. 前言 通过一个问题引出 我们接下来要学习的 Redis 管道 : 提问 : 如何优化频繁命令往返造成的性能瓶颈 ? 另外 &#xff1a; 关于上面这个问题的由来 也可以简单的说一说 上面所说的思路 其实就是管道的概念 &#xff0…

读俞敏洪的书

没有认真写过一篇关于书籍的读后感文章&#xff0c;但在读完俞敏洪老师这本书后&#xff0c;想推荐给大家&#xff0c;也想分享下我的想法。 几周前&#xff0c;我在微信读书首页看到了俞敏洪老师的读书推荐 《在绝望中寻找希望》——俞敏洪写给迷茫不安的年轻人 有好几个晚上&…

电池只能充电500次?别太荒谬!收下这份真正的充电秘籍

我们的生活已经离不开电子设备了&#xff0c;而电子设备嘛&#xff0c;又离不开给它们提供能源的电池。在网上有许许多多的“延长电池寿命小技巧”&#xff0c;比如“新买的电子设备&#xff0c;第一次充电之前要把电都放完”“笔记本电脑一直插着电源可以保护电池”“长期不用…

区分COCO数据集的coco minival和coco test-dev、conda常用命令和python -m 的作用

1、COCO数据集的测试集coco minival和coco test-dev: 两个数据集在官方网站对应的内容如下所示&#xff1a; COCO数据集官网&#xff1a;https://cocodataset.org/#download 两个数据集的区分参考网址&#xff1a;https://zhuanlan.zhihu.com/p/533676547 2、conda常用命令…

linux中epoll+socket实战

目录 参考前言案例 一、epoll的基本使用首先是epoll_create函数&#xff1a;然后是epoll_ctl函数&#xff1a;最后是epoll_wait函数&#xff1a;关于ET&#xff08;边沿触发&#xff09;、LT&#xff08;水平触发&#xff09;两种工作模式可以得出这样的结论: 二、使用代码简易…

基于html+css的图展示71

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

某音X-Bogus算法研究 2023-05-15

本文以教学为基准&#xff0c;研究JavaScript算法及反爬策略、本文提供的可操作性不得用于任何商业用途和违法违规场景。 如有侵权&#xff0c;请联系我进行删除。 今天我们分析一下douyin个人主页数据获取。 大多数小伙伴应该都知道想要拿到douyin的数据也不是那么容易的&a…

近世代数 笔记与题型连载 第十三章(环与域)

文章目录 基本概念1.环1.1.环的定义1.2.环的性质1.3.几种特殊的环1.4.子环 2.域2.1.域的定义2.2.环与域的同态 相关题型1.验证一个代数系统是否是一个环2.判断一个代数系统是否是整环3.判断一个代数系统是否是另一个代数系统的子环4.判断一个代数系统是否是域 基本概念 1.环 …