C语言实现三子棋强化学习算法AI,思路详解+完整代码

写个三子棋的强化学习AI玩玩。写这玩意只需要有一点C语言基础就可以了,至于AI部分,也是很好理解的。

三子棋

在3*3的棋盘中,先手方画O,后手方画X,连成3个就赢了。事实上,只需要很简单的试验,你就会明白,如果双方都走最优解,最后一定是和棋

让电脑随机下棋显然没有什么意思,那能不能让电脑聪明点呢?

强化学习

强化学习的描述如下,看不太明白没关系,我会举例子的。

强化学习是机器学习的一个分支,它着重于如何让智能系统(称为代理)通过与环境的交互来学习做出最优的决策或者行动。在强化学习中,代理试图通过执行行动并接收环境反馈(通常是奖励)来最大化其累计获得的总奖励。这一过程涉及到学习行动的策略,即在给定的状态下应采取什么行动。

强化学习的核心组成部分包括:

1. 代理(Agent):执行行动的实体,其目标是学习最佳行动序列(策略)以最大化奖励。
2. 环境(Environment):代理所处并与之交互的系统或问题域。环境根据代理的当前状态和执行的行动,反馈新的状态和奖励。
3. 状态(State):环境的一个描述,代理根据状态做出决策。
4. 行动(Action):代理可以执行的操作。
5. 奖励(Reward):环境对代理执行行动的即时反馈,指导学习过程。

强化学习的学习过程通常涉及探索(尝试新行动以了解它们的效果)和利用(使用已知的信息来获得最大奖励)之间的平衡。这一平衡的目标是发现最优策略,即一个从状态到行动的映射,使得累积奖励最大化。

强化学习在多个领域有广泛的应用,如自动驾驶汽车、游戏、机器人导航和控制、推荐系统等。与其他类型的机器学习不同,强化学习不是直接从数据集学习,而是通过试错和适应环境的反馈来学习。

下面我来谈谈最简单的强化学习,在三子棋中的应用。虽然有点杀鸡用牛刀的嫌疑,但这是个很好的例子。

电脑是很笨的,它只知道游戏规则:只有空位才能下棋、一人一步、连成3个获胜……如果你不告诉它下棋的思路,它就只会随机下。

状态

棋局在某个时刻,会有一个状态:

我们可以用3*3的二维数组来描述,即:

0 0 1
0 1 0
2 1 2

其中0表示空位,1表示先手方的O,2表示后手方的X。

如果我把这个二维数组从右向左、从下到上排成一排,得到212010100,这是一个只由012组成的数字,可以看作三进制,即(212010100)_3,再转换为十进制int即可。如果要从这个整数还原棋局的状态,只需要重新转换成三进制,再填到二维数组中。

这样,我们成功地用int来描述棋局的状态。棋局所有可能的状态,不超过3^9种,实际还要更少,因为有一些情况是达不到的。

int GetState(int board[3][3])
{int state = 0;// 转换为3进制数for (int i = 2; i >= 0; --i){for (int j = 2; j >= 0; --j){state = state * 3 + board[i][j];}}return state;
}

行动

在某个状态下,比如:

此时轮到X走,假设棋盘的9个位置分别是:

0 1 2
3 4 5
6 7 8

显然二维数组的第x行第y列(x、y从0开始)表示数字n=x*3+y,而x=n/3,y=n%3。

那么对于上图中的棋局状态,X所有可能的走法就是:0,1,3,5。这样,我们就用一个整数表示了某个状态下的行动

分数

某个状态下的某个行动都可以赋一个得分,这个分数越高,表示这个行动是越有利的。那么如何准确得到每个状态下的每个行动的分数呢?

我们可以这样初始化分数:

  • 如果这步棋走完后能直接获胜,分数为1
  • 如果这步棋走完后和棋,或者棋局未结束,分数为0.5
  • 如果这步棋不能走(位置已被占用),分数为-1
for (int state = 0; state < STATES_CNT; ++state)
{int tmpState = state;// 把状态转换为棋盘int board[3][3] = { 0 };for (int i = 0; i < 3; ++i){for (int j = 0; j < 3; ++j){board[i][j] = tmpState % 3;tmpState /= 3;}}// 根据走棋后的状态,初始化分数for (int point = 0; point < 9; ++point){int tmpBoard[3][3] = { 0 };memcpy(tmpBoard, board, 9 * sizeof(int));if (Move(tmpBoard, point / 3, point % 3)){int res = IsWin(tmpBoard);// 赢 - 1// 和棋 - 0.5// 棋局未结束 - 0.5if (res == 1 || res == 2)value[state][point] = 1;elsevalue[state][point] = 0.5;}else{// 该位置非法value[state][point] = -1;}}
}

