started modeling 5th ed fight mechanics
This commit is contained in:
parent
e96a231d3c
commit
526caf9690
21
src/main/kotlin/simulation/Attack.kt
Normal file
21
src/main/kotlin/simulation/Attack.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
86
src/main/kotlin/simulation/Dice.kt
Normal file
86
src/main/kotlin/simulation/Dice.kt
Normal 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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
31
src/main/kotlin/simulation/MeleeAttack.kt
Normal file
31
src/main/kotlin/simulation/MeleeAttack.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
63
src/main/kotlin/simulation/Modifier.kt
Normal file
63
src/main/kotlin/simulation/Modifier.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,18 +21,14 @@ class SimulatorTest {
|
|||||||
val simulator = Simulator.getInstance<Int>(Runtime.getRuntime().availableProcessors())
|
val simulator = Simulator.getInstance<Int>(Runtime.getRuntime().availableProcessors())
|
||||||
|
|
||||||
val attack = SimpleMeleeAttack(
|
val attack = SimpleMeleeAttack(
|
||||||
Dice("1d20"),
|
Dice("1d20", RollType.Normal, FlatModifier(5)),
|
||||||
arrayListOf(FlatBonus(5)),
|
Dice("2d6", RollType.Normal, FlatModifier(5)),
|
||||||
Dice("2d6"),
|
|
||||||
arrayListOf(FlatBonus(5)),
|
|
||||||
15
|
15
|
||||||
)
|
)
|
||||||
|
|
||||||
val attackWithAdvantageAndBless = SimpleMeleeAttack(
|
val attackWithAdvantageAndBless = SimpleMeleeAttack(
|
||||||
Dice("1d20", RollType.Advantage),
|
Dice("1d20", RollType.Advantage,FlatModifier(5), DiceBonus(Dice("1d4"))),
|
||||||
arrayListOf(FlatBonus(5), DiceBonus(Dice("1d4"))),
|
Dice("2d6", RollType.Normal, FlatModifier(5)),
|
||||||
Dice("2d6"),
|
|
||||||
arrayListOf(FlatBonus(5)),
|
|
||||||
15
|
15
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user