基于C++的五子棋的二次开发
原程序简介
该软件利用C++和easyx图形化界面的构建搭建了一个简洁的五子棋下棋界面,如下:
点击查看代码
// 绘制棋盘
void draw()
{
int modle = 0; // 棋盘样式
int number = 0; // 坐标输出的位置
// 坐标(数值)
TCHAR strnum[19][3] = { _T("1"),_T("2") ,_T("3") ,_T("4"),_T("5") ,_T("6") ,_T("7"),_T("8"),_T("9"),_T("10"), _T("11"),_T("12") ,_T("13") ,_T("14"),_T("15") ,_T("16") ,_T("17"),_T("18"),_T("19") };
// 坐标(字母)
TCHAR strabc[19][3] = { _T("A"),_T("B") ,_T("C") ,_T("D"),_T("E") ,_T("F") ,_T("G"),_T("H"),_T("I"),_T("J"), _T("K"),_T("L") ,_T("M") ,_T("N"),_T("O") ,_T("P") ,_T("Q"),_T("R"),_T("S") };
for (int i = 0, k = 0; i < 570; i += 30)
{
for (int j = 0, g = 0; j < 570; j += 30)
{
BOX[k][g].color = RGB(255, 205, 150);// 棋盘底色
// x、y 坐标
BOX[k][g].x = 65 + j;
BOX[k][g].y = 50 + i;
BOX[k][g].number = 0;// 初始化分数
// 棋盘样式的判断
if (k == 0 && g == 0)
{
modle = 8;
}
else if (k == 0 && g == 18)
{
modle = 7;
}
else if (k == 18 && g == 18)
{
modle = 6;
}
else if (k == 18 && g == 0)
{
modle = 5;
}
else if (k == 0)
{
modle = 3;
}
else if (k == 18)
{
modle = 4;
}
else if (g == 0)
{
modle = 1;
}
else if (g == 18)
{
modle = 2;
}
else if ((k == 3 && g == 3) || (k == 3 && g == 15) || (k == 15 && g == 3) || (k == 15 && g == 15) || (k == 3 && g == 9) || (k == 9 && g == 3) || (k == 15 && g == 9) || (k == 9 && g == 15) || (k == 9 && g == 9))
{
modle = 9;
}
else
{
modle = 0;
}
BOX[k][g].modle = modle;
BOX[k][g].draw(); // 绘制
if (BOX[k][g].isnew == true)
{
BOX[k][g].isnew = false; // 把上一个下棋位置的黑框清除
}
g++;
}
k++;
}
// 画坐标
LOGFONT nowstyle;
gettextstyle(&nowstyle);
settextstyle(0, 0, NULL);
for (int i = 0; i < 19; i++)
{
outtextxy(75 + number, 35, strnum[i]);
outtextxy(53, 55 + number, strabc[i]);
number += 30;
}
settextstyle(&nowstyle);
}
基本功能包括下棋后黑白棋的转换,以及输赢的判定,棋盘落子的生成等,代码如下:
点击查看代码
// 判断输赢
void is WIN()
{
bool isinit = true; // 是否刚刚开局
for (int i = 0; i < 19; i++)
{
for (int j = 0; j < 19; j++)
{
if (BOX[i][j].value != -1)
{
// 遍历每个可能的位置
isinit = false; // 如果有,那么就不是刚刚开局
int nowcolor = BOX[i][j].value; // 现在遍历到的颜色
int length[4] = { 0,0,0,0 }; // 四个方向的长度
for (int k = 0; k < 4; k++)
{
// 原理同寻找最佳位置
int nowi = i;
int nowj = j;
while (nowi <= 18 && nowj <= 18 && nowi >= 0 && nowj >= 0 && BOX[nowi][nowj].value == nowcolor)
{
length[k]++;
nowj += dx[k];
nowi += dy[k];
}
nowi = i;
nowj = j;
while (nowi <= 18 && nowj <= 18 && nowi >= 0 && nowj >= 0 && BOX[nowi][nowj].value == 1 - nowcolor)
{
length[k]++;
nowj -= dx[k];
nowi -= dy[k];
}
}
for (int k = 0; k < 4; k++)
{
if (length[k] >= 5) {
// 如果满五子
if (nowcolor == playercolor)
{
win = playercolor; // 玩家胜
}
if (nowcolor == 1 - playercolor)
{
win = 1 - playercolor; // 电脑胜
}
}
}
if ((!isinit) && findbestseat(playercolor).number == 0 && findbestseat(1 - playercolor).number == 0)
{
// 如果不是开局且双方无最佳位置
win = 2; // 平局
}
}
}
}
}
// 胜利处理
settextcolor(RGB(0, 255, 0));
Sleep(1000);
if (win == 0)
{
outtextxy(320, 320, _T("白胜"));
}
if (win == 1)
{
outtextxy(320, 320, _T("黑胜"));
}
if (win == 2)
{
outtextxy(320, 320, _T("平局"));
}
// 给反应时间
Sleep(5000);
系统缺陷
1、落子动作过于僵硬,界面太小,如果没有注视到很难看出上一个落子在哪里。
2、由于格子太小,鼠标瞄点过于模糊,容易点错格子造成失误操作。
3、仅提供棋盘支持两个人对弈,对于孤独的使用者不够友好。
改进方向
1、添加标识框,对最后一颗落子进行框定,达到方便查看的效果。
2、同上,利用标识框瞄准格子方便落子。
3、添加五子棋自动下棋功能,考虑利用逻辑算分判优达到效果。
功能实现
1、新棋子标识框
首先在棋盘类中新建变量用于确定新棋子
bool isnew = false; // 是否有选择框
然后编写函数,画八根颜色较浅的线进行框选
点击查看代码
COLORREF thefillcolor = getfillcolor(); // 备份填充颜色
setlinestyle(PS_SOLID, 2); // 线样式设置
setfillcolor(color); // 填充颜色设置
solidrectangle(x, y, x + 30, y + 30); // 绘制无边框的正方形
if (isnew)
{
// 如果是新下的
// 绘制边框线
setlinecolor(LIGHTGRAY);
line(x + 1, y + 2, x + 8, y + 2);
line(x + 2, y + 1, x + 2, y + 8);
line(x + 29, y + 2, x + 22, y + 2);
line(x + 29, y + 1, x + 29, y + 8);
line(x + 2, y + 29, x + 8, y + 29);
line(x + 2, y + 22, x + 2, y + 29);
line(x + 29, y + 29, x + 22, y + 29);
line(x + 29, y + 22, x + 29, y + 29);
}
在main函数中的下棋主要while循环前加入判断,触发选框,使得在鼠标在棋盘移动的时候不断更新框的位置
点击查看代码
if (mouse.mkLButton)
{
// 如果按下了
BOX[i][j].value = playercolor; // 下棋
BOX[i][j].isnew = true; // 新位置更新
oldi = -1;
oldj = -1;
// 下一个玩家
whoplay = 1;
goto DRAW;
}
// 更新选择框
BOX[oldi][oldj].isnew = false;
BOX[oldi][oldj].draw();
BOX[i][j].isnew = true;
BOX[i][j].draw();
oldi = i;
oldj = j;
效果如下:
最终效果为,第一颗棋子落下出现框框住棋子,第二颗棋子落下前另出现一个框追随鼠标;待第二颗棋子落
下是框住新落下的棋子,第一个框消失,新的框追随鼠标移动;保持界面上一个框框住最新出现的棋子,另一个框追随鼠标。效果如
下:
2、电脑下棋功能
参考GitHub大神进行魔改了一个下棋功能,利用基础棋谱进行情况的分数赋值后找到最佳位置落子,步骤如下:
评分表(Score)
我们首先定义了一个评分表,用于评估棋局的局势。该表以不同的局势情况为索引,例如防守0子、防守1子、防守2子
等。每个局势情况都对应一个具体的评分,这些评分是根据经验和策略确定的,反映了对应局势的优劣程度。
点击查看代码
// 评分表
int Score[3][5] = {
{ 0, 20, 360, 5800, 92840 }, // 防守0子
{ 0, 0, 20, 360, 92840 }, // 防守1子
{ 0, 0, 0, 0, 92840 } // 防守2子
};
seat bestseat; // 最佳位置
int MAXnumber[361] = { 0 }; // 最佳分数(可能有多个)
int MAXx[361] = { 0 }; // 最佳 x 坐标(可能有多个)
int MAXy[361] = { 0 }; // 最佳 y 坐标(可能有多个)
int number = 0; // 下一个最佳分数储存位置
int truenumber; // 输出的最佳分数位置
int nowi = 0; // 现在遍历到的y坐标
int nowj = 0; // 现在遍历到的x坐标
int length[4]; // 四个方向的长度
int emeny[4]; // 四个方向的敌子
遍历空位
程序会遍历棋盘上的每个空位,尝试在这些空位下一步棋。
点击查看代码
for (int i = 0; i < 19; i++)
{
for (int j = 0; j < 19; j++)
{
if (BOX[i][j].value == -1)
{
// 遍历每一个可能的位置
// 自己
BOX[i][j].value = color; // 尝试下在这里
评分计算
对于每个空位,程序会分别考虑在该位置下自己的棋子和敌人的棋子对局势的影响:
-
对于自己的棋子,程序会模拟在该位置下棋后,沿着四个方向(水平、垂直、两个对角线)计算棋子的连续长度,并根据连续长度和评分表计算该位置的得分。
-
对于敌人的棋子,程序同样会模拟在该位置下敌人的棋子后,沿着四个方向计算敌人棋子的连续长度,并根据评分表计算该位置的得分。
点击查看代码
for (int k = 0; k < 4; k++)
{
length[k] = 0;
emeny[k] = 0;
nowi = i;
nowj = j;
while (nowi <= 18 && nowj <= 18 && nowi >= 0 && nowj >= 0 && BOX[nowi][nowj].value == color)
{
length[k]++;
nowj += dx[k];
nowi += dy[k];
}
if (BOX[nowi][nowj].value == 1 - color || nowi < 0 || nowj < 0 || nowi > 18 || nowj > 18)
{
emeny[k]++;
}
nowi = i;
nowj = j;
while (nowi <= 18 && nowj <= 18 && nowi >= 0 && nowj >= 0 && BOX[nowi][nowj].value == color)
{
length[k]++;
nowj -= dx[k];
nowi -= dy[k];
}
if (BOX[nowi][nowj].value == 1 - color || nowi < 0 || nowj < 0 || nowi > 18 || nowj > 18)
{
emeny[k]++;
}
length[k] -= 2; // 判断长度
if (length[k] > 4)
{
length[k] = 4;
}
BOX[i][j].number += Score[emeny[k]][length[k]] * 4 + !(!length[k]) * 20;//加分系统
length[k] = 0;
emeny[k] = 0;
}
// 敌人(原理同上)
BOX[i][j].value = 1 - color;
for (int k = 0; k < 4; k++)
{
length[k] = 0;
emeny[k] = 0;
nowi = i;
nowj = j;
while (nowi <= 18 && nowj <= 18 && nowi >= 0 && nowj >= 0 && BOX[nowi][nowj].value == 1 - color)
{
length[k]++;
nowj += dx[k];
nowi += dy[k];
}
if (BOX[nowi][nowj].value == color || nowi < 0 || nowj < 0 || nowi > 18 || nowj > 18)
{
emeny[k]++;
}
nowi = i;
nowj = j;
while (nowi <= 18 && nowj <= 18 && nowi >= 0 && nowj >= 0 && BOX[nowi][nowj].value == 1 - color)
{
length[k]++;
nowj -= dx[k];
nowi -= dy[k];
}
if (BOX[nowi][nowj].value == color || nowi < 0 || nowj < 0 || nowi > 18 || nowj > 18)
{
emeny[k]++;
}
length[k] -= 2;
if (length[k] > 4)
{
length[k] = 4;
}
BOX[i][j].number += Score[emeny[k]][length[k]];
length[k] = 0;
emeny[k] = 0;
}
BOX[i][j].value = -1;
}
选择最佳位置
程序会记录每个空位的得分,并找出其中得分最高的位置或位置们。如果有多个位置的得分相同,则随机选择其中一个位
置作为最佳位置。
点击查看代码
if (BOX[i][j].number == MAXnumber[0])
{
// 如果和最高分数相同
MAXnumber[number] = BOX[i][j].number;
MAXy[number] = i;
MAXx[number] = j;
number++;
// 新增一个分数及坐标
}
if (BOX[i][j].number > MAXnumber[0])
{
// 如果比最高分数高
for (int k = 0; k < number; k++)
{
MAXnumber[k] = 0;
MAXy[k] = 0;
MAXx[k] = 0;
}
number = 0;
MAXnumber[number] = BOX[i][j].number;
MAXy[number] = i;
MAXx[number] = j;
number++;
// 清空数组再加入
}
返回最佳位置
return bestseat;
这样就完成了一个五子棋机器人,舍友姚某创实名上网认证打不过(
效果如下:
结语
这样就完成了这个五子棋游戏的改造,需要整个的源代码可以去我的GitHub仓库查看:https://github.com/GaoYao2023/C-gobang

浙公网安备 33010602011771号