角色扮演笔中骰子概率的动态规划&纸游戏

时间:2014-05-12 07:57:19

标签: python algorithm

黑眼圈是一款流行的幻想角色扮演游戏,德语相当于龙与地下城。在这个系统中,角色具有许多属性和才能。每个天赋都有几个与之相关的属性,并且为了进行天赋检查,玩家为每个相关属性滚动d20(20面骰子)。每次滚动结果超过属性得分时,滚动和属性之间的差异就会相加。如果此差异总计(超出部分)大于人才值,则检查失败。

本教程的第一项任务是编写一个函数,该函数将与人才和人才等级相关联的属性分数列表作为输入,并返回测试成功的概率。您的算法必须能够有效地处理任意长度的列表。

提示:首先编写一个计算超额概率分布的函数。这可以使用动态编程有效地完成。


这是什么意思?我解决了背包问题,没有任何重大问题(0/1和无界),但我不知道该如何处理?

首先要解决的最小问题是等级1,单个属性为12(使用上面的示例) - 传递的概率是12/20对吗?然后排名2将是13/20然后排名3将是14/20?

我是否在正确的轨道上?我觉得我可能误解了实际的游戏规则。

3 个答案:

答案 0 :(得分:0)

有一点涉及概率和多项式的数学。例如,滚动d6可以用多项式

表示
x + x^2 + x^3 + x^4 + x^5 + x^6
-------------------------------
               6                ,

由于滚动1的概率为1/6(x / 6项),滚动a 2的概率为1/6(x ^ 2/6项)等。滚动d20并计算超过13的余量是

13 + x + x^2 + x^3 + x^4 + x^5 + x^6 + x^7
------------------------------------------
                    20                     .

这种表示的关键在于,如果Y是具有多项式p(x)的随机变量,并且Z是具有多项式q(x)的随机变量,则Y + Z的多项式是乘积p(x) )q(x)。例如,将d6多项式乘以其自身,得到2d6多项式,

x^2 + 2 x^3 + 3 x^4 + 4 x^5 + 5 x^6 + 6 x^7 + 5 x^8 + 4 x^9 + 3 x^10 + 2 x^11 + x^12
------------------------------------------------------------------------------------
                                         36                                          ,

应该被认为是正确的分发。

这里的强力算法对应于第一个 - outers-inners-lasts(FOIL)规则的通用版本,用于乘以两个线性多项式,其中,对于每个多项式因子的每个可能选择的一个项,我们将产品添加到最后的总和。这是低效的,因为,例如,如果我们试图通过检查((1 + x)/ 2)^ n来计算n个硬币的头部分布,我们最终将总计2 ^ n项。

提到的动态编程算法是计算部分产品的更合理的方法。例如,我们计算((1 + x)/ 2)^ 2 =(1 + 2 x + x ^ 2)/ 4,然后(1 + 2 x + x ^ 2)/ 4(1 + x)/ 2 =(1 + 3 x + 3 x ^ 2 + 1)/ 8等

答案 1 :(得分:0)

所以问题是:有多少种方法可以使用属性12,13和12以及7的天赋。让我们假设您知道第一个骰子的结果,让我们说11。然后问题就减少到如何有很多方法可以使用属性13和12以及7的天赋进行滚动。现在尝试使用不同的第一个滚动,让我们说你第一次滚动14。你已经结束了2,所以现在的问题是你能用多少种方式使用属性13和12以及5的天赋。现在尝试使用第一卷20。现在的问题是你能用多少种方式滚动属性13和12并且具有-1的天赋(在最后一种情况下它显然为0)。

答案 2 :(得分:0)

针对值k的属性的单个滚动是随机变量,其取值为0,1,2,3,...,20-k,概率为k / 20,1 / 20,1 / 20, ...

您可以将其表示为大小为21-k的数组,其概率为数组中的值。

如果你有两个(独立的)随机变量X1和X2,则表示其总和的随机变量是:

P(X1+X2=n) = sum(i=0 to n)P(X1=i)*P(X2=n-i)

您可以迭代地使用它来计算所有属性卷的总和的分布。然后,您可以通过将最终分布中的概率相加到值S(保存卷值)来找到进行保存卷的概率。

您可以做的一个简洁的优化是不存储任何高于值S的概率,因为它们从未使用过。这只是意味着将数组的大小限制为S + 1并进行边界检查。

将所有这些放在一起就可以得到这个代码。 multiply函数具有复杂度O(len(a1)* len(a2))但是因为a1总是长度为S + 1而a2的长度最多为21,所以它具有复杂度O(S)。总的来说,这为代码提供了复杂度O(nS),其中n是您拥有的属性数。我已经在属性[5,10]中包含了一些测试运行代码。

def attribute_dist(k):
    return [k/20.0] + [1/20.0] * (20-k)

def multiply(a1, a2):
    result = [0] * len(a1)
    for n in xrange(len(a1)):
        for i in xrange(len(a2)):
            if 0 <= n - i < len(a1):
                result[n] += a1[n-i] * a2[i]
    return result

def saving_throw_probability(attributes, S):
    d = [1.0] + [0] * S
    for a in attributes:
        d = multiply(d, attribute_dist(a))
    return sum(d)

for i in xrange(30):
    print i, saving_throw_probability([5, 10], i)