接前文:

再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(2) —— 游戏环境设计篇 - Hello_BeautifulWorld - 博客园 (cnblogs.com)

=========================================

在前文中主要是大致介绍了  https://gitee.com/devilmaycry812839668/highest_vote_2048_ai  中游戏环境部分的内容,其实游戏环境的实现只介绍了很小一部分,原本打算多说一些,不过后来发现意义价值不大,对于有兴趣研究这个游戏的实现逻辑的人知道这个游戏是用64bit的整数来表示一个游戏状态中16个数字并且每个数字是用半字节4bit来进行表示的就已经足够了,其他剩下的就是如何对这个64bit的整数进行移位操作了,如:transpose函数就是把这个64bit的整数按照游戏状态装置的方式变化为另一个游戏状态的64bit整数,move_0,  move_1, move_2, move_3,  则是表示对这个64bit的整数进行上下左右移动操作后变化得到的新状态所表示的64bit整数;而对于那些对这个游戏实现逻辑没有兴趣的人来说自然是没有必要多说这个environment的实现细节了;对于我来说这个游戏实现细节确实不好理解但是要完整的说清楚又显得十分的琐碎和费事,于是也就作罢了。

其实这个系列主要的重点不是说这个游戏的实现,而是如何用AI方式来解决这个游戏。

现在很多人一听说AI就知道深度学习,Deep Learning,机器学习之类的,但是AI这个概念其实是一个比较宽泛的,所包容的内容也是很多的,在某种程度上说方式可以仿造人类去实现某个操作,或是具有人类某项功能的部分能力的方法都可以叫做AI方法,而现在的机器学习方法是AI方法中最为流行的方法,对于当前的CV,NLP等领域有很好的解决能力的一种方法,但这绝不是唯一,在这个《2048》游戏中所使用的方法就是AI方法中的启发式方法,启发式方法这个名字也是十分的唬人,其实启发式算法就是按照一定的规则条件来进行运算的算法,大致形式就是:

IF CONDITION:

DO SOMETHING;

ELSE:

DO ANOTHER SOMETHING;

ENDIF;

个人一般把启发式算法看做是根据预定规则(条件)设计好的算法。

启发式算法是听起来很高大上,实际一看又感觉很平民,那么他实际上的原理在哪呢?因为我们希望利用计算机程序来模仿实现人类的某项能力,说到底就是设计一个具备人类某项能力的算法代码,其中最为直接的方法就是把人类处理某个问题所采用的方法(策略)用条件规则写出来,那么我们编写计算机算法时只要按照这个预设的条件规则来进行设计就可以了。

启发式算法:

人类策略       ===》      计算机可识别的条件规则

那么在这个《2048》游戏中这个启发算法时如何设计的呢?

具体的实现代码:

cpp_source/2048_algorithm.cpp · 鬼&泣/2048-ai - 码云 - 开源中国 (gitee.com)

实现的启发式算法核心代码:

static float heur_score_table[65536];

// Heuristic scoring settings
static const float SCORE_LOST_PENALTY = 200000.0f;
static const float SCORE_MONOTONICITY_POWER = 4.0f;
static const float SCORE_MONOTONICITY_WEIGHT = 47.0f;
static const float SCORE_SUM_POWER = 3.5f;
static const float SCORE_SUM_WEIGHT = 11.0f;
static const float SCORE_MERGES_WEIGHT = 700.0f;
static const float SCORE_EMPTY_WEIGHT = 270.0f; void init_score_table()
{
for (unsigned row = 0; row < 65536; ++row)
{
unsigned line[4] = {
(row >> 0) & 0xf,
(row >> 4) & 0xf,
(row >> 8) & 0xf,
(row >> 12) & 0xf
}; // Heuristic score
float sum = 0;
int empty = 0;
int merges = 0; int prev = 0;
int counter = 0;
for (int i = 0; i < 4; ++i)
{
int rank = line[i];
sum += pow(rank, SCORE_SUM_POWER); if (rank == 0)
{
empty++;
}
else
{
if (prev == rank)
{
counter++;
}
else if (counter > 0)
{
merges += 1 + counter;
counter = 0;
}
prev = rank;
}
} if (counter > 0) {
merges += 1 + counter;
} float monotonicity_left = 0;
float monotonicity_right = 0;
for (int i = 1; i < 4; ++i) {
if (line[i-1] > line[i]) {
monotonicity_left += pow(line[i-1], SCORE_MONOTONICITY_POWER) - pow(line[i], SCORE_MONOTONICITY_POWER);
} else {
monotonicity_right += pow(line[i], SCORE_MONOTONICITY_POWER) - pow(line[i-1], SCORE_MONOTONICITY_POWER);
}
} heur_score_table[row] = SCORE_LOST_PENALTY +
SCORE_EMPTY_WEIGHT * empty +
SCORE_MERGES_WEIGHT * merges -
SCORE_MONOTONICITY_WEIGHT * std::min(monotonicity_left, monotonicity_right) -
SCORE_SUM_WEIGHT * sum;
}
}

