xiezhenggang 发表于 2015-11-18 01:48

原创RPG系统设计,概率求助(我数学没学好)

本帖最后由 xiezhenggang 于 2015-11-18 01:55 编辑

自制RPG系统,属性&技能部分。

4项基础属性,取值范围2~4,分别对应2d6~4d6。
若干技能,每项分别对应某个基础属性。比如“科技/维修”就对应“智能”。
假设智能4,需要做科技检定时就投4d6。

技能可以提升,提升后的技能有更多“重投1次”的机会。
已受训1次,职业级2次,专家级3次,大师级4次。
假设某人智能4,科技/维修达到职业级,则投4d6,并有2次重投机会。

方案a:一次投4颗,不满意重投任何一颗,再不满意再重投任何一颗。
优点:无论如何,只需4颗骰子。
缺点:重投多次。

方案b:一次投6颗,取最大的4颗,其余舍去。
优点:一次出结果,快速、直接。
缺点:一大把骰子。

个人感觉这两个方案的数学期待值应该会有些差异,但我概率是自修的,基础公式不熟。
求计算这两个方案分别对应的数学期待值。
考虑到非人类生物的情况,考虑到超级英雄的超级牛逼,我需要知道如下数据:

方案a不重投重投1次重投2次重投3次重投4次重投5次
1d6 3.5





2d6 7.0





3d6 10.5





4d6 14.0





5d6 17.5





6d6 21.0






方案b无额外骰多投1颗多投2颗多投3颗多投4颗多投5颗
1d6 3.5





2d6 7.0





3d6 10.5





4d6 14.0





5d6 17.5





6d6 21.0






另:最好能提供计算公式。这样如果以后把d6改成d10,可以自己套公式。

thest 发表于 2015-11-18 04:00

本帖最后由 thest 于 2015-11-18 04:07 编辑

