cleaning things up, trying to get the API into a sensible place

This commit is contained in:
dtookey 2023-09-03 09:04:01 -04:00
parent 20be086198
commit 0107aeffb0
13 changed files with 198 additions and 153 deletions

View File

@ -2,35 +2,61 @@ package entries
import simulation.* import simulation.*
fun main(){ fun main() {
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
doSimulation() doSimulation()
val finish = System.currentTimeMillis() val finish = System.currentTimeMillis()
println("Simulation finished in: ${finish-start}ms") println("Simulation finished in: ${finish - start}ms")
} }
fun doSimulation(){
val itt = 10_000_000
fun doSimulation() {
val rounds = 10_000_000
val defense = 18
val simulator = Simulator.getInstance<AttackResult>(Runtime.getRuntime().availableProcessors()) val simulator = Simulator.getInstance<AttackResult>(Runtime.getRuntime().availableProcessors())
val attackModifiers = listOf(FlatModifier(4), DiceBonusModifier("1d4")) val attackDice = "1d20"
val weaponDice = "2d6"
RollType.entries.parallelStream()
.forEach{
val normalAttack = SimpleMeleeAttack(
actionRoll = AttackDice("1d20", it, attackModifiers),
damageRoll = Dice.makeDice("1d8"),
19
)
val normalAttackModel = AttackSimulatorModel(itt, normalAttack)
val normalResults = simulator.doSimulation(normalAttackModel)
AttackResult.printSimulationStatistics(normalResults, "Normal Attack (${it.name})") val normalAttack = MeleeAttackBuilder(attackDice, weaponDice, defense)
.withAtkBonus(2) // weapon bonus
.withAtkBonus(4) // proficiency bonus
.withAtkBonus(5) // str mod
.withDmgBonus(2) // weapon bonus
.withDmgBonus(5) // str mod
.build()
val gwmAttack = MeleeAttackBuilder(attackDice, weaponDice, defense)
.withAtkBonus(2)// weapon bonus
.withAtkBonus(4)// proficiency bonus
.withAtkBonus(5)// str mod
.withAtkBonus(-5)// gwm penalty
.withDmgBonus(2) // weapon bonus
.withDmgBonus(5) // str mod
.withDmgBonus(10) // gwm bonus
.build()
mapOf(Pair("normalAttack", normalAttack), Pair("gwmAttack", gwmAttack))
.entries
.forEach{ attackInfo ->
val label = attackInfo.key
val attack = attackInfo.value
RollType.entries.parallelStream()
// .filter{it == RollType.Advantage || it == RollType.Normal}
.forEach {
val attackModel = AttackSimulatorModel(rounds, attack, it)
val normalResults = simulator.doSimulation(attackModel)
AttackResult.printSimulationStatistics(normalResults, "$label (${it.name})")
}
} }
} }

View File

