C语言课程设计之井字棋

井字棋,想必我们大家都玩过,那么今天就让我们一起来看一下如何利用c语言和EasyX图形库实现井字棋的制作。

编写前的准备

  • 下载好EasyX图形库,并安装在VS上。
  • 对井字棋规则进行深度了解
  • 对函数,数组,循环知识的复习

整体思路

下棋第一步,我们需要准备棋盘和棋子,我们可以利用图片制作棋盘,同时利用图片制作棋子,由于井字棋是3X3布局所以,这里我们采用二维数组的形式来对棋盘上每个位置进行标记。定义map[3] [3]并将其全部初始化为0,同时为了区分棋子,我们规定用户棋子在数组中的值为1,AI为-1,当棋子落入棋盘后,将对应的数组位置填入对应的数字。考虑到规则,一共有八种情况每一个里面有三个位置,每个位置有横纵坐标,此时我们可以想到通过三维数组惊醒定义Key[8] [3] [2]。在这里我们使用loadimage函数实现地图与棋子的加载。由于我们要多次使用棋子,我们可以将棋子定义为IMAGE类型的变量用来标记地图。当棋子下在对应方格中时,对应的map[i] [j]应该发生改变。当然,前面的-1,1我们可以定义为枚举类型来使用,这样能够使我们的结构更加清晰。

void gameInit() {
	//创建游戏窗口并设计大小
	initgraph(654,654);//宽度和高度
	//构建背景
	loadimage(0,"S_game4.png");//地图
	loadimage(&imgMan, "Man.png");//棋子
	loadimage(&imgAI, "AI.png");//棋子
	//对地图进行初始化
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			map[i][j] = 0;
		}
	}
	srand(time(NULL));
}

用户信息的输入

首先我们需要写一个死循环保证游戏的运行,由于用户是从鼠标点击棋盘不同位置进行下棋,故我们通过下列代码实现操作:

MOUSEMSG msg= GetMouseMsg();//定义MOUSEMSG类型的变量,获取用户鼠标信息
		//判断鼠标信息类型
		if (msg.uMsg == WM_LBUTTONDOWN) {//WM_LBUTTONDOWN  鼠标左键按下  
			//用户下棋
			manGo(&msg);
			//判断结果

manGo函数接收到鼠标信息后,进行确定位置和输出棋子:

void manGo(MOUSEMSG*msg) {
	int H = msg->y / 218; //行
	int L = msg->x /218; //列
	//判断用户所选位置是否有棋子
	if (map[H][L] == 0) {
		map[H][L] = Man;//将该位置标记为用户使用
		//设置坐标,将标记呈现在所在行列
		int x = L * 218 +10;
		int y = H * 218 +10;
		//使用putimage画图
		putimage(x,y,&imgMan);
	}
}

AI信息的输入

AI是要模拟人的思想进行操作具体想法如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bYYHOFiF-1671679880549)(C:/Users/wwwZ1/AppData/Roaming/Typora/typora-user-images/image-20221222105055552.png)]

​ 我们可以定义一个find函数用于发现用户与电脑所下棋子与八种情况之间的重合度。根据井字棋规则可知,中间位置为最佳位置,四个顶角位置次之,故先通过map数组对中间位置进行判断,如果map[1] [1] ==0,则说明用户没有下在中间,如果map[1] [1]==1说明用户下在中间,自己则下在四个顶角的任意位置。当下两个棋子以上时,通过find函数的返回值进行对用户或自己是否能胜利进行判断,如果返回值大于0,说明在八种情况中自己符合某种情况,可以取胜,反之不能取胜。

定义的find函数的原理:例如:find(2)首先对八种情况的每一种进行遍历,判断用户所下位置是否是在一种情况中的两个位置,如果是,返回i,说明用户在下一次能够取胜,反之,不能。AI情况与之类似。

int find(int value) {
	for (int i = 0; i < 8; i++) {//step1:对8种制胜的情况进行遍历		
		int s = 0;//用于记录3个位置的标记情况
		for (int j = 0; j < 3; j++) {//step2:对每种情况中的3个可能位置进行遍历
			int H = Key[i][j][0];//行坐标
			int L = Key[i][j][1];//列坐标
			s += map[H][L];
			}
		if (s == value) {
			return i;
		}
	}	return -1;
}