你可能会问,那如果这步棋走完后输了呢?emmm,这种可能不存在!注意这里只表示走一步棋的分数。如果这步棋走完后几步真的会输,那么分数应该为0,这点后面会讲。这样,如果这个位置没有违反规则,分数就在[0,1]的范围内。

我们可以用一个二维数组来存储所有状态下的所有行动的得分。二维数组的行标表示棋局状态(前面已经用整数描述状态了,为0~3^9-1),列标(0~8)表示行动,二维数组内存储分数。

显然,这个分数是不准确的,有可能这步棋很烂,但分数却是0.5,这就需要让AI强化学习了。

奖励

我们让电脑自己和自己下棋,每一步都选择当前状态下分数最高的位置,如果分数相同(比如一开始的9个位置分数都是0.5),就随机选择一个位置,或者选择看到的第一个位置,这并不影响结果。

最终,会产生一个结果。有可能是先手方O赢了,也有可能是后手方X赢了,还有可能是和棋。

让电脑吸取经验教训,也就是给奖励,也可以是惩罚。

具体的做法是,从最后一步往前推,更新每一步的分数。我们规定:

  • 如果是某一方赢了,那么最后一步棋的得分就是1(这点和前面分数的初始化保持一致),而倒数第二步棋是输的那方下的,这步棋的得分设置成0,因为是这步棋直接导致了输棋。
  • 如果是和棋,那么最后一步棋的得分就是0.5(这点和前面分数的初始化保持一致),而倒数第二步棋是另一方下的,这步棋的得分设置成0.5,因为是这步棋直接导致了和棋。

那么倒数第三步和倒数第一步是同一方下的。倒数第三步的新的分数=倒数第三步的旧的分数+0.1*(倒数第一步的新的分数-倒数第三步旧的分数)。

同理,倒数第四步和倒数第二步是同一方下的。倒数第四步的新的分数=倒数第四步的旧的分数+0.1*(倒数第二步的新的分数-倒数第四步旧的分数)。

接下来是倒数第五步、倒数第六步……一直到正数第一步。这样,这盘棋出现的状态中,对于走过的行为,就有了新的分数,这就是强化学习!

注意到更新的分数乘了0.1,这样越往前的分数,变动的幅度就越小,这也是合理的,因为越接近棋局开始,走棋的影响就越小。

经过大量的对局后,所有状态下的行为的得分就会更加准确,无限趋近于理论值。然而,为了防止出现局部最优,也就是AI自我感觉良好,最好让AI有一定的概率随机走棋,而不是每次都选择最优走法。

以下是一次强化学习:

// 棋盘
int board[3][3] = { 0 };
// 下棋的状态数组
int states[9] = { 0 };
// states数组记录的状态数量
int stSize = 0;
// 下棋的位置数组
int points[9] = { 0 };
// point数组记录的位置数量
int ptSize = 0;
// 记录state
int state = 0;
// 保存初始的状态
states[stSize++] = state;
// 记录胜负和
// 0 - 未分胜负
// 1 - 先手方获胜
// 2 - 后手方获胜
// 3 - 和棋
int res = 0;
// 下一盘棋
while (true)
{// 一定概率随机走棋// 否则选择value最大的走法if (rand() % 10 < 3){while (true){// 随机走棋int x = rand() % 3;int y = rand() % 3;if (Move(board, x, y)){// 保存当前pointpoints[ptSize++] = x * 3 + y;break;}}}else{// 选择value最大的走法// 找到最大的value和对应的pointdouble maxVal = -1;int point = 0;for (int i = 0; i < 9; ++i){if (value[state][i] > maxVal){maxVal = value[state][i];point = i;}}// 在point位置下棋Move(board, point / 3, point % 3);// 保存pointpoints[ptSize++] = point;}// 棋局是否结束if (res = IsWin(board))break;// 计算并保存新的状态state = GetState(board);states[stSize++] = state;
}// 根据这盘棋的信息,总结经验
// 不考虑最后一步
--stSize;
--ptSize;
// 倒数第二步直接导致输棋或和棋
// 输棋分数设为0
// 和棋分数设为0.5
value[states[stSize - 1]][points[ptSize - 1]]= (res == 3 ? 0.5 : 0);
--stSize;
--ptSize;
// 从倒数第三步开始,每一步的分数更新为
// 原来的分数 + 0.1 * (后两步的分数-原来的分数)
while (stSize > 0 && ptSize > 0)
{value[states[stSize - 1]][points[ptSize - 1]]= value[states[stSize - 1]][points[ptSize - 1]]+ 0.1 * (value[states[stSize + 1]][points[ptSize + 1]]- value[states[stSize - 1]][points[ptSize - 1]]);--stSize;--ptSize;
}

