五子棋

前言

一个不是很好的五子棋项目,因为以前没写过五子棋,或者说对于没有人机对决的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

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

本文分享自微信公众号 - 编程学习基地(LearnBase),作者:DeRoy

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • qt国际化

    Translate_CN.ts 汉语­>汉语(一般不用改,我们使用的是汉语,只需要改汉译英的)

    DeROy
  • 继承

    虚继承是为了解决棱形继承中成员访问的二义性。在A B继承方式前加关键字virtual,编译器将Base的数据保存在一个公共位置,通过虚基表访问。

    DeROy
  • 函数

    在定义的函数的时候,如果在函数定义前调用,需要在调用的前面加上一个函数声明 告知这个函数存在

    DeROy
  • 为创业公司CEO定制的完美日程表:如何安排你的一天最高产?

    大数据文摘
  • 男人的三十岁是个多么大的分水岭?

    我是一名老程序员,也早过了30岁。因此,无论是我本人的经历,还是曾经见过的,还是眼前的30岁的程序员都不少。所以,今天我就以“过来人”的身份谈一下,程序员该如何...

    范蠡
  • 大数据售前的中年危机

    F今年三十八岁,有一个小孩,八岁了,老婆比他小5岁,最近又怀了二胎,也挺喜庆的。F是一家大数据公司的售前,算上奖金一年可以拿个五十万吧,勉强算个中产,按说小日子...

    Fayson
  • Hbase 学习(十一)使用hive往hbase当中导入数据

      我们可以有很多方式可以把数据导入到hbase当中,比如说用map-reduce,使用TableOutputFormat这个类,但是这种方式不是最优的方式。 ...

    岑玉海
  • linux定时任务的设置

    为当前用户创建cron服务 1.  键入 crontab  -e 编辑crontab服务文件       例如 文件内容如下:      */2 * * * *...

    joshua317
  • linux创建定时任务

         */2 * * * * /bin/sh /home/admin/jiaoben/buy/deleteFile.sh 

    似水的流年
  • 宝塔面板如何设置提高服务器性能

    前期准备:安装宝塔Linux面板最新版,安装Linux工具箱,安装LNMP或LAMP环境

    路小白

扫码关注云+社区

领取腾讯云代金券