我正在用 C# 编写一个基于骰子的游戏。我希望我所有的游戏逻辑都是纯粹的,所以我设计了一个像这样的掷骰子生成器:
public static IEnumerable<int> CreateDiceStream(int seed)
{
var random = new Random(seed);
while (true)
{
yield return 1 + random.Next(5);
}
}
现在我可以在我的游戏逻辑中使用它了:
var playerRolls = players.Zip(diceRolls, (player, roll) => Tuple.Create(player, roll));
问题是,下次我从 diceRolls
中获取时,我想跳过我已经获取的掷骰:
var secondPlayerRolls = players.Zip(
diceRolls.Skip(playerRolls.Count()),
(player, roll) => Tuple.Create(player, roll));
这已经很丑陋并且容易出错。随着代码变得更加复杂,它不能很好地扩展。
这也意味着我在函数之间使用掷骰子序列时必须小心:
var x = DoSomeGameLogic(diceRolls);
var nextRoll = diceRolls.Skip(x.NumberOfDiceRollsUsed).First();
我应该在这里使用一个好的设计模式吗?
请注意,由于同步和回放要求,我的函数保持纯净很重要。
这道题不是关于正确初始化 System.Random
的。请阅读我写的内容,如果不清楚,请发表评论。
最佳答案
这是一个非常好的谜题。
由于操作 diceRolls
的状态是不可能的(否则,我们会遇到您提到的那些同步和重播问题),我们需要一个将 (a) 值返回到被消耗和 (b) 一个新的 diceRolls 可枚举,它在消耗的项目之后开始。
我的建议是使用 (a) 的返回值和 (b) 的输出参数:
static IEnumerable<int> Consume(this IEnumerable<int> rolls, int count, out IEnumerable<int> remainder)
{
remainder = rolls.Skip(count);
return rolls.Take(count);
}
用法:
var firstRolls = diceRolls.Consume(players.Count(), out diceRolls);
var secondRolls = diceRolls.Consume(players.Count(), out diceRolls);
DoSomeGameLogic
将在内部使用 Consume
并返回剩余的掷骰。因此,需要按如下方式调用它:
var x = DoSomeGameLogic(diceRolls, out diceRolls);
// or
var x = DoSomeGameLogic(ref diceRolls);
// or
x = DoSomeGameLogic(diceRolls);
diceRolls = x.RemainingDiceRolls;
关于c# - C# 中的功能纯骰子掷,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40794594/