最终训练的结果,也就是value数组只需要保存到文件中,需要对局时再从文件中读取数据,这样就不用每次都重新训练了。

void SaveValue(value_t value)
{FILE* fin = fopen("value.dat", "wb");if (fin == NULL){perror("fopen");exit(2);}// 保存valuefwrite(value, sizeof(double), 177147, fin);fclose(fin);fin = NULL;
}bool LoadValue(value_t value)
{FILE* fout = fopen("value.dat", "rb");// 没有这个文件if (fout == NULL)return false;// 加载valuefread(value, sizeof(double), 177147, fout);fclose(fout);fout = NULL;return true;
}

完整代码

已上传至gitee链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/766053.shtml

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

相关文章

大学期末考试搜题软件?这4款足够解决问题 #知识分享#笔记#职场发展

当代大学生面临着繁重的学业压力和海量的知识点&#xff0c;如何高效地进行学习和搜题成了他们关注的焦点。幸运的是&#xff0c;随着科技的不断进步&#xff0c;我们有越来越多的日常搜题和学习软件可以帮助我们更好地应对这些挑战。在本文中&#xff0c;我将为大家介绍10款备…

最新消息!Stable Diffusion核心研究团队已集体辞职!Stable Diffusion 3后可能再无开源

最新消息&#xff0c;Stable Diffusion核心研究团队已集体辞职&#xff01; 名单包括研究团队领导、论文一作Robin Rombach&#xff0c;共同一作Andreas Blattmann&#xff0c;以及另一位作者Dominik Lorenz合作开发了图像生成模型Stable Diffusion。这项技术帮助Stability AI…

linux下线程分离属性

linux下线程分离属性 一、线程的属性---分离属性二、线程属性设置2.1 线程创建前设置分离属性2.2 线程创建后设置分离属性 一、线程的属性—分离属性 什么是分离属性&#xff1f; 首先分离属性是线程的一个属性&#xff0c;有了分离属性的线程&#xff0c;不需要别的线程去接合…

常见通信协议及其端口号

1. TCP/IP协议族&#xff1a; •HTTP (超文本传输协议): 默认端口 80&#xff0c;用于网页浏览和数据传输。 •HTTPS (安全超文本传输协议): 默认端口 443&#xff0c;提供了HTTP协议基础上的数据加密和服务器身份验证功能。 •FTP (文件传输协议): 控制通道端口 21&#xf…

oracle设置主键自增步骤

设置主键自增步骤&#xff1a; 每一张表都要设置序列&#xff0c;然后设置触发器。比mysql繁琐。 一、设置序列 选中表后&#xff0c;—》 文件—》新建—》其他—》序列. 设置如下四个值即可。 crtls保存。 给序列起个名字&#xff0c;一定要全大写字母。 二、设置触发器…

移相全桥DC-DC变换器

本篇将基于PPEC-86CA3A移相全桥数字电源控制芯片以及PPEC Workbench开发软件带领大家进行实际移相全桥DC-DC变换器的设计与开发 。 一、移相全桥变换器设计与开发 1、外围电路设计与硬件平台搭建 1&#xff09;外围电路设计 这里给出了PPEC-86CA3A移相全桥数字电源控制芯片的…

字节跳动春招必看:2024最全Spring Controller面试题解析,每位候选人的收藏宝典!

随着Spring框架在企业级应用开发中的普及&#xff0c;对于掌握其核心组件如Spring MVC控制器的需求也随之增长。特别是在面向2024年字节跳动春季招聘时&#xff0c;对于求职者来说&#xff0c;深入理解Spring MVC控制器的运作机制、高级功能及最佳实践变得尤为关键。本篇文章旨…

校招C++大概学习到什么程度?

游戏引擎、图形学的主要开发语言就是C&#xff0c;所以在面试中回答了了几个C问题。我在面试之前完全没有看过任何面试经验&#xff0c;也没有做过类似的题目。可能是因为招实习生&#xff0c;也可能是因为不是C开发&#xff0c;所以问题我觉得都比较简单。在开始前我有一些资料…

QT:三大特性

QT的三大特性&#xff1a; 1、信号与槽 2、内存管理 3、事件处理 1、信号与槽 当信号产生时&#xff0c;就会自动调用绑定的槽函数。 自定义信号: 类中需要添加O_OBJECT宏 声明: signals标签之下进行声明 定义&#xff1a; 信号不需要定义 …

