基于博弈树的开源五子棋AI教程[4 静态棋盘评估]

引子

静态棋盘的评估是棋力的一个很重要的体现,一个优秀的基于博弈树搜索的AI往往有上千行工作量,本文没有做深入讨论,仅仅写了个引子用来抛砖引玉。
评估一般从两个角度入手,一个是子力,另一个是局势。

1 评估维度

1.1子力

所谓的子力,也就是每个子的重要程度,这边用基础棋型来衡量。通过扫描匹配棋型,重要的棋型给予更大的值,这里棋型得分表参考了网上的数值。
另一种衡量子力的方式是是利用五元组,通过判定五元组内子的连续性和阻断性赋予不同的分数。

//------定义基础棋型------//
#define ChessNone          0    // 空棋型:0
#define ChessSleepingOne   1    // 眠一  :0
#define ChessActiveOne     2    // 活一  :20
#define ChessSleepingTwo   3    // 眠二  :20
#define ChessActiveTwo     4    // 活二  :120
#define ChessSleepingThree 5    // 眠三  :120
#define ChessActiveThree   6    // 活三  :720
#define ChessBrokenFour    7    // 冲四  :720
#define ChessActiveFour    8    // 活四  :4320
#define ChessFive          9    // 连五  :50000
#define ChessSix           10   // 长连  :50000
//基础棋型得分表
static const QHash<quint8, int> UtilChessPatternScore ={
    {ChessNone,          0},       // 0: 无棋子
    {ChessSleepingOne,   0},       // 1: 眠一
    {ChessActiveOne,     20},      // 2: 活一
    {ChessSleepingTwo,   20},      // 3: 眠二
    {ChessActiveTwo,     120},     // 4: 活二
    {ChessSleepingThree, 120},     // 5: 眠三
    {ChessActiveThree,   720},     // 6: 活三
    {ChessBrokenFour,    720},     // 7: 冲四
    {ChessActiveFour,    4320},    // 8: 活四
    {ChessFive,          50000},   // 9: 连五
    {ChessSix,           50000}    // 10: 长连
};

在后续棋型评估中,本文可以有选择性的开启可识别的基础棋型。

    //定义搜索棋型
    QVector<quint8> activateChessPattern = {
        //活棋型
        ChessActiveOne,ChessActiveTwo,ChessActiveThree,ChessActiveFour,ChessBrokenFour,
        //眠棋型
//        ChessSleepingTwo,ChessSleepingThree,
//        ChessSleepingOne,
        ChessFive,ChessSix
    };

一些特殊棋型需要进行修正,例如双活三,三四。本文在后面会依次介绍。

1.2 局势

所谓局势,就是一方可以轻松的组织起攻势,另一方或许防守,或许反击。通常来说,棋局子力越大,局势可能会更好。由于子力评估天然不关注空间位置,注定了无法准确衡量局势。图中子力[只评估了活棋型]相同,但是两者局势截然不同。

在这里插入图片描述
AI中并没有找到合适的方案来衡量不同的局势,因此这一块暂时为空白状态。

2 实现

实现分成两个部分,一是基础棋型子力计算,二是基础棋型匹配算法。

2.1 子力计算

棋盘得分即是棋盘上所有点的子力。单点子力分成三步实现,第一步计算基础得分。第二步修正分数,修正分数的逻辑就是将活三,三四修正成一个活四。第三步禁手逻辑的处理。

