started modeling 5th ed fight mechanics

This commit is contained in:
dtookey 2023-09-01 10:08:27 -04:00
parent e96a231d3c
commit 526caf9690
6 changed files with 205 additions and 107 deletions

View File

@ -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<Int>{
override fun simulate(r: Random): Int {
return attack.getResultingDamage(r)
}
}

View File

@ -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<Int>) {
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) }
}
}

View File

@ -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<Int>{
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<Bonus>,
val damageRoll: Dice,
val damageBonus: ArrayList<Bonus>,
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
}
}
}

View File

@ -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
}
}
}

View File

@ -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<T> {
/**
* 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<Int> {
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<Int>{
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<Int> {
override fun getBonus(r: Random): Int {
return bonus
}
}

View File

@ -21,18 +21,14 @@ class SimulatorTest {
val simulator = Simulator.getInstance<Int>(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
)