在这里启发式算法设计是按照四个预设规则来进行的:(算法中是计算每一行数据中符合规则的值,列是按照转置后按行的方式计算的)

1. 行中空格的数量, 算法中用变量 int empty 来进行计数,权值为 SCORE_EMPTY_WEIGHT = 270.0f ;

2. 行中可以合并的块数,变量为 merges,权值 float SCORE_MERGES_WEIGHT = 700.0f;

3. 行中数字排列的单调性,变量 std::min(monotonicity_left, monotonicity_right), 权值SCORE_MONOTONICITY_WEIGHT;

4. 行中数字的大小,变量sum,权值SCORE_SUM_WEIGHT 。

由于数字越大越不好合并,因此是  -SCORE_SUM_WEIGHT * sum;由于单调性越不好越不利于合并,因此是

-SCORE_MONOTONICITY_WEIGHT * std::min(monotonicity_left, monotonicity_right) 。

最终给每个游戏状态预设的启发值的计算方式为:

========================================

算法设定,每次进行选择时(上下左右四个方向),都要进行一定次数的推演,也就是假设执行在当前游戏状态执行某个移动后到达新的游戏状态,如此往复计算,最终向下推演多少步数(层数)是根据两个条件来判断的,一个是按照当前状态的不同数字个数来给出的,一个是最后探索到的状态其可能达到的概率的值是否满足阈值。

// Statistics and controls
// cprob: cumulative probability
// don't recurse into a node with a cprob less than this threshold
static const float CPROB_THRESH_BASE = 0.0001f;
static const int CACHE_DEPTH_LIMIT = 15;

需要注意的是在算法中对游戏的状态其实是分为两种的,一种是移动后到达的游戏状态A,一种是移动后游戏自动生成块后的游戏状态B。我们计算启发值都是根据移动后到达的游戏状态A。

如果移动后到达的游戏状态不满足条件则返回该状态的启发值:

如果当前的移动后状态曾经探索过,并且该状态探索的层数低于现在,那么直接返回保存的值:

因为探索的终止条件是达到一定层数和探索到达的概率小于预设阈值,因为曾经探索过该游戏状态并且层数低于现在那么曾经探索的游戏值一定要比此刻往下探索进行过更多的探索,因此不需要再重新探索直接返回曾经探索后保存的值。

移动后到达的游戏状态后游戏自动生成新数字块,当前移动后的游戏状态的下一层补上新块后游戏状态的返回的期望值作为当前游戏状态的值并进行保存:

生成新块的游戏值为移动(上下左右四个移动)后的最大的游戏返回值。

===================================================