//评分视角为evaluatePlayer
int GameBoard::evaluateBoard(MPlayerType evalPlayer)
{
    int score = 0;
    if (evalPlayer == PLAYER_NONE) return score;

    if(zobristSearchHash.getLeafTable(evalPlayer, score)){
        aiCalInfo.hitEvaluateBoardZobristHashCurrentTurn ++;
        return score;
    }
    QElapsedTimer timer;
    timer.start();
    aiCalInfo.evaluateBoardTimesCurrentTurn ++;

    int evaluatePlayerScore = 0;
    int enemyPlayerScore = 0;

    // 遍历整个棋盘
    for(const auto &curPoint : searchSpacePlayers){
        MPlayerType curPlayer = getSearchBoardPiece(curPoint.x(), curPoint.y());
        quint8 curChessPatterns[Direction4Num];
        getSearchBoardPatternDirection(curPoint.x(), curPoint.y(), curChessPatterns);
        int chessPatternCount[ChessPatternsNum] = {0};

        for(int direction = 0;direction < Direction4Num; ++direction){
            if(curPlayer == evalPlayer){
                evaluatePlayerScore += globalParam::utilChessPatternInfo.utilSpecialPatternScoreTable[curChessPatterns[direction]];
            }
            else{
                enemyPlayerScore += globalParam::utilChessPatternInfo.utilSpecialPatternScoreTable[curChessPatterns[direction]];
            }
            ++ chessPatternCount[curChessPatterns[direction]];
        }
        int fixedScore = 0;
        //修正分数
        if(chessPatternCount[ChessActiveThree] > 1){
            //多个活三修正成一个活四
            fixedScore += globalParam::utilChessPatternInfo.utilSpecialPatternScoreTable[ChessActiveFour] * 3;
        }

        if(chessPatternCount[ChessBrokenFour] + chessPatternCount[ChessActiveThree] > 1 || chessPatternCount[ChessBrokenFour] > 1)
        {
            //单活三单冲四修正成一个活四
            //双冲四修正成一个活四
            fixedScore += globalParam::utilChessPatternInfo.utilSpecialPatternScoreTable[ChessActiveFour] * 2;
        }


        //禁手逻辑
        if(globalParam::utilGameSetting.IsOpenBalanceBreaker && evalPlayer == PLAYER_BLACK){
            bool isTriggerBalanceBreaker = false;
            if(chessPatternCount[ChessActiveThree] > 1){
                //三三禁手(黑棋一子落下同时形成两个活三,此子必须为两个活三共同的构成子)
                fixedScore -= chessPatternCount[ChessActiveThree] * globalParam::utilChessPatternInfo.utilSpecialPatternScoreTable[ChessActiveThree];
                isTriggerBalanceBreaker = true;
            }
            if(chessPatternCount[ChessActiveFour] + chessPatternCount[ChessBrokenFour]>1)
            {
                //四四禁手(黑棋一子落下同时形成两个或两个以上的冲四或活四)
                fixedScore -= chessPatternCount[ChessActiveFour] * globalParam::utilChessPatternInfo.utilSpecialPatternScoreTable[ChessActiveFour];
                fixedScore -= chessPatternCount[ChessBrokenFour] * globalParam::utilChessPatternInfo.utilSpecialPatternScoreTable[ChessBrokenFour];
                isTriggerBalanceBreaker = true;
            }
            if(chessPatternCount[ChessSix] > 0)
            {
                //长连禁手(黑棋一子落下形成一个或一个以上的长连)
                fixedScore -= chessPatternCount[ChessSix] * globalParam::utilChessPatternInfo.utilSpecialPatternScoreTable[ChessSix];
                isTriggerBalanceBreaker = true;
            }

            if(isTriggerBalanceBreaker)
                fixedScore -= 5 * globalParam::utilChessPatternInfo.utilSpecialPatternScoreTable[ChessSix];
        }

        if(curPlayer == evalPlayer)
            evaluatePlayerScore += fixedScore;
        else
            enemyPlayerScore += fixedScore;
    }

    UtilCalculateScore(score, evaluatePlayerScore, enemyPlayerScore,globalParam::utilGameSetting.AttackParam);

    zobristSearchHash.appendLeafTable(evalPlayer, evaluatePlayerScore, enemyPlayerScore);

    aiCalInfo.AIEvaluateBoardTime += timer.nsecsElapsed();
    return score;
}

2.2 棋型匹配算法