感觉像山屋惊魂+CV
方案A,如果说平均期望值的话,因为不管怎么投都是D6,每次投出来的结果也都是独立事件,所以方案A重投和不重投的期望是没有区别的吧?
至于方案B的话,实际上有两种标准
一种是以有效结果来计算(1111)+(1112)+(1122)+(1222)+...+(6666)的平均数,直接拿最终结果来计算就是(4+5+6+...+24)21=14
另一种是以投骰子事件的概率来计算,既ABCD四个骰子投出1112和投出1121分别算作两种不同的结果,但因为我是一个高中数学早就被吃掉了的没知识的文科生,所以我不知道这里该怎么列公式怎么计算
至于你需要哪种就看你需求了,思路不一定对,有帮助最好,没帮助权当抛砖引玉
另外我投方案B一票,只要不贪的话玩家重投肯定是不会吃亏的,这样一来设计的目的可能会比较合理——我花费时间和金钱去受训,不是为了给自己一个被自己给坑了的机会toka。当然如果你的目的就是强调随机性,或者说所有的受训技能都是有利有弊的,那方案A也未尝不可
拍个脑袋:现在是只有“重投次数”的限制与成长,如果再加一个关于重投“骰子个数”方面的内容,会不会灵活很多?比如像DND五版规则每3级选择一个道途那样,每次受训可以选择强化方向(然而这样就真的成CV的丢骰规则了

thest 发表于 2015-11-18 04:09

另外一大把骰子丢出去的感觉很爽啊很爽啊很爽啊说三遍

frosta 发表于 2015-11-18 06:19

本帖最后由 frosta 于 2015-11-17 14:27 编辑

方案一如果不是强制reroll某个骰子的话期望值会变的,因为出了6没人会想reroll
假设是reroll最小的小于期望值3.5的骰子的话,那么概率就不是不能求了,只不过一拍脑子想出来的公式可能不太准,仅供参考

xd6期望值为3.5x
每单个骰子reroll后期望值为3.5
reroll有且仅有三种情况:
1. xd6中出现了1,期望值增加2.5
2. xd6中没有1,且出现了2,期望值增加1.5
3. xd6中没有1/2,且出现了3,期望值增加0.5

1的出现概率:1-(5/6)^x
   5/6为没有1的概率,^x为x个均不为1的概率,1-为出现1的概率
1不出现的概率:(5/6)^x
   2出现的概率:1-(4/5)^x
          因为5/6已经强制1不出现,那么不出现2的概率就是4/5
                1不出现,2出现的概率:(5/6)^x* (1-(4/5)^x)
         
1/2不出现的概率:(4/6)^x
   3出现的概率:1-(3/4)^x


剩下的用分别的概率乘上相应增加的期望值就能得出reroll一次的期望

reroll多次可以用同样的思路,reroll1次后有1的概率是reroll前1个一下“1”*reroll出1+reroll前1个以上“1”的概率,多次就是多层嵌套下去,公式会写的很恶心,等高人看有没有更好的算法

frosta 发表于 2015-11-18 06:30

然后方案B本质上是方案A的升级版,同样是多roll n 次,方案B允许玩家选择reroll过程中出现的最高值,而方案A只能选择第一个值>3的,所以很明显方案B期望值大于方案A(就单个骰子来说,复数个骰子期望值的差值可能会相应减少)
不过具体大多少还是要算,先等高人,没人回复的话晚上回来试试

dalos 发表于 2015-11-18 08:39

桌游我只一个建议,不要复杂化。

deadbeef 发表于 2015-11-18 11:14

方案A和方案B在大体上是一样的

如果强制方案A必须把重骰的次数用尽 则完全等同于方案B
但是因为方案A能够选择重骰多少次 所以比方案B的选择性更大


就楼主说到的那些数据 最近脑子有点转不动 重骰确实一时想不出该怎么计算期望
所以图个方便 拿HASKELL写了个程序直接计算 代码如下
import Control.Applicative
import Data.List

main = do
    print "E(4)"
    print $ nexpect 4
    print "D(4)"
    print $ nvariance 4
    print "E(5)"
    print $ nexpect 5
    print "D(5)"
    print $ nvariance 5
    print "E(6)"
    print $ nexpect 6
    print "D(6)"
    print $ nvariance 6

nmax :: (Ord a) => Int -> ->
nmax n = take n . reverse . sort

type Dice = Int
type Props =

roll :: Props -> Dice -> Props
roll props dice
    | length props < 4 = dice : props
    | otherwise = nmax 4 (dice : props)

roll_all :: ->
roll_all props_all = roll <$> props_all <*>

nroll :: Int -> ->
nroll 0 = \x -> x
nroll n = roll_all . nroll (n-1)

expect :: -> Float
expect props_all = (fromIntegral . sum . fmap sum $ props_all) / (fromIntegral . length $ props_all)

variance_ :: -> Float -> Float
variance_ props_all e = (sum . fmap (\x -> (fromIntegral (sum x) - e) ^ 2) $ props_all) / (fromIntegral . length $ props_all)
variance :: -> Float
variance props_all = variance_ props_all $ expect props_all

nexpect :: Int -> Float
nexpect n = expect . nroll n $ [[]]

nvariance :: Int -> Float
nvariance n = variance . nroll n $ [[]]

输出的结果是留4个最大值时 骰4次,5次,6次的期望和方差
"E(4)"
14.0
"D(4)"
11.666667
"E(5)"
15.930942
"D(5)"
11.362313
"E(6)"
17.344479
"D(6)"
10.020688


在4次以前的则是很简单的初等概率问题 期望是3.5*n 就不列了

deadbeef 发表于 2015-11-18 12:39

我又改了下代码用来输出楼主要的那个表格形式
import Control.Applicative
import Data.List

main = putStr $ output_all 6 3 ++ "\n"

nmax :: (Ord a) => Int -> ->
nmax n = take n . reverse . sort

type Dice = Int
type Props =

roll :: Int -> Props -> Dice -> Props
roll n props dice
    | length props < n = dice : props
    | otherwise = nmax n (dice : props)

roll_all :: Int -> ->
roll_all n props_all = roll n <$> props_all <*>

nroll :: Int -> Int -> ->
nroll _ 0 = \x -> x
nroll m n = roll_all m . nroll m (n-1)

expect :: -> Float
expect props_all = (fromIntegral . sum . fmap sum $ props_all) / (fromIntegral . length $ props_all)

variance_ :: -> Float -> Float
variance_ props_all e = (sum . fmap (\x -> (fromIntegral (sum x) - e) ^ 2) $ props_all) / (fromIntegral . length $ props_all)
variance :: -> Float
variance props_all = variance_ props_all $ expect props_all

nexpect :: Int -> Int -> Float
nexpect m n = expect . nroll m n $ [[]]

nvariance :: Int -> Int -> Float
nvariance m n = variance . nroll m n $ [[]]

output_u :: Int -> Int -> String
output_u m n = (\x -> "E:" ++ show (x) ++ "/D:" ++ show ( variance_ (nroll m (m+n) [[]]) x )) (nexpect m (m+n))
output_line :: Int -> Int -> String
output_line m 0 = output_u m 0
output_line m n = output_line m (n-1) ++ "    " ++ output_u m n
output_all :: Int -> Int -> String
output_all 1 n = output_line 1 n
output_all m n = output_all (m-1) n ++ "\n" ++ output_line m n


因为计算的空间复杂度是重骰次数的指数级数 我这里只算了最大3次重骰的结果

E:3.5/D:2.9166667    E:4.4722223/D:1.9714514    E:4.9583335/D:1.3084502    E:5.244599/D:0.91007966
E:7.0/D:5.8333335    E:8.458333/D:4.905668    E:9.344136/D:3.7735531    E:9.93017/D:2.9142535
E:10.5/D:8.75    E:12.244598/D:8.104541    E:13.43017/D:6.778021    E:14.273791/D:5.589378
E:14.0/D:11.666667    E:15.930942/D:11.362313    E:17.344479/D:10.020688    E:18.402756/D:8.627638
E:17.5/D:14.583333    E:19.560293/D:14.613639    E:21.151459/D:13.348709    E:22.388805/D:11.86123
E:21.0/D:17.5    E:23.154118/D:17.843287    E:24.886812/D:16.695683    E:26.274813/D:14.912097


前面也说了 当方案A强制重骰 并且强制只能舍弃最小的一个数值时 是等同于方案B的
所以这个表对于上述情况 方案A/B是通用的

frosta 发表于 2015-11-18 13:36

deadbeef 发表于 2015-11-17 19:14
方案A和方案B在大体上是一样的

如果强制方案A必须把重骰的次数用尽 则完全等同于方案B


如果强制方案A必须把重骰的次数用尽 则完全等同于方案B
但是因为方案A能够选择重骰多少次 所以比方案B的选择性更大

感觉这说法有点问题吧
方案A是重骰之后强制用重骰的结果,方案B则是相当于重骰之后选择想要的结果
拿个最简单的例子,一个骰子+重掷10次,
方案A无论重掷多少次都只有最后一次有效,期望是3.5
方案B则是掷11个骰子,选其中的最大值,很显然期望要远大于3.5

xiezhenggang 发表于 2015-11-18 13:39

deadbeef 发表于 2015-11-18 12:39
我又改了下代码用来输出楼主要的那个表格形式因为计算的空间复杂度是重骰次数的指数级数 我这里只算了最大3 ...

拜谢!
既然两个方案没什么本质差别,我就不去纠结了。
索性两种处理方式都开放,让玩家自行选择好了。

xiezhenggang 发表于 2015-11-18 13:56

frosta 发表于 2015-11-18 13:36
如果强制方案A必须把重骰的次数用尽 则完全等同于方案B
但是因为方案A能够选择重骰多少次 所以比方案B的 ...

允许提前终止啊。
方案a是每次选择数字最小的那个重投,直到最小数字大于3,或达到重投次数上限。

xiezhenggang 发表于 2015-11-18 14:09

deadbeef 发表于 2015-11-18 12:39
我又改了下代码用来输出楼主要的那个表格形式因为计算的空间复杂度是重骰次数的指数级数 我这里只算了最大3 ...

原则上,方案b应该会比方案a的期待值略高一点点。
因为在“投”的时候两个方案没太大差别,在“选”的时候,方案b的可知信息更多一些。

deadbeef 发表于 2015-11-18 14:19

xiezhenggang 发表于 2015-11-18 13:39
拜谢!
既然两个方案没什么本质差别,我就不去纠结了。
索性两种处理方式都开放,让玩家自行选择好了。 ...

2个方案并不是没区别
应该说B是A的一种特化情况
也就是A强制玩家每次舍弃最下值 并且用尽重骰次数

单独考虑A的话 期望实际是没有意义的 因为并没有给定一个玩家选择规则

关于不同选择对于总期望的影响,方案A下玩家能选择2个参数
1,重骰后舍弃哪一个数值
2,还有机会时,是否继续重骰

就参数1来说,选择舍弃最小值肯定是最大化期望的最优解
直观上来说 这个局部最优应该是等效于全局最优的(没力气去证明 所以只能说是直观认为)

而对于参数2来说
直观上,每一次重骰只有可能增加总数值,并不可能降低总数值
所以如果玩家依照某个决策规则来决定何时停止重骰,虽然总期望并不好计算,但是可以说总期望一定无法超越方案B的期望

也就是说 方案B实际上应该是方案A的期望最大化的最优决策

deadbeef 发表于 2015-11-18 14:27

我觉得这里 方案A 让玩家选择是否继续 实际上的意义并不大

正如我上面说的 因为无论如何选择 选择重骰 都是没有坏处的
即使没有摇出更大的数值 至少也能舍弃掉这次数值 保证总数值不改变
也就是说玩家理应无条件选择一直重骰 直到机会用尽
这时候方案A也就等同于方案B了

若想给玩家在方案A的选择上增加一些风险让这个选择变得更有意义 则可以加上一些限制
比如重骰出的数值 不允许舍弃只能舍弃已保留的数值中的一个也就是说强制要替换掉一个原始数值

这样做的话 玩家的选择决策就会大大的影响到总的期望了
而且就算玩家不选择 只是每次都用尽机会 期望也将比方案B低
不过无论决策如何 方案B 的期望永远是上限

只是这么加个限制的话 感觉会徒增玩家的烦恼……

Scrummble 发表于 2015-11-18 14:33

方案B一定比方案A要强。
方案A中第六颗骰子无论多么低,结果都会保留,而B的话无论第几颗数值太低都可以被其他骰子换掉。

deadbeef 发表于 2015-11-18 14:35

frosta 发表于 2015-11-18 13:36
如果强制方案A必须把重骰的次数用尽 则完全等同于方案B
但是因为方案A能够选择重骰多少次 所以比方案B的 ...

对 你看到了盲点
这里A方案和B方案是否同质化的最大问题
就是重骰出来的这个数值 是否可以舍弃
也就是是否强制替换原始数值

楼主的主帖里好像并没有强调这一点
所以我前面当然是挑比较简单的情况
也就是不强制替换 可以选择舍弃重骰数值 来分析的

至于强制替换 就像我楼上说的 结果和玩家选择有很大关系 但是期望不会超过方案B

deadbeef 发表于 2015-11-18 14:55

好吧 我又趴在地上仔细体会了一下主楼的意思
方案A看来是我理解错了 应该就像上面几个说的那样是不允许舍弃重骰值的

那么方案A和方案B就是不一样的了
代码上就是把上面的roll描述里的nmax n (dice : props)改成dice : (nmax (n-1) props)即可
下面是方案A的表

E:3.5/D:2.9166667    E:3.5/D:2.9166667    E:3.5/D:2.9166667    E:3.5/D:2.9166667
E:7.0/D:5.8333335    E:7.9722223/D:4.8881164    E:8.458333/D:4.225122    E:8.744598/D:3.826655
E:10.5/D:8.75    E:11.958333/D:7.8223433    E:12.844136/D:6.6901445    E:13.43017/D:5.832705
E:14.0/D:11.666667    E:15.744598/D:11.0210085    E:16.93017/D:9.695634    E:17.77379/D:8.50433
E:17.5/D:14.583333    E:19.43094/D:14.279203    E:20.844479/D:12.937392    E:21.902756/D:11.53673
E:21.0/D:17.5    E:23.060293/D:17.53684    E:24.651459/D:16.273966    E:25.888805/D:14.48855

xiezhenggang 发表于 2015-11-18 16:37

deadbeef 发表于 2015-11-18 14:55
好吧 我又趴在地上仔细体会了一下主楼的意思
方案A看来是我理解错了 应该就像上面几个说的那样是不允许舍弃 ...

细看了一下,感觉好像有点问题。
比如说1颗骰子的情况,平均期待值是3.5。
当有重投机会时,典型的策略是:
1~3,重投;
4~6,保留。
目标是争取平均值>3.5。

方案b就简单了,一把投完,取最大的那几个,出啥算啥。

deadbeef 发表于 2015-11-18 16:39

本帖最后由 deadbeef 于 2015-11-18 16:52 编辑

xiezhenggang 发表于 2015-11-18 16:37
细看了一下,感觉好像有点问题。
比如说1颗骰子的情况,平均期待值是3.5。
当有重投机会时,典型的策略是 ...
上面那个方案A的表没有考虑决策问题
横轴是几 就是重投几次的期望和方差

也就是计算的 重骰N次的期望 而不是计算的 最多重骰N次的期望
换句话说 就是强制N次重骰用完的情况

最多重骰N次的情况 因为实际骰几次 只由玩家决定 所以并没有合在一起计算期望的意义


另外 你说的1颗骰子的情况
就是最多重骰N次的情况 因为有玩家决策在里面加成 所以决策没有确定时并不能计算实际期望


顺便提一种一定能比上述列出的表中期望高的决策
当最大重骰次数是N时重骰N次的期望为E(N)(即表中值)
那么重骰过程中一旦骰出的总值大于E(N) 则立刻停止
那么按照这种决策得出的新的总期望E'(N)一定是大于E(N)的


简化到1颗骰子的情况 也就是你上面提到的决策方法因为对于1颗骰 E(N)恒等于3.5
所以只要骰出大于3.5的值就停止 结果的总期望一定比表中大

但是这种决策也只是一种局部最优决策 很难认为这是全局最优决策



deadbeef 发表于 2015-11-18 17:33

本帖最后由 deadbeef 于 2015-11-18 17:35 编辑

按照我上述的决策 也试着计算了一下期望
因为决策是基于之前那张表的 所以计算量增加了
这次就只算了最多5个骰子的情况 也只算了期望没算方差
代码什么的估计没人在意就不放了

E:3.5    E:4.25    E:4.625    E:4.8125
E:7.0    E:8.097222    E:8.858796    E:9.25
E:10.5    E:11.770833    E:12.744598    E:13.553433
E:14.0    E:15.42554    E:16.544624    E:17.489359
E:17.5    E:19.045074    E:20.288633    E:21.34347

就像我前面说的 这种方案无论如何决策 总期望还是无法达到或超过方案B的

fcmorrowind 发表于 2015-11-18 18:06

天哪,概率论都是怎么学的……居然连A比B期望要高都出来了……

deadbeef 发表于 2015-12-30 17:40

因为私信怕有字数限制啥的蛮麻烦就直接回在这帖里了
LZ要的最大8D6 重骰5次的方案B的表 等同于8楼那个表的扩展

E:3.500000/D:2.916667E:4.472222/D:1.971451E:4.958333/D:1.308449E:5.244599/D:0.910079E:5.430941/D:0.659326E:5.560292/D:0.492850
E:7.000000/D:5.833333E:8.458333/D:4.905671E:9.344136/D:3.773546E:9.930170/D:2.914234E:10.344479/D:2.288613E:10.651460/D:1.827833
E:10.500000/D:8.750000E:12.244599/D:8.104523E:13.430170/D:6.778046E:14.273791/D:5.586818E:14.902756/D:4.624624E:15.388805/D:3.860632
E:14.000000/D:11.666667E:15.930941/D:11.362129E:17.344479/D:10.016237E:18.402756/D:8.626289E:19.222277/D:7.407950E:19.875062/D:6.380374
E:17.500000/D:14.583333E:19.560292/D:14.615149E:21.151460/D:13.350342E:22.388805/D:11.863961E:23.375062/D:10.469903E:24.179310/D:9.233598
E:21.000000/D:17.500000E:23.154117/D:17.844043E:24.886813/D:16.716255E:26.274812/D:15.204926E:27.406675/D:13.702282E:28.347241/D:12.312309
E:24.500000/D:20.416667E:26.724354/D:21.044089E:28.571949/D:20.083840E:30.088016/D:18.595534E:31.347410/D:17.035640E:32.410309/D:15.540219
E:28.000000/D:23.333333E:30.278177/D:24.215711E:32.220495/D:23.439162E:33.846563/D:22.005679E:35.218241/D:20.425908E:36.390983/D:18.864474

这次用的PYTHON算的 因为好优化算法
速度挺快的 应该附近数值内的参数调整都能算
方差上和8楼那个表存在微妙的差异 应该是浮点精度的问题
下面是代码 PYTHON相对还是比较普及 LZ可以拿去自己改改参数算需要的

class stat_table(object):
   
    def __init__(self):
      self._tab = {}
      self.total_prob = 0
      self.expected = None
      self.variance = None

    def insert(self, mainv, extrav, prob):
      if not mainv in self._tab:
            self._tab = {}
      if not extrav in self._tab:
            self._tab = 0
      self._tab += prob
      self.total_prob += prob

    def each(self):
      for mainv in self._tab:
            for extrav in self._tab:
                yield mainv, extrav, self._tab

    def add(self, tab2, val_hndl):
      ntab = type(self)()
      for smv, sev, sp in self.each():
            for dmv, dev, dp in tab2.each():
                nmv, nev = val_hndl(smv, dmv, sev, dev)
                np = sp * dp
                ntab.insert(nmv, nev, np)
      return ntab

    def check_total_prob(self):
      return abs(self.total_prob - 1.) < 1e-6

    def calc_expected(self):
      assert self.check_total_prob()
      self.expected = 0
      for mv, ev, p in self.each():
            self.expected += mv * p
      return self.expected

    def calc_variance(self):
      assert self.check_total_prob()
      assert not self.expected == None
      self.variance = 0
      for mv, ev, p in self.each():
            self.variance += pow(mv - self.expected, 2) * p
      return self.variance

class dice(stat_table):
   
    def __init__(self):
      super(dice, self).__init__()

    def init_val(self):
      self.insert(1, 1, 1./6)
      self.insert(2, 2, 1./6)
      self.insert(3, 3, 1./6)
      self.insert(4, 4, 1./6)
      self.insert(5, 5, 1./6)
      self.insert(6, 6, 1./6)
      return self
   
    def roll_hndl(self, smv, dmv, sev, dev):
      nmv = smv + dmv
      assert type(dev) in
      if type(sev) in :
            nev =
      else:
            nev = list(sev)
            nev.sort()
      nev.append(dev)
      nev.sort()
      return nmv, tuple(nev)

    def reroll_hndl(self, smv, dmv, sev, dev):
      assert type(dev) in
      if type(sev) in :
            nev =
      else:
            nev = list(sev)
            nev.sort()
      if(dev <= nev):
            return smv, sev
      nmv = smv - nev + dev
      nev = dev
      nev.sort()
      return nmv, tuple(nev)

    def roll(self):
      return self.add(dice().init_val(), self.roll_hndl)

    def reroll(self):
      return self.add(dice().init_val(), self.reroll_hndl)

def dice_op(roll, reroll):
    assert roll > 0
    rslt = dice().init_val()
    for i in range(0, roll - 1):
      rslt = rslt.roll()
    for i in range(0, reroll):
      rslt = rslt.reroll()
    rslt.calc_expected()
    rslt.calc_variance()
    return rslt

if __name__ == '__main__':
    for i in range(1, 9):
      r = []
      for j in range(0, 6):
            dc = dice_op(i, j)
            r.append('E:{0:.6f}/D:{1:.6f}'.format(dc.expected, dc.variance))
      print ''.join(r)

strider_oy 发表于 2015-12-31 10:53

页: [1]
查看完整版本: 原创RPG系统设计,概率求助(我数学没学好)