再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(3) —— 游戏AI解法设计篇的更多相关文章

  1. 跟k8s工作负载Deployments的缘起缘灭

    跟k8s工作负载Deployments的缘起缘灭 考点之简单介绍一下什么是Deployments吧? 考点之怎么查看 Deployment 上线状态? 考点之集群中能不能设置多个Deployments ...

  2. 再探JS数组原生方法—没想到你是这样的数组

    最近作死又去做了一遍javascript-puzzlers上的44道变态题,这些题号称"JS语言专业八级"的水准,建议可以去试试,这里我不去解析这44道题了, ...

  3. 2048游戏分析、讨论与扩展 - Part I - 游戏分析与讨论

    2048这个游戏从刚出開始就风靡整个世界. 本技术博客的目的是想对2048涉及到相关的全部问题进行仔细的分析与讨论,得到一些大家能够接受而且理解的结果. 在这基础上,扩展2048的游戏性,使其变得更好 ...

  4. Android 带你玩转实现游戏2048 其实2048只是个普通的控件(转)

    1.概述 博主本想踏入游戏开放行业,无奈水太深,不会游泳:于是乎,只能继续开发应用,但是原生Android也能开发游戏么,2048.像素鸟.别踩什么来着:今天给大家带来一篇2048的开发篇,别怕不分上 ...

  5. Android 带你玩转实现游戏2048 其实2048只是个普通的控件

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40020137,本文出自:[张鸿洋的博客] 1.概述 博主本想踏入游戏开放行业,无 ...

  6. 制作 2D 素材|基于 AI 5 天创建一个农场游戏,第 4 天

    欢迎使用 AI 进行游戏开发! 在本系列中,我们将使用 AI 工具在 5 天内创建一个功能完备的农场游戏.到本系列结束时,您将了解到如何将多种 AI 工具整合到游戏开发流程中.本系列文章将向您展示如何 ...

  7. ChatGPT 设计游戏剧情 | 基于 AI 5 天创建一个农场游戏,完结篇!

    欢迎使用 AI 进行游戏开发! 在本系列中,我们将使用 AI 工具在 5 天内创建一个功能完备的农场游戏.到本系列结束时,您将了解到如何将多种 AI 工具整合到游戏开发流程中.本文将向您展示如何将 A ...

  8. 【再探backbone 02】集合-Collection

    前言 昨天我们一起学习了backbone的model,我个人对backbone的熟悉程度提高了,但是也发现一个严重的问题!!! 我平时压根没有用到model这块的东西,事实上我只用到了view,所以昨 ...

  9. 再探jQuery

    再探jQuery 前言:在使用jQuery的时候发现一些知识点记得并不牢固,因此希望通过总结知识点加深对jQuery的应用,也希望和各位博友共同分享. jQuery是一个JavaScript库,它极大 ...

  10. [老老实实学WCF] 第五篇 再探通信--ClientBase

    老老实实学WCF 第五篇 再探通信--ClientBase 在上一篇中,我们抛开了服务引用和元数据交换,在客户端中手动添加了元数据代码,并利用通道工厂ChannelFactory<>类创 ...

随机推荐

  1. Spring事务隔离级别和传播机制

    引言 什么是事务? 在理解事务之前,我们要先了解事务的基本作用 比如在生活中有这样一个场景----取钱,每个人应该都干过的事 在ATM机上取钱,1.输入密码----2.输入金额----3.银行扣钱-- ...

  2. 简单测下C++20 vector array lambda 的常数

    某天打了一下 CF,遇到了一道 https://codeforces.com/contest/1806/problem/E 这里需要卡常. 于是在 C++20(64) 下测出来了一些神奇的结果. 结果 ...

  3. Ubuntu 18.04 安装OneDrive自动同步

    Ubuntu 18.04 安装OneDrive自动同步 Windows10系统已经自带了OneDrive的自动同步功能,对于多设备用户而言已经成为了一个非常方便传输保存文件的途径,在Ubuntu下也有 ...

  4. 学习.NET 8 MiniApis入门

    介绍篇 什么是MiniApis? MiniApis的特点和优势 MiniApis的应用场景 环境搭建 系统要求 安装MiniApis 配置开发环境 基础概念 MiniApis架构概述 关键术语解释(如 ...

  5. 统信 UOS 重置Root账号密码 获取 Root 权限

    统信服务器默认无法用root 账号登入系统,用普通管理员登入后在切换至root账户下即可,初次使用系统时 切换至root账户前需要做很多配置 1.同信切换root账户首先要激活下系统,可以选择试用期激 ...

  6. unity中Shader实现地形中根据实际高度绘制等高线,剖切功能,颜色渐变等功能

    问题背景 在做地形模块时,需要根据实际地形高度画出世界相应的等高线,以及根据高度做颜色渐变,以及剖切功能. 解决方法 通过像素点在世界坐标系下的真实高度值来判断计算绘制等高线,剖切功能以及颜色渐变均有 ...

  7. debian11 简单搭建go环境

    简单环境,目前仅支持单版本go,后续可以考虑直接把go环境放到docker中或podman中,这样每个容器都是一套go版本. 新建文件夹目录 # 我直接用的root账户 cd /root mkdir ...

  8. Vue 的父组件和子组件生命周期钩子函数执行顺序?

    https://www.cnblogs.com/thinheader/p/9462125.html 参考连接 Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分: 加载渲染过程 ...

  9. 国产开源存储之光:Curve 通过信创认证

    网易数帆喜讯再传,Curve 近日通过信息技术应用创新(信创)认证! Curve 是一款高性能.易运维.云原生的分布式存储系统,由网易数帆存储团队发起开源,现为 CNCF 沙箱项目.国家工业信息安全发 ...

  10. 在ubuntu16.04下,源码编译安装特定版本的MongoDB PHP扩展

    背景:我的php项目在连接其他mongo库时报:Server at xxx:27017 reports wire version 5, but this version of libmongoc re ...