fix: replace DiceRoll sentinel value (-1) with nullable Int? (Closes #78) (#120)

This commit was merged in pull request #120.
This commit is contained in:
2026-04-04 21:46:24 +02:00
parent 240b994900
commit c94341df62
9 changed files with 26 additions and 24 deletions
@@ -29,7 +29,8 @@ fun DiceRollResultDialog(
) {
val ones = diceRoll.result.count { it == 1 }
val isGlitch = ones > diceRoll.numberOfDice / 2
val isCriticalGlitch = isGlitch && diceRoll.numberOfSuccesses == 0
val successes = diceRoll.numberOfSuccesses ?: 0
val isCriticalGlitch = isGlitch && successes == 0
AlertDialog(
onDismissRequest = onDismiss,
@@ -55,19 +56,19 @@ fun DiceRollResultDialog(
style = MaterialTheme.typography.bodyMedium
)
Text(
text = "${diceRoll.numberOfSuccesses} successes",
text = "$successes successes",
modifier = Modifier.testTag(TestTags.DICE_ROLL_SUCCESS_COUNT),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
color = if (diceRoll.numberOfSuccesses > 0)
color = if (successes > 0)
Color(0xFF4CAF50) else MaterialTheme.colorScheme.onSurface
)
}
// Threshold result for simple tests
if (threshold != null) {
val success = diceRoll.numberOfSuccesses >= threshold
val netHits = if (success) diceRoll.numberOfSuccesses - threshold else 0
val success = successes >= threshold
val netHits = if (success) successes - threshold else 0
Surface(
color = if (success) Color(0xFF4CAF50).copy(alpha = 0.12f)
else MaterialTheme.colorScheme.errorContainer,
@@ -62,11 +62,11 @@ fun ExtendedTestResultDialog(
// Per-round breakdown
var runningTotal = 0
result.rolls.forEachIndexed { index, roll ->
runningTotal += roll.numberOfSuccesses
runningTotal += roll.numberOfSuccesses ?: 0
RoundEntry(
roundNumber = index + 1,
poolSize = roll.numberOfDice,
hits = roll.numberOfSuccesses,
hits = roll.numberOfSuccesses ?: 0,
cumulativeHits = runningTotal,
results = roll.result,
testTag = TestTags.extendedRoundEntry(index)
@@ -141,7 +141,7 @@ fun HealingDialog(
style = MaterialTheme.typography.bodySmall
)
Text(
text = "Successes: ${result.diceRoll.numberOfSuccesses}",
text = "Successes: ${result.diceRoll.numberOfSuccesses ?: 0}",
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium
)
@@ -53,11 +53,11 @@ fun OpposedTestResultDialog(
style = MaterialTheme.typography.bodyMedium
)
Text(
text = "${result.attackRoll.numberOfSuccesses} hits",
text = "${result.attackRoll.numberOfSuccesses ?: 0} hits",
modifier = Modifier.testTag(TestTags.OPPOSED_ATTACK_HITS),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
color = if (result.attackRoll.numberOfSuccesses > 0)
color = if ((result.attackRoll.numberOfSuccesses ?: 0) > 0)
Color(0xFF4CAF50) else MaterialTheme.colorScheme.onSurface
)
}
@@ -80,11 +80,11 @@ fun OpposedTestResultDialog(
style = MaterialTheme.typography.bodyMedium
)
Text(
text = "${result.defenseRoll.numberOfSuccesses} hits",
text = "${result.defenseRoll.numberOfSuccesses ?: 0} hits",
modifier = Modifier.testTag(TestTags.OPPOSED_DEFENSE_HITS),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
color = if (result.defenseRoll.numberOfSuccesses > 0)
color = if ((result.defenseRoll.numberOfSuccesses ?: 0) > 0)
Color(0xFF4CAF50) else MaterialTheme.colorScheme.onSurface
)
}
@@ -16,7 +16,7 @@ data class DiceRoll(
val numberOfSides: Int = 6,
val numberForSuccessOrHigher: Int = 5,
val result: List<Int> = listOf(),
val numberOfSuccesses: Int = -1
val numberOfSuccesses: Int? = null
) {
fun roll(numberForSuccessOrHigher: Int = 5): DiceRoll {
val newResult = rollXDice(sides = this.numberOfSides, numberOfDice = this.numberOfDice)
@@ -33,8 +33,9 @@ data class DiceRoll(
*/
fun performSimpleTest(pool: Int, threshold: Int): SimpleTestResult {
val roll = DiceRoll(numberOfDice = pool).roll()
val success = roll.numberOfSuccesses >= threshold
val netHits = if (success) roll.numberOfSuccesses - threshold else 0
val successes = roll.numberOfSuccesses ?: 0
val success = successes >= threshold
val netHits = if (success) successes - threshold else 0
return SimpleTestResult(
diceRoll = roll,
threshold = threshold,
@@ -51,7 +52,7 @@ fun performSimpleTest(pool: Int, threshold: Int): SimpleTestResult {
fun performOpposedTest(attackPool: Int, defensePool: Int): OpposedTestResult {
val attackRoll = DiceRoll(numberOfDice = attackPool).roll()
val defenseRoll = DiceRoll(numberOfDice = defensePool).roll()
val netHits = attackRoll.numberOfSuccesses - defenseRoll.numberOfSuccesses
val netHits = (attackRoll.numberOfSuccesses ?: 0) - (defenseRoll.numberOfSuccesses ?: 0)
return OpposedTestResult(
attackRoll = attackRoll,
defenseRoll = defenseRoll,
@@ -76,7 +77,7 @@ fun performExtendedTest(pool: Int, targetHits: Int, maxRolls: Int = 10): Extende
if (currentPool <= 0) break
val roll = DiceRoll(numberOfDice = currentPool).roll()
rolls.add(roll)
cumulativeHits += roll.numberOfSuccesses
cumulativeHits += roll.numberOfSuccesses ?: 0
if (cumulativeHits >= targetHits) {
return ExtendedTestResult(
rolls = rolls.toList(),
@@ -41,7 +41,7 @@ class DiceRollHistory(private val maxEntries: Int = 20) {
label = label,
poolSize = roll.numberOfDice,
results = roll.result,
successes = roll.numberOfSuccesses,
successes = roll.numberOfSuccesses ?: 0,
sequenceNumber = nextSequence++,
rollType = rollType
)
@@ -93,7 +93,7 @@ object HealingSystem {
damageTrack = damageTrack,
dicePool = dicePool,
diceRoll = roll,
boxesHealed = roll.numberOfSuccesses
boxesHealed = roll.numberOfSuccesses ?: 0
)
}
@@ -55,7 +55,7 @@ class DiceTest {
// successes from the stale empty result list.
val initial = DiceRoll(numberOfDice = 20)
assertEquals(listOf<Int>(), initial.result, "Initial result should be empty")
assertEquals(-1, initial.numberOfSuccesses, "Initial numberOfSuccesses should be -1")
assertEquals(null, initial.numberOfSuccesses, "Initial numberOfSuccesses should be null")
val rolled = initial.roll()
assertEquals(20, rolled.result.size, "Rolled result should have 20 dice")
@@ -23,7 +23,7 @@ class RollTypeTest {
atLeastOneSuccess = true
assertTrue(result.netHits >= 0, "Net hits should be >= 0 on success")
assertEquals(
result.diceRoll.numberOfSuccesses - result.threshold,
result.diceRoll.numberOfSuccesses!! - result.threshold,
result.netHits,
"Net hits = successes - threshold"
)
@@ -46,7 +46,7 @@ class RollTypeTest {
val result = performSimpleTest(pool = 5, threshold = 0)
assertTrue(result.success, "Zero threshold should always succeed")
assertEquals(
result.diceRoll.numberOfSuccesses,
result.diceRoll.numberOfSuccesses!!,
result.netHits,
"Net hits should equal successes when threshold is 0"
)
@@ -61,7 +61,7 @@ class RollTypeTest {
val result = performOpposedTest(attackPool = 8, defensePool = 6)
assertEquals(8, result.attackRoll.result.size, "Attacker should roll 8 dice")
assertEquals(6, result.defenseRoll.result.size, "Defender should roll 6 dice")
val expectedNetHits = result.attackRoll.numberOfSuccesses - result.defenseRoll.numberOfSuccesses
val expectedNetHits = result.attackRoll.numberOfSuccesses!! - result.defenseRoll.numberOfSuccesses!!
assertEquals(expectedNetHits, result.netHits, "Net hits = attack successes - defense successes")
assertEquals(expectedNetHits > 0, result.attackerWins, "Attacker wins if net hits > 0")
}
@@ -86,7 +86,7 @@ class RollTypeTest {
repeat(20) {
val result = performExtendedTest(pool = 10, targetHits = 1, maxRolls = 10)
assertTrue(result.rolls.isNotEmpty(), "Should have at least one round")
val manualSum = result.rolls.sumOf { it.numberOfSuccesses }
val manualSum = result.rolls.sumOf { it.numberOfSuccesses ?: 0 }
assertEquals(manualSum, result.cumulativeHits, "Cumulative hits should be sum of per-round hits")
assertEquals(result.rolls.size, result.roundsUsed, "Rounds used should match number of rolls")
}