结果的判断

下棋的结局一共有三种情况,分别是用户胜利,AI胜利,平局,这时我们可以用之前定义的find函数进行判断是否胜利。平局的情况,我们可以从棋盘是否被占满来进行判断,首先定义一个count变量进行计数,当棋盘中map[i] [j]==0,count++,最后再使用if条件语句进行判断棋盘是否占满。

注意:棋盘的横纵坐标,枚举,函数的使用,EasyX图形库的应用。
在这里插入图片描述

代码实现

#define _CRT_SECURE_NO_WARNINGS 1
//编写前的准备
/*
* 安装Easyx图形库
* 了解井字棋规则
* 对数组、循环、函数知识回顾
//作用:
initgraph   创建一个窗口
loadimage   加载图片到指定位置
GetMouseMsg();   获取用户鼠标信息
*/
#include<stdio.h>
#include<graphics.h>//用于画图,Easyx图形库
#include<time.h>//用于生成随机数
//用于播放后台音乐
#include<windows.h>
#pragma comment(lib,"Winmm.lib")
enum { AI = -1, Man = 1 };//枚举------>标记棋子
int map[3][3];//设计地图
IMAGE imgMan;//定义一个IMAGE类型的变量用于存放用户输入的标志
IMAGE imgAI;//定义一个IMAGE类型的变量用于存放电脑输入的标志
//棋子胜利有8种情况,3个格子,每个格子有x、y两个坐标
/*********************************************************************************************************/
int Key[8][3][2] = {
	0,0, 0,1, 0,2,//第一行
	1,0, 1,1, 1,2,//第二行
	2,0, 2,1, 2,2,//第三行
	0,0, 1,0, 2,0,//第一列
	0,1, 1,1, 2,1,//第二列
	0,2, 1,2, 2,2,//第三列
	0,0, 1,1, 2,2,//正对角线
	0,2, 1,1, 2,0//反对角线
};
/*********************************************************************************************************/
int find(int value) {
	for (int i = 0; i < 8; i++) {//step1:对8种制胜的情况进行遍历		
		int s = 0;//用于记录3个位置的标记情况
		for (int j = 0; j < 3; j++) {//step2:对每种情况中的3个可能位置进行遍历
			int H = Key[i][j][0];//行坐标
			int L = Key[i][j][1];//列坐标
			s += map[H][L];
			}
		if (s == value) {//判断结果
			return i;
		}
	}	return -1;
}
/*********************************************************************************************************/
void gameInit() {
	//创建游戏窗口并设计大小
	initgraph(654,654);//宽度和高度
	//构建背景
	loadimage(0,"S_game4.png");
	loadimage(&imgMan, "Man.png");
	loadimage(&imgAI, "AI.png");
	//初始化棋盘
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			map[i][j] = 0;
		}
	}
	srand(time(NULL));
}
/*********************************************************************************************************/
//用户下棋
void manGo(MOUSEMSG*msg) {
	int H = msg->y / 218; //行
	int L = msg->x /218; //列
	//判断用户所选位置是否有棋子
	if (map[H][L] == 0) {
		map[H][L] = Man;//将该位置标记为用户使用
		//设置坐标,将标记呈现在所在行列
		int x = L * 218 +10;
		int y = H * 218 +10;
		//使用putimage画图
		putimage(x,y,&imgMan);
	}
}
/*********************************************************************************************************/
//AI下棋
void aiGo() {
	int step = 0;
	int H, L;
/*********************************************************************************************************
* 判断哪些位置已经被选择,并记录总共所走的步数
* 遍历数组map的9个位置,确定哪些位置已经被占用
/*********************************************************************************************************/
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			if (map[i][j]) {
				step++;
			}
		}
	}
/*********************************************************************************************************
* 如果step=0,说明棋盘中没有位置被选中,只需要等待用户选择即可
* 井字棋最佳位置时中心位置,其次是四个顶角位置
/*********************************************************************************************************/
	if (step == 1) {
		//判断中间位置是否有棋子
		if (map[1][1] == 0) {
			H = 1;
			L = 1;
		}//将棋子位置放在中间
		else {
			H = 0;
			L = 2;
		}//将棋子位置放在四个顶角之一的位置
	}
