众妙之门玄之又玄,游戏系统中的伪随机(Pseudo-Randomization)和真随机(True-Randomization)算法实现Python3
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_212
有人说,如果一个人相信运气,那么他一定参透了人生。想象一下,如果你在某款moba游戏中,在装备平平,队友天坑的情况下,却刀刀暴击,在一小波gank中轻松拿下五杀,也许你会感叹自己的神操作和好运气,但其实,还有另外一种神秘的力量在支配着这一切,那就是:随机算法。
伪随机(Pseudo-Randomization)
其实,竞技游戏通常是拒绝随机性干预的,因为它干扰了玩家实际操作水平的考量。但是,应对突发情况也应该是玩家应变能力的一种表现。因此,在moba游戏中,有很多随机事件,这些随机事件降低了游戏的可预测性,增加了变数。为了限制这种随机性的影响,伪随机算法应运而生。
伪随机分布(pseudo-random distribution,简称PRD)在游戏中用来表示关于一些有一定几率的装备和技能的统计机制。在这种实现中,事件的几率会在每一次没有发生时增加,但作为补偿,第一次的几率较低。这使得效果的触发结果更加一致。
以Dota2为例,在大量的英雄技能中,比如说斯拉达的重击、酒仙的醉拳、主宰的剑舞之类的技能,都利用了伪随机机制:
具体的实现逻辑是这样的,每次释放技能,都使用一个不断增加的概率来进行计算,如果这个事件一直触发不成功,那么概率就不断上升,直到事件发生为止。
要完成这个伪随机算法,要解决的问题就是,对于一个发生概率为p的事件,在我们第n次释放技能的时候,发生的几率在第N次成功触发的几率为P(N) = C × N,对于每一个没有成功触发的实例来说,伪随机分布PRD会通过一个常数C来增加下一次效果触发的几率。这个常数会作为初始几率,比效果说明中的几率要低,一旦效果触发,计数器会重置,几率重新恢复到初始几率。
举个例子,斯拉达的重击有25%几率对目标造成眩晕,那么第一次攻击,他实际上只有大约8.5%几率触发重击,随后每一次没有成功的触发实例都会增加大约8.5%触发几率,于是到了第二次攻击,几率就变成大约17%,第三次大约25.5%……以此类推,直到重击的概率达到100%。在一次重击触发后,下一次攻击的触发几率又会重置到大约8.5%,那么经过一段时间之后,这些重击几率的平均值就会接近25%。
基于伪随机的效果使得多次触发或多次不触发的极端情况都变得罕见,这使得游戏的运气成分相对降低了一些。然而虽然理论上可行,但是在游戏中玩家很难运用这个机制来“刻意”增加下一次触发的几率。值得一提的是,Dota2对伪随机技能算法也有限制,如果被释放技能的对象根本就不可能触发效果,那么触发几率不会增加,也就是说,一个英雄反补或攻击建筑不会增加他下一次攻击触发的致命一击几率,因为致命一击对反补和建筑无效。
马尔可夫链(Markov chain)
那么,Dota2底层到底怎么实现的呢?这涉及到一个算法公式:马尔可夫链(Markov chain)
马尔可夫链因俄国数学家Andrey Andreyevich Markov得名,为状态空间中经过从一个状态到另一个状态的转换的随机过程。该过程要求具备“无记忆”的性质:下一状态的概率分布只能由当前状态决定,在时间序列中它前面的事件均与之无关。
说白了就是,如果对于一个触发概率为5%暴击的技能,那么我砍第一刀出现暴击的概率是c,第二刀是2c,如果一直没有暴击,直到第N刀,出现了(c*N)大于1了,那么这次暴击就必然发生了,而在中间的每一次,如果暴击发生了,那么我们就把随机概率重置为c。
P = 1*c + 2*c(1-c) + 3*c(1-c)(1-2c)+4*c(1-c)(1-2c)(1-3c)
其中 P = 1/p,N=1/c(第N刀,即累加概率的最后一刀必然暴击)
那么我们就可以用折半查找在(0,1)之间不断估算c,直到这个公式成立就行了。
首先,模拟N次触发,计算是否会在N次触发之后必然发生:
import math
def p_from_c(c):
po, pb = 0, 0
sumN = 0
maxTries = math.ceil(1/c)
for n in range(maxTries):
po = min(1, c*n) * (1-pb)
pb = pb + po
sumN = sumN + n * po
return (1 / sumN)
随后,在遍历中,不断地取中值判断,如果触发的概率足够小,那么认为已经找到了对应的c系数:
def c_from_p(p):
cu = p
cl = 0.0
p1, p2 = 0, 1
while True:
cm = (cu + cl) / 2
p1 = p_from_c(cm)
if abs(p1 - p2) <= 0.000000001:
break
if p1>p:
cu = cm
else:
cl = cm
p2 = p1
return cm
具体使用上,我们需要单独存储一个阈值变量,那就是释放次数 fail,如果 fail 一直处于未暴击的状态,那就累加对应的释放概率:
fail = 1
print(c_from_p(0.20)*100*fail)
fail = 2
print(c_from_p(0.20)*100*fail)
fail = 3
print(c_from_p(0.20)*100*fail)
输出返回值:
5.570398829877376
11.140797659754751
16.711196489632126
对于一个20%暴击的技能,第一刀实际上只有5%,第二刀11%,等到砍到第三刀就有16%。
那么知道了底层算法和实现,有什么用呢?我们就可以在游戏中超神了吗?事实上,底层算法对玩家在游戏实际操作技巧是有一定指导意义的,比如,如果玩家能够记住释放技能以后的攻击次数,对应的,玩家脑子里就会有一个概率,事实上,第一刀5%触发的概率还是非常低的,而反补和打建筑物又不能增加fail阈值的次数,所以如果是在团战中,面对半血或者残血英雄,第一刀完全可以不砍他,因为概率太小,完全可以前两刀砍对方别的英雄,留出后面几刀再砍,这样就会在无形中增加暴击或者眩晕技能,是的,如果半血被晕,基本上人头就交出去了,电光石火之间,算法可以帮我们增大超神的概率,要知道,职业玩家的反应能力不是业余玩家可以想象的。
这就好比,在牌局中,真正的高手会靠记忆力将手牌中间段的数量记住,如9/10/J,来保证自己的顺子能够在最后时刻打通或者逼出对手炸弹。
真随机(True-Randomization)
什么叫真随机?有人会说,抛硬币、掷骰子,这些都是真随机事件。
是的,一枚銀色的硬币在半空中快速地翻转着,一闪一闪地泛着光辉,你看不清楚哪面向上、哪面向下,甚至连硬币的主人自己也不清楚。
但其实,抛硬币的角度、力量、周围风速等等因素都会影响最终结果,所以,严格意义上来说,拋硬币当然不是真随机事件,因为这个宏观运动过程和结果严格遵守物理定律,而每次的输入变量也是有限且确定的。
所以,我们所定义的真随机是有条件的,即如果伪随机是靠次数做关联系递增,那么真随机就跟它相反,多次实施过程中没有关联的事件,我们称之为真随机。
那么,在Python中,能否用逻辑实现这种“真随机”?
假设我从1-100个数里,“真随机”挑数字,计数器进行随机挑选后的记录,挑一万次,理论上,它会存在正态分布吗:
import random
from collections import Counter
c = Counter()
for _ in range(10000):
c[random.randint(1, 100)] += 1
print(c)
print(c.values())
print(max(c.values()))
返回输出:
Counter({90: 123, 51: 122, 84: 121, 77: 119, 74: 118, 2: 117, 86: 116, 33: 116, 72: 113, 81: 112, 56: 112, 42: 112, 9: 111, 11: 110, 97: 110, 16: 109, 27: 109, 8: 109, 6: 109, 62: 109, 15: 108, 29: 108, 12: 107, 22: 106, 28: 106, 82: 106, 7: 105, 94: 105, 89: 105, 71: 105, 5: 105, 24: 105, 80: 105, 65: 104, 20: 104, 48: 104, 93: 104, 1: 104, 79: 103, 57: 103, 40: 103, 26: 103, 63: 103, 30: 102, 68: 102, 75: 101, 18: 101, 23: 101, 39: 100, 44: 100, 54: 99, 85: 99, 91: 99, 59: 99, 76: 99, 43: 98, 31: 98, 66: 98, 25: 98, 60: 97, 58: 97, 35: 97, 64: 97, 70: 97, 19: 97, 34: 97, 96: 96, 13: 96, 52: 96, 61: 95, 100: 95, 21: 95, 98: 95, 49: 94, 69: 94, 99: 93, 87: 93, 88: 93, 78: 92, 73: 91, 17: 91, 67: 91, 4: 91, 46: 90, 92: 90, 36: 90, 3: 89, 14: 89, 41: 89, 55: 87, 53: 85, 32: 85, 38: 84, 37: 84, 50: 83, 83: 83, 10: 83, 45: 82, 47: 80, 95: 75})
dict_values([121, 99, 112, 99, 94, 110, 89, 93, 93, 98, 95, 91, 109, 100, 109, 116, 104, 105, 91, 84, 106, 104, 105, 92, 106, 83, 104, 105, 110, 103, 82, 102, 112, 85, 105, 103, 85, 89, 103, 99, 117, 83, 87, 96, 100, 96, 90, 105, 123, 99, 91, 104, 101, 118, 99, 103, 91, 83, 98, 95, 98, 107, 111, 97, 104, 101, 113, 116, 97, 98, 97, 122, 90, 101, 108, 94, 96, 106, 112, 97, 102, 103, 108, 75, 97, 109, 80, 93, 109, 109, 95, 95, 90, 97, 89, 84, 105, 119, 97, 105])
123
最高的一次出现了123次,接着我们来换个方式,不用random:
import random
from collections import Counter
c = Counter()
for _ in range(10000):
c[100] += 1
print(c)
print(c.values())
print(max(c.values()))
返回输出:
Counter({100: 10000})
dict_values([10000])
10000
对比之下,我们可以这么理解,出现次数的最大值约大,我们的随机性就越小。
所以,虽然每一次获取没有表面上关联性,但这并不是“真随机”,所以说,计算机到底能不能实现“真随机”?并不能,因为Python的random模块本身就是基于PRD伪随机算法,可以理解为Python中的随机是“使用随机算法”计算出的随机,而使用恰当的随机算法可以让这个随机很逼近“真正”的随机。
结语:
伪随机指的是“从逻辑层面对随机算法的结果进行干扰”,真随机指的是“现有技术或可支出成本无法修正的系统误差”,两套逻辑均被大量应用在游戏领域,但不能否认的是,运气这东西也确实存在,所以古代玩家难免也会发出“时来天地皆同力,运去英雄不自由。”的感慨。
原文转载自「刘悦的技术博客」 https://v3u.cn/a_id_212
众妙之门玄之又玄,游戏系统中的伪随机(Pseudo-Randomization)和真随机(True-Randomization)算法实现Python3的更多相关文章
- 《众妙之门——精通CSS3》一书知识点剖析
不得不佩服京东的速度,昨天刚下单的两本书今天上午就到了.其中一本是全彩页的<众妙之门 - 精通CSS3>,细看了前几十页,书上的叙述方式给我的印象其实不如“彩页”来的讨喜——接连说上几个例 ...
- jQuery几个易混淆之处(参考《众妙之门》及相关博客)
parent() && parents() && closest() 这三个方法都与沿着DOM向上导航有关,在由选择器返回的元素上方,匹配父元素或之前的祖先元素,但是每 ...
- JavaScript十大古怪之处(出自众妙之门)
1. null是一个对象: alert(typeof null); //objects NULL表示没有值,那么很明显他不能作为任何东西的实例,所以下式应该等于false: alert(null i ...
- css 众妙之门 学习笔记
伪类: 结构伪类: :empty :only-child :before :after :active :hover :focus :link :visited :first-child :last- ...
- userAgent,JS这么屌的用户代理,你造吗?——判断浏览器内核、浏览器、浏览器平台、windows操作系统版本、移动设备、游戏系统
1.识别浏览器呈现引擎 为了不在全局作用域中添加多余变量,这里使用单例模式(什么是单例模式?)来封装检测脚本.检测脚本的基本代码如下所示: var client = function() { var ...
- Linux系统中有趣的命令(可以玩小游戏)
Linux系统中有趣的命令(可以玩小游戏) 前言 最近,我在看一些关于Linux系统的内容,这里面的内容是真的越学越枯燥,果然学习的过程还是不容易的.记得前几个月初学Linux时,有时候就会碰到小彩蛋 ...
- 新买苹果电脑,mac系统中小白应该了解哪些东西?
本文旨在分享新买了mac电脑,应该做哪些设置,帮助苹果电脑小白轻松上手使用mac电脑,当然,新电脑肯定是需要安装各种软件,这里,小编推荐一下可以看看小编写的mac软件装机必备Mac 装机必备软件推荐, ...
- 腾讯云分布式数据库TDSQL在银行传统核心系统中的应用实践
本文是腾讯云TDSQL首席架构师张文在腾讯云Techo开发者大会现场的演讲实录,演讲主题是<TDSQL在银行传统核心系统中的应用实践>. 我是TDSQL架构师张文,同时也是TDSQL的开发 ...
- Unity游戏开发中的内存管理_资料
内存是手游的硬伤——Unity游戏Mono内存管理及泄漏http://wetest.qq.com/lab/view/135.html 深入浅出再谈Unity内存泄漏http://wetest.qq.c ...
随机推荐
- 斯坦福NLP课程 | 第12讲 - NLP子词模型
作者:韩信子@ShowMeAI,路遥@ShowMeAI,奇异果@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/36 本文地址:http://www. ...
- install ubuntu on raspberry pi 4b
how to install 第一次连 wifi 时总会失败,需要 sudo reboot 重启后,就可以正常连接 当需要切换 wifi 时,修改 network-config 文件是无效的,需要 s ...
- 【单片机】CH32V103C8T6定时器3程序实验
代码功能:每隔1毫秒进入一次定时器中断. 每隔1秒串口打印一次数据. time.c #include "time.h" #include "ch32v10x.h" ...
- salesforce零基础学习(一百一十五)记一个有趣的bug
本篇参考:https://help.salesforce.com/s/articleView?language=en_US&type=1&id=000319486 page layou ...
- Docker容器手动安装oracle19C
Docker容器手动安装oracle19C docker容器体积小,与宿主机共用内核参数,因此修改宿主机的内核参数即是修改容器的内核参数 1.修改宿主机内核参数 [root@localhost ~]# ...
- C语言- 基础数据结构和算法 - 队列的顺序存储
听黑马程序员教程<基础数据结构和算法 (C版本)>, 照着老师所讲抄的, 视频地址https://www.bilibili.com/video/BV1vE411f7Jh?p=1 喜欢的朋友 ...
- ExtJS 布局-Anchor 布局(Anchor layout)
更新记录: 2022年5月30日 发布本篇 1.说明 anchor布局类似auto布局从上到下进行堆叠,但不同的是其可以指定每个元素相对于容器大小的比例. 当调整父容器大小,容器根据指定的规则调整所有 ...
- word-制作三线表
找一个表格或插入一个表格, 找到 [设计] [新建表格样式] [将格式应用于: 整个表格] 点击"框线设置"按钮,在弹出的下拉菜单中分别选择 [上框线] 和 [下框线],然后分别设 ...
- ASP.NET Core 根据环境变量支持多个 appsettings.json配置文件 (开发和生产)
新建一个项目,web根目录会出现一个 appsettings.json 配置文件, 此时添加--新建项,输入 appsettings.Development.json 再新增一个,appsetti ...
- 异常注意事项_多异常的捕获处理和异常注意事项_finally有return语句
异常注意事项_多异常的捕获处理 多个异常使用捕获又该如何处理呢? 1. 多个异常分别处理 2. 多个异常一次捕获,多次处理 3. 多个异常一次捕获一次处理 public class Demo01Exc ...