IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言之五子棋项目 基于EasyX图形库 -> 正文阅读

[C++知识库]C语言之五子棋项目 基于EasyX图形库


前言

??极客领航搁置许久,正临寒假,时间充裕,便多写写,并无商业用途,只做技术分析,若有兴趣一起交流。
??文末有完整的工程文件,若有所获,请点赞支持可否。

极客领航网址
极客领航教程体系

一、EasyX图形库下载与安装

1.EasyX图形库下载

若已经添加EasyX图形库,可跳过该步骤。
??EasyX图形库官网下载
??文末工程文件中也有,可用百度网盘下载。
下载会得到下面这些文件。
在这里插入图片描述

2.VS2019设置EasyX图形库

??解压之后,将include中的两个文件放到VS相应include目录中,lib中的文件一样。进入自己安装VS的目录,加粗的固定的目录。

  • 下面是我安装的目录:
    C:\Program Files (x86)\Microsoft Visual Studio\ 2019\Community\VC\Auxiliary\VS
    在这里插入图片描述
    ??把easyx里include的两个文件粘贴到VS的include文件夹里面,lib里面的文件也一样,但是需注意,lib里面有x64和x86两个文件夹,这两个文件夹都需要复制文件进去。
    在这里插入图片描述

下面是具体操作步骤:

  • 打开下载的EasyX文件,将include中的文件复制到VS文件夹下的include中,如下图。
    在这里插入图片描述
    在这里插入图片描述
  • 打开下载的EasyX文件,将lib下的x64和x86里面的文件分别复制出来,粘贴到VS安装的对应目录中。
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述在这里插入图片描述
??这样easyx图形库就成功的安装的VS2019上面了,但是需要注意复制和粘贴的路径,别弄混了。

二、五子棋中用到的图形库知识

1.初始化页面

??在项目中我们是创建一个520×520的窗口,然后加载指定的图片,设置相同的大小,替换成背景。我们需要了解相关函数的使用,可以下载参考手册查阅,也能浏览在线文档
查看参考手册可知:
??initgraph()函数介绍:用于初始化图形窗口。
??loadimage()函数介绍:用于从文件中读取图像。
??putimage()函数介绍:用于在当前设备上绘制指定的图像。
??setbkmode()函数介绍:设置当前设备孵化和文本输出时的背景模式。

代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <graphics.h> //引用图形库头文件
#include <conio.h>
IMAGE backImg;	 //保存图片的数据类型
int main(void)
{
	initgraph(520, 520); //520界面的宽 520界面的高 520*520个像素点
	loadimage(&backImg, L"3.jpg", 520, 520);
	//backImg 输出图片  //图形库  win32 MFC duilib opencv openGL qt
	putimage(0, 0, &backImg);
	//msg.  //mkCtrl 在发出指令的时候 是否按下Ctrl
	setbkmode(TRANSPARENT);  //设置背景的风格 tranparent(透明的)

	while (1);
	return 0;
}

??这里需要注意图片的路径与名称要正确,比如程序的3.jpg,就是图片的相对路径,我是把照片放在与工程文件相同的文件夹下。
程序运行:在这里插入图片描述

2.文字显示

通过下面这个代码设置显示的字体为楷体,
??settextstyle()函数介绍:设置当前字体的样式。
??settextcolor()函数介绍:设置当前文本颜色。
??outtextxy()函数介绍:用于输出指定位置的字符串。

代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <graphics.h> //引用图形库头文件
#include <conio.h>
IMAGE backImg;	 //保存图片的数据类型
int main(void)
{
	initgraph(520, 520); //520界面的宽 520界面的高 520*520个像素点
	loadimage(&backImg, L"3.jpg", 520, 520);
	//backImg 输出图片  //图形库  win32 MFC duilib opencv openGL qt
	putimage(0, 0, &backImg);
	//msg.  //mkCtrl 在发出指令的时候 是否按下Ctrl
	setbkmode(TRANSPARENT);  //设置背景的风格 tranparent(透明的)

	settextstyle(55, 0, L"楷体");    //设置字体的风格
	settextcolor(RGB(137, 57, 196)); //设置字体的颜色
	outtextxy(250, 180, L"人机对战!");   //输出文字
	outtextxy(250, 250, L"人人对战!");
	while (1);
	return 0;
}

程序运行:
在这里插入图片描述

3.鼠标操作

