基于博弈树的开源五子棋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…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
 
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
 
相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
 
免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...
 
elementUI点击浏览table所选行数据查看文档
项目场景: table按照要求特定的数据变成按钮可以点击 解决方案: <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...
 
Copilot for Xcode (iOS的 AI辅助编程)
Copilot for Xcode 简介Copilot下载与安装 体验环境要求下载最新的安装包安装登录系统权限设置 AI辅助编程生成注释代码补全简单需求代码生成辅助编程行间代码生成注释联想 代码生成 总结 简介 尝试使用了Copilot,它能根据上下文补全代码,快速生成常用…...
ffmpeg(三):处理原始数据命令
FFmpeg 可以直接处理原始音频和视频数据(Raw PCM、YUV 等),常见场景包括: 将原始 YUV 图像编码为 H.264 视频将 PCM 音频编码为 AAC 或 MP3对原始音视频数据进行封装(如封装为 MP4、TS) 处理原始 YUV 视频…...
