前言
??极客领航搁置许久,正临寒假,时间充裕,便多写写,并无商业用途,只做技术分析,若有兴趣一起交流。 ??文末有完整的工程文件,若有所获,请点赞支持可否。
极客领航网址 极客领航教程体系
一、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);
loadimage(&backImg, L"3.jpg", 520, 520);
putimage(0, 0, &backImg);
setbkmode(TRANSPARENT);
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);
loadimage(&backImg, L"3.jpg", 520, 520);
putimage(0, 0, &backImg);
setbkmode(TRANSPARENT);
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;
bool mkShift;
bool mkLButton;
bool mkMButton;
bool mkRButton;
short x;
short y;
short wheel;
};
??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
#define offsety 20
#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);
}
else if (color == 2)
{
setfillcolor(BLACK);
}
solidcircle(x * piece_size + offsetx, y * piece_size + offsety, piece_size / 2);
}
三、五子棋核心程序
1.放置棋子
??放置棋子,返回true表示放置成功,false 表示放置失败。因为有棋子的地方不能再放置棋子,所以绘制棋子之前要判断该位置是否还能放置棋子,下面是放置棋子的函数。
bool pieceSet(int y, int x, int color)
{
if (map[x][y] != 0)
{
return false;
}
map[x][y] = color;
return true;
}
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;
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) {
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);
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);
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();
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
|