??对于图形化界面,鼠标操作不可缺少。在讲解程序前先简单为大家科普一下鼠标事件:
??鼠标是输入设备,只要发生以下的事件,就会暂存在鼠标消息列表中,我们的操作系统就会依次响应列表中的鼠标消息事件,常用的鼠标事件如下:

	WM_MOUSEMOVE——鼠标移动
	WM_MOUSEWHEEL——鼠标滚轮滚动
	WM_LBUTTONDOWN——鼠标左键按下
	WM_LBUTTONUP——鼠标左键弹起
	WM_LBUTTONDBLCLK——鼠标左键双击
	WM_RBUTTONDOWN——鼠标右键按下
	WM_RBUTTONUP——鼠标右键弹起
	WM_RBUTTONDBLCLK——鼠标左键双击
	WM_MBUTTONDOWN——鼠标中键按下
	WM_MBUTTONUP——鼠标中键弹起
	WM_MBUTTONDBLCLK——鼠标中键双击

??我们只需要根据不断获取鼠标消息队列的消息并根据消息依次进行响应即可。

提出两个问题:
?? 1. 在程序中怎么判断鼠标的位置?
?? 2. 在程序中怎么判断鼠标按下?

	MOUSEMSG msg = { 0 }; //定义一个结构体变量,与鼠标相关

??既然是结构体,那我们需要了解这个结构体的内容是什么:

struct MOUSEMSG
{
	UINT uMsg;				// 当前鼠标消息
	bool mkCtrl;			// Ctrl 键是否按下
	bool mkShift;			// Shift 键是否按下
	bool mkLButton;			// 鼠标左键是否按下
	bool mkMButton;			// 鼠标中键是否按下
	bool mkRButton;			// 鼠标右键是否按下
	short x;				// 当前鼠标 x 坐标
	short y;				// 当前鼠标 y 坐标
	short wheel;			// 鼠标滚轮滚动值 (120 的倍数)
};

??getmousemsg()函数介绍:用于获取鼠标消息。如果鼠标消息队列为空,请等待鼠标消息可用于检索。

	msg = GetMouseMsg();  //获取鼠标的消息

??就这样我们可以得到鼠标的消息,我们只要判断鼠标的消息,执行对应的内容,看下面代码。

    //鼠标的位置
	if (msg.x >= 255 && msg.x < 520 && msg.y >= 180 && msg.y <= 230)
	{
        //执行对应的内容
	}
	else if (msg.x >= 255 && msg.x < 520 && msg.y >= 250 && msg.y <= 300)
	{
  		//执行对应的内容
	}
	else
	{
		//执行对应的内容
	}

??因为msg = GetMouseMsg(); 获取了鼠标的消息,我们通过判断msg.x和msg.y的坐标,也就是判断鼠标的位置,就可以执行对应的内容。
??需要注意的是这里只是判断鼠标的位置,并没有按下按键。

鼠标按下检测:

switch (msg.uMsg) 
{
case WM_LBUTTONDOWN: //鼠标左键按下
	if (msg.x >= 255 && msg.x < 520 && msg.y >= 180 && msg.y <= 230) //在人机对战区域按下
	{
		playerVSAI(); //人机对战
		InitPage(); //重新显示页面
		break;
	}
	if (msg.x >= 255 && msg.x < 520 && msg.y >= 250 && msg.y <= 300) //在人人对战区域按下
	{
		playerVSplayer(); //人人对战
		InitPage(); //重新显示页面
		break;
	}
}

解读程序:
??当鼠标有消息,msg.uMsg就为真,进入switch ,然后case对应动作,比如

	case WM_LBUTTONDOWN: //鼠标左键按下
	case WM_RBUTTONDOWN: //鼠标右键按下

其他动作case 对应的数值就行。
??在上面的程序是判断鼠标左键,当左键按下,继续通过判断msg.x和msg.y的坐标,就能确定鼠标是在什么位置按下,如果是在人机对战文字区域上按下,就执行人机对战的函数;如果是在人人对战的文字区域上按下,就执行人人对战的函数,其他区域点击无效。

4.棋盘绘制

??setfillcolor()函数介绍:用于设置当前设备的填充颜色,程序中是填充黑色和白色,对应白棋和黑棋。
??solidcircle()函数介绍:用于绘制一个没有边框的实心圆,在项目中是画棋子。
??setlinecolor()函数介绍:用于设置当前线条颜色。
line()函数介绍:用于画一条线,通过循环画线,可以画出棋盘。