@ -6,14 +6,14 @@ import java.util.*
* Attack defines the interface for performing an attack action. * Attack defines the interface for performing an attack action.
* It handles rolling attack dice, calculating modifiers, checking for hits/crits, and triggering damage calculation. * It handles rolling attack dice, calculating modifiers, checking for hits/crits, and triggering damage calculation.
*/ */
interface Attack { interface AttackAction {
/** /**
* We can't call this an 'attack' roll because it could be the case that we're attacking by forcing a DC. So whoever * We can't call this an 'attack' roll because it could be the case that we're attacking by forcing a DC. So whoever
* is taking the positive action makes this roll. For melee attacks, this is a normal attack. For spell checks this * is taking the positive action makes this roll. For melee attacks, this is a normal attack. For spell checks this
* would be the save roll. * would be the save roll.
*/ */
val actionRoll: AttackDice val actionRoll: CritDice
/** /**
* Similar to [actionRoll], we cannot call this 'defense', because it might be a spell DC. This is the value of the * Similar to [actionRoll], we cannot call this 'defense', because it might be a spell DC. This is the value of the
@ -29,9 +29,9 @@ interface Attack {
* - Calling onCriticalHit, onNormalHit or onMiss based on hit result * - Calling onCriticalHit, onNormalHit or onMiss based on hit result
* - Returning the AttackResult * - Returning the AttackResult
*/ */
fun calculateDamage(r: Random): AttackResult { fun calculateDamage(r: Random, rollType: RollType): AttackResult {
val attackResult = actionRoll.roll(r) val attackResult = actionRoll.roll(r, rollType)
val attackBonus = actionRoll.evaluateModifiers(r, false) val attackBonus = actionRoll.evaluateModifiers(r, false)
@ -42,11 +42,14 @@ interface Attack {
onNormalHit(r) onNormalHit(r)
} }
} else { } else {
onMiss(r) onMiss(r)
} }
} }
private fun isHit(roll: RollResult, actionBonus: Int): Boolean { private fun isHit(roll: RollResult, actionBonus: Int): Boolean {
if (roll.result == 1){
return false
}
//ties go to the roller //ties go to the roller
return (roll.result + actionBonus) >= responseValue return (roll.result + actionBonus) >= responseValue
} }
@ -63,8 +66,62 @@ interface Attack {
* AttackSimulatorModel simulates attacks by running [sampleSize] simulations using the provided [attack] instance. * AttackSimulatorModel simulates attacks by running [sampleSize] simulations using the provided [attack] instance.
* It implements [SimulationModel] to run the simulations and return [AttackResult]. * It implements [SimulationModel] to run the simulations and return [AttackResult].
*/ */
class AttackSimulatorModel(override val sampleSize: Int, private val attack: Attack) : SimulationModel<AttackResult> { class AttackSimulatorModel(
override val sampleSize: Int,
private val attack: AttackAction,
private val rollType: RollType
) : SimulationModel<AttackResult> {
override fun simulate(r: Random): AttackResult { override fun simulate(r: Random): AttackResult {
return attack.calculateDamage(r) return attack.calculateDamage(r, rollType)
}
}
class MeleeAttackBuilder(
private val attackRollString: String,
private val dmgRollString: String,
private val defense: Int
) {
private val attackModifiers = ArrayList<Modifier<Int>>()
private val damageModifiers = ArrayList<Modifier<Int>>()
fun withAtkBonus(flat: Int): MeleeAttackBuilder {
attackModifiers.add(FlatModifier(flat))
return this
}
fun withAtkBonus(dice: Dice): MeleeAttackBuilder {
attackModifiers.add(DiceBonusModifier(dice))
return this
}
fun withAtkPenalty(dice: Dice): MeleeAttackBuilder {
attackModifiers.add(DicePenaltyModifier(dice))
return this
}
fun withDmgBonus(flat: Int): MeleeAttackBuilder {
damageModifiers.add(FlatModifier(flat))
return this
}
fun withDmgBonus(dice: Dice): MeleeAttackBuilder {
damageModifiers.add(DiceBonusModifier(dice))
return this
}
fun withDmgPenalty(dice: Dice): MeleeAttackBuilder {
damageModifiers.add(DicePenaltyModifier(dice))
return this
}
fun build(): SimpleMeleeAttackAction {
return SimpleMeleeAttackAction(
MeleeAttack(
Dice.critDice(attackRollString, attackModifiers),
Dice.plainDice(dmgRollString, damageModifiers)
),
defense
)
} }
} }

View File

@ -26,7 +26,7 @@ data class AttackResult(val attackWasHit: Boolean, val attackWasCrit: Boolean, v
val reportString = "Hit Rate: %.2f\tCrit Rate: %.2f\tAvg Dmg: %.2f".format(hitRate, critRate, avgDamage) val reportString = "Hit Rate: %.2f\tCrit Rate: %.2f\tAvg Dmg: %.2f".format(hitRate, critRate, avgDamage)
if(label.isNotBlank()){ if(label.isNotBlank()){
println("[$label] $reportString") println("[$label]\t$reportString")
}else{ }else{
println(reportString) println(reportString)
} }

View File

@ -1,5 +1,28 @@
package simulation package simulation
/**
* Dice with a modifiable crit threshold
*/
interface CritDice : Dice {
val critThreshold: Int
/**
* Checks if the given [RollResult] meets or exceeds the critical hit threshold
* for these attack dice.
*
* The critical hit threshold is determined based on the roll string used to construct
* this [CritDice] instance.
*
* @param result The [RollResult] to check for crit.
* @return True if result meets or exceeds the crit threshold.
*/
fun isCrit(result: RollResult): Boolean {
return result.result >= critThreshold
}
}
/** /**
* AttackDice class represents dice used for attack rolls. * AttackDice class represents dice used for attack rolls.
* If the roll string contains a 'c' modifier, the value after 'c' is used as the crit threshold. * If the roll string contains a 'c' modifier, the value after 'c' is used as the crit threshold.
@ -16,15 +39,14 @@ package simulation
* *
* The isCrit() method checks if a roll result meets the crit threshold. * The isCrit() method checks if a roll result meets the crit threshold.
*/ */
class AttackDice( class AttackDiceImpl(
override val rollString: String, override val rollString: String,
override val rollType: RollType = RollType.Normal,
override val modifiers: List<Modifier<Int>> = ArrayList() override val modifiers: List<Modifier<Int>> = ArrayList()
) : Dice { ) : CritDice {
override val nDice: Int override val nDice: Int
override val dieSize: Int override val dieSize: Int
private val critThreshold: Int override val critThreshold: Int
init { init {
val cleanRollString = rollString.lowercase() val cleanRollString = rollString.lowercase()
@ -42,20 +64,4 @@ class AttackDice(
critThreshold = dieSize * nDice critThreshold = dieSize * nDice
} }
} }
/**
* Checks if the given [RollResult] meets or exceeds the critical hit threshold
* for these attack dice.
*
* The critical hit threshold is determined based on the roll string used to construct
* this [AttackDice] instance.
*
* @param result The [RollResult] to check for crit.
* @return True if result meets or exceeds the crit threshold.
*/
fun isCrit(result: RollResult): Boolean {
return result.result >= critThreshold
}
} }

View File

@ -35,15 +35,18 @@ data class RollResult(val min: Int, val max: Int, val result: Int)
interface Dice { interface Dice {
val rollString: String val rollString: String
val rollType: RollType
val modifiers: List<Modifier<Int>> val modifiers: List<Modifier<Int>>
val nDice: Int val nDice: Int
val dieSize: Int val dieSize: Int
companion object { companion object {
fun makeDice(rollString: String, rollType: RollType = Normal, modifiers: List<Modifier<Int>> = ArrayList()): Dice { fun plainDice(rollString: String, modifiers: List<Modifier<Int>> = ArrayList()): Dice {
return SimpleDice(rollString, rollType, modifiers) return DiceImpl(rollString, modifiers)
}
fun critDice(rollString: String, modifiers: List<Modifier<Int>> = ArrayList()): CritDice{
return AttackDiceImpl(rollString, modifiers)
} }
} }
@ -58,7 +61,7 @@ interface Dice {
* @return The result of the dice roll with modifiers applied * @return The result of the dice roll with modifiers applied
* @see RollType * @see RollType
*/ */
fun roll(r: Random): RollResult { fun roll(r: Random, rollType: RollType = Normal): RollResult {
val range = (dieSize * nDice) - nDice val range = (dieSize * nDice) - nDice
val result = when (rollType) { val result = when (rollType) {
Advantage -> advantageRoll(r, nDice, range) Advantage -> advantageRoll(r, nDice, range)
@ -98,9 +101,8 @@ interface Dice {
} }
} }
private class SimpleDice( private class DiceImpl(
override val rollString: String, override val rollString: String,
override val rollType: RollType = Normal,
override val modifiers: List<Modifier<Int>> = ArrayList() override val modifiers: List<Modifier<Int>> = ArrayList()
) : Dice { ) : Dice {
override val nDice: Int override val nDice: Int

View File

@ -1,6 +1,9 @@
package simulation package simulation
import java.util.* import java.util.*
import kotlin.collections.ArrayList
data class MeleeAttack(val attackRoll: CritDice, val damageRoll: Dice)
/** /**
* Represents a simple melee attack in a simulation. * Represents a simple melee attack in a simulation.
@ -9,11 +12,12 @@ import java.util.*
* @param damageRoll The dice roll used to determine damage if attack hits. * @param damageRoll The dice roll used to determine damage if attack hits.
* @param responseValue The defense value the attack must exceed to hit. * @param responseValue The defense value the attack must exceed to hit.
*/ */
class SimpleMeleeAttack( class SimpleMeleeAttackAction(
override val actionRoll: AttackDice, attackInfo: MeleeAttack,
val damageRoll: Dice,
override val responseValue: Int override val responseValue: Int
) : Attack { ) : AttackAction {
override val actionRoll: CritDice = attackInfo.attackRoll
private val damageRoll: Dice = attackInfo.damageRoll
override fun onNormalHit(r: Random): AttackResult { override fun onNormalHit(r: Random): AttackResult {
val damage = damageRoll.roll(r).result + damageRoll.evaluateModifiers(r, false) val damage = damageRoll.roll(r).result + damageRoll.evaluateModifiers(r, false)

View File

@ -30,7 +30,7 @@ interface Modifier<T> {
*/ */
class DiceBonusModifier(private val dice: Dice) : Modifier<Int> { class DiceBonusModifier(private val dice: Dice) : Modifier<Int> {
constructor(diceString: String):this(Dice.makeDice(diceString)) constructor(diceString: String):this(Dice.plainDice(diceString))
override fun getBonus(r: Random, crit: Boolean): Int { override fun getBonus(r: Random, crit: Boolean): Int {
return if (crit){ return if (crit){

View File

@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Assertions.*
import kotlin.math.max
import kotlin.random.Random import kotlin.random.Random
internal class TemporalControllerTest { internal class TemporalControllerTest {
@ -11,25 +12,7 @@ internal class TemporalControllerTest {
/** /**
* Creates an instance of TemporalController for testing. * Creates an instance of TemporalController for testing.
*/ */
private val controller = object : TemporalController { private val controller = object : TemporalController{}
/**
* Sleeps for around the given duration, with variance.
*
* @param baseDuration the desired duration to sleep
* @param maxAdditionalDuration the amount of variance in the actual duration
*/
override fun sleep(baseDuration: Long, maxAdditionalDuration: Long) {
if (baseDuration < 0 || maxAdditionalDuration <= 1) {
return
}
val dSize = (maxAdditionalDuration) / 2
val r1 = Random.nextLong(dSize)
val r2 = Random.nextLong(dSize)
sleep(baseDuration + r1 + r2)
}
}
/** /**
* Tests that [TemporalController.sleep] blocks for the given duration. * Tests that [TemporalController.sleep] blocks for the given duration.
@ -37,7 +20,7 @@ internal class TemporalControllerTest {
@Test @Test
fun `sleep blocks for given duration`() { fun `sleep blocks for given duration`() {
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
controller.sleep(500) controller.sleep(501)
val end = System.currentTimeMillis() val end = System.currentTimeMillis()
val elapsed = end - start val elapsed = end - start
@ -58,9 +41,8 @@ internal class TemporalControllerTest {
val end = System.currentTimeMillis() val end = System.currentTimeMillis()
val elapsed = end - start val elapsed = end - start
val lowerBound = (duration - variance / 2) - 1 val lowerBound = duration
val upperBound = (duration + variance / 2) + 20 val upperBound = duration + variance
println("elapsed: $elapsed, [$lowerBound, $upperBound]")
assertTrue(elapsed >= lowerBound) assertTrue(elapsed >= lowerBound)
assertTrue(elapsed <= upperBound) assertTrue(elapsed <= upperBound)
} }
@ -79,18 +61,4 @@ internal class TemporalControllerTest {
assertTrue(elapsed < 10) // assert sleep was very short assertTrue(elapsed < 10) // assert sleep was very short
} }
/**
* Tests that [TemporalController.sleep] returns immediately
* if the variance parameter is 0.
*/
@Test
fun `sleepWithVariance returns immediately if variance is 0`() {
val start = System.currentTimeMillis()
controller.sleep(100, 0)
val end = System.currentTimeMillis()
val elapsed = end - start
assertTrue(elapsed < 10) // assert sleep was very short
}
} }

View File

@ -1,23 +0,0 @@
package controllers
import java.awt.image.BufferedImage
import java.nio.file.Paths
import javax.imageio.ImageIO
import kotlin.test.Test
import kotlin.test.assertNotNull
class VisionControllerTest {
@Test
fun testImageCapture() {
val vc = ConcreteVisionController()
val bi = vc.takeScreenshotOfForeground()
assertNotNull(bi)
for (i in 0..<bi.resolutionVariants.size) {
val path = Paths.get("image$i.png")
val image = bi.resolutionVariants[i] as BufferedImage
ImageIO.write(image , "png", path.toFile())
}
}
}

View File

@ -10,7 +10,7 @@ class AttackDiceTest {
fun testAttackDiceImplementation() { fun testAttackDiceImplementation() {
val r = Random() val r = Random()
// Test no crit below threshold // Test no crit below threshold
val dice = AttackDice("1d20") val dice = Dice.critDice("1d20")
val result = dice.roll(r) val result = dice.roll(r)
if (result.result < 20) { if (result.result < 20) {
Assertions.assertFalse(dice.isCrit(result)) Assertions.assertFalse(dice.isCrit(result))
@ -18,7 +18,7 @@ class AttackDiceTest {
Assertions.assertTrue(dice.isCrit(result)) Assertions.assertTrue(dice.isCrit(result))
} }
// Test no crit below threshold // Test no crit below threshold
val dice2 = AttackDice("1d20c19") val dice2 = Dice.critDice("1d20c10")
val result2 = dice2.roll(r) val result2 = dice2.roll(r)
if (result2.result < 10) { if (result2.result < 10) {
Assertions.assertFalse(dice2.isCrit(result2)) Assertions.assertFalse(dice2.isCrit(result2))
@ -27,7 +27,7 @@ class AttackDiceTest {
} }
// Test crit threshold other than max // Test crit threshold other than max
val dice3 = AttackDice("2d10c8") val dice3 = Dice.critDice("2d10c8")
val result3 = dice3.roll(r) val result3 = dice3.roll(r)
if (result3.result >= 8) { if (result3.result >= 8) {
Assertions.assertTrue(dice3.isCrit(result3)) Assertions.assertTrue(dice3.isCrit(result3))
@ -41,9 +41,9 @@ class AttackDiceTest {
val trueCritResult = RollResult(1, 20, 20) val trueCritResult = RollResult(1, 20, 20)
val fakeCritResult = RollResult(1, 20, 19) val fakeCritResult = RollResult(1, 20, 19)
val defaultRoll = AttackDice("1d20") val defaultRoll = Dice.critDice("1d20")
val verboseDefaultCrit = AttackDice("1d20c20") val verboseDefaultCrit = Dice.critDice("1d20c20")
val normalModifiedCrit = AttackDice("1d20c19") val normalModifiedCrit = Dice.critDice("1d20c19")
Assertions.assertFalse(defaultRoll.isCrit(fakeCritResult)) Assertions.assertFalse(defaultRoll.isCrit(fakeCritResult))
Assertions.assertFalse(verboseDefaultCrit.isCrit(fakeCritResult)) Assertions.assertFalse(verboseDefaultCrit.isCrit(fakeCritResult))

View File

@ -7,23 +7,27 @@ import java.util.*
internal class DiceTests { internal class DiceTests {
private val random = Random(1) private val random = Random(1)
private val d20 = Dice.makeDice("1d20") private val d20 = Dice.plainDice("1d20")
@Test @Test
fun roll_normal() { fun roll_normal() {
val dice = Dice.makeDice("2d6") val dice = Dice.plainDice("2d6")
val result = dice.roll(random) val result = dice.roll(random, RollType.Normal)
val result2 = dice.roll(random)
assertEquals(2, result.min) assertEquals(2, result.min)
assertEquals(12, result.max) assertEquals(12, result.max)
Assertions.assertTrue(result.min <= result.result && result.result <= result.max) Assertions.assertTrue(result.min <= result.result && result.result <= result.max)
assertEquals(2, result2.min)
assertEquals(12, result2.max)
Assertions.assertTrue(result2.min <= result2.result && result2.result <= result2.max)
} }
@Test @Test
fun roll_advantage() { fun roll_advantage() {
val dice = Dice.makeDice("2d6", RollType.Advantage) val dice = Dice.plainDice("2d6")
val result = dice.roll(random) val result = dice.roll(random, RollType.Advantage)
assertEquals(2, result.min) assertEquals(2, result.min)
assertEquals(12, result.max) assertEquals(12, result.max)
@ -33,8 +37,8 @@ internal class DiceTests {
@Test @Test
fun roll_disadvantage() { fun roll_disadvantage() {
val dice = Dice.makeDice("2d6", RollType.Disadvantage) val dice = Dice.plainDice("2d6")
val result = dice.roll(random) val result = dice.roll(random, RollType.Disadvantage)
assertEquals(2, result.min) assertEquals(2, result.min)
assertEquals(12, result.max) assertEquals(12, result.max)
@ -45,7 +49,7 @@ internal class DiceTests {
fun evaluate_modifiers() { fun evaluate_modifiers() {
val mod1 = FlatModifier(1) val mod1 = FlatModifier(1)
val mod2 = FlatModifier(2) val mod2 = FlatModifier(2)
val dice = Dice.makeDice("1d20", RollType.Normal, arrayListOf(mod1, mod2)) val dice = Dice.plainDice("1d20", arrayListOf(mod1, mod2))
val bonus = dice.evaluateModifiers(random) val bonus = dice.evaluateModifiers(random)
@ -59,10 +63,11 @@ internal class DiceTests {
RollType.entries.parallelStream() RollType.entries.parallelStream()
.forEach { .forEach {
val dice = Dice.makeDice(rollString, it) val dice = Dice.plainDice(rollString)
val r = Random(1) val r = Random(1)
val rollType = it
repeat(iterations) { repeat(iterations) {
val res = dice.roll(r) val res = dice.roll(r, rollType)
Assertions.assertTrue(res.min <= res.result) Assertions.assertTrue(res.min <= res.result)
Assertions.assertTrue(res.result <= res.max) Assertions.assertTrue(res.result <= res.max)
} }
@ -90,10 +95,10 @@ internal class DiceTests {
RollType.entries.parallelStream() RollType.entries.parallelStream()
.forEach { .forEach {
val dice = Dice.makeDice(rollString, it) val dice = Dice.plainDice(rollString)
val r = Random(1) val r = Random(1)
for (i in 0..<iterations) { for (i in 0..<iterations) {
val res = dice.roll(r) val res = dice.roll(r, it)
if (!observedMin && res.result == nDice) { if (!observedMin && res.result == nDice) {
observedMin = true observedMin = true
} }
@ -120,7 +125,7 @@ internal class DiceTests {
val expectedAverageLowerBound = ((n + (n * max)) / 2) * (1 - tolerance) val expectedAverageLowerBound = ((n + (n * max)) / 2) * (1 - tolerance)
val expectedAverageUpperBound = ((n + (n * max)) / 2) * (1 + tolerance) val expectedAverageUpperBound = ((n + (n * max)) / 2) * (1 + tolerance)
val dice = Dice.makeDice(rollString, RollType.Normal) val dice = Dice.plainDice(rollString)
var total = 0L var total = 0L
repeat(iterations) { repeat(iterations) {
total += dice.roll(random).result.toLong() total += dice.roll(random).result.toLong()
@ -144,11 +149,11 @@ internal class DiceTests {
val expectedAverageUpperBound = ((n + (n * max)) / 2) * tolerance val expectedAverageUpperBound = ((n + (n * max)) / 2) * tolerance
val dice = Dice.makeDice(rollString, RollType.Advantage) val dice = Dice.plainDice(rollString)
var total = 0L var total = 0L
repeat(iterations) { repeat(iterations) {
total += dice.roll(random).result.toLong() total += dice.roll(random, RollType.Advantage).result.toLong()
} }
val avg = total.toDouble() / iterations.toDouble() val avg = total.toDouble() / iterations.toDouble()
@ -168,13 +173,13 @@ internal class DiceTests {
val expectedAverageLowerBound = ((n + (n * max)) / 2) * tolerance val expectedAverageLowerBound = ((n + (n * max)) / 2) * tolerance
val dice = Dice.makeDice(rollString, RollType.Disadvantage) val dice = Dice.plainDice(rollString)
var total = 0L var total = 0L
repeat(iterations) { repeat(iterations) {
total += dice.roll(random).result.toLong() total += dice.roll(random, RollType.Disadvantage).result.toLong()
} }
val avg = total.toDouble() / iterations.toDouble() val avg = total.toDouble() / iterations.toDouble()
@ -187,7 +192,7 @@ internal class DiceTests {
@Test @Test
fun verifyDistribution() { fun verifyDistribution() {
val size = 20 val size = 20
val dice = Dice.makeDice("1d20") val dice = Dice.plainDice("1d20")
val iterations = 10_000_000 val iterations = 10_000_000
val tolerance = 0.05 //5% wiggle on distribution val tolerance = 0.05 //5% wiggle on distribution

View File

@ -9,15 +9,15 @@ class MeleeAttackTest {
val itt = 1_000_000 val itt = 1_000_000
val simulator = Simulator.getInstance<AttackResult>(Runtime.getRuntime().availableProcessors()) val simulator = Simulator.getInstance<AttackResult>(Runtime.getRuntime().availableProcessors())
val critAttack = SimpleMeleeAttack( val attackAction = MeleeAttack(Dice.critDice("1d20c19"), Dice.plainDice("1d8"))
actionRoll = AttackDice("1d20c19"), val critAttack = SimpleMeleeAttackAction(
damageRoll = Dice.makeDice("1d8"), attackAction,
10 10
) )
val normalAttackModel = AttackSimulatorModel(itt, critAttack) val normalAttackModel = AttackSimulatorModel(itt, critAttack, RollType.Normal)
val normalResults = simulator.doSimulation(normalAttackModel) val normalResults = simulator.doSimulation(normalAttackModel)

View File

@ -9,7 +9,7 @@ class HelperFunctionsTest {
@Test @Test
fun `test getRandomLongFromNormalDistribution generates normal distribution`() { fun `test getApproximatelyNormalLong generates normal distribution`() {
// Generate a large number of samples // Generate a large number of samples
val numSamples = 10000 val numSamples = 10000
@ -28,7 +28,7 @@ class HelperFunctionsTest {
} }
@Test
fun `test benchmark getNextLongJanky`() { fun `test benchmark getNextLongJanky`() {
val iterations = 1000000 val iterations = 1000000
val upperBound = 1000L val upperBound = 1000L
@ -52,7 +52,7 @@ class HelperFunctionsTest {
} }
} }
@Test
fun `test benchmark getNextLongGaussian`() { fun `test benchmark getNextLongGaussian`() {
val iterations = 1000000 val iterations = 1000000
@ -91,9 +91,9 @@ class HelperFunctionsTest {
} }
@Test
fun `test Direct Comparison of performance`() { fun `test Direct Comparison of performance`() {
val heats = 1000 val heats = 10
val iterations = 1000000 val iterations = 1000000
val upperBound = 1000L val upperBound = 1000L