如何为 N 骰子生成“先行”骰子?

Posted

技术标签:

【中文标题】如何为 N 骰子生成“先行”骰子?【英文标题】:How can I generate "Go First" Dice for N dice? 【发布时间】:2012-09-01 18:59:33 【问题描述】:

背景

如http://www.ericharshbarger.org/dice/#gofirst_4d12 所述,“先走”骰子是一组四个骰子,每个骰子都有唯一的编号,因此:

任何两个或更多骰子的掷骰都不会产生平局。 与该组中的任何其他骰子掷出的任何骰子都有相同的机会“赢/输”该骰子。

这里是提到的四个骰子的编号:

DICE COUNT: 4
FACE COUNT: 12
D1: 1,8,11,14,19,22,27,30,35,38,41,48
D2: 2,7,10,15,18,23,26,31,34,39,42,47
D3: 3,6,12,13,17,24,25,32,36,37,43,46
D4: 4,5, 9,16,20,21,28,29,33,40,44,45

(via)

问题

我讨厌数学。我难住了。鉴于上述信息,我希望能够在给定多个骰子的情况下生成整数列表(“骰子”)。这样,示例输出可能看起来像这样(格式化,python 控制台) :

    >>> generate_dice(players=4)
    [[1,8,11,14,19,22,27,30,35,38,41,48],
     [2,7,10,15,18,23,26,31,34,39,42,47],
     [3,6,12,13,17,24,25,32,36,37,43,46],
     [4,5,9,16,20,21,28,29,33,40,44,45]]    

此处选择的边数仅用于示例目的,因为它与给出的其他示例相匹配。每个骰子的“公平性”确实是我正在寻找的。​​p>

我向你保证,这不是家庭作业。这只是一个坚定的极客,对一个看似微不足道的难题感到恼火,只是不会让我一个人呆着......出于某种原因,我似乎无法正确解决。

我确信这里涉及到一些相对微不足道的数学和基本算法,而这正是我正在寻找的。如果这对您来说很明显,我应该搜索什么术语?因为对我来说,不是。

理想的解决方案是 Python,但我也可以很好地阅读 phpjavascript、一些 Ruby。

【问题讨论】:

看来只有X 能被N 整除才公平。否则,一个或多个骰子将不平衡。 所以你的意思是有一种方法可以为任意数量的玩家和每个骰子的任意数量的面生成这样一组骰子?我对此表示怀疑。 也许骰子的边是通过程序确定的。这是完全有道理的,并且在我的涂鸦中,我意识到我把它遗漏了。 我认为你应该能够从数学上算出来... 显然我过于依赖提供的示例,并且以愚蠢的方式编写了签名。重点是生成数字,我将尝试重写问题,不那么强调边数。 【参考方案1】:

这是一个(计算上)困难的问题。 乍一看,每个骰子的预期值相同是不够的(尽管奇怪的是,在您给出的示例中)。每个 die 必须在每个 die 元素的点积的所有实例的 50% 上“获胜”。

文章提到一位数学家“手动”生成了您给出的示例,这让我更愿意建议以下蛮力方法:

import itertools

nplayers=4
nsides=2
max_number=8

assert nplayers*nsides <= max_number
assert nsides % 2 == 0 #otherwise x^x (dot product) is not even, so half_wins_fairness always fails

iterations=[]
def half_wins_fairness( dice1,dice2 ):
    dice1_wins= map( lambda x: x[0]>x[1], itertools.product(dice1,dice2) )
    dice_wins_prob= float(sum(dice1_wins))/len(dice1_wins)
    #probs.append(dice_wins_prob)
    return dice_wins_prob==0.5

def fair( all_dice ):
    all_fair= True
    for d1,d2 in itertools.combinations( all_dice, 2):
        if not half_wins_fairness(d1,d2):
            all_fair=False
    return all_fair

for i,dice_pattern in enumerate(itertools.permutations(range(max_number), nplayers*nsides)):
    #cut dice pattern into dices
    dice= [dice_pattern[p*nsides:(p+1)*nsides] for p in range(nplayers)]
    if fair(dice):
        print dice
        iterations.append(i)

def discrete_derivative(l):
    last=0
    for i,x in enumerate(l):
        tmp= x
        l[i]=x-last
        last=tmp

#discrete_derivative(iterations)
#import pylab
#pylab.plot(iterations)
#pylab.show()

这里的复杂性是 n^n,所以这本身只能解决你的问题,因为 nplayers 和 nsides 的数量非常少。 但是,通过取消注释注释行,您可以检查骰子沿点积迭代的公平性图,该图似乎有很多模式,这表明可以使用几种启发式方法来加快搜索速度,甚至可能找到一个通用的解决方案。

编辑

更改了代码以改进图形。 这是一些图片,以防有人特别擅长发现模式。

nplayers=2, nsides=2, max_number=8 nplayers=2, nsides=4, max_number=8 nplayers=4, nsides=2, max_number=8

一些初步观察:

    它是对称的 “最干净”的图表似乎是在以下情况下生成的 max_number % (nplayers*nsides) == 0

【讨论】:

这只会检查他们成对是否公平,所以我认为它会产生与我(现已删除)答案相同的问题的答案:如果你有 n 个玩家的 n > 2,不是所有玩家都有 1/n 的获胜机会。还是我错过了什么? @David 如果每个骰子对其他骰子有 1/2 的机会,则通过计算获胜顺序的排列来证明每个玩家获胜的机会是 1/n 相对简单。 n 名玩家。 这绝对不是真的。例如,如果您有四个玩家并且骰子是 [1,2,3,22,23,24];[4,5,6,19,20,21];[7,8,9,16,17 ,18];和 [10,11,12,13,14,15],那么任何骰子都有 1/2 的机会对抗其他骰子。但是,如果所有四个玩家都掷骰子,那么拥有 [1,2,3,22,23,24] 的玩家仍然有 1/2 的获胜机会,而不是 1/4。 (当然,OP 的问题实际上并没有要求 1/n 条件,但链接网站将其作为这些骰子定义的一部分。) @David 你说得对,这似乎不成立。显然,必须对n个骰子的点积进行模拟。我不确定为什么计算中奖顺序排列的概率会给出不同的结果...【参考方案2】:

为了记录,codegolf 上的这个答案有一个简单的算法,似乎至少在骰子上的边数是偶数的任何时候都有效:https://codegolf.stackexchange.com/a/7238/5376

def generate_dice(count, sides = 12):
  dice = []
  for i in range(count):
    dice.append([])

  value = 0
  for sindex in range(sides):
    if sindex % 2:
      for dindex in range(count):
        value += 1
        dice[dindex].append(value)
    else:
      for dindex in reversed(range(count)):
        value += 1
        dice[dindex].append(value)
  return dice

【讨论】:

我没查过,但是根据codegolf上的一个评论,这个问题和我原来的答案一样:如果三个人在玩,他们不是每个人都有1/3的机会的胜利。

以上是关于如何为 N 骰子生成“先行”骰子?的主要内容,如果未能解决你的问题,请参考以下文章

生成一个包含所有可能掷骰子结果的矩阵(忽略顺序)

n个骰子的点数

掷骰子程序在每次运行时生成相同的随机数序列

如何用 numpy 编写骰子损失反向传播

算法:n个骰子的点数

n个骰子的点数之和