Files
RpgRoller/RpgRoller/Services/D6RollEngine.cs
2026-04-05 02:05:24 +02:00

87 lines
2.6 KiB
C#

using RpgRoller.Contracts;
using RpgRoller.Domain;
namespace RpgRoller.Services;
public sealed class D6RollEngine(IDiceRoller diceRoller)
{
public (int Total, string Breakdown, IReadOnlyList<RollDieResult> Dice) Roll(DiceExpression expression, int wildDice, bool allowFumble)
{
var initialDice = expression.DiceCount;
var currentDice = initialDice;
var pendingExplodingDice = 0;
var pendingFumbles = 0;
var dieResults = new List<RollDieResult>(initialDice);
for (var i = 0; i < currentDice; i += 1)
{
var roll = diceRoller.Roll(expression.Sides);
var isWild = i < wildDice;
var isCrit = false;
var isFumble = false;
var isAdded = false;
if (isWild)
{
if (roll == expression.Sides)
{
pendingExplodingDice += 1;
currentDice += 1;
isCrit = true;
}
else if (allowFumble && roll == 1)
{
pendingFumbles += 1;
isFumble = true;
}
}
if (pendingExplodingDice > 0 && i >= initialDice)
{
pendingExplodingDice -= 1;
isAdded = true;
if (roll == expression.Sides)
{
pendingExplodingDice += 1;
currentDice += 1;
}
}
dieResults.Add(new(roll, isCrit, isFumble, isWild, false, isAdded));
}
for (var roll = expression.Sides; roll >= 1 && pendingFumbles > 0; roll -= 1)
for (var i = currentDice - 1; i >= 0 && pendingFumbles > 0; i -= 1)
{
if (dieResults[i].Roll != roll)
continue;
dieResults[i] = dieResults[i] with
{
Removed = true,
Added = false,
Crit = false,
Fumble = false
};
pendingFumbles -= 1;
}
var total = expression.Modifier;
var includedDice = new List<int>(dieResults.Count);
foreach (var die in dieResults)
{
if (die.Fumble)
{
total += 1;
includedDice.Add(1);
}
else if (!die.Removed)
{
total += die.Roll;
includedDice.Add(die.Roll);
}
}
return (total, RollBreakdownFormatter.BuildBreakdown(includedDice, expression.Modifier, total), dieResults);
}
}