示例代码:

#include <stdlib.h>
#include <stdio.h>
#include <graphics.h> //引用图形库头文件
#include <conio.h>
#define offsetx 20 //绘图时x的偏移值
#define offsety 20 //绘图时x的偏移值
#define map_width 15//棋盘宽
#define map_height 15//棋盘高
#define piece_size 30	//棋子直径
IMAGE backImg;	 //保存图片的数据类型
int main(void)
{
	initgraph((map_width - 1) * piece_size + offsetx * 2, (map_height - 1) * piece_size + offsety * 2);
	//加载背景
	loadimage(NULL, _T("2.jpg"), map_width * piece_size + offsetx * 2, map_height * piece_size + offsety * 2);// 读取棋盘图片至绘图窗口 

	//画线
	setlinecolor(BLACK);
	for (int i = 0; i < map_width; i++)//竖线
	{
		line(i * piece_size + offsetx, offsety, i * piece_size + offsetx, offsety + (map_height - 1) * piece_size);
	}
	for (int i = 0; i < map_height; i++)//横线
	{
		line(offsetx, offsety + i * piece_size, (map_width - 1) * piece_size + offsetx, offsety + i * piece_size);
	}
	while (1);
	return 0;
}

注意: 示例程序需要加载背景图片,在上例程序是加载2.jpg,这个图片我是已经放在工程文件中了的。如果没有这个图片,会运行不了或者运行错误。
程序运行:
在这里插入图片描述

5.绘制棋子

??棋子绘制要结合鼠标判断,鼠标在对应的位置按下,就会在对应的位置绘制棋子,下面是绘制棋子的函数。

void drawPiece(int x, int y, int color) //绘制棋子
{
	if (color == 1)
	{
		setfillcolor(WHITE); //设置当前的填充颜色。 WHITE  0xFFFFFF 白色
	}
	else if (color == 2)
	{
		setfillcolor(BLACK); //黑色
	}
	solidcircle(x * piece_size + offsetx, y * piece_size + offsety, piece_size / 2); //用于画填充圆(无边框)。
}

三、五子棋核心程序

1.放置棋子

??放置棋子,返回true表示放置成功,false 表示放置失败。因为有棋子的地方不能再放置棋子,所以绘制棋子之前要判断该位置是否还能放置棋子,下面是放置棋子的函数。

//放置棋子,返回true表示放置成功,false 表示放置失败
bool pieceSet(int y, int x, int color) 
{
	if (map[x][y] != 0)//当前位置有棋子
	{
		return false;
	}
	map[x][y] = color;
	return true;
}

2.判断连棋

??当放置一个棋子过后,需要判断 - | \ /四个方向的棋子数,标记- | / \ 四个方向能连几个棋子,还要存储进行记录。这一部分比较复杂,也是五子棋最为核心的地方,可以用项目工程进行分析。

