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

基于博弈树的开源五子棋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 历史表启发历史表以及杀手表的维护初始化追加杀手表项清空杀手表 启发式搜索的姿势千奇百怪&#xff0c;本文只讨论一下几种 //搜索空间 #define Search_Space_MVA 0 //最优价值攻击者[分数最大] #d…...

JavaScript原型,原型链 ? 有什么特点?

一、原型 JavaScript 常被描述为一种基于原型的语言——每个对象拥有一个原型对象 当试图访问一个对象的属性时&#xff0c;它不仅仅在该对象上搜寻&#xff0c;还会搜寻该对象的原型&#xff0c;以及该对象的原型的原型&#xff0c;依次层层向上搜索&#xff0c;直到找到一个…...

Unity 问题 之 ScrollView ,LayoutGroup,ContentSizeFitter 一起使用时,动态变化时无法及时刷新更新适配界面的问题

Unity 问题 之 ScrollView ,LayoutGroup,ContentSizeFitter 一起使用时&#xff0c;动态变化时无法及时刷新更新适配界面的问题 目录 Unity 问题 之 ScrollView ,LayoutGroup,ContentSizeFitter 一起使用时&#xff0c;动态变化时无法及时刷新更新适配界面的问题 一、简单介绍…...

linux 中 C++的环境搭建以及测试工具的简单介绍

文章目录 makefleCMakegdb调试 与 coredumpValgrind 内存检测gtest 单元测试 makefile 介绍 安装 : sudo apt install make makefile 的规则: 举例说明 包括&#xff1a;目标文件 、 依赖文件 、 生成规则 使用 &#xff1a; make make clean CMake : CMake是一个…...

448. 找到所有数组中消失的数字

找到所有数组中消失的数字 描述 : 给你一个含 n 个整数的数组 nums &#xff0c;其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字&#xff0c;并以数组的形式返回结果。 题目 : LeetCode 448. 找到所有数组中消失的数字: 448. 找…...

为何在下雪天它“失宠”了,传统雪地靴居然不适合下雪穿

随着冬至的到来&#xff0c;一年之中最寒冷的“三九天”正式拉开序幕。近期各地纷纷下起了大雪&#xff0c;在这场大雪中雪地靴似乎“失宠”了。在社交媒体上&#xff0c;有网友吐槽“雪地靴根本不能下雪穿”&#xff0c;后面有不少网友纷纷分享了自己在雪地靴上尴尬的经历&…...

第34节: Vue3 调用内联处理程序中的方法

在UniApp中使用Vue3框架时&#xff0c;你可以在模板中直接调用组件内联处理程序中的方法。以下是一个示例&#xff1a; <template> <view> <button click"handleClick">Click me</button> <p>{{ message }}</p> </view&…...

JavaScript--明明白白Promise (Park One)

明明白白Promise (Park One) Promise是一种用于处理异步操作的特殊对象。它代表了一个尚未完成但最终会完成的操作&#xff0c;并可以在操作完成后返回结果或错误。 Promise有三种状态&#xff1a;pending&#xff08;进行中&#xff09;、fulfilled&#xff08;已完成&#…...

el-form与el-upload结合上传带附件的表单数据(后端篇)

1.写在之前 本文采用Spring Boot MinIO MySQLMybatis Plus技术栈&#xff0c;参考ruoyi-vue-pro项目。 前端实现请看本篇文章el-form与el-upload结合上传带附件的表单数据&#xff08;前端篇&#xff09;-CSDN博客。 2.需求描述 在OA办公系统中&#xff0c;流程表单申请人…...

postMessage——不同源的网页直接通过localStorage/sessionStorage/Cookies——技能提升

最近遇到一个问题&#xff0c;就是不同源的两个网页之间进行localstorage或者cookie的共享。 上周其实遇到过一次&#xff0c;觉得麻烦就让后端换了种方式处理了&#xff0c;昨天又遇到了同样的问题。 使用场景 比如从网页A通过iframe跳转到网页B&#xff0c;而且这两个网页…...

上市公司-绿色投资者数据集(2000-2022)