/*********************************************************************************************************
* 当step=2时,对可能出现的情况进行分析
/**********************************************************************************************************/
	else {
		int i = find(-2);
		//当返回值后,i<0,证明没有找到使得AI一举获胜的情况
		if (i < 0) {
			i = find(2);//对用户情况的判断
		}
		if (i >= 0)//说明用户有获胜的可能 
		{
			//在第i条路径上的空白位置落子(每条路径有三个位置)
			for (int j = 0; j < 3; j++) {
				int H1 = Key[i][j][0];//确定获胜路径中的横坐标
				int L1 = Key[i][j][1];//确定获胜路径中的纵坐标
				if (map[H1][L1] == 0) {//将棋子放在获胜路径中的空位置,实现阻止用户获胜
					H = H1;
					L = L1;
					break;//已经找到位置,终止循环
				}
			}
		}
/*********************************************************************************************************/
		else//用户不能一举获胜的情况,棋子位置随意
		{
			while (1) {
				H = rand() % 3;//产生小于3的随机数,赋给行坐标
				L = rand() % 3;//产生小于3的随机数,赋给列坐标
				if (map[H][L] == 0) {
					break;//棋子填入空位置,终止循环
				}
			}
		}
	}
	putimage(L * 218 + 15, H* 218 + 15, &imgAI);//将标记放在AI所选位置
	map[H][L] = AI;//将-1给当前坐标,表明该位置已经被占用
}
/*********************************************************************************************************/
//定义布尔型函数,检查结果
bool check() {
	int i = find(3);
	//用户获胜
	Sleep(250);//保证每次操作都有间歇期
	if (i >= 0)
	{
		loadimage(0, "WIN2.png");
		system("pause");
		//对地图进行初始化,实现新游戏
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 3; j++) {
				map[i][j] = 0;
			}
		}
		loadimage(0, "S_game4.png");//地图重加载
		return true;
	}
/*********************************************************************************************************/
	//AI获胜
	i = find(-3);
	Sleep(250);//保证每次操作都有间歇期
	if(i>=0){
		loadimage(0, "Defeat.png");
		system("pause");
		//对地图进行初始化,实现新游戏
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 3; j++) {
				map[i][j] = 0;
			}
		}
		loadimage(0, "S_game4.png");//地图重加载
		return true;
	}
/*********************************************************************************************************/
	//平局情况  地图上面没有空余位置
	int count = 0;//用于统计map数组中是否含有0
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			if (map[i][j] == 0) {
				count++;
			}
		}
	}
	if (count == 0)//count=0说明棋盘中没有空余位置
	{
		loadimage(0, "Ping.png");
		system("pause");
		//对地图进行初始化,实现新游戏
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 3; j++) {
				map[i][j] = 0;
			}
		}
		loadimage(0, "S_game4.png");//地图重加载
		return true;
	}
	return false;
}
/*********************************************************************************************************/
int main(void) {
	//初始化
	gameInit();//用于构建游戏所需要的条件
	//游戏的主循环
	//设置游戏背景音乐
	mciSendString("open 井字游戏.mp3 alias bkmusic", NULL, 0, NULL);
	mciSendString("play bkmusic repeat", NULL, 0, NULL);//循环播放音乐
	while (1) {
		//开始下棋
		MOUSEMSG msg= GetMouseMsg();//定义MOUSEMSG类型的变量,获取用户鼠标信息
		//判断鼠标信息类型
		if (msg.uMsg == WM_LBUTTONDOWN) {//WM_LBUTTONDOWN  鼠标左键按下  
			//用户下棋
			manGo(&msg);
			//判断结果
			if (check()) {
				continue;//如果胜利,跳过循环
			};
			//电脑下棋
			aiGo();
			check();
		}
	}
	system("pause");//设置暂停,使窗口停留
	return 0;
}
/*********************************************************************************************************/

效果展示

在这里插入图片描述

感兴趣的小伙伴还可以为它添加多种音效,开始界面的美化,以及其他功能。井字棋的教学到这里就结束了。

重要的事情说三遍:

一定要先捋清楚思路再尝试代码实现!!!一定要先捋清楚思路再尝试代码实现!!!一定要先捋清楚思路再尝试代码实现!!!

在这里插入图片描述

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啥也不会写呢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值