前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >五子棋

五子棋

作者头像
DeROy
修改2020-08-19 11:03:17
9640
修改2020-08-19 11:03:17
举报
文章被收录于专栏:编程学习基地编程学习基地

前言

一个不是很好的五子棋项目,因为以前没写过五子棋,或者说对于没有人机对决的AI五子棋,感觉没什么好写的。当然,我对算法这块也不怎么强,上次有朋友留言要五子棋项目,所以试着去写了下五子棋AI算法,用的是贪心算法,还没写完整,就先发个简单的双人对局五子棋简单版.

网络上找了一个不错的带AI的C++五子棋项目,五子棋算法看的也是这个博主提供的文章,整个源代码以及示例程序获取方式

目录

五子棋项目设计地图双方对决标志主体设计需要实现的功能:主函数初始化游戏绘制数据更新判断输赢左右判断AI

五子棋项目设计

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(&currentPos, 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

我想狡辩一下:其实我代码虽然多,但是一旦横向判断五子连着,就结束后面的判断,整体可能….卧槽,狡辩不下去了,好吧是我菜。

AI

AI我还没写完整,因为发现研究的越深,头越大,不知道会不会写完整的发到公众号上面去,不管发不发,思路我给大家,这是一篇,前辈写的关于五子棋,带人机对决的,遗憾的是界面用的是控制台,

五子棋AI思路:

https://www.cnblogs.com/songdechiu/p/5768999.html

博主的资源我已经下载下来了,免费给大家安排一波

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程学习基地 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 五子棋项目设计
    • 地图
      • 双方对决标志
        • 主体设计
          • 需要实现的功能:
          • 主函数
        • 初始化
          • 游戏绘制
            • 数据更新
              • 判断输赢
                • 左右判断
              • AI
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档