众妙之门玄之又玄,游戏系统中的伪随机(Pseudo-Randomization)和真随机(True-Randomization)算法实现Python3

简介: 有人说,如果一个人相信运气,那么他一定参透了人生。想象一下,如果你在某款moba游戏中,在装备平平,队友天坑的情况下,却刀刀暴击,在一小波gank中轻松拿下五杀,也许你会感叹自己的神操作和好运气,但其实,还有另外一种神秘的力量在支配着这一切,那就是:随机算法。

有人说,如果一个人相信运气,那么他一定参透了人生。想象一下,如果你在某款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中的随机是“使用随机算法”计算出的随机,而使用恰当的随机算法可以让这个随机很逼近“真正”的随机。

结语:

伪随机指的是“从逻辑层面对随机算法的结果进行干扰”,真随机指的是“现有技术或可支出成本无法修正的系统误差”,两套逻辑均被大量应用在游戏领域,但不能否认的是,运气这东西也确实存在,所以古代玩家难免也会发出“时来天地皆同力,运去英雄不自由。”的感慨。

相关文章
|
28天前
|
机器学习/深度学习 传感器 存储
使用 Python 实现智能地震预警系统
使用 Python 实现智能地震预警系统
114 61
|
12天前
|
弹性计算 数据管理 数据库
从零开始构建员工管理系统:Python与SQLite3的完美结合
本文介绍如何使用Python和Tkinter构建一个图形界面的员工管理系统(EMS)。系统包括数据库设计、核心功能实现和图形用户界面创建。主要功能有查询、添加、删除员工信息及统计员工数量。通过本文,你将学会如何结合SQLite数据库进行数据管理,并使用Tkinter创建友好的用户界面。
从零开始构建员工管理系统:Python与SQLite3的完美结合
|
4天前
|
机器学习/深度学习 人工智能 算法
基于Python深度学习的【垃圾识别系统】实现~TensorFlow+人工智能+算法网络
垃圾识别分类系统。本系统采用Python作为主要编程语言,通过收集了5种常见的垃圾数据集('塑料', '玻璃', '纸张', '纸板', '金属'),然后基于TensorFlow搭建卷积神经网络算法模型,通过对图像数据集进行多轮迭代训练,最后得到一个识别精度较高的模型文件。然后使用Django搭建Web网页端可视化操作界面,实现用户在网页端上传一张垃圾图片识别其名称。
25 0
基于Python深度学习的【垃圾识别系统】实现~TensorFlow+人工智能+算法网络
|
4天前
|
机器学习/深度学习 人工智能 算法
【手写数字识别】Python+深度学习+机器学习+人工智能+TensorFlow+算法模型
手写数字识别系统,使用Python作为主要开发语言,基于深度学习TensorFlow框架,搭建卷积神经网络算法。并通过对数据集进行训练,最后得到一个识别精度较高的模型。并基于Flask框架,开发网页端操作平台,实现用户上传一张图片识别其名称。
19 0
【手写数字识别】Python+深度学习+机器学习+人工智能+TensorFlow+算法模型
|
4天前
|
机器学习/深度学习 人工智能 算法
基于深度学习的【蔬菜识别】系统实现~Python+人工智能+TensorFlow+算法模型
蔬菜识别系统,本系统使用Python作为主要编程语言,通过收集了8种常见的蔬菜图像数据集('土豆', '大白菜', '大葱', '莲藕', '菠菜', '西红柿', '韭菜', '黄瓜'),然后基于TensorFlow搭建卷积神经网络算法模型,通过多轮迭代训练最后得到一个识别精度较高的模型文件。在使用Django开发web网页端操作界面,实现用户上传一张蔬菜图片识别其名称。
22 0
基于深度学习的【蔬菜识别】系统实现~Python+人工智能+TensorFlow+算法模型
|
8天前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
25 2
|
10天前
|
机器学习/深度学习 算法 5G
基于MIMO系统的SDR-AltMin混合预编码算法matlab性能仿真
基于MIMO系统的SDR-AltMin混合预编码算法通过结合半定松弛和交替最小化技术,优化大规模MIMO系统的预编码矩阵,提高信号质量。Matlab 2022a仿真结果显示,该算法能有效提升系统性能并降低计算复杂度。核心程序包括预编码和接收矩阵的设计,以及不同信噪比下的性能评估。
27 3
|
16天前
|
机器学习/深度学习 数据采集 存储
使用Python实现智能农业灌溉系统的深度学习模型
使用Python实现智能农业灌溉系统的深度学习模型
67 6
|
20天前
|
机器学习/深度学习 数据采集 算法框架/工具
使用Python实现智能生态系统监测与保护的深度学习模型
使用Python实现智能生态系统监测与保护的深度学习模型
59 4
|
20天前
|
机器学习/深度学习 人工智能 算法
【车辆车型识别】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+算法模型
车辆车型识别,使用Python作为主要编程语言,通过收集多种车辆车型图像数据集,然后基于TensorFlow搭建卷积网络算法模型,并对数据集进行训练,最后得到一个识别精度较高的模型文件。再基于Django搭建web网页端操作界面,实现用户上传一张车辆图片识别其类型。
65 0
【车辆车型识别】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+算法模型