当前位置: 首页 > news >正文

基于博弈树的开源五子棋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));auto checkAndUpdatePattern = [&](int xx, int yy, int* board, int* boardMask) {quint16 curEvaluatePointChessPattern = ChessNone;for(int chessPatternId = globalParam::utilChessPatternInfo.chessPatternSize-1; chessPatternId >= 0; chessPatternId--) {int chessPatternLength = globalParam::utilChessPatternInfo.standLengthInfo[chessPatternId];int mask = (1 << chessPatternLength) - 1;int Datamask = (boardMask[xx] >> yy) & mask;int Data = (board[xx] >> yy) & Datamask;if(globalParam::utilChessPatternInfo.standPatternInfo[chessPatternId] <= curEvaluatePointChessPattern) continue;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;}}};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;int xx, yy, *board, *boardMask;switch (direction) {case MMainDiagonal:if(abs(tmpRow - tmpCol) > boardSize - 5) continue;xx = (tmpRow > tmpCol) ? boardSize - 5 - tmpRow + tmpCol : boardSize - 5 - tmpRow + tmpCol;yy = (tmpRow > tmpCol) ? tmpCol : tmpRow;board = searchBoardMainDiag;boardMask = searchBoardMainDiagMask;break;case MSubDiagonal:if(tmpRow + tmpCol < 6 || tmpRow + tmpCol > boardSize * 2 - 4) continue;xx = tmpRow + tmpCol - 6;yy = (tmpRow + tmpCol < boardSize + 1) ? tmpCol : boardSize + 1 - tmpRow;board = searchBoardSubDiag;boardMask = searchBoardSubDiagMask;break;case MHorizontal:xx = tmpRow;yy = tmpCol;board = searchBoard;boardMask = searchBoardMask;break;case MVertical:xx = tmpCol;yy = tmpRow;board = searchBoardVertical;boardMask = searchBoardVerticalMask;break;}checkAndUpdatePattern(xx, yy, board, boardMask);}
}

相关文章:

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

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

STL--排序与检索

题目 现有N个大理石&#xff0c;每个大理石上写了一个非负整数。首先把各数从小到大排序&#xff0c;然后回答Q个问题。每个问题是否有一个大理石写着某个整数x,如果是&#xff0c;还要回答哪个大理石写着x。排序后的大理石从左到右编写为1-N。&#xff08;样例中&#xff0c;…...

大数据处理与分析-Spark