【MySql】1.mysql数据库

一、数据库的基本概念 1.数据 记录事物的信息&#xff1b;按统一的格式进行存储 2.表 数据的集合&#xff0c;行和列的组合&#xff1b;将多条数据组织在一起 3.数据库 表的集合&#xff0c;是存储 相互有关 数据的仓库 二、数据库管理系统 DBMS的主要功能&#xff1a; …

每周一算法:迭代加深A*

题目链接 AcWing 180. 排书 题目描述 给定 n n n 本书&#xff0c;编号为 1 ∼ n 1\sim n 1∼n。 在初始状态下&#xff0c;书是任意排列的。 在每一次操作中&#xff0c;可以抽取其中连续的一段&#xff0c;再把这段插入到其他某个位置。 我们的目标状态是把书按照 1 ∼…

智能风扇的新篇章:唯创知音WTK6900G语音识别芯片引领行业革新

随着科技浪潮的推进&#xff0c;智能化技术逐渐渗透到生活的每一个角落&#xff0c;家电领域尤为明显。风扇&#xff0c;这一夏日清凉神器&#xff0c;也通过智能化改造&#xff0c;焕发出前所未有的光彩。其中&#xff0c;智能语音控制功能的加入&#xff0c;为风扇的使用带来…

Linux基础语法学习外加练习题,训练一、创建文件相关练习题二、文件管理相关练习题三、vim编辑器的练习四、用户管理相关操作

练习题答案请点击链接查看&#xff1a;​​​​​​​Linux基础语法练习题&#xff0c;配有答案&#xff0c;题目内容如下&#xff1a;一、创建文件相关练习题二、文件管理相关练习题三、vim编辑器的练习四、用户管理相关操作-CSDN博客 一、常用命令 1 、 linux 命令特点 1. …

服务器软件express搭建web服务器

文章目录 1.express 是什么2.路由2.1&#xff08;参数一&#xff09;读取用户的请求&#xff08;request&#xff09;2.2&#xff08;参数二&#xff09;给用户响应&#xff08;response&#xff09;2.3&#xff08;参数三&#xff09;next()函数&#xff08;传递请求到下一个处…

科研学习|论文解读——真实与综合:研究设置和任务配置对搜索行为的影响(JASIST,2021)

原文题目 Authentic versus synthetic: An investigation of the influences of study settings and task configurations on search behaviors 摘要 在信息检索和检索研究中,研究者经常收集用户行为数据来预测任务特征,为用户提供个性化信息提供参考。数据采集方法可能会直接…

[AIGC] 主流工作流引擎对比与适用场景介绍

主流工作流引擎对比与适用场景介绍 工作流引擎在业务流程管理中扮演着重要的角色&#xff0c;它可以帮助组织将复杂的工作流程自动化&#xff0c;降低错误率&#xff0c;提高工作效率。目前市面上有许多优秀的工作流引擎&#xff0c;各自都有着独特的优点和适用的场景。本文将介…

动态规划15 | ● 392.判断子序列 ● *115.不同的子序列

392.判断子序列 https://programmercarl.com/0392.%E5%88%A4%E6%96%AD%E5%AD%90%E5%BA%8F%E5%88%97.html 考点 子序列问题 我的思路 dp[i][j]的含义是&#xff0c;两个序列分别取到下标为i和j的时候&#xff0c;他们是否满足前者是后者的子序列&#xff0c;满足为True&#x…

涨知识啦!如何使用3dMax和Vray渲染三维室内平面图效果图?

使用3dMax渲染三维室内平面图教程 在建筑和室内设计领域,3D平面效果图在建筑师或设计师与其客户之间更好地沟通方面发挥着重要作用。并不是每个人都能“阅读”建筑二维平面图及其所有技术元素,因此将该平面图转换为更易于理解的布局是很重要的。一个简单的方法是只使用2D图形…

第二十二章 TypeScript weakMap,weakSet,set,map

在es5的时候常用的Array object &#xff0c;在es6又新增了两个类型&#xff0c;Set和Map&#xff0c;类似于数组和对象。 1.set 集合是由一组无序且唯一(即不能重复)的项组成的&#xff0c;可以想象成集合是一个既没有重复元素&#xff0c;也没有顺序概念的数组 属性 size&…

flutter-elinux的基本介绍及安装调试

搜集到两个很有用的网站&#xff1a; 1、flutter-elinux的基本介绍&#xff1a;https://juejin.cn/post/7257285697383612453 2、flutter-elinux的安装调试等&#xff1a;https://github.com/sony/flutter-elinux/wiki 其中&#xff0c;在flutter-elinux设置环境变量时&#…