//判断当前位置4个方向连接的棋子数量,参数为棋子颜色 1白 2黑
void judge(int y, int x, int color) 
{
	//标记方向两头是不是有相反颜色的棋子
	bool flag1 = false, flag2 = false; 
	int count = 0;//连接棋子数量
	int addx = 0, addy = -1;//增量
	int posx = x, posy = y;//增加后的x,y
	// - 向
	while (1) 
	{
		posx += addx;
		posy += addy;
		if (posy < 0)//如果越界
		{
			flag1 = true;
			break;
		}
		if (map[posx][posy] == 0) //如果没有棋子
		{
			break;
		}
		if (map[posx][posy] != color) //如果为对方棋子
		{
			flag1 = true;
			break;
		}
		count++;
	}
	addy = 1;
	posx = x;
	posy = y;
	while (1) 
	{
		posx += addx;
		posy += addy;
		if (posy >= map_width)//如果越界
		{
			flag2 = true;
			break;
		}
		if (map[posx][posy] == 0) //如果没有棋子
		{
			break;
		}
		if (map[posx][posy] != color) //如果为对方棋子
		{
			flag2 = true;
			break;
		}
		count++;
	}
	flag[1][0] = 2;
	if (flag1) 
	{
		flag[1][0]--;
	}
	if (flag2) 
	{
		flag[1][0]--;
	}

	flag[0][0] = count + 1;
	count = 0;
	// | 向
	flag1 = false;
	flag2 = false;
	posx = x;
	posy = y;
	addx = -1;
	addy = 0;
	while (1) 
	{
		posx += addx;
		posy += addy;
		if (posx < 0) //如果越界
		{
			flag1 = true;
			break;
		}
		if (map[posx][posy] == 0) //如果没有棋子
		{
			break;
		}
		if (map[posx][posy] != color) //如果为对方棋子
		{
			flag1 = true;
			break;
		}
		count++;
	}
	addx = 1;
	posx = x;
	posy = y;
	while (1) 
	{
		posx += addx;
		posy += addy;
		if (posx >= map_height) //如果越界
		{
			flag2 = true;
			break;
		}
		if (map[posx][posy] == 0) //如果没有棋子
		{
			break;
		}
		if (map[posx][posy] != color) //如果为对方棋子
		{
			flag2 = true;
			break;
		}
		count++;
	}
	flag[1][1] = 2;
	if (flag1) 
	{
		flag[1][1]--;
	}
	if (flag2) 
	{
		flag[1][1]--;
	}
	flag[0][1] = count + 1;
	count = 0;
	// / 向
	flag1 = false;
	flag2 = false;
	posx = x;
	posy = y;
	addx = -1;
	addy = 1;
	while (1) 
	{
		posx += addx;
		posy += addy;
		if (posx < 0 || posy >= map_width) //如果越界
		{
			flag1 = true;
			break;
		}
		if (map[posx][posy] == 0) //如果没有棋子
		{
			break;
		}
		if (map[posx][posy] != color) //如果为对方棋子
		{
			flag1 = true;
			break;
		}
		count++;
	}
	addx = 1;
	addy = -1;
	posx = x;
	posy = y;
	while (1) 
	{
		posx += addx;
		posy += addy;
		if (posx >= map_height || posy < 0) //如果越界
		{
			flag2 = true;
			break;
		}
		if (map[posx][posy] == 0) //如果没有棋子
		{
			break;
		}
		if (map[posx][posy] != color) //如果为对方棋子
		{
			flag2 = true;
			break;
		}
		count++;
	}
	flag[1][2] = 2;
	if (flag1) 
	{
		flag[1][2]--;
	}
	if (flag2) 
	{
		flag[1][2]--;
	}
	flag[0][2] = count + 1;
	count = 0;
	// \ 向
	flag1 = false;
	flag2 = false;
	posx = x;
	posy = y;
	addx = -1;
	addy = -1;
	while (1) 
	{
		posx += addx;
		posy += addy;
		if (posy < 0 || posx < 0) //如果越界
		{
			flag1 = true;
			break;
		}
		if (map[posx][posy] == 0) //如果没有棋子
		{
			break;
		}
		if (map[posx][posy] != color) //如果为对方棋子
		{
			flag1 = true;
			break;
		}
		count++;
	}
	addx = 1;
	addy = 1;
	posx = x;
	posy = y;
	while (1) 
	{
		posx += addx;
		posy += addy;
		if (posx >= map_height || posy >= map_width) //如果越界
		{
			flag2 = true;
			break;
		}
		if (map[posx][posy] == 0) //如果没有棋子
		{
			break;
		}
		if (map[posx][posy] != color) //如果为对方棋子
		{
			flag2 = true;
			break;
		}
		count++;
	}
	flag[1][3] = 2;
	if (flag1) 
	{
		flag[1][3]--;
	}
	if (flag2) 
	{
		flag[1][3]--;
	}
	flag[0][3] = count + 1;
	count = 0;
}

3.人人对战

??人人对战比较简单,就是依次改变棋子的颜色放置,然后判断连棋。

void playerVSplayer() //人人对战
{
	initGame();
	initView();
	MOUSEMSG m;
	int x, y;
	while (1) {//game looping
		m = GetMouseMsg();
		//左键点击放置棋子
		if (m.mkLButton) {
			x = (m.x - offsetx + piece_size / 2) / piece_size;
			y = (m.y - offsety + piece_size / 2) / piece_size;
			if (pieceSet(x, y, currentPiece)) {
				drawPiece(x, y, currentPiece);
				judge(x, y, currentPiece);
				if (flag[0][0] >= 5 || flag[0][1] >= 5 || flag[0][2] >= 5 || flag[0][3] >= 5) {//分出胜负则退出
					if (currentPiece == 2)
						MessageBox(GetHWnd(), L"黑子胜出!", L"游戏结束:", MB_OKCANCEL);
					else
						MessageBox(GetHWnd(), L"白子胜出!", L"游戏结束:", MB_OKCANCEL);
					break;
				}
				//改变棋子颜色
				currentPiece += changePiece;
				changePiece *= -1;
			}
		}
	}
	closegraph();
}