导论 (基于Hadoop的MapReduce的优缺点&#xff09; MapReduce是一个分布式运算程序的编程框架&#xff0c;是用户开发“基于Hadoop的数据分析应用”的核心框架 MapReduce是一种用于处理大规模数据集的编程模型和计算框架。它将数据处理过程分为两个主要阶段&#xff1a;Map阶…...

虚拟机的下载、安装(模拟出服务器)

下载 vmware workstation&#xff08;收费的虚拟机&#xff09; 下载vbox 网址&#xff1a;Oracle VM VirtualBox&#xff08;免费的虚拟机&#xff09; 以下选择一个下载即可&#xff0c;建议下载vbox&#xff0c;因为是免费的。安装的时候默认下一步即可&#xff08;路径最好…...

K8S Pod Terminating/Unknown故障排查

一、pod异常出现现象 优雅终止周期(Graceful termination period): 当pod被删除时&#xff0c;会进入"Terminating"状态&#xff0c;等待容器优雅关闭。如果容器关闭所需时间超过默认期限(默认30秒)&#xff0c;则pod将保持在"Terminating"状态。 Finalize…...

labelme标注的json文件数据转成coco数据集格式(可处理目标框和实例分割)

这里主要是搬运一下能找到的 labelme标注的json文件数据转成coco数据集格式&#xff08;可处理目标框和实例分割&#xff09;的代码&#xff0c;以供需要时参考和提供相关帮助。 1、官方labelme实现 如下是labelme官方网址&#xff0c;提供了源代码&#xff0c;以及相关使用方…...

MySQL报错:1366 - Incorrect integer value: ‘xx‘ for column ‘xx‘ at row 1的解决方法

我在插入表数据时遇到了1366报错&#xff0c;报错内容&#xff1a;1366 - Incorrect integer value: Cindy for column name at row 1&#xff0c;下面我演示解决方法。 根据上图&#xff0c;原因是Cindy’对应的name字段数据类型不正确。我们在左侧找到该字段所在的grade_6表&…...

MySQL中MVCC的流程

参考文章一 参考文章二 当谈到数据库的并发控制时&#xff0c;多版本并发控制&#xff08;MVCC&#xff09;是一个重要的概念。MVCC 是一种用于实现数据库事务隔离性的技术&#xff0c;常见于像 PostgreSQL 和 Oracle 这样的数据库系统中。 MVCC 的核心思想是为每个数据行维护…...

朴素贝叶斯法_naive_Bayes

朴素贝叶斯法&#xff08;naive Bayes&#xff09;是基于贝叶斯定理与特征条件独立假设的分类方法。对于给定的训练数据集&#xff0c;首先基于特征条件独立假设学习输入输出的联合概率分布&#xff1b;然后基于此模型&#xff0c;对给定的输入 x x x&#xff0c;利用贝叶斯定理…...

Windows下安装MongoDB实践总结

本文记录Windows环境下的MongoDB安装与使用总结。 【1】官网下载 官网下载地址&#xff1a;Download MongoDB Community Server | MongoDB 这里可以选择下载zip或者msi&#xff0c;zip是解压后自己配置&#xff0c;msi是傻瓜式一键安装。这里我们分别对比进行实践。 【2】ZI…...

华为云Stack 8.X 流量模型分析(二)

二、流量模型分析相关知识 1.vNIC ​ 虚拟网络接口卡(vNIC)是基于主机物理 NIC 的虚拟网络接口。每个主机可以有多个 NIC&#xff0c;每个 NIC 可以是多个 vNIC 的基础。 ​ 将 vNIC 附加到虚拟机时&#xff0c;Red Hat Virtualization Manager 会在虚拟机之间创建多个关联的…...

rk3588 之启动

目录 uboot版本配置修改编译 linux版本配置修改编译 启动sd卡启动制作spi 烧录 参考 uboot 版本 v2024.01-rc2 https://github.com/u-boot/u-boot https://github.com/rockchip-linux/rkbin 配置修改 使用这两个配置即可&#xff1a; orangepi-5-plus-rk3588_defconfig r…...

ARM GIC (五)gicv3架构-LPI

在gicv3中,引入了一种新的中断类型。message based interrupts,消息中断。 一、消息中断 外设,不在通过专用中断线,向gic发送中断,而是写gic的寄存器,来发送中断。 这样的一个好处是,可以减少中断线的个数。 为了支持消息中断,gicv3,增加了LPI,来支持消息中断。并且…...

sql-labs服务器结构

双层服务器结构 一个是tomcat的jsp服务器&#xff0c;一个是apache的php服务器&#xff0c;提供服务的是php服务器&#xff0c;只是tomcat向php服务器请求数据&#xff0c;php服务器返回数据给tomcat。 此处的29-32关都是这个结构&#xff0c;不是用docker拉取的镜像要搭建一下…...

【小沐学写作】Docsify制作在线电子书、技术文档(Docsify + Markdown + node)

文章目录 1、简介2、安装2.1 node2.2 docsify-cli 3、配置3.1 初始化3.2 预览效果3.3 加载对话框3.4 更多页面3.5 侧 栏3.6 自定义导航栏 结语 1、简介 https://docsify.js.org/#/?iddocsify 一个神奇的文档网站生成器。 简单轻巧没有静态构建的 html 文件多个主题 Docsify…...

电脑完全重装教程——原版系统镜像安装

注意事项 本教程会清除所有个人文件 请谨慎操作 请谨慎操作 请谨慎操作 前言 本教程是以系统安装U盘为介质进行系统重装操作&#xff0c;照着流程操作会清除整个硬盘里的文件&#xff0c;请考虑清楚哦&#xff5e; 有些小伙伴可能随便在百度上找个WinPE作为启动盘就直接…...

【智慧办公】如何让智能会议室的电子标签实现远程、批量更新信息?东胜物联网硬件网关让解决方案更具竞争力

近年来&#xff0c;为了减少办公耗能、节能环保、降本增效&#xff0c;越来越多的企业开始从传统的办公模式转向智慧办公。 以智能会议室为例&#xff0c;会议是企业业务中不可或缺的一部分&#xff0c;但在传统办公模式下&#xff0c;一来会议前行政人员需要提前准备会议材料…...

面向对象设计与分析40讲(16)静态工厂方法模式

前面我们介绍了简单工厂模式&#xff0c;在创建对象前&#xff0c;我们需要先创建工厂&#xff0c;然后再通过工厂去创建产品。 如果将工厂的创建方法static化&#xff0c;那么无需创建工厂即可通过静态方法直接调用的方式创建产品&#xff1a; // 工厂类&#xff0c;定义了静…...

【贪心】买卖股票的最佳时机含手续费

/** 贪心&#xff1a;每次选取更低的价格买入&#xff0c;遇到高于买入的价格就出售(此时不一定是最大收益)。* 使用buy表示买入股票的价格和手续费的和。遍历数组&#xff0c;如果后面的股票价格加上手续费* 小于buy&#xff0c;说明有更低的买入价格更新buy。如…...

Altium Designer入门到就业【目录】

&#x1f3e1;《AD目录》 欢迎大家来到《Altium Designer入门到就业》该专栏包括【电路设计篇】【PCB设计篇】【电路仿真篇】【PCB仿真篇】四个部分&#xff0c;以供大家参考。大家直接点击大纲中蓝色标题即可轻松传送。 【电路设计篇】 Altium Designer&#xff08;AD24&#…...

cmake 查看编译命令,以及在vscode中如何使用cmke

通过设置如下配置选项&#xff0c;可以生成compile_commands.json 文件&#xff0c;记录使用的编译命令 set(CMAKE_EXPORT_COMPILE_COMMANDS ON)获得现有模块列表 cmake --help-module-list查看命令文档 cmake --help-command find_file查看模块的详细信息 cmake --help-mo…...

玩转 Scrapy 框架 (一):Scrapy 框架介绍及使用入门

目录 一、Scrapy 框架介绍二、Scrapy 入门 一、Scrapy 框架介绍 简介&#xff1a; Scrapy 是一个基于 Python 开发的爬虫框架&#xff0c;可以说它是当前 Python 爬虫生态中最流行的爬虫框架&#xff0c;该框架提供了非常多爬虫的相关组件&#xff0c;架构清晰&#xff0c;可扩…...

node.js mongoose index(索引)

目录 简介 索引类型 单索引 复合索引 文本索引 简介 在 Mongoose 中&#xff0c;索引&#xff08;Index&#xff09;是一种用于提高查询性能的数据结构&#xff0c;它可以加速对数据库中文档的检索操作 索引类型 单索引、复合索引、文本索引、多键索引、哈希索引、地理…...

谷歌推大语言模型VideoPoet:文本图片皆可生成视频和音频

Google Research最近发布了一款名为VideoPoet的大型语言模型&#xff08;LLM&#xff09;&#xff0c;旨在解决当前视频生成领域的挑战。该领域近年来涌现出许多视频生成模型&#xff0c;但在生成连贯的大运动时仍存在瓶颈。现有领先模型要么生成较小的运动&#xff0c;要么在生…...

ES-mapping

类似数据库中的表结构定义&#xff0c;主要作用如下 定义Index下的字段名( Field Name) 定义字段的类型&#xff0c;比如数值型、字符串型、布尔型等定义倒排索引相关的配置&#xff0c;比如是否索引、记录 position 等 index_options 用于控制倒排索记录的内容&#xff0c;有如…...

Centos 7.9安装Oracle19c步骤亲测可用有视频

视频介绍了在虚拟机安装centos 7.9并安装数据库软件的全过程 视频链接&#xff1a;https://www.zhihu.com/zvideo/1721267375351996416 下面的文字描述是安装数据库的部分介绍 一.安装环境准备 链接&#xff1a;https://pan.baidu.com/s/1Ogn47UZQ2w7iiHAiVdWDSQ 提取码&am…...

.NET中的Swagger使用

目录 前言 一、Swagger是什么&#xff1f; 二、如何Swagger文档说明的信息 1.在AddSwaggerGen方法中写入文档信息 2.运行效果 二、文档UI界面标题、路由设置 1.在中间件UseSwaggerUI方法中配置 三、文档UI界面添加接口注释 1.在 .csproj中配置 2.在AddSwaggerGen方法中配置Incl…...

结构屈曲分析

结构屈曲分析主要用于判定结构受载后是否有失稳风险&#xff0c;作为工程应用&#xff0c;一般分为线性屈曲分析和非线性屈曲分析。 线性屈曲分析需要具备较多的前提条件&#xff0c;如载荷无偏心、材料无缺陷等&#xff0c;在实际工程应用中结构制作过程和加载方式很难达到线性…...

Flink 客户端操作命令及可视化工具

Flink提供了丰富的客户端操作来提交任务和与任务进行交互。下面主要从Flink命令行、Scala Shell、SQL Client、Restful API和 Web五个方面进行整理。 在Flink安装目录的bin目录下可以看到flink&#xff0c;start-scala-shell.sh和sql-client.sh等文件&#xff0c;这些都是客户…...

csrf自动化检测调研

https://github.com/pillarjs/understanding-csrf/blob/master/README_zh.md CSRF 攻击者在钓鱼站点&#xff0c;可以通过创建一个AJAX按钮或者表单来针对你的网站创建一个请求&#xff1a; <form action"https://my.site.com/me/something-destructive" metho…...