棋型匹配方案和算法都有多种。方案一般就及时匹配,增广的匹配。及时匹配是指对于一个给定的棋盘,扫描所有的行来匹配棋型。增广匹配是指利用在已知原有棋型的棋盘上增加一子后,仅扫描匹配变动行的棋型。对于算法我尝试了三种,第一种是字符串的暴力匹配,第二种是改进的位暴力匹配,第三种是AC自动机的匹配。
本文采用的是增广匹配+位暴力匹配的模式来完成的。

//这一段代码即是在原有棋盘上添加evaluatePoint后,更新evaluatePoint所在行列上点的棋型
void GameBoard::updatePointPattern(const MPoint &evaluatePoint)
{
    //拓展后的位置
    if(!isValidSearchPosition(evaluatePoint)) return;

    int row = evaluatePoint.x();
    int col = evaluatePoint.y();

    for(int direction = 0;direction < Direction4Num;direction ++){
        int dx = UtilsSearchDirection4[direction].x();
        int dy = UtilsSearchDirection4[direction].y();
        for(int i = -globalParam::utilChessPatternInfo.maxChessPatternLength + 1;i <=globalParam::utilChessPatternInfo.maxChessPatternLength-1;i ++){
            //更新所在方向上的棋型
            int tmpRow = row + dx*i;
            int tmpCol = col + dy*i;
            if(searchBoardHasPiece(tmpRow,tmpCol)){
                setSearchBoardPatternDirection(tmpRow,tmpCol,direction,ChessNone);
                updatePointPattern(tmpRow, tmpCol, direction);
            }
        }
    }
}

下面给出的更新MPoint(row,col)direction上的棋型,四个方向的处理逻辑大同小异,仅以水平方向为例,循环匹配已经从大到小排好序的基础棋型直到找到一个最大的棋型后退出。匹配过程包含两部分,通过位运算提取棋盘的棋型,接着和库中棋型比较。对于比较也就是简单的几个int值的比较。

