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

news2025/4/21 3:19:51

引子

静态棋盘的评估是棋力的一个很重要的体现,一个优秀的基于博弈树搜索的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;
        }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【视觉实践】使用Mediapipe进行目标检测:杯子检测和椅子检测实践

目录 1 Mediapipe 2 Solutions 3 安装mediapipe 4 实践 1 Mediapipe Mediapipe是google的一个开源项目,可以提供开源的、跨平台的常用机器学习(machine learning,ML)方案。MediaPipe是一个用于构建机器学习管道</

Python之Django项目的功能配置

1.创建Django项目 进入项目管理目录&#xff0c;比如&#xff1a;D盘 执行命令&#xff1a;diango-admin startproject demo1 创建项目 如果提示diango命令不存在&#xff0c;搜索diango-admin程序的位置&#xff0c;然后加入到环境变量path中。 进入项目&#xff0c;cd demo…

层次分析法

层次分析法主要用于解决评价类问题(例如选择哪种方案最好&#xff0c;哪位运动员或者员工表现的更优秀) 先用一道引出层次分析法的例题&#xff1a;小明同学高考填完志愿后&#xff0c;小明想出去旅游。在查阅了网上的攻略后&#xff0c;他初步选择了苏杭、北戴河和桂林三地之一…

[node]Node.js 模块系统

[node]模块系统 Node.js中的模块系统模块的使用模块的导入模块的导出导出多个值导出默认值导出可传参的函数 文件查找策略从文件模块缓存中加载从原生模块加载从文件加载 Node.js中的模块系统 为了让Node.js的文件可以相互调用&#xff0c;Node.js提供了一个简单的模块系统。 …

Web组态可视化编辑器-by组态

演示地址&#xff1a; http://www.by-lot.com http://www.byzt.net web组态可视化编辑器&#xff1a;引领未来可视化编辑的新潮流 随着网络的普及和快速发展&#xff0c;web组态可视化编辑器应运而生&#xff0c;为人们在网络世界中创建和编辑内容提供了更加便捷的操作方式。这…

步兵 cocos2dx 加密和混淆

文章目录 摘要引言正文代码加密具体步骤代码加密具体步骤测试和配置阶段IPA 重签名操作步骤 总结参考资料 摘要 本篇博客介绍了针对 iOS 应用中的 Lua 代码进行加密和混淆的相关技术。通过对 Lua 代码进行加密处理&#xff0c;可以确保应用代码的安全性&#xff0c;同时提高性…

H266/VVC帧内预测编码

预测编码技术 预测编码&#xff08;Prediction Coding&#xff09;是指利用已编码的一个或多个样本值&#xff0c;根据某种模型或方法&#xff0c;对当前的样本值进行预测&#xff0c;并对样本真实值和预测值之间的差值进行编码。 视频中的每个像素看成一个信源符号&#xff…

基于iOS平台的车牌识别表情识别项目

基于iOS平台的车牌识别&&表情识别项目 简介 ​ 该项目客户端搭载于iOS平台&#xff0c;服务端搭载于阿里云服务器&#xff0c;主要功能是通过拍照或选取相册图片来进行车牌的识别以及人脸表情识别。本文便是对项目整体流程设计思路和具体实现做一个详细介绍。 整体实…

LSTM和GRU vs 普通的循环神经网络RNN

1、考虑下列三种情况下&#xff0c;对比一下普通RNN的表现和LSTM和GRU表现&#xff1a; &#xff08;1&#xff09;早期观测值对预测未来观测者具有非常重要的意义。 考虑一个极端情况&#xff0c;其中第一个观测值包含一个校验和&#xff0c; 目标是在序列的末尾辨别校验和是…

微软的word文档中内置背景音乐步骤(打开自动播放)

目录 一、前言 二、操作步骤 一、前言 有时候需要在word文档里面打开的时候就自动播放音乐或者音频&#xff0c;那么可以用微软的word来按照操作步骤去这样完成。 如果没有微软office的&#xff0c;可以下载这个是2021专业版的。因为office只能免费使用一段时间&#xff0c…

蓝牙物联网与嵌入式开发如何结合?

蓝牙物联网与嵌入式开发可以紧密结合&#xff0c;以实现更高效、更智能的物联网应用。以下是一些结合的方式&#xff1a; 嵌入式开发为蓝牙设备提供硬件基础设施和控制逻辑&#xff1a;嵌入式系统可以利用微处理器和各种外设组成的系统&#xff0c;为蓝牙设备提供硬件基础设施和…

【三】【C语言\动态规划】珠宝的最高价值、下降路径最小和、最小路径和,三道题目深度解析

动态规划 动态规划就像是解决问题的一种策略&#xff0c;它可以帮助我们更高效地找到问题的解决方案。这个策略的核心思想就是将问题分解为一系列的小问题&#xff0c;并将每个小问题的解保存起来。这样&#xff0c;当我们需要解决原始问题的时候&#xff0c;我们就可以直接利…

docusaurus简介及使用心得

docusaurus简介 Docusaurus 是 Facebook 专门为开源项目开发者提供的一款易于维护的静态网站创建工具&#xff0c;使用 Markdown 即可更新网站。构建一个带有主页、文档、API、帮助以及博客页面的静态网站&#xff0c;只需5分钟。 同类竞品还有vuepress&#xff0c;docusaurus…

2.3_2 进程互斥的软件实现方法

2.3_2 进程互斥的软件实现方法 #mermaid-svg-MEJSSglXzFe6Q501 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-MEJSSglXzFe6Q501 .error-icon{fill:#552222;}#mermaid-svg-MEJSSglXzFe6Q501 .error-text{fill:#5522…

Python实现AR协方差结构线性回归模型(GLSAR算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 GLSAR是具有AR协方差结构的广义最小二乘法线性回归模型。 本项目通过GLSAR回归算法来构建AR协方差结构…

Postman接口测试(超详细整理)

常用的接口测试工具主要有以下几种 Postman&#xff1a;简单方便的接口调试工具&#xff0c;便于分享和协作。具有接口调试&#xff0c;接口集管理&#xff0c;环境配置&#xff0c;参数化&#xff0c;断言&#xff0c;批量执行&#xff0c;录制接口&#xff0c;Mock Server, …

Rust学习:HelloWorld

Rust学习&#xff1a;HelloWorld HelloWorldRust语言简介主要特点先看程序分析程序 HelloWorld Rust语言简介 Rust是一种系统编程语言&#xff0c;旨在提供内存安全、并发性和性能。它由Mozilla Research开发&#xff0c;旨在解决C和C语言中的一些关键问题&#xff0c;特别是…

STM32的以太网外设+PHY(LAN8720)使用详解(7):以太网数据接收及发送测试

0 工具准备 1.野火 stm32f407霸天虎开发板 2.LAN8720数据手册 3.STM32F4xx中文参考手册 4.Wireshark1 以太网数据接收测试 1.1 以太网数据接收测试&#xff08;轮询&#xff09; 我们在主循环内轮询RX DMA描述符标志位查看是否接收到了数据&#xff0c;如果接收到了则将数据…

漏洞复现-大唐电信AC集中管理平台敏感信息泄漏漏洞(附漏洞检测脚本)

免责声明 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的…

Baumer工业相机堡盟工业相机如何通过BGAPI SDK实现Raw格式的图像保存(C#)

Baumer工业相机堡盟工业相机如何通过BGAPI SDK实现Raw格式的图像保存&#xff08;C#&#xff09; Baumer工业相机Baumer工业相机通过SDK实现Raw格式的图像保存的技术背景通过SDK获取相机信息的代码分析Baumer工业相机回调函数里保存原始图像数据Baumer保存Raw图像格式重要核心代…