From 526caf969033dfd562ddb3f9974b14f60ff97199 Mon Sep 17 00:00:00 2001 From: dtookey Date: Fri, 1 Sep 2023 10:08:27 -0400 Subject: [PATCH] started modeling 5th ed fight mechanics --- src/main/kotlin/simulation/Attack.kt | 21 ++++ src/main/kotlin/simulation/Dice.kt | 86 ++++++++++++++++ .../kotlin/simulation/FifthEdSimulator.kt | 99 ------------------- src/main/kotlin/simulation/MeleeAttack.kt | 31 ++++++ src/main/kotlin/simulation/Modifier.kt | 63 ++++++++++++ src/test/kotlin/simulation/SimulatorTest.kt | 12 +-- 6 files changed, 205 insertions(+), 107 deletions(-) create mode 100644 src/main/kotlin/simulation/Attack.kt create mode 100644 src/main/kotlin/simulation/Dice.kt delete mode 100644 src/main/kotlin/simulation/FifthEdSimulator.kt create mode 100644 src/main/kotlin/simulation/MeleeAttack.kt create mode 100644 src/main/kotlin/simulation/Modifier.kt diff --git a/src/main/kotlin/simulation/Attack.kt b/src/main/kotlin/simulation/Attack.kt new file mode 100644 index 0000000..02c1d7c --- /dev/null +++ b/src/main/kotlin/simulation/Attack.kt @@ -0,0 +1,21 @@ +package simulation + +import java.util.* + +interface Attack { + fun attackerSuccessful(r: Random): Boolean + + fun resultingDamage(r: Random, attackSuccessful: Boolean): Int + + fun getResultingDamage(r: Random): Int{ + val success = attackerSuccessful(r) + return resultingDamage(r, success) + } +} + + +class AttackSimulatorModel(override val sampleSize: Int, private val attack: Attack) : SimulationModel{ + override fun simulate(r: Random): Int { + return attack.getResultingDamage(r) + } +} diff --git a/src/main/kotlin/simulation/Dice.kt b/src/main/kotlin/simulation/Dice.kt new file mode 100644 index 0000000..36ddcdb --- /dev/null +++ b/src/main/kotlin/simulation/Dice.kt @@ -0,0 +1,86 @@ +package simulation + +import java.util.* +import kotlin.math.max +import kotlin.math.min + +/** + * Enumeration of different dice roll types. + * + * @property Advantage Rolls the dice twice and takes the higher result. + * @property Normal Rolls the dice normally once. + * @property Disadvantage Rolls the dice twice and takes the lower result. + */ +enum class RollType{ + /** + * Rolls the dice twice and returns the higher of the two results. + */ + Advantage, + /** + * Rolls the dice once normally. + */ + Normal, + /** + * Rolls the dice twice and returns the lower of the two results. + */ + Disadvantage +} + +/** + * Represents dice that can be rolled with different roll types and modifiers. + * + * @param rollString The dice roll notation, e.g. "2d6" + * @param rollType The roll type to use, which determines how the dice are rolled + * @param modifiers Optional modifier functions that add a bonus to the roll result + */ +class Dice(rollString: String, private val rollType: RollType = RollType.Normal, private vararg val modifiers: Modifier) { + private val nDice: Int + private val dieSize: Int + + init { + val parts = rollString.lowercase().split("d") + nDice = parts[0].toInt() + dieSize = parts[1].toInt() + } + + /** + * Rolls the dice and returns the result. + * + * The roll result is determined based on the [rollType]. + * + * The result is also modified by any [modifiers] that have been added to this [Dice]. + * + * @param r The [Random] instance to use for the dice rolls + * @return The result of the dice roll with modifiers applied + * @see RollType + */ + fun roll(r: Random): Int { + val result = when(rollType){ + RollType.Advantage->{ + val range1 = (dieSize * nDice) - nDice + val range2 = (dieSize * nDice) - nDice + max(range1, range2) + nDice + } + RollType.Disadvantage->{ + val range1 = (dieSize * nDice) - nDice + val range2 = (dieSize * nDice) - nDice + min(range1, range2) + nDice + } + else->{ + val range = (dieSize * nDice) - nDice + r.nextInt(range) + nDice + } + } + return result + evaluateModifiers(r) + } + + /** + * Evaluates all the modifiers passed to this Dice instance and returns their sum. + * + * @param r The Random instance to pass to each modifier's getBonus() method + * @return The summed bonus values from all modifiers + */ + fun evaluateModifiers(r: Random): Int{ + return modifiers.sumOf{ it.getBonus(r) } + } +} \ No newline at end of file diff --git a/src/main/kotlin/simulation/FifthEdSimulator.kt b/src/main/kotlin/simulation/FifthEdSimulator.kt deleted file mode 100644 index daaf705..0000000 --- a/src/main/kotlin/simulation/FifthEdSimulator.kt +++ /dev/null @@ -1,99 +0,0 @@ -package simulation - -import java.util.* -import kotlin.math.max -import kotlin.math.min - -interface Attack { - fun attackerSuccessful(r: Random): Boolean - - fun resultingDamage(r: Random, attackSuccessful: Boolean): Int - - fun getResultingDamage(r: Random): Int{ - val success = attackerSuccessful(r) - return resultingDamage(r, success) - } -} - -interface Bonus { - fun getBonus(r: Random): Int -} - -class AttackSimulatorModel(override val sampleSize: Int, private val attack: Attack) : SimulationModel{ - override fun simulate(r: Random): Int { - return attack.getResultingDamage(r) - } -} - -enum class RollType{ - Advantage, - Normal, - Disadvantage -} - -class Dice(rollString: String, val rollType: RollType = RollType.Normal) { - private val nDice: Int - private val dieSize: Int - - init { - val parts = rollString.lowercase().split("d") - nDice = parts[0].toInt() - dieSize = parts[1].toInt() - } - - fun roll(r: Random): Int { - return when(rollType){ - RollType.Advantage->{ - val range1 = (dieSize * nDice) - nDice - val range2 = (dieSize * nDice) - nDice - return max(range1, range2) + nDice - } - RollType.Disadvantage->{ - val range1 = (dieSize * nDice) - nDice - val range2 = (dieSize * nDice) - nDice - return min(range1, range2) + nDice - } - else->{ - val range = (dieSize * nDice) - nDice - r.nextInt(range) + nDice - } - } - - } -} - -class DiceBonus(private val dice: Dice) : Bonus { - override fun getBonus(r: Random): Int { - return dice.roll(r) - } - -} - -class FlatBonus(private val bonus: Int) : Bonus { - override fun getBonus(r: Random): Int { - return bonus - } -} - -class SimpleMeleeAttack( - val attack: Dice, - val attackBonus: ArrayList, - val damageRoll: Dice, - val damageBonus: ArrayList, - val defense: Int -) : Attack { - override fun attackerSuccessful(r: Random): Boolean { - val attackTotal = attack.roll(r) + attackBonus.sumOf { it.getBonus(r) } - - return attackTotal >= defense - } - - override fun resultingDamage(r: Random, attackSuccessful: Boolean): Int { - return if(attackSuccessful){ - damageRoll.roll(r) + damageBonus.sumOf { it.getBonus(r) } - }else{ - 0 - } - } - -} diff --git a/src/main/kotlin/simulation/MeleeAttack.kt b/src/main/kotlin/simulation/MeleeAttack.kt new file mode 100644 index 0000000..8bcc01d --- /dev/null +++ b/src/main/kotlin/simulation/MeleeAttack.kt @@ -0,0 +1,31 @@ +package simulation + +import java.util.* + +/** + * Represents a simple melee attack in a simulation. + * + * @param attackRoll The dice roll used to determine if an attack hits. + * @param damageRoll The dice roll used to determine damage if attack hits. + * @param defense The defense value the attack must exceed to hit. + */ +class SimpleMeleeAttack( + val attackRoll: Dice, + val damageRoll: Dice, + val defense: Int +) : Attack { + override fun attackerSuccessful(r: Random): Boolean { + val attackTotal = attackRoll.roll(r) + + return attackTotal >= defense + } + + override fun resultingDamage(r: Random, attackSuccessful: Boolean): Int { + return if(attackSuccessful){ + damageRoll.roll(r) + }else{ + 0 + } + } + +} diff --git a/src/main/kotlin/simulation/Modifier.kt b/src/main/kotlin/simulation/Modifier.kt new file mode 100644 index 0000000..5df88db --- /dev/null +++ b/src/main/kotlin/simulation/Modifier.kt @@ -0,0 +1,63 @@ +package simulation + +import java.util.* + +/** + * Interface for objects that can modify a value with a bonus. + * + * Implementations will generate a bonus value based on their internal logic. + * + * A [Random] object is provided if the bonus is variable. + */ +interface Modifier { + + /** + * Generates a bonus integer, potentially using the provided Random instance if needs be. + * + * @param r Random instance to use for random number generation + * @return a generated bonus integer + */ + fun getBonus(r: Random): T +} + +/** + * A [Modifier] that generates a random bonus integer based on a provided [Dice]. + * + * On each call to [getBonus], it will roll the given [Dice] using the passed [Random] + * instance and return the result as a positive bonus amount. + * + * @param dice The [Dice] instance to use for generating bonus values. + */ +class DiceBonus(private val dice: Dice) : Modifier { + override fun getBonus(r: Random): Int { + return dice.roll(r) + } + +} + +/** + * A [Modifier] that applies a random penalty based on a [Dice]. + * + * On each call to [getBonus], it will roll the provided [Dice] object and return the + * result as a negative number. + * + * @param dice The [Dice] to use for generating penalty values. + */ +class DicePenalty(private val dice: Dice): Modifier{ + override fun getBonus(r: Random): Int { + return -dice.roll(r) + } +} + +/** + * A [Modifier] that applies a fixed bonus amount. + * + * The bonus value is set on construction and does not vary. + * + * @param bonus The fixed bonus amount to apply. + */ +class FlatModifier(private val bonus: Int) : Modifier { + override fun getBonus(r: Random): Int { + return bonus + } +} diff --git a/src/test/kotlin/simulation/SimulatorTest.kt b/src/test/kotlin/simulation/SimulatorTest.kt index 489f056..db40b90 100644 --- a/src/test/kotlin/simulation/SimulatorTest.kt +++ b/src/test/kotlin/simulation/SimulatorTest.kt @@ -21,18 +21,14 @@ class SimulatorTest { val simulator = Simulator.getInstance(Runtime.getRuntime().availableProcessors()) val attack = SimpleMeleeAttack( - Dice("1d20"), - arrayListOf(FlatBonus(5)), - Dice("2d6"), - arrayListOf(FlatBonus(5)), + Dice("1d20", RollType.Normal, FlatModifier(5)), + Dice("2d6", RollType.Normal, FlatModifier(5)), 15 ) val attackWithAdvantageAndBless = SimpleMeleeAttack( - Dice("1d20", RollType.Advantage), - arrayListOf(FlatBonus(5), DiceBonus(Dice("1d4"))), - Dice("2d6"), - arrayListOf(FlatBonus(5)), + Dice("1d20", RollType.Advantage,FlatModifier(5), DiceBonus(Dice("1d4"))), + Dice("2d6", RollType.Normal, FlatModifier(5)), 15 )