基于博弈树的开源五子棋AI教程[5 启发式搜索]
文章目录
- 1 最大化攻击者/最小化防守者排序
- 2 置换表启发
- 3 杀手表启发
- 4 历史表启发
- 历史表以及杀手表的维护
- 初始化
- 追加杀手表项
- 清空杀手表
启发式搜索的姿势千奇百怪,本文只讨论一下几种
//搜索空间
#define Search_Space_MVA 0 //最优价值攻击者[分数最大]
#define Search_Space_MCP 1 //最优棋型
#define Search_Space_MHT 2 //历史表排序
#define Search_Space_MKT 3 //杀手表排序
#define Search_Space_MT 4 //综合技术[MVA + MHT + MKT + 置换表]
#define Search_Space_FS 5 //第一手生成策略
1 最大化攻击者/最小化防守者排序
这里就是最简单对于当前节点棋盘分数/分数的增长量进行排序。得分越高,出现越靠前。
//视角为 evalPlayer
void GameBoard::getSearchSpaceAroundStonesMaxValuableAttacter(QList<MPoint> &searchVectors,QList<MPoint> &seachSpace,MPlayerType evalPlayer, MPlayerType searchPlayer,quint8 searchType /* = MINIMAX_SEARCH*/) {bool maximizingPlayer = evalPlayer == searchPlayer;int cutLength = getSearchCutLength(seachSpace, maximizingPlayer, searchType);QList<int> searchVectorsScores;searchVectors.reserve(cutLength);searchVectorsScores.reserve(cutLength);quint16 savedSearchBoardPatternDirection[boardSize][boardSize];int minScore = INT_MAX; // 记录最小分数for (const auto& s : seachSpace) {quint64 key = zobristSearchHash.generateHash(s, PLAYER_NONE, searchPlayer);int scoreCur;if (!zobristSearchHash.getLeafTable(searchPlayer, scoreCur, key)) {setSearchBoard(s, searchPlayer, savedSearchBoardPatternDirection); // 模拟落子scoreCur = evaluateBoard(searchPlayer);setSearchBoard(s, PLAYER_NONE, savedSearchBoardPatternDirection); // 撤销模拟落子}if (scoreCur <= minScore && searchVectors.size() >= cutLength) {continue; // 如果当前分数不够高且列表已满,跳过}// 插入排序int insertPos = 0;for (; insertPos < qMin(searchVectors.size(), cutLength); ++insertPos) {if (scoreCur > searchVectorsScores[insertPos]) {break;}}if (insertPos < cutLength) {searchVectors.insert(insertPos, s);searchVectorsScores.insert(insertPos, scoreCur);if (searchVectors.size() > cutLength) {searchVectors.removeLast(); // 移除最小分数的元素searchVectorsScores.removeLast();}minScore = searchVectorsScores.last(); // 更新最小分数}}// sortSearchResultByCoference(searchVectors, searchHash, maximizingPlayer, searchType);
}
2 置换表启发
置换表保存了搜索过程中最优价值的节点信息,如果发现当前搜索状态在置换表,那么这一状态应该率先被搜索。
//返回的值:[0,id-1]是使用历史表启发后的结果
int GameBoard::sortMoveBySearchHistoryTable(QVector<MPoint> &searchVector,quint8 startPosition/*=0*/, quint8 searchType/*=minimax_search*/)
{Q_UNUSED(searchType);if(!globalParam::utilGameSetting.IsOpenSearchHistoryTable) return startPosition;QReadLocker locker(&globalParam::historyTableLock);std::sort(searchVector.begin() + startPosition, searchVector.end(), [](const MPoint &a, const MPoint &b) {return globalParam::historyTable[a.x()][a.y()] > globalParam::historyTable[b.x()][b.y()];});//只保留有值点for(;startPosition < searchVector.size(); startPosition ++){if(globalParam::historyTable[searchVector.at(startPosition).x()][searchVector.at(startPosition).y()] == 0) break;}return startPosition;
}
3 杀手表启发
杀手表保存了搜索过程中某一指定搜索深度下各个节点的剪枝信息。搜索过程中,在因为某节点产生的剪枝次数越多,这个值也就越大。
//返回的值:[0,id-1]是使用杀手表启发后的结果
int GameBoard::sortMoveBySearchKillerTable(QVector<MPoint> &searchVector, quint8 startPosition/*=0*/, quint8 searchType/*=minimax_search*/)
{Q_UNUSED(searchType);if(!globalParam::utilGameSetting.IsOpenKillerTable) return startPosition;QReadLocker locker(&globalParam::killerTableLock);int depth = searchSpacePlayers.size();std::sort(searchVector.begin() + startPosition, searchVector.end(), [depth](const MPoint &a, const MPoint &b) {return globalParam::killerTable[depth][UtilGetMPointPosID(a)] > globalParam::killerTable[depth][UtilGetMPointPosID(b)];});//只保留有值点for(;startPosition < searchVector.size(); startPosition ++){if(globalParam::killerTable[depth][UtilGetMPointPosID(searchVector.at(startPosition))] == 0) break;}return startPosition;
}
4 历史表启发
类似于杀手表,但是历史表忽略了搜索深度信息。只要在搜索过程中因为某一节点产生了剪枝,这个节点的价值就会变大。
//返回的值:[0,id-1]是使用历史表启发后的结果
int GameBoard::sortMoveBySearchHistoryTable(QVector<MPoint> &searchVector,quint8 startPosition/*=0*/, quint8 searchType/*=minimax_search*/)
{Q_UNUSED(searchType);if(!globalParam::utilGameSetting.IsOpenSearchHistoryTable) return startPosition;QReadLocker locker(&globalParam::historyTableLock);std::sort(searchVector.begin() + startPosition, searchVector.end(), [](const MPoint &a, const MPoint &b) {return globalParam::historyTable[a.x()][a.y()] > globalParam::historyTable[b.x()][b.y()];});//只保留有值点for(;startPosition < searchVector.size(); startPosition ++){if(globalParam::historyTable[searchVector.at(startPosition).x()][searchVector.at(startPosition).y()] == 0) break;}return startPosition;
}
历史表以及杀手表的维护
杀手表和历史表的方法类似,这里仅提供杀手表的维护过程。
初始化
QReadWriteLock globalParam::killerTableLock;
int globalParam::killerTable[GameSetting::BoardSize * GameSetting::BoardSize + 1][GameSetting::BoardSize * GameSetting::BoardSize] = {{0}};
追加杀手表项
//追加杀手表表项
//depth:表示距离叶子的深度
void GameBoard::appendSearchKillerTable(const MPoint &position, int depth, quint8 NABSearchHashFlag)
{if(!globalParam::utilGameSetting.IsOpenKillerTable) return;QWriteLocker locker(&globalParam::killerTableLock);//随着搜索的加深,逐渐靠近叶子节点,depth的值逐步变小,对于历史表的贡献也在变小if(NABSearchHashFlag == hashfLowerBound){globalParam::killerTable[searchSpacePlayers.size()][UtilGetMPointPosID(position)] += depth * depth;}
}
清空杀手表
//清楚所有的杀手表
void GameBoard::clearSearchKillerTable()
{if(!globalParam::utilGameSetting.IsOpenKillerTable) return;QWriteLocker locker(&globalParam::killerTableLock);//全部置零清除杀手表for(int row = 0;row < boardSizeSquare + 1; ++row){for(int col = 0; col < boardSizeSquare; ++col){globalParam::killerTable[row][col] = 0;}}// //使用累积影响的方式清除杀手表// for(int row = 0;row < boardSizeSquare + 1; ++row){// for(int col = 0; col < boardSizeSquare; ++col){// globalParam::killerTable[row][col] = globalParam::historyTable[row][col] >> 2;// }// }
}
相关文章:
基于博弈树的开源五子棋AI教程[5 启发式搜索]
文章目录 1 最大化攻击者/最小化防守者排序2 置换表启发3 杀手表启发4 历史表启发历史表以及杀手表的维护初始化追加杀手表项清空杀手表 启发式搜索的姿势千奇百怪,本文只讨论一下几种 //搜索空间 #define Search_Space_MVA 0 //最优价值攻击者[分数最大] #d…...
JavaScript原型,原型链 ? 有什么特点?
一、原型 JavaScript 常被描述为一种基于原型的语言——每个对象拥有一个原型对象 当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个…...
Unity 问题 之 ScrollView ,LayoutGroup,ContentSizeFitter 一起使用时,动态变化时无法及时刷新更新适配界面的问题
Unity 问题 之 ScrollView ,LayoutGroup,ContentSizeFitter 一起使用时,动态变化时无法及时刷新更新适配界面的问题 目录 Unity 问题 之 ScrollView ,LayoutGroup,ContentSizeFitter 一起使用时,动态变化时无法及时刷新更新适配界面的问题 一、简单介绍…...
linux 中 C++的环境搭建以及测试工具的简单介绍
文章目录 makefleCMakegdb调试 与 coredumpValgrind 内存检测gtest 单元测试 makefile 介绍 安装 : sudo apt install make makefile 的规则: 举例说明 包括:目标文件 、 依赖文件 、 生成规则 使用 : make make clean CMake : CMake是一个…...
448. 找到所有数组中消失的数字
找到所有数组中消失的数字 描述 : 给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。 题目 : LeetCode 448. 找到所有数组中消失的数字: 448. 找…...
为何在下雪天它“失宠”了,传统雪地靴居然不适合下雪穿
随着冬至的到来,一年之中最寒冷的“三九天”正式拉开序幕。近期各地纷纷下起了大雪,在这场大雪中雪地靴似乎“失宠”了。在社交媒体上,有网友吐槽“雪地靴根本不能下雪穿”,后面有不少网友纷纷分享了自己在雪地靴上尴尬的经历&…...
第34节: Vue3 调用内联处理程序中的方法
在UniApp中使用Vue3框架时,你可以在模板中直接调用组件内联处理程序中的方法。以下是一个示例: <template> <view> <button click"handleClick">Click me</button> <p>{{ message }}</p> </view&…...
JavaScript--明明白白Promise (Park One)
明明白白Promise (Park One) Promise是一种用于处理异步操作的特殊对象。它代表了一个尚未完成但最终会完成的操作,并可以在操作完成后返回结果或错误。 Promise有三种状态:pending(进行中)、fulfilled(已完成&#…...
el-form与el-upload结合上传带附件的表单数据(后端篇)
1.写在之前 本文采用Spring Boot MinIO MySQLMybatis Plus技术栈,参考ruoyi-vue-pro项目。 前端实现请看本篇文章el-form与el-upload结合上传带附件的表单数据(前端篇)-CSDN博客。 2.需求描述 在OA办公系统中,流程表单申请人…...
postMessage——不同源的网页直接通过localStorage/sessionStorage/Cookies——技能提升
最近遇到一个问题,就是不同源的两个网页之间进行localstorage或者cookie的共享。 上周其实遇到过一次,觉得麻烦就让后端换了种方式处理了,昨天又遇到了同样的问题。 使用场景 比如从网页A通过iframe跳转到网页B,而且这两个网页…...
上市公司-绿色投资者数据集(2000-2022)
上市公司-绿色投资者数据(2000-2022年)是一份涵盖了过去二十多年中国上市公司绿色投资情况的详细数据集。该数据集包括了各上市公司的股票代码、年份、会计年度、股票简称,以及STPT(特殊处理股票的标识),行…...
3 pandas之dataframe
定义 DataFrame是一个二维数据结构,即数据以行和列的方式以表格形式对齐。 DataFrame特点: 存在不同类型的列大小可变带有标签的轴可对列和行进行算数运算 构造函数 pandas.DataFrame( data, index, columns, dtype, copy)参数解释: 序号…...
vue-内网,离线使用百度地图(地图瓦片图下载静态资源展示定位)
前言 最近发现很多小伙伴都在问内网怎么使用百度地图,或者是断网情况下能使用百度地图吗 后面经过一番研究,主要难点是,正常情况下我们是访问公网百度图片,数据,才能使用 内网时访问不了百度地图资源时就会使用不了&…...
OpenFeign 万字教程详解
OpenFeign 万字教程详解 目录 一、概述 1.1.OpenFeign是什么?1.2.OpenFeign能干什么1.3.OpenFeign和Feign的区别1.4.FeignClient 二、OpenFeign使用 2.1.OpenFeign 常规远程调用2.2.OpenFeign 微服务使用步骤2.3.OpenFeign 超时控制2.4.OpenFeign 日志打印2.5.O…...
全自动双轴晶圆划片机:半导体制造的关键利器
随着科技的飞速发展,半导体行业正以前所未有的速度向前迈进。在这个过程中,全自动双轴晶圆划片机作为一种重要的设备,在半导体晶圆、集成电路、QFN、发光二极管、miniLED、太阳能电池、电子基片等材料的划切过程中发挥着举足轻重的作用。 全自…...
Android Studio 安装和使用
前些天,打开了几年前的一个Android Studio app项目,使用安卓虚拟机仿真app崩溃,怀疑是不是中间升级过Android Studio导致异常的,马上脑子一热卸载了,结果上次踩过的坑,一个没少又踩一次,谨以此文…...
【已解决】Java中,判断:集合中是否包含指定元素(模糊匹配)比如权限中的user:list或者是user:*这种判断
背景描述 在工作中,有时候,我们需要对list中是否包含了指定元素进行判断,但是,有时候又需要支持模糊匹配,这个时候怎么办呢? 比如权限,我们知道,权限不仅可以配置完整的路径&#…...
【基于激光雷达的路沿检测用于自动驾驶的真值标注】
文章目录 概要主要贡献内容概述实验小结 概要 论文地址:https://arxiv.org/pdf/2312.00534.pdf 路沿检测在自动驾驶中扮演着重要的角色,因为它能够帮助车辆感知道可行驶区域和不可行驶区域。为了开发和验证自动驾驶功能,标注的数据是必不可…...
【Spring实战】配置多数据源
文章目录 1. 配置数据源信息2. 创建第一个数据源3. 创建第二个数据源4. 创建启动类及查询方法5. 启动服务6. 创建表及做数据7. 查询验证8. 详细代码总结 通过上一节的介绍,我们已经知道了如何使用 Spring 进行数据源的配置以及应用。在一些复杂的应用中,…...
DevOps系列文章 : 使用dpkg命令打deb包
创建一个打包的目录,类似rpmbuild,这里创建了目录deb_build mkdir deb_build目标 我有一个hello的二进制文件hello和源码hello.c, 准备安装到/opt/helloworld目录中 步骤 在deb_build目录创建一个文件夹用于存放我的安装文件 mkdir helloworld在he…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...
