基于博弈树的开源五子棋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;elseenemyPlayerScore += 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.rhkb.cn/news/222706.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

R语言贝叶斯网络模型、INLA下的贝叶斯回归、R语言现代贝叶斯统计学方法、R语言混合效应(多水平/层次/嵌套)模型

目录 ㈠ 基于R语言的贝叶斯网络模型的实践技术应用 ㈡ R语言贝叶斯方法在生态环境领域中的高阶技术应用 ㈢ 基于R语言贝叶斯进阶:INLA下的贝叶斯回归、生存分析、随机游走、广义可加模型、极端数据的贝叶斯分析 ㈣ 基于R语言的现代贝叶斯统计学方法&#xff08;贝叶斯参数估…

微信小程序promise封装

一. 在utils文件夹内创建一个request.js 写以下封装的 wx.request() 方法 const baseURL https:// 域名 ; //公用总路径地址 export const request (params) > { //暴露出去一个函数&#xff0c;并且接收一个外部传入的参数let dataObj params.data || {}; //…

数字化生活:数据可视化的力量

数据可视化已经逐渐渗透到我们的日常生活中&#xff0c;并在许多方面为我们提供了便利与启发。它不仅仅是数据的图形展示&#xff0c;更是一种改变我们对信息理解方式的工具。下面我就以可视化从业者的角度出发&#xff0c;简单聊聊如何让数据可视化为我们的生活服务。 首先&am…

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

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

[BackdoorCTF 2023] pwn

一个国外小比赛&#xff0c;还好能下载附件。 Baby Formatter 一连3道格式化字符串&#xff0c;但都不难 有两个菜单&#xff0c;一个是泄露栈地址和libc&#xff0c;另一个是格式化字符串但过滤掉了pudx&#xff0c;好在还有大量可用的符号。比如li 以长整型的方式输出。 …

华为全屋wifi6蜂鸟套装标准

华为政企42 华为政企 目录 上一篇华为安防监控摄像头下一篇华为企业级无线路由器

<JavaEE> 网络编程 -- 网络编程和 Socket 套接字

目录 一、网络编程的概念 1&#xff09;什么是网络编程&#xff1f; 2&#xff09;网络编程中的基本概念 1> 收发端 2> 请求和响应 3> 客户端和服务端 二、Socket套接字 1&#xff09;什么是“套接字”&#xff1f; 2&#xff09;Socket套接字的概念 3&…

案例135:基于微信小程序的房屋租赁管理系统的设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

RIPV1配置实验

查看路由器路由表&#xff1a; 删除手工配置的静态路由项&#xff1a; Route1->Config->static Remove删除路由项 删除Route3的路由项&#xff0c;方法同上删除Route2的路由项&#xff0c;方法同上 完成路由器RIP配置&#xff1a; Route1->Config->RIP->Ne…

软件渗透测试有哪些测试流程?权威安全测试报告的重要性

软件渗透测试也是安全测试的一种&#xff0c;是通过模拟恶意黑客的攻击方法&#xff0c;来评估计算机网络系统安全的一种评估方法。作为网络安全防范的一种新技术&#xff0c;对于网络安全组织具有实际应用价值。 一、软件渗透测试的过程   软件渗透测试的过程通常包括四个主…

使用bs4 分析html文件

首先需要 pip install beautifulsoup4安装 然后为了方便学习此插件&#xff0c;随便打开一个网页&#xff0c;然后鼠标右键&#xff0c;打开源网页&#xff0c;如下图片 这样就可以获得一个网页源码&#xff0c;全选复制粘贴到本地&#xff0c;存储为 .html 文件&#xff0c;…

案例101:基于微信小程序的停车共享小程序

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

【网络安全】学习Web安全必须知道的一本书

【文末送书】今天推荐一本网络安全领域优质书籍。 目录 正文实战案例1&#xff1a;使用Docker搭建LAMP环境实战案例2&#xff1a;使用Docker搭建LAMP环境文末送书 正文 学习Web安全离不开Web&#xff0c;那么&#xff0c;需要先来学习网站的搭建。搭建网站是每一个Web安全学习…

项目中webpack优化配置(1)

项目中webpack优化配置 一. 开发效率&#xff0c; 体验 1. DLL&#xff08;开发过程中减少构建时间和增加应用程序的性能&#xff09; 使用 DllPlugin 进行分包&#xff0c;使用 DllReferencePlugin(索引链接) 对 manifest.json 引用&#xff0c;让一些基本不会改动的代码先…

【Java JMM】编译和优化

1 前端编译 在 Java 技术下, “编译期” 是一个比较含糊的表述, 因为它可能指的是 前端编译器 (“编译器的前端” 更准确一些) 把 *.java 文件转变成 *.class 文件的过程Java 虚拟机的即时编译器 (常称 JIT 编译器, Just In Time Compiler) 运行期把字节码转变成本地机器码的过…

Ubuntu 常用命令之 chown 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 chown 命令在 Ubuntu 系统中用于改变文件或目录的所有者和组。这个命令的基本格式是 chown [选项]... [所有者][:[组]] 文件...。 chown 命令的主要参数有 -c 或 --changes&#xff1a;类似 verbose&#xff0c;但只在发生改变时…

【泛型中K T V E? Object等分别代表什么含 义】

✅ 泛型中K T V E? Object等分别代表什么含义 ✅ 典型解析✅代码示例 ✅ 典型解析 E - Element (在集合中使用&#xff0c;因为集合中存放的是元素) T-Type (Java 类) K- Key (键) V - Value (值) N - Number (数值类型) ? - 表示不确定的iava类型 (无限制通配符类型) …

树莓派-Pico控制舵机

目录 前言一、SG90舵机是什么&#xff1f;参数介绍工作原理 二、与舵机信号线的接线图三、给树莓派Pico注入灵魂&#xff08;代码&#xff09;总结 前言 这价格便宜的树莓派Pico总觉得应该拿来做点什么&#xff0c;它总不能只用来点亮几个灯就没别的用途了吧&#xff0c;所以就…

自制数据库空洞率清理工具-C版-01-EasyClean-V1.0(支持南大通用数据库Gbase8a)

目录 一、环境信息 二、简述 三、支持功能 四、空洞率 五、工具流程图 六、安装包下载地址 七、参数介绍 1、命令模板 2、命令样例 3、参数表格 八、安装步骤 1、配置环境变量 2、生效环境变量 3、检验动态链接是否正常 九、运行效果 一、环境信息 名称值CPUInt…

力扣题目学习笔记(OC + Swift)19. 删除链表的倒数第 N 个结点

19. 删除链表的倒数第 N 个结点 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 此题目为链表题&#xff0c;拿出我们的杀手锏&#xff0c;链表解题经典三把斧&#xff1a; 哑巴节点栈快慢指针 关于内存问题&#xff1a;由于Swift及…