4.人机对战

??人机对战对比人人对战,多了一个AI判断,通过调用AI函数,自己得到一个绘制的位置,而人人对战都是自己点击。这个部分难在你怎么写AI判断函数,写的简单人机就蠢,写的复杂人机就厉害,对应困难模式。下面这个AI行动函数比较简陋,有点蠢,以后有时间再修改。

void AI() 
{
	int oldScore = 0, newScore = 0;
	for (int i = 0; i < map_height; i++) //遍历棋盘
	{
		for (int j = 0; j < map_width; j++) 
		{
			if (map[i][j] != 0)
			{
				continue;//如果当前位置有棋子,不判断
			}
			judge(j, i, 1);//先判断白子(AI)
			for (int z = 0; z < 4; z++) 
			{
				if (flag[0][z] == 5)
				{
					newScore += 1000;
				}
				else if (flag[0][z] == 4)
				{
					newScore += 100 * flag[1][z];
				}
				else if (flag[0][z] == 3)
				{
					newScore += 10 * flag[1][z];
				}
				else if (flag[0][z] == 2)
				{
					newScore += 1 * flag[1][z];
				}
			}
			if (newScore > oldScore) 
			{
				oldScore = newScore;
				AIx = j; AIy = i;
			}
			newScore = 0;
			judge(j, i, 2);//再判断黑子(player)
			for (int z = 0; z < 4; z++) 
			{
				if (flag[0][z] == 5)
				{
					newScore += 1000;
				}
				else if (flag[0][z] == 4)
				{
					newScore += 100 * flag[1][z];
				}
				else if (flag[0][z] == 3)
				{
					newScore += 10 * flag[1][z];
				}
				else if (flag[0][z] == 2)
				{
					newScore += 1 * flag[1][z];
				}
			}
			if (newScore > oldScore) 
			{
				oldScore = newScore;
				AIx = j; AIy = i;
			}
		}
	}
}
void playerVSAI() //人机对战
{
	initGame(); //游戏初始化
	initView(); //页面初始化
	MOUSEMSG m;
	int x, y;
	while (1) 
	{
		m = GetMouseMsg(); //获取一个鼠标消息
		//左键点击放置棋子
		if (m.mkLButton) //左键按下
		{
			x = (m.x - offsetx + piece_size / 2) / piece_size;
			y = (m.y - offsety + piece_size / 2) / piece_size;
			if (pieceSet(x, y, currentPiece))
			{
				drawPiece(x, y, currentPiece);
				judge(x, y, currentPiece);
				if (flag[0][0] >= 5 || flag[0][1] >= 5 || flag[0][2] >= 5 || flag[0][3] >= 5) //分出胜负则退出
				{
					if (currentPiece == 2)
					{
						MessageBox(GetHWnd(), L"黑子胜出!", L"游戏结束:", MB_OKCANCEL);
					}
					else
					{
						MessageBox(GetHWnd(), L"白子胜出!", L"游戏结束:", MB_OKCANCEL);
					}
					break;
				}
				//改变棋子颜色
				currentPiece += changePiece;
				changePiece *= -1;
				//AI行动
				AI();
				pieceSet(AIx, AIy, 1);
				drawPiece(AIx, AIy, currentPiece);
				judge(AIx, AIy, currentPiece);
				if (flag[0][0] >= 5 || flag[0][1] >= 5 || flag[0][2] >= 5 || flag[0][3] >= 5) //分出胜负则退出
				{
					if (currentPiece == 2) 
					{
						MessageBox(GetHWnd(), L"黑子胜出!", L"游戏结束:", MB_OKCANCEL);
					}
					else
					{
						MessageBox(GetHWnd(), L"白子胜出!", L"游戏结束:", MB_OKCANCEL);
					}	
					break;
				}
				//改变棋子颜色
				currentPiece += changePiece;
				changePiece *= -1;
			}
		}
	}
	closegraph();
}

总结

??写的较为潦草,请见谅,有什么疑惑可留言或私聊。
??在程序中如果遇到不认识的系统函数,建议在参考手册中查阅,里面包含详细的解释。

链接:五子棋工程文件
提取码:0shl

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-01-03 15:53:52  更:2022-01-03 15:56:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:24:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码