void GameBoard::updatePointPattern(MPositionType row, MPositionType col, int direction)
{
    //拓展后的位置
    MPlayerType evalPlayer = getSearchBoardPiece(row, col);

    int dx = UtilsSearchDirection4[direction].x();
    int dy = UtilsSearchDirection4[direction].y();

    if(getSearchBoardPiece(0,0,true) == evalPlayer)
        setSearchBoardBoarder(UtilReservePlayer(evalPlayer));

    quint16 curEvaluatePointChessPattern = ChessNone;

    int xx,yy;

    switch (direction) {
    case MHorizontal:
        //水平[右]
        for(int i = -globalParam::utilChessPatternInfo.maxChessPatternLength+1;i <=0;i ++){
            int tmpRow = row + dx*i;
            int tmpCol = col + dy*i;
            if(!isValidSearchPosition(tmpRow,tmpCol,true)) continue;

            for(int chessPatternId = globalParam::utilChessPatternInfo.chessPatternSize-1;chessPatternId>=0; chessPatternId --){
                int chessPatternLength = globalParam::utilChessPatternInfo.standLengthInfo[chessPatternId];
                int searchLength = - i + 1;
                if(searchLength > chessPatternLength) continue;
                if(globalParam::utilChessPatternInfo.standPatternInfo[chessPatternId] <= curEvaluatePointChessPattern) continue;

                int mask = (1 << chessPatternLength) - 1;
                int Datamask = (searchBoardMask[tmpRow] >> tmpCol) & mask;
                int Data = (searchBoard[tmpRow] >> tmpCol) & Datamask;

                int cpmask = globalParam::utilChessPatternInfo.utilWhiteChessPatternMaskInfo[chessPatternId];
                int cp = globalParam::utilChessPatternInfo.utilWhiteChessPatternDataInfo[chessPatternId];
                int cpReverse = globalParam::utilChessPatternInfo.utilBlackChessPatternDataInfo[chessPatternId];

                if( Datamask == cpmask && ((Data == cp && evalPlayer == PLAYER_WHITE)
                                           || (Data == cpReverse && evalPlayer == PLAYER_BLACK)))
                {
                    quint8 chessPattern = globalParam::utilChessPatternInfo.standPatternInfo[chessPatternId];
                    setSearchBoardPatternDirection(row,col,direction,chessPattern);
                    curEvaluatePointChessPattern = chessPattern;
                    break;
                }

            }
        }
        break;
        }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/263082.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SSH无密登陆配置

1 SSH介绍 ssh命令用于远程登录到其他计算机&#xff0c;实现安全的远程管理。 基本语法&#xff1a; ssh 域名/IP地址 示例&#xff1a; &#xff08;1&#xff09;从hadoop100服务器上远程连接hadoop101服务器 [hadoophadoop100 ~]$ ssh hadoop101 如果出现如下内容 Ar…

【C语言】动态内存管理基础知识——动态通讯录,如何实现通讯录容量的动态化

引言 动态内存管理的函数有&#xff1a;malloc,calloc,ralloc,free,本文讲解动态内存函数和使用&#xff0c;如何进行动态内存管理,实现通讯录联系人容量的动态化&#xff0c;对常见动态内存错误进行总结。 ✨ 猪巴戒&#xff1a;个人主页✨ 所属专栏&#xff1a;《C语言进阶》…

idea 远程调试linux上的代码

背景介绍 开发过程中&#xff0c;我们经常会遇到部署的代码运行出问题、看日志由不是很直观、我们希望可以像调试本地代码一样去调试远程代码; IDEA提供了Remote工具,基于JVM的跨平台能力&#xff0c;我们可以远程调试部署的代码。 前提 保证远程和本地跑的代码是一致的 操…

yocto系列讲解[实战篇]93 - 添加Qtwebengine和Browser实例

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 概述集成meta-qt5移植过程中的问题问题1:virtual/libgl set to mesa, not mesa-gl问题2:dmabuf-server-buffer tries to use undecl…

GBASE南大通用数据库在Windows和Linux中创建数据源

Windows 中数据源信息可能存在于两个地方&#xff1a;在 Windows 注册表中&#xff08;对 Windows 系统&#xff09;&#xff0c; 或在一个 DSN 文件中&#xff08;对任何系统&#xff09;。 如果信息在 Windows 注册表中&#xff0c;它叫做“机器数据源”。它可能是一个“用 …

Sentinel 流量治理组件教程

前言 官网首页&#xff1a;home | Sentinel (sentinelguard.io) 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形…

京东tp26旋转验证

记录一下&#xff0c;狗东的tp26旋转验证码&#xff0c;难点还是在这个轨迹上。我真的是一点都不喜欢玩轨迹&#xff01;&#xff01;&#xff01;&#xff01; 类似于百度旋转的图&#xff0c;不过他这个东西还是稍微有点差距的。 鉴于生病了脑子不太好使&#xff0c;就不过多…

全光谱全天候耐久性性能测试氙灯老化箱太阳光模拟器

氙灯老化箱应用领域 添加剂 & 着色剂胶粘剂 & 密封剂建材汽车食品& 饮料平面艺术 包装材料油漆& 涂料光伏塑料纺织品风能 & 太阳能消费类电子产品 氙灯老化箱描述 氙灯老化箱是一种用于模拟阳光、雨水和温度循环的老化测试设备。它使用氙灯作为光源&am…

移动SEO:如何针对任何设备优化您的网站

您快速进行 Google 搜索并阅读一堆结果。然后&#xff0c;您会发现一些网站具有您正在寻找的答案。 但是你从SERP中选择的第一个&#xff0c;也是最有前途的网站&#xff0c;在你最喜欢的移动设备上无法正常工作。 所以&#xff0c;你关闭它&#xff0c;看看下一个网站是否有…

Ansible自动化工具之Playbook剧本编写

目录 Playbook的组成部分 实例模版 切换用户 指定声明用户 声明和引用变量&#xff0c;以及外部传参变量 playbook的条件判断 ​编辑 习题 ​编辑 ansible-playbook的循环 item的循环 ​编辑 list循环 ​编辑 together的循环&#xff08;列表对应的列&#xff0…

初识Docker-什么是docker

Docker是一个快速交付应用、运行应用的技术 目录 一、Docker 二、运用场景 一、什么是Docker&#xff1f;它的作用是什么&#xff1f; Docker如何解决大型项目依赖关系复杂&#xff0c;不同组件依赖的兼容性问题? Docker允许开发中将应用、依赖、函数库、配置一起打包&…

专业数据中台哪个好?亿发数字化运营平台解决方案,助力大中型企业腾飞

数据中台的核心在于避免数据的重复计算&#xff0c;通过数据服务化的方式提升数据的共享能力&#xff0c;为数据应用赋能。 在信息技术时代&#xff0c;企业的信息系统建设通常是由各个组织和功能单元独立完成&#xff0c;以满足各自的需求。这导致了“数据孤岛”和“数据烟囱”…

华为端口隔离简单使用方法同vlan下控制个别电脑不给互通

必须得用access接口&#xff0c;hybrid口不行 dhcp enable interface Vlanif1 ip address 192.168.1.1 255.255.255.0 dhcp select interface interface MEth0/0/1 interface GigabitEthernet0/0/1 port link-type access port-isolate enable group 1 interface GigabitEther…

GBASE南大通用GBase 8a ODBC的安装文件

GBASE南大通用GBase 8a ODBC 体系结构是基于五个组件&#xff0c;在下图中所示&#xff1a; GBase 8a ODBC 体系结构图  应用 应用是通过调用 ODBC API 实现对 GBase 数据访问的程序。应用使用标准的 ODBC 调用与驱动程序管理器通信。应用并不关心数据存储在哪里&#xff…

技术分享-Jenkins

持续集成及Jenkins介绍 软件开发生命周期叫SDLC&#xff08;Software Development Life Cycle&#xff09;&#xff0c;集合了计划、开发、测试、部署过程。 在平常的开发过程中&#xff0c; 需要频繁地&#xff08;一天多次&#xff09;将代码集成到主干&#xff0c;这个叫持…

js 如何判断对象自身为空?很多人错了~

前言 如何判断一个对象为空是我们在开发中经常会遇到的问题&#xff0c;今天我们来聊聊几种经常使用的方法&#xff0c;以及在不同的场景下我们如何去使用。 1. JSON.stringify JSON.stringify 方法可以使对象序列化&#xff0c;转为相应的 JSON 格式。 const obj {};conso…

Qt 6.3 学习笔记

文章目录 Qt的安装和配置创建一个Qt项目信号和槽布局和控件绘图和动画数据库和网络 Qt是一个跨平台的C图形用户界面应用程序开发框架。它提供了创建GUI应用程序的工具和库。Qt 6.3是Qt的最新版本&#xff0c;引入了许多新特性和改进。在这个章节中&#xff0c;我们将详细介绍如…

Appearance-Motion Memory Consistency Network for Video Anomaly Detection 论文阅读

Appearance-Motion Memory Consistency Network for Video Anomaly Detection 论文阅读 AbstractIntroductionRelated WorkMethodExperimentsConclusions阅读总结 论文标题&#xff1a;Appearance-Motion Memory Consistency Network for Video Anomaly Detection 文章信息&am…

安全狗云原生安全-云甲·云原生容器安全管理系统

随着云计算的快速发展&#xff0c;容器技术逐渐成为主流。然而&#xff0c;随着容器的普及&#xff0c;安全问题也日益突出。为了解决这一问题&#xff0c;安全狗推出了云原生容器安全管理系统——云甲。 云甲是安全狗云原生安全的重要组成部分&#xff0c;它采用了先进的云原生…

二分查找法详解(6种变形)

前言 在之前的博客中&#xff0c;我给大家介绍了最基础的二分查找法&#xff08;没学的话点我点我&#xff01;&#xff09; 今天我将带大家学习二分法的六种变形如何使用&#xff0c;小伙伴们&#xff0c;快来开始今天的学习吧&#xff01; 文章目录 1&#xff0c;查找第一个…