五子棋项目设计
1.0版本,实现基本的双人对决,判断输赢
简单的双人对决很简单,AI才是重点内容,涉及了大量的算法和数学知识,贪婪算法,博弈树、评估函数、极大极小值搜索、启发式搜索、α-β剪枝等等,博主不是研究算法的,可能就是做到给个评分机制,简单AI实现
地图
传统的地图是15*15,这次的项目也不例外
#define MAP_ROW 15
#define MAP_COL 15
int Map[MAP_ROW][MAP_COL];
为了美观,给地图添加一个外边框,上下左右留30个像素
双方对决标志
两个人对决,白子下在什么位置,黑子即将落在什么位置
COORD currentPos, waitPos;
注意:COORD是Easyx提供的类,或者说是windows.h提供的坐标类
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
轮到谁下棋了,也得给个标志
int flag; // 1白棋 或 -1黑棋
主体设计
初始化,游戏绘制,数据更新
需要实现的功能:
void GameInit(); //游戏初始化
void GameDraw(); //游戏绘制
void GameUpdate(); //数据更新
bool Juge(); //返回游戏结果 比赛结束返回1 仍比赛中返回0
void Game_Juge(); //判断
主函数
int main()
{
initgraph(420 + 60, 420 + 60, 0);
GameInit();
while (1)
{
GameDraw();
GameUpdate();
}
closegraph();
return 0;
}
初始化
初始化比较简单,因为地图不是图片资源,所以只需要初始化一些数据
void GameInit()
{
//初始化当前坐标和待下坐标
ZeroMemory(&Map, sizeof(Map));
ZeroMemory(¤tPos, sizeof(COORD));
ZeroMemory(&waitPos, sizeof(COORD));
Map[7][7] = -1; //黑棋先下
flag = 1; //白子后下
currentPos.X = 7;
currentPos.Y = 7;
}
游戏绘制
地图绘制的话我是一步一步绘制
第一步:绘制整个地图,480*480
第二步:绘制棋盘左、上的数字和字母
第三步:绘制棋盘行列线
上面的步骤是绘制棋盘,下面就开始绘制棋子和标识
第四步:绘制棋子
第五步:绘制标识(标识就是最近下棋位置和等待落子位置
完整代码如下:
void GameDraw()
{
BeginBatchDraw();
/******************* 绘制棋盘 ********************/
wchar_t arr[4];
setbkcolor(WHITE); //设置窗口背景颜色
setbkmode(1); //设置文字背景透明
setlinestyle(PS_SOLID, 2); //线样式设置
setlinecolor(BLACK); //设置线颜色
settextcolor(BLACK); //设置文字颜色
setfillcolor(RGB(255, 205, 150)); // 填充颜色设置
cleardevice(); //清屏
solidrectangle(0, 0, 480, 480); //绘制无边框的正方形
//绘制行
for (int row = 1; row <= 15; row++)
{
outtextxy(10, row * 30 - 15, 64 + row);
line(30, row * 30, 420 + 30, row * 30);
}
//绘制列
for (int col = 1; col <= 15; col++)
{
wsprintf(arr, L"%d", col);
outtextxy(col * 30 - 5, 5, arr);
line(col * 30, 30, col * 30, 420 + 30);
}
setfillcolor(BLACK); // 填充颜色设置
solidcircle(4 * 30, 4 * 30, 5);
solidcircle(12 * 30, 4 * 30, 5);
solidcircle(4 * 30, 12 * 30, 5);
solidcircle(12 * 30, 12 * 30, 5);
solidcircle(8 * 30, 8 * 30, 5);
/******************* 绘制棋子 ********************/
for (int y = 0; y < MAP_ROW; y++)
{
for (int x = 0; x < MAP_COL; x++)
{
if (Map[y][x] == 1)
{
//绘制白子
setfillcolor(WHITE); // 填充颜色设置
solidcircle((x + 1) * 30, (y + 1) * 30, 10);
}
if (Map[y][x] == -1)
{
//绘制白子
setfillcolor(BLACK); // 填充颜色设置
solidcircle((x + 1) * 30, (y + 1) * 30, 10);
}
}
}
/******************* 绘制状态 ********************/
setlinecolor(RED); //设置线颜色
for (int y = 0; y < 2; y++)
{
for (int x = 0; x < 2; x++)
{
//currentPos绘制
//画横线
line(currentPos.X * 30 + 30 - 15, currentPos.Y * 30 + 30 - 15 + y * 30,
currentPos.X * 30 + 30 - 5, currentPos.Y * 30 + 30 - 15 + y * 30);
line(currentPos.X * 30 + 30 + 5, currentPos.Y * 30 + 30 - 15 + y * 30,
currentPos.X * 30 + 30 + 15, currentPos.Y * 30 + 30 - 15 + y * 30);
//画竖线
line(currentPos.X * 30 + 30 - 15 + 30 * y, currentPos.Y * 30 + 30 - 15,
currentPos.X * 30 + 30 - 15 + 30 * y, currentPos.Y * 30 + 30 - 5);
line(currentPos.X * 30 + 30 - 15 + 30 * y, currentPos.Y * 30 + 30 + 5,
currentPos.X * 30 + 30 - 15 + 30 * y, currentPos.Y * 30 + 30 + 15);
//waitPos绘制
line(waitPos.X * 30 + 30 - 15, waitPos.Y * 30 + 30 - 15 + y * 30,
waitPos.X * 30 + 30 - 5, waitPos.Y * 30 + 30 - 15 + y * 30);
line(waitPos.X * 30 + 30 + 5, waitPos.Y * 30 + 30 - 15 + y * 30,
waitPos.X * 30 + 30 + 15, waitPos.Y * 30 + 30 - 15 + y * 30);
//画竖线
line(waitPos.X * 30 + 30 - 15 + 30 * y, waitPos.Y * 30 + 30 - 15,
waitPos.X * 30 + 30 - 15 + 30 * y, waitPos.Y * 30 + 30 - 5);
line(waitPos.X * 30 + 30 - 15 + 30 * y, waitPos.Y * 30 + 30 + 5,
waitPos.X * 30 + 30 - 15 + 30 * y, waitPos.Y * 30 + 30 + 15);
}
}
EndBatchDraw();
}
数据更新
这需要你了解鼠标消息,了解书标配移动消息WM_MOUSEMOVE和鼠标左键点击消息WM_LBUTTONUP;其实这里换成鼠标左键双击WM_LBUTTONDBLCLK更好。
更多详细鼠标操作请查看Easyx提供的帮助文档示例程序
void GameUpdate()
{
MOUSEMSG msg = GetMouseMsg(); // 获取鼠标信息
int x = msg.x;
int y = msg.y;
switch (msg.uMsg)
{
case WM_MOUSEMOVE:
// 鼠标移动 确保鼠标在棋盘区域
if (x >= 30 && x <= 450 && y >= 30 && y <= 450)
{
waitPos.X = (x - 30 + 15) / 30;
waitPos.Y = (y - 30 + 15) / 30;
}
break;
case WM_LBUTTONUP:
//确保鼠标在棋盘区域
if (x >= 15 && x <= 465 && y >= 15 && y <= 465)
{
//此处无子
if (Map[(y - 30 + 15) / 30][(x - 30 + 15) / 30] == 0)
{
//下子,改变currentPos
currentPos.X = (x - 30 + 15) / 30;
currentPos.Y = (y - 30 + 15) / 30;
//map赋值
Map[currentPos.Y][currentPos.X] = flag;
flag = -flag;
}
}
GameDraw();
Game_Juge();
break; // 按鼠标右键退出程序
}
}
判断输赢
上面的所有操作都是计算地图位置,简单无脑的计算,所以代码量也很少,才150行.现在到了输赢判断阶段,会有大量的逻辑操作,因为你要判断4个方向是否五子连珠其实也可以说是8个方向
因为上面描述了一个标识坐标currentPos,就是最近一次落子的地方,我们只需要判断最近一次落子的地方是否存在五子连珠就可以
左右判断
左右找的规则就是找左边(currentPos.X - index)第n个和currentpos不一样的子,
找右边(currentPos.X + index)第n个和currentpos不一样的子,上下左右东南西北都找一遍即可。因为篇幅问题只举例左右判断。
int begin = 0, end = 0;
/*========左右========*/
for (int index = 1; index <= 5; index++)
{
//往左找
if (currentPos.X - index >= 0 && Map[currentPos.Y][currentPos.X - index] != Map[currentPos.Y][currentPos.X])
{
begin = index;
break;
}
else if (currentPos.X - index < 0)
{
begin = index;
break;
}
}
for (int index = 1; index <= 5; index++)
{
//往右找
if (currentPos.X + index < 15 && Map[currentPos.Y][currentPos.X + index] != Map[currentPos.Y][currentPos.X])
{
end = index;
break;
}
else if (currentPos.X + index >= 15)
{
end = index;
break;
}
}
if (begin + end >= 6)
{
return true;
}
很繁琐有木有,代码还多的一批,因为以前没写过五子棋,自己找感觉判断的。后来写的差不多,遇到了AI问题,就去博客逛了逛,发现一个思路一致但是代码更精练的同学写的一篇博客,地址如下
https://www.cnblogs.com/liugangjiayou/p/10686886.html
我想狡辩一下:其实我代码虽然多,但是一旦横向判断五子连着,就结束后面的判断,整体可能…卧槽,狡辩不下去了,好吧是我菜。
源码获取
关注微信公众号 编程学习基地 发送关键字 五子棋 免费获取源码