上市公司-绿色投资者数据&#xff08;2000-2022年&#xff09;是一份涵盖了过去二十多年中国上市公司绿色投资情况的详细数据集。该数据集包括了各上市公司的股票代码、年份、会计年度、股票简称&#xff0c;以及STPT&#xff08;特殊处理股票的标识&#xff09;&#xff0c;行…...

3 pandas之dataframe

定义 DataFrame是一个二维数据结构&#xff0c;即数据以行和列的方式以表格形式对齐。 DataFrame特点&#xff1a; 存在不同类型的列大小可变带有标签的轴可对列和行进行算数运算 构造函数 pandas.DataFrame( data, index, columns, dtype, copy)参数解释&#xff1a; 序号…...

vue-内网,离线使用百度地图(地图瓦片图下载静态资源展示定位)

前言 最近发现很多小伙伴都在问内网怎么使用百度地图&#xff0c;或者是断网情况下能使用百度地图吗 后面经过一番研究&#xff0c;主要难点是&#xff0c;正常情况下我们是访问公网百度图片&#xff0c;数据&#xff0c;才能使用 内网时访问不了百度地图资源时就会使用不了&…...

OpenFeign 万字教程详解

OpenFeign 万字教程详解 目录 一、概述 1.1.OpenFeign是什么&#xff1f;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…...

全自动双轴晶圆划片机:半导体制造的关键利器

随着科技的飞速发展&#xff0c;半导体行业正以前所未有的速度向前迈进。在这个过程中&#xff0c;全自动双轴晶圆划片机作为一种重要的设备&#xff0c;在半导体晶圆、集成电路、QFN、发光二极管、miniLED、太阳能电池、电子基片等材料的划切过程中发挥着举足轻重的作用。 全自…...

Android Studio 安装和使用

前些天&#xff0c;打开了几年前的一个Android Studio app项目&#xff0c;使用安卓虚拟机仿真app崩溃&#xff0c;怀疑是不是中间升级过Android Studio导致异常的&#xff0c;马上脑子一热卸载了&#xff0c;结果上次踩过的坑&#xff0c;一个没少又踩一次&#xff0c;谨以此文…...

【已解决】Java中,判断:集合中是否包含指定元素(模糊匹配)比如权限中的user:list或者是user:*这种判断

背景描述 在工作中&#xff0c;有时候&#xff0c;我们需要对list中是否包含了指定元素进行判断&#xff0c;但是&#xff0c;有时候又需要支持模糊匹配&#xff0c;这个时候怎么办呢&#xff1f; 比如权限&#xff0c;我们知道&#xff0c;权限不仅可以配置完整的路径&#…...

【基于激光雷达的路沿检测用于自动驾驶的真值标注】

文章目录 概要主要贡献内容概述实验小结 概要 论文地址&#xff1a;https://arxiv.org/pdf/2312.00534.pdf 路沿检测在自动驾驶中扮演着重要的角色&#xff0c;因为它能够帮助车辆感知道可行驶区域和不可行驶区域。为了开发和验证自动驾驶功能&#xff0c;标注的数据是必不可…...

【Spring实战】配置多数据源

文章目录 1. 配置数据源信息2. 创建第一个数据源3. 创建第二个数据源4. 创建启动类及查询方法5. 启动服务6. 创建表及做数据7. 查询验证8. 详细代码总结 通过上一节的介绍&#xff0c;我们已经知道了如何使用 Spring 进行数据源的配置以及应用。在一些复杂的应用中&#xff0c;…...

DevOps系列文章 : 使用dpkg命令打deb包

创建一个打包的目录&#xff0c;类似rpmbuild&#xff0c;这里创建了目录deb_build mkdir deb_build目标 我有一个hello的二进制文件hello和源码hello.c, 准备安装到/opt/helloworld目录中 步骤 在deb_build目录创建一个文件夹用于存放我的安装文件 mkdir helloworld在he…...

vscode里如何用git

打开vs终端执行如下&#xff1a; 1 初始化 Git 仓库&#xff08;如果尚未初始化&#xff09; git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

HBuilderX安装(uni-app和小程序开发)

下载HBuilderX 访问官方网站&#xff1a;https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本&#xff1a; Windows版&#xff08;推荐下载标准版&#xff09; Windows系统安装步骤 运行安装程序&#xff1a; 双击下载的.exe安装文件 如果出现安全提示&…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...