Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functionally pure dice rolls in C#

I am writing a dice-based game in C#. I want all of my game-logic to be pure, so I have devised a dice-roll generator like this:

public static IEnumerable<int> CreateDiceStream(int seed)
{
    var random = new Random(seed);

    while (true)
    {
        yield return 1 + random.Next(5);
    }
}

Now I can use this in my game logic:

var playerRolls = players.Zip(diceRolls, (player, roll) => Tuple.Create(player, roll));

The problem is that the next time I take from diceRolls I want to skip the rolls that I have already taken:

var secondPlayerRolls = players.Zip(
    diceRolls.Skip(playerRolls.Count()), 
    (player, roll) => Tuple.Create(player, roll));

This is already quite ugly and error prone. It doesn't scale well as the code becomes more complex.

It also means that I have to be careful when using a dice roll sequence between functions:

var x = DoSomeGameLogic(diceRolls);

var nextRoll = diceRolls.Skip(x.NumberOfDiceRollsUsed).First();

Is there a good design pattern that I should be using here?

Note that it is important that my functions remain pure due to syncronisation and play-back requirements.


This question is not about correctly initializing System.Random. Please read what I have written, and leave a comment if it is unclear.

like image 210
sdgfsdh Avatar asked Dec 08 '25 14:12

sdgfsdh


1 Answers

That's a very nice puzzle.

Since manipulating diceRolls's state is out of the question (otherwise, we'd have those sync and replaying issues you mentioned), we need an operation which returns both (a) the values to be consumed and (b) a new diceRolls enumerable which starts after the consumed items.

My suggestion would be to use the return value for (a) and an out parameter for (b):

static IEnumerable<int> Consume(this IEnumerable<int> rolls, int count, out IEnumerable<int> remainder)
{
    remainder = rolls.Skip(count);
    return rolls.Take(count);
}

Usage:

var firstRolls = diceRolls.Consume(players.Count(), out diceRolls);
var secondRolls = diceRolls.Consume(players.Count(), out diceRolls);

DoSomeGameLogic would use Consume internally and return the remaining rolls. Thus, it would need to be called as follows:

var x = DoSomeGameLogic(diceRolls, out diceRolls);
// or
var x = DoSomeGameLogic(ref diceRolls);
// or
x = DoSomeGameLogic(diceRolls);
diceRolls = x.RemainingDiceRolls;
like image 122
Heinzi Avatar answered Dec 11 '25 04:12

Heinzi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!