【问题标题】:Show all the combinations of exploding dice显示所有爆炸骰子的组合
【发布时间】:2020-05-20 22:53:36
【问题描述】:

我正在尝试评估使用爆炸八面骰子 (D8) 的桌面游戏(Mantic 的死区,任何感兴趣的人)中不同单位的有效性。例如,玩家掷出3D8;对于每个显示8 的骰子,玩家可以再掷一次D8,并且可以继续这样做直到infinity

不是数学家,我决定采用蛮力方法,并在 C# 中创建了一个递归函数,它可以编写所有可能的掷骰子组合(最多一定数量的额外骰子- 由 max_generations 变量 - 表示,之后概率变得太小以至于不重要)。

    class Program
    {
        private int dice_faces = 8;
        private int max_generations = 5;

        static void Main(string[] args)
        {
            new Program().GenerateRoll(new List<int>() { 1 });
        }
        private List<int> GenerateRoll(List<int> dice)
        {
            if (dice == null || dice.Count == 0) 
                return new List<int>();

            if (dice[dice.Count - 1] == dice_faces)
            {
                if (dice.Count < max_generations)
                {
                    dice.Add(1);
                }
                else
                {
                    Console.WriteLine(string.Join(" ", dice));
                    dice = null;
                }
            }
            else
            {
                Console.WriteLine(string.Join(" ", dice));
                dice[dice.Count - 1]++;
            }

            return GenerateRoll(dice);
        }
    }

这个函数工作得很好当从一个单个骰子开始时,就像上面的例子一样,但是它不会产生可能的全部范围从多个骰子开始时滚动(即new Program().GenerateRoll(new List&lt;int&gt;() { 1, 1, 1 });); 它只显示列表中最后一个骰子的滚动。

如果能在更新函数以使用任意数量的起始骰子方面提供任何帮助,我将不胜感激。

已编辑以包含示例预期输出(显示爆炸骰子的世代)

2 dice, 3 faces, 4 generations

1 G1, 1 G1
1 G1, 2 G1
1 G1, 3 G1, 1 G2
1 G1, 3 G1, 2 G2
1 G1, 3 G1, 3 G2, 1 G3
1 G1, 3 G1, 3 G2, 2 G3
1 G1, 3 G1, 3 G2, 3 G3, 1 G4
1 G1, 3 G1, 3 G2, 3 G3, 2 G4
1 G1, 3 G1, 3 G2, 3 G3, 3 G4 # max generation reached
2 G1, 1 G1
2 G1, 2 G1
2 G1, 3 G1, 1 G2
2 G1, 3 G1, 2 G2
2 G1, 3 G1, 3 G2, 1 G3
2 G1, 3 G1, 3 G2, 2 G3
2 G1, 3 G1, 3 G2, 3 G3, 1 G4
2 G1, 3 G1, 3 G2, 3 G3, 2 G4
2 G1, 3 G1, 3 G2, 3 G3, 3 G4 # max generation reached
3 G1, 1 G1, 1 G2
3 G1, 1 G1, 2 G2
3 G1, 1 G1, 3 G2, 1 G3
3 G1, 1 G1, 3 G2, 2 G3
3 G1, 1 G1, 3 G2, 3 G3, 1 G4
3 G1, 1 G1, 3 G2, 3 G3, 2 G4
3 G1, 1 G1, 3 G2, 3 G3, 3 G4 # max generation reached
3 G1, 2 G1, 1 G2
3 G1, 2 G1, 2 G2
3 G1, 2 G1, 3 G2, 1 G3
3 G1, 2 G1, 3 G2, 2 G3
3 G1, 2 G1, 3 G2, 3 G3, 1 G4
3 G1, 2 G1, 3 G2, 3 G3, 2 G4
3 G1, 2 G1, 3 G2, 3 G3, 3 G4 # max generation reached
3 G1, 3 G1, 1 G2, 1 G2
3 G1, 3 G1, 1 G2, 2 G2
3 G1, 3 G1, 1 G2, 3 G2, 1 G3
3 G1, 3 G1, 1 G2, 3 G2, 2 G3
3 G1, 3 G1, 1 G2, 3 G2, 3 G3, 1 G4
3 G1, 3 G1, 1 G2, 3 G2, 3 G3, 2 G4
3 G1, 3 G1, 1 G2, 3 G2, 3 G3, 3 G4 # max generation reached
3 G1, 3 G1, 2 G2, 1 G2
3 G1, 3 G1, 2 G2, 2 G2
3 G1, 3 G1, 2 G2, 3 G2, 1 G3
3 G1, 3 G1, 2 G2, 3 G2, 2 G3
3 G1, 3 G1, 2 G2, 3 G2, 3 G3, 1 G4
3 G1, 3 G1, 2 G2, 3 G2, 3 G3, 2 G4
3 G1, 3 G1, 2 G2, 3 G2, 3 G3, 3 G4 # max generation reached
3 G1, 3 G1, 3 G2, 3 G2, 1 G3, 1 G3
3 G1, 3 G1, 3 G2, 3 G2, 1 G3, 2 G3
3 G1, 3 G1, 3 G2, 3 G2, 1 G3, 3 G3, 1 G4
3 G1, 3 G1, 3 G2, 3 G2, 1 G3, 3 G3, 2 G4
3 G1, 3 G1, 3 G2, 3 G2, 1 G3, 3 G3, 3 G4 # max generation reached
3 G1, 3 G1, 3 G2, 3 G2, 2 G3, 1 G3
3 G1, 3 G1, 3 G2, 3 G2, 2 G3, 2 G3
3 G1, 3 G1, 3 G2, 3 G2, 2 G3, 3 G3, 1 G4
3 G1, 3 G1, 3 G2, 3 G2, 2 G3, 3 G3, 2 G4
3 G1, 3 G1, 3 G2, 3 G2, 2 G3, 3 G3, 3 G4 # max generation reached
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 1 G3, 1 G4
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 1 G3, 2 G4
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 1 G3, 3 G4 # max generation reached
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 2 G3, 1 G4
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 2 G3, 2 G4
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 2 G3, 3 G4 # max generation reached
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 3 G3, 1 G4, 1 G4
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 3 G3, 1 G4, 2 G4
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 3 G3, 1 G4, 3 G4 # max generation reached
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 3 G3, 2 G4, 1 G4
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 3 G3, 2 G4, 2 G4
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 3 G3, 2 G4, 3 G4 # max generation reached
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 3 G3, 3 G4, 1 G4 # max generation reached
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 3 G3, 3 G4, 2 G4 # max generation reached
3 G1, 3 G1, 3 G2, 3 G2, 3 G3, 3 G3, 3 G4, 3 G4 # max generation reached

【问题讨论】:

  • 如果你知道 1ED8(一个爆炸 D8 :p),你就完成了:3ED8 = 1ED8 + 1ED8 + 1ED8。
  • 但它不起作用”不是对问题的技术描述
  • “如上例所示,这个函数在从单个骰子开始时效果很好” 真的吗?当我运行上面的代码时,输​​出对我来说毫无意义。它应该做什么?
  • @RufusL:输出显示每个可能的掷骰,从单个八面骰子开始,如果前一个骰子掷出 8,则包括额外骰子的掷骰。
  • 但是对于每个添加的骰子,输出总是显示一堆8,后跟一个不同的数字。我正在运行您的代码here。输出与您看到的相符吗?

标签: c# algorithm recursion probability dice


【解决方案1】:

为了减少问题,我们将所有可能的掷骰子都用来自

的编码表示

0(只投一个)到faces ^ dies - 1(投全八)。 例如,对于两个 8 面模具(0 到 63)。

然后我们将每个编码转换为 faces 的新基数(8 面芯片的基数为 8)

IEnumerable<int[]> GenerateRoll(int dies, int dice_faces)
{
    var allFaces = Enumerable.Range(1, dice_faces).ToArray();
    var allOnes = Enumerable.Repeat(1, dies);

    var count = (int)Math.Pow(dice_faces, dies);

    return
        Enumerable.Range(0, count)
        .Select(roll => ToBaseX(roll, dice_faces, allFaces, allOnes.ToArray()));                    
}

int[] ToBaseX(long value, int baseValue, int[] target, int[] buffer)
{
    var i = buffer.Length;

    do
    {
        buffer[--i] = target[value % baseValue];
        value = value / baseValue;
    }
    while (value > 0);

    return buffer;
}

嗯,还不错。 现在,如果我们想要爆炸骰子,我们选择为每个爆炸骰子生成一个新的掷骰序列(Where 条件)

IEnumerable<(int gen, int value)[]> GenerateRoll(int dies, int faces, int max_generations, int gen = 1)
{
    if (max_generations == gen)
        return Enumerable.Empty<(int, int)[]>();

    var allFaces = Enumerable.Range(1, faces).ToArray();
    var allOnes = Enumerable.Repeat(1, dies);

    return
         Enumerable.Range(0, (int)Math.Pow(faces, dies))
        .Select(roll => ToBaseX(roll, faces, allFaces, allOnes.ToArray()))
        .Select(roll => roll.Select(value => (gen, value)).ToArray())
        .SelectMany(roll =>
        {
            var explosions = roll.Count(r => r.value == faces);

            if (explosions == 0)
                return new[] { roll };

            return GenerateRoll(explosions, faces, max_generations, gen + 1) //roll explosion dies
                   .Select(last => roll.Concat(last).ToArray()); //the new roll gets appended to the streak
        });
}

测试程序

static void Main(string[] args)
{
    foreach (var roll in new Program().GenerateRoll(dies: 2, faces: 3, max_generations: 4))
    {
        Console.WriteLine(String.Join(", ", roll.Select(v => $"G{v.gen} {v.value}")));
    }

    Console.ReadKey();
}

输出

G1 1, G1 1
G1 1, G1 2
G1 1, G1 3, G2 1
G1 1, G1 3, G2 2
G1 1, G1 3, G2 3, G3 1
G1 1, G1 3, G2 3, G3 2
G1 2, G1 1
G1 2, G1 2
G1 2, G1 3, G2 1
G1 2, G1 3, G2 2
G1 2, G1 3, G2 3, G3 1
G1 2, G1 3, G2 3, G3 2
G1 3, G1 1, G2 1
G1 3, G1 1, G2 2
G1 3, G1 1, G2 3, G3 1
G1 3, G1 1, G2 3, G3 2
G1 3, G1 2, G2 1
G1 3, G1 2, G2 2
G1 3, G1 2, G2 3, G3 1
G1 3, G1 2, G2 3, G3 2
G1 3, G1 3, G2 1, G2 1
G1 3, G1 3, G2 1, G2 2
G1 3, G1 3, G2 1, G2 3, G3 1
G1 3, G1 3, G2 1, G2 3, G3 2
G1 3, G1 3, G2 2, G2 1
G1 3, G1 3, G2 2, G2 2
G1 3, G1 3, G2 2, G2 3, G3 1
G1 3, G1 3, G2 2, G2 3, G3 2
G1 3, G1 3, G2 3, G2 1, G3 1
G1 3, G1 3, G2 3, G2 1, G3 2
G1 3, G1 3, G2 3, G2 2, G3 1
G1 3, G1 3, G2 3, G2 2, G3 2
G1 3, G1 3, G2 3, G2 3, G3 1, G3 1
G1 3, G1 3, G2 3, G2 3, G3 1, G3 2
G1 3, G1 3, G2 3, G2 3, G3 2, G3 1
G1 3, G1 3, G2 3, G2 3, G3 2, G3 2

【讨论】:

  • 这是一个有趣的解决方案。唯一的问题是“1,8”,“2,8”,......不是有效的输出,因为它们会立即触发额外骰子的滚动。与德米特里的回答一样,您将如何修改您的程序以跟踪每个掷骰子属于哪一代(即第一代是最初掷出的骰子,第二代是由于第一代掷出 8 而掷出的任何额外骰子,等等。 )?
  • 我以为你想要那些状态——这就是为什么那里有一个Prepend(roll)。我更新了这个以跟踪世代。代码还是很简单的。
  • 感谢更新版本。但是,输出在末尾缺少几行(G3 骰子显示为 3),并且输出的代数比指定的少(示例将 max_generations 设置为 4,但仅显示输出到 G3 )。
【解决方案2】:

好吧,让我们从生成器开始不爆炸,这很容易:

private static IEnumerable<int[]> Generator(int faces, int count) {
  int[] state = Enumerable.Repeat(1, count).ToArray();

  do {
    yield return state.ToArray(); // safety : let's return a copy of state

    for (int i = state.Length - 1; i >= 0; --i)
      if (state[i] == faces)
        state[i] = 1;
      else {
        state[i] += 1;

        break;
      }
  }
  while (!state.All(item => item == 1));
}

现在,让我们使用上面的生成器来创建带有爆炸的生成器:

private static IEnumerable<int[]> Generator(int faces, int count, int extra) {
  IEnumerable<(int[], int)> agenda = Generator(faces, count)
    .Select(state => (state, 0));

  for (bool hasWork = true; hasWork; ) {
    hasWork = false;
    List<(int[], int)> next = new List<(int[], int)>();

    foreach (var state in agenda) {
      int explosions = Math.Min(
        state.Item1.Skip(state.Item2).Count(item => item == faces),
        extra - state.Item1.Length + count);

      if (explosions <= 0)
        yield return state.Item1;
      else 
        foreach (var newState in 
          Generator(faces, explosions).Select(adds => state.Item1.Concat(adds)))
            next.Add((newState.ToArray(), state.Item1.Length));
    }

    agenda = next;
    hasWork = next.Count > 0;
  }
}

演示:

// 2 dice (3 faces each) with at most 4 explosions (extra dice) allowed
var results = string.Join(Environment.NewLine, Generator(3, 2, 4) 
  .Select(item => string.Join(", ", item)));

Console.Write(results);

结果:

1, 1          # No explosion
1, 2
2, 1
2, 2
1, 3, 1       # last 3 exploded 
1, 3, 2
2, 3, 1
2, 3, 2
3, 1, 1       # first 3 exploded
3, 1, 2
3, 2, 1
3, 2, 2
3, 3, 1, 1    # both first and last 3 exploded
3, 3, 1, 2
3, 3, 2, 1
3, 3, 2, 2
1, 3, 3, 1    # last 3 exploded, we have 3 which we exploded again
1, 3, 3, 2
2, 3, 3, 1
2, 3, 3, 2
3, 1, 3, 1    # first 3 exploded, we have 3 which we exploded again
3, 1, 3, 2
3, 2, 3, 1
3, 2, 3, 2
3, 3, 1, 3, 1 # both first and last 3 exploded, we have 3 which we exploded again
3, 3, 1, 3, 2
3, 3, 2, 3, 1
3, 3, 2, 3, 2
3, 3, 3, 1, 1
3, 3, 3, 1, 2
3, 3, 3, 2, 1
3, 3, 3, 2, 2
3, 3, 3, 3, 1, 1
3, 3, 3, 3, 1, 2
3, 3, 3, 3, 1, 3
3, 3, 3, 3, 2, 1
3, 3, 3, 3, 2, 2
3, 3, 3, 3, 2, 3
3, 3, 3, 3, 3, 1
3, 3, 3, 3, 3, 2
3, 3, 3, 3, 3, 3
1, 3, 3, 3, 1
1, 3, 3, 3, 2
2, 3, 3, 3, 1
2, 3, 3, 3, 2
3, 1, 3, 3, 1
3, 1, 3, 3, 2
3, 2, 3, 3, 1
3, 2, 3, 3, 2
3, 3, 1, 3, 3, 1
3, 3, 1, 3, 3, 2
3, 3, 1, 3, 3, 3
3, 3, 2, 3, 3, 1
3, 3, 2, 3, 3, 2
3, 3, 2, 3, 3, 3
3, 3, 3, 1, 3, 1
3, 3, 3, 1, 3, 2
3, 3, 3, 1, 3, 3
3, 3, 3, 2, 3, 1
3, 3, 3, 2, 3, 2
3, 3, 3, 2, 3, 3
1, 3, 3, 3, 3, 1
1, 3, 3, 3, 3, 2
1, 3, 3, 3, 3, 3
2, 3, 3, 3, 3, 1
2, 3, 3, 3, 3, 2
2, 3, 3, 3, 3, 3
3, 1, 3, 3, 3, 1
3, 1, 3, 3, 3, 2
3, 1, 3, 3, 3, 3
3, 2, 3, 3, 3, 1
3, 2, 3, 3, 3, 2
3, 2, 3, 3, 3, 3

编辑:如果您想获取/跟踪世代(或爆炸),您可以实现额外的方法:

private static int[] Explosions(int[] record, int faces, int count) {
  int[] result = new int[record.Length];

  int extra = count;
  int startAt = count;
  int completed = 0;
  int generation = 0;

  while (true) {
    generation += 1;
    int take = extra;

    extra = record
      .Skip(completed)
      .Take(take)
      .Count(item => item == faces);

    if (extra <= 0)
      break;

    for (int i = 0; i < extra; ++i)
      if (startAt + i >= result.Length)
        return result;
      else
        result[startAt + i] = generation;

    startAt += extra;
    completed += take;
  }

  return result;      
}

让我们有一些可读的文本:

private static String Explain(int[] record, int faces, int count) {
  return string.Join(" then ", record
    .Zip(Explosions(record, faces, count), (item, rank) => new { item, rank})
    .GroupBy(value => value.rank, value => value.item)
    .Select(group => $"explosion #{group.Key} ({string.Join(", ", group)})"));
}

演示:

Console.WriteLine(string.Join(", ", 
  new int[] { 3, 3, 3, 3, 1, 3, 2 })); 
// We have 2 dice with 3 faces each;
// We want to explain 3, 3, 3, 3, 1, 3, 2 sequence
Console.WriteLine(string.Join(", ", Explosions(
  new int[] { 3, 3, 3, 3, 1, 3, 2 }, 3, 2)));
Console.WriteLine();
Console.Write(Explain(new int[] { 3, 3, 3, 3, 1, 3, 2 }, 3, 2));

结果:

3, 3, 3, 3, 1, 3, 2 # Initial serie
0, 0, 1, 1, 2, 2, 3 # Corresponding explosions (generations)

explosion #0 (3, 3) then explosion #1 (3, 3) then explosion #2 (1, 3) then explosion #3 (2) 

编辑 2: 最后,如果你想限制的不是 extra 骰子,而是 generations0 - 仅初始投射,最多 1 爆炸,无论它是什么骰子等等.):

private static IEnumerable<int[]> Generator(int faces, int count, int generations) {
  IEnumerable<(int[], int, int)> agenda = Generator(faces, count)
    .Select(state => (state, 0, 0));

  for (bool hasWork = true; hasWork;) {
    hasWork = false;
    List<(int[], int, int)> next = new List<(int[], int, int)>();

    foreach (var state in agenda) {
      int explosions = state.Item1.Skip(state.Item2).Count(item => item == faces);

      if (explosions <= 0 || state.Item3 >= generations)
        yield return state.Item1;
      else
        foreach (var newState in
          Generator(faces, explosions).Select(adds => state.Item1.Concat(adds)))
            next.Add((newState.ToArray(), state.Item1.Length, state.Item3 + 1));
    }

    agenda = next;
    hasWork = next.Count > 0;
  }
}

【讨论】:

  • 谢谢你,德米特里。这是一个非常优雅的解决方案。您将如何修改它以跟踪每个掷骰子属于哪一代(即第一代是原始骰子掷出的,第二代是由于第一代掷出 8 的结果而掷出的任何额外骰子,等等)?
  • @JimmyTheOne:我已经使用了 零基 代(0 是初始情况,1 - 第一次爆炸等)。查看我的编辑
  • 感谢更新版本。但是,我不理解更新后的结果(显示初始系列和相应的爆炸),因为它似乎没有像原始示例结果那样显示可能的滚动的各种组合。
  • @JimmyTheOne:请您提供反例(即必须出现但未返回的组合)?
  • 我添加了一个完整的预期样本输出。如果还有什么需要澄清的,请告诉我。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-17
  • 1970-01-01
  • 1970-01-01
  • 2020-06-19
  • 2019-07-26
  • 2012-04-06
  • 1970-01-01
相关资源
最近更新 更多