diff --git a/indigo/indigo/src/main/scala/indigo/shared/dice/Dice.scala b/indigo/indigo/src/main/scala/indigo/shared/dice/Dice.scala index 3c26a1063..8342c546f 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/dice/Dice.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/dice/Dice.scala @@ -168,9 +168,7 @@ object Dice: val r: Random = new Random(seed) - def roll: Int = - r.nextInt(sanitise(sides)) + 1 - + def roll: Int = roll(sides) def roll(sides: Int): Int = r.nextInt(sanitise(sides)) + 1 diff --git a/indigo/indigo/src/test/scala/indigo/shared/dice/DiceTests.scala b/indigo/indigo/src/test/scala/indigo/shared/dice/DiceTests.scala index d558ae5a0..923c72a4d 100644 --- a/indigo/indigo/src/test/scala/indigo/shared/dice/DiceTests.scala +++ b/indigo/indigo/src/test/scala/indigo/shared/dice/DiceTests.scala @@ -2,6 +2,8 @@ package indigo.shared.dice import indigo.shared.collections.NonEmptyList +import scala.collection.immutable.SortedMap + @SuppressWarnings(Array("scalafix:DisableSyntax.throw")) class DiceTests extends munit.FunSuite { @@ -10,6 +12,8 @@ class DiceTests extends munit.FunSuite { def checkDice(roll: Int, to: Int): Boolean = roll >= 1 && roll <= to + def almostEquals(d: Double, d2: Double, p: Double) = (d - d2).abs <= p + test("diceSidesN") { val roll: Int = Dice.diceSidesN(1, 0).roll(10) @@ -57,4 +61,81 @@ class DiceTests extends munit.FunSuite { assertEquals(actual, expected) } + test("all dice rolls have an approximately uniform distribution") { + val diceSides = 64 + val numRuns = 200_000_000 + val dice = Dice.diceSidesN(diceSides, 0) + val generatedNums = + Array + .range(0, numRuns) + .foldLeft(SortedMap[Int, Int]()) { (acc, _) => + val roll = dice.roll + acc.updated(roll, acc.getOrElse(roll, 0) + 1) + } + + assertEquals(generatedNums.size, diceSides) + assertEquals(generatedNums.head._1, 1) + assertEquals(generatedNums.last._1, diceSides) + + val expectedDistribution = 1.0 / diceSides + generatedNums.foreach { case (num, count) => + val distribution = count.toDouble / numRuns + assert( + almostEquals(distribution, expectedDistribution, 0.01), + s"""The distribution for $num was $distribution, but expected $expectedDistribution""" + ) + } + } + + test("all dice rolls in rollRange have an approximately uniform distribution") { + val diceSides = 64 + val halfSides = diceSides / 2 + val numRuns = 200_000_000 + val dice = Dice.diceSidesN(diceSides, 0) + val generatedNums = + Array + .range(0, numRuns) + .foldLeft(SortedMap[Int, Int]()) { (acc, _) => + val roll = dice.rollRange(halfSides, diceSides) + acc.updated(roll, acc.getOrElse(roll, 0) + 1) + } + + assertEquals(generatedNums.size, halfSides + 1) + assertEquals(generatedNums.head._1, halfSides) + assertEquals(generatedNums.last._1, diceSides) + + val expectedDistribution = 1.0 / halfSides + generatedNums.foreach { case (num, count) => + val distribution = count.toDouble / numRuns + assert( + almostEquals(distribution, expectedDistribution, 0.01), + s"""The distribution for $num was $distribution, but expected $expectedDistribution""" + ) + } + } + + test("all dice rolls in rollRange(1, 4) have an approximately uniform distribution") { + val numRuns = 200_000_000 + val dice = Dice.diceSidesN(4, 0) + val generatedNums = + Array + .range(0, numRuns) + .foldLeft(SortedMap[Int, Int]()) { (acc, _) => + val roll = dice.rollRange(1, 4) + acc.updated(roll, acc.getOrElse(roll, 0) + 1) + } + + assertEquals(generatedNums.size, 4) + assertEquals(generatedNums.head._1, 1) + assertEquals(generatedNums.last._1, 4) + + val expectedDistribution = 0.25 + generatedNums.foreach { case (num, count) => + val distribution = count.toDouble / numRuns + assert( + almostEquals(distribution, expectedDistribution, 0.01), + s"""The distribution for $num was $distribution, but expected $expectedDistribution""" + ) + } + } }