Хорошо, давайте начнем с генератора без взрыва , это довольно просто:
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
кости, а generations
( 0
- только начальное заклинание, самое большее 1
взрыв, независимо от того, что это за кости и т.д. c.):
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;
}
}