broke up reporting into some component pieces
This commit is contained in:
parent
5c7d72ea86
commit
28bb5e30e0
@ -7,6 +7,10 @@ import simulation.fifthEd.AttackResult
|
||||
import simulation.fifthEd.AttackSimulatorModel
|
||||
import simulation.fifthEd.MeleeAttackBuilder
|
||||
import simulation.fifthEd.SimpleMeleeAttackAction
|
||||
import simulation.reporting.MetricReport
|
||||
import simulation.reporting.MetricResult
|
||||
import simulation.reporting.Report
|
||||
import simulation.reporting.ReportBuilder
|
||||
|
||||
fun main() {
|
||||
val rounds = 1_000_000
|
||||
|
||||
@ -1,116 +0,0 @@
|
||||
package simulation
|
||||
|
||||
import simulation.fifthEd.AttackResult
|
||||
import kotlin.math.pow
|
||||
|
||||
data class MetricReport(val name: String, val results: List<MetricResult>)
|
||||
data class MetricResult(val name: String, val label: String, val metricValue: Double)
|
||||
|
||||
interface Report {
|
||||
val name: String
|
||||
val metrics: List<Metric<Double>>
|
||||
|
||||
companion object {
|
||||
fun formatReport(name: String, results: List<MetricResult>): String {
|
||||
val builder = StringBuilder()
|
||||
results.forEach {
|
||||
builder.append(it.label)
|
||||
.append('\t')
|
||||
|
||||
}
|
||||
return builder.toString()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun computeResults(results: List<AttackResult>): MetricReport {
|
||||
val m = ArrayList<MetricResult>(metrics.size)
|
||||
metrics.forEach {
|
||||
val value = it.mapToMetric(results)
|
||||
m.add(MetricResult(it.metricName, it.formatResults(value), value))
|
||||
}
|
||||
return MetricReport(name, m)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
internal class ReportImpl(override val name: String, override val metrics: List<Metric<Double>>) : Report
|
||||
|
||||
interface ReportBuilder {
|
||||
val metrics: MutableList<Metric<Double>>
|
||||
|
||||
companion object {
|
||||
fun getInstance(): ReportBuilder {
|
||||
return ReportBuilderImpl()
|
||||
}
|
||||
}
|
||||
|
||||
fun build(reportName: String): Report {
|
||||
return ReportImpl(reportName, metrics)
|
||||
}
|
||||
|
||||
fun addAverageMetric(metricName: String, fieldMapFn: (AttackResult) -> Long): ReportBuilder {
|
||||
metrics.add(AverageMetric(metricName, fieldMapFn))
|
||||
return this
|
||||
}
|
||||
|
||||
fun addRateMetric(metricName: String, fieldMapFn: (AttackResult) -> Boolean): ReportBuilder {
|
||||
metrics.add(RateMetric(metricName, fieldMapFn))
|
||||
return this
|
||||
}
|
||||
|
||||
fun addStdDevMetric(metricName: String, fieldMapFn: (AttackResult) -> Long): ReportBuilder {
|
||||
metrics.add(StdDevMetric(metricName, fieldMapFn))
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
class ReportBuilderImpl : ReportBuilder {
|
||||
override val metrics: MutableList<Metric<Double>> = ArrayList()
|
||||
|
||||
}
|
||||
|
||||
interface Metric<T> {
|
||||
val metricName: String
|
||||
fun mapToMetric(results: List<AttackResult>): T
|
||||
|
||||
fun formatResults(result: T): String
|
||||
|
||||
}
|
||||
|
||||
class AverageMetric(override val metricName: String, private val fieldMapFn: (AttackResult) -> Long) : Metric<Double> {
|
||||
override fun mapToMetric(results: List<AttackResult>): Double {
|
||||
return results.map(fieldMapFn).average()
|
||||
}
|
||||
|
||||
override fun formatResults(result: Double): String {
|
||||
return "$metricName: %.2f".format(result)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RateMetric(override val metricName: String, private val fieldMapFn: (AttackResult) -> Boolean) : Metric<Double> {
|
||||
override fun mapToMetric(results: List<AttackResult>): Double {
|
||||
return results.map(fieldMapFn).map { boolToInt(it) }.average() * 100.0
|
||||
}
|
||||
|
||||
override fun formatResults(result: Double): String {
|
||||
return "$metricName: %.2f".format(result) +"%"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class StdDevMetric(override val metricName: String, private val fieldMapFn: (AttackResult) -> Long) : Metric<Double> {
|
||||
override fun mapToMetric(results: List<AttackResult>): Double {
|
||||
val mean = results.map(fieldMapFn).average()
|
||||
return results.map(fieldMapFn).map { (it - mean).pow(2) }.average().pow(0.5)
|
||||
}
|
||||
|
||||
override fun formatResults(result: Double): String {
|
||||
return "$metricName: %.2f".format(result)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun boolToInt(b: Boolean): Int = if (b) 1 else 0
|
||||
@ -39,7 +39,7 @@ interface CritDice : Dice {
|
||||
*
|
||||
* The isCrit() method checks if a roll result meets the crit threshold.
|
||||
*/
|
||||
internal class AttackDiceImpl(
|
||||
internal class CritDiceImpl(
|
||||
override val rollString: String,
|
||||
override val modifiers: List<DiceModifier<Int>> = ArrayList()
|
||||
) : CritDice {
|
||||
|
||||
@ -6,7 +6,7 @@ object DiceBag {
|
||||
}
|
||||
|
||||
fun critDice(rollString: String, modifiers: List<DiceModifier<Int>> = ArrayList()): CritDice {
|
||||
return AttackDiceImpl(rollString, modifiers)
|
||||
return CritDiceImpl(rollString, modifiers)
|
||||
}
|
||||
|
||||
fun rerollDice(
|
||||
|
||||
@ -5,35 +5,3 @@ import simulation.dice.Dice
|
||||
import java.util.*
|
||||
|
||||
data class ActionRollInfo(val actionRoll: CritDice, val damageRoll: Dice)
|
||||
|
||||
/**
|
||||
* Represents a simple melee attack in a simulation.
|
||||
*
|
||||
* @param actionRoll The dice roll used to determine if an 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.
|
||||
*/
|
||||
class SimpleMeleeAttackAction(
|
||||
override val actionRollInfo: ActionRollInfo,
|
||||
override val responseValue: Int
|
||||
) : AttackAction {
|
||||
|
||||
|
||||
override fun onNormalAction(r: Random): AttackResult {
|
||||
val damage = actionRollInfo.damageRoll.roll(r).result + actionRollInfo.damageRoll.evaluateModifiers(r, false)
|
||||
return AttackResult(rollSucceeded = true, rollWasCritical = false, damage)
|
||||
}
|
||||
|
||||
override fun onCriticalAction(r: Random): AttackResult {
|
||||
val damage = actionRollInfo.damageRoll.roll(r).result +
|
||||
actionRollInfo.damageRoll.roll(r).result +
|
||||
actionRollInfo.damageRoll.evaluateModifiers(r, true)
|
||||
|
||||
return AttackResult(rollSucceeded = true, rollWasCritical = true, damage)
|
||||
}
|
||||
|
||||
override fun onActionFailure(r: Random): AttackResult {
|
||||
return AttackResult(rollSucceeded = false, rollWasCritical = false, 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
package simulation.fifthEd
|
||||
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Represents a simple melee attack in a simulation.
|
||||
*
|
||||
* @param actionRoll The dice roll used to determine if an 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.
|
||||
*/
|
||||
class SimpleMeleeAttackAction(
|
||||
override val actionRollInfo: ActionRollInfo,
|
||||
override val responseValue: Int
|
||||
) : AttackAction {
|
||||
|
||||
|
||||
override fun onNormalAction(r: Random): AttackResult {
|
||||
val damage = actionRollInfo.damageRoll.roll(r).result + actionRollInfo.damageRoll.evaluateModifiers(r, false)
|
||||
return AttackResult(rollSucceeded = true, rollWasCritical = false, damage)
|
||||
}
|
||||
|
||||
override fun onCriticalAction(r: Random): AttackResult {
|
||||
val damage = actionRollInfo.damageRoll.roll(r).result +
|
||||
actionRollInfo.damageRoll.roll(r).result +
|
||||
actionRollInfo.damageRoll.evaluateModifiers(r, true)
|
||||
|
||||
return AttackResult(rollSucceeded = true, rollWasCritical = true, damage)
|
||||
}
|
||||
|
||||
override fun onActionFailure(r: Random): AttackResult {
|
||||
return AttackResult(rollSucceeded = false, rollWasCritical = false, 0)
|
||||
}
|
||||
|
||||
}
|
||||
56
src/main/kotlin/simulation/reporting/Metric.kt
Normal file
56
src/main/kotlin/simulation/reporting/Metric.kt
Normal file
@ -0,0 +1,56 @@
|
||||
package simulation.reporting
|
||||
|
||||
import simulation.fifthEd.AttackResult
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
interface Metric<T: Number> {
|
||||
val metricName: String
|
||||
fun mapToMetric(results: List<AttackResult>): T
|
||||
|
||||
fun formatResults(result: T): String
|
||||
|
||||
fun toResult(results: T): MetricResult {
|
||||
return MetricResult(this.metricName, this.formatResults(results), results.toDouble())
|
||||
}
|
||||
}
|
||||
|
||||
private fun Boolean.toInt(): Int = if (this) 1 else 0
|
||||
|
||||
class AverageMetric(override val metricName: String, private val fieldMapFn: (AttackResult) -> Long) : Metric<Double> {
|
||||
override fun mapToMetric(results: List<AttackResult>): Double {
|
||||
return results.map(fieldMapFn).average()
|
||||
}
|
||||
|
||||
override fun formatResults(result: Double): String {
|
||||
return "$metricName: %.2f".format(result)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class RateMetric(override val metricName: String, private val fieldMapFn: (AttackResult) -> Boolean) : Metric<Double> {
|
||||
override fun mapToMetric(results: List<AttackResult>): Double {
|
||||
return results.map(fieldMapFn).map(Boolean::toInt).average() * 100.0
|
||||
}
|
||||
|
||||
override fun formatResults(result: Double): String {
|
||||
return "$metricName: %.2f".format(result) + "%"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class StdDevMetric(override val metricName: String, private val fieldMapFn: (AttackResult) -> Long) : Metric<Double> {
|
||||
override fun mapToMetric(results: List<AttackResult>): Double {
|
||||
val mean = results.map(fieldMapFn).average()
|
||||
return results.map(fieldMapFn).map { (it - mean).pow(2) }.average().pow(0.5)
|
||||
}
|
||||
|
||||
override fun formatResults(result: Double): String {
|
||||
return "$metricName: %.2f".format(result)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class MetricResult(val name: String, val label: String, val metricValue: Double)
|
||||
|
||||
|
||||
63
src/main/kotlin/simulation/reporting/Report.kt
Normal file
63
src/main/kotlin/simulation/reporting/Report.kt
Normal file
@ -0,0 +1,63 @@
|
||||
package simulation.reporting
|
||||
|
||||
import simulation.fifthEd.AttackResult
|
||||
|
||||
|
||||
/**
|
||||
* Interface for generating metric reports.
|
||||
*
|
||||
* @property name The name of the report.
|
||||
* @property metrics The list of metrics to compute for the report.
|
||||
*
|
||||
* @see Metric
|
||||
*/
|
||||
interface Report {
|
||||
val name: String
|
||||
val metrics: List<Metric<Double>>
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Formats the given report results into a string.
|
||||
*
|
||||
* @param name The name of the report.
|
||||
* @param results The computed metric results.
|
||||
* @return The formatted report string.
|
||||
*/
|
||||
fun formatReport(name: String, results: List<MetricResult>): String {
|
||||
val builder = StringBuilder("[$name]:\n")
|
||||
results.forEach {
|
||||
builder.append(it.label)
|
||||
.append('\t')
|
||||
}
|
||||
return builder.toString()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the metric results for this report.
|
||||
*
|
||||
* @param results The list of attack results to compute metrics over.
|
||||
* @return A MetricReport containing the computed metric results.
|
||||
*/
|
||||
fun computeResults(results: List<AttackResult>): MetricReport {
|
||||
val m = ArrayList<MetricResult>(metrics.size)
|
||||
metrics.forEach {
|
||||
val value = it.mapToMetric(results)
|
||||
m.add(it.toResult(value))
|
||||
}
|
||||
return MetricReport(name, m)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal implementation of the [Report] interface.
|
||||
*
|
||||
* @param name The name of the report.
|
||||
* @param metrics The list of metrics to compute for this report.
|
||||
*
|
||||
* @constructor Creates a new [ReportImpl] instance.
|
||||
*/
|
||||
internal class ReportImpl(override val name: String, override val metrics: List<Metric<Double>>) : Report
|
||||
|
||||
|
||||
data class MetricReport(val name: String, val results: List<MetricResult>)
|
||||
54
src/main/kotlin/simulation/reporting/ReportBuilder.kt
Normal file
54
src/main/kotlin/simulation/reporting/ReportBuilder.kt
Normal file
@ -0,0 +1,54 @@
|
||||
package simulation.reporting
|
||||
|
||||
import simulation.fifthEd.AttackResult
|
||||
|
||||
/**
|
||||
* Builder class to construct [Report] instances.
|
||||
*
|
||||
* This class allows you to configure the metrics that will be included in the report.
|
||||
*
|
||||
* Use the companion object's [getInstance] method to get an instance of [ReportBuilder],
|
||||
* then call methods like [addAverageMetric] to add metrics.
|
||||
*
|
||||
* Call [build] and pass the name of the report to construct the [Report] instance with
|
||||
* the configured metrics.
|
||||
*/
|
||||
interface ReportBuilder {
|
||||
val metrics: MutableList<Metric<Double>>
|
||||
|
||||
companion object {
|
||||
fun getInstance(): ReportBuilder {
|
||||
return ReportBuilderImpl()
|
||||
}
|
||||
}
|
||||
|
||||
fun build(reportName: String): Report {
|
||||
return ReportImpl(reportName, metrics)
|
||||
}
|
||||
|
||||
fun addAverageMetric(metricName: String, fieldMapFn: (AttackResult) -> Long): ReportBuilder {
|
||||
metrics.add(AverageMetric(metricName, fieldMapFn))
|
||||
return this
|
||||
}
|
||||
|
||||
fun addRateMetric(metricName: String, fieldMapFn: (AttackResult) -> Boolean): ReportBuilder {
|
||||
metrics.add(RateMetric(metricName, fieldMapFn))
|
||||
return this
|
||||
}
|
||||
|
||||
fun addStdDevMetric(metricName: String, fieldMapFn: (AttackResult) -> Long): ReportBuilder {
|
||||
metrics.add(StdDevMetric(metricName, fieldMapFn))
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal implementation of the [ReportBuilder] interface.
|
||||
*
|
||||
* This class holds the list of metrics and allows adding new metrics.
|
||||
*
|
||||
* @param metrics The mutable list of metrics to include in the report.
|
||||
*
|
||||
* @constructor Creates a new [ReportBuilderImpl] instance with an empty metrics list.
|
||||
*/
|
||||
internal class ReportBuilderImpl(override val metrics: MutableList<Metric<Double>> = ArrayList()) : ReportBuilder
|
||||
@ -6,6 +6,8 @@ import simulation.fifthEd.ActionRollInfo
|
||||
import simulation.fifthEd.AttackResult
|
||||
import simulation.fifthEd.AttackSimulatorModel
|
||||
import simulation.fifthEd.SimpleMeleeAttackAction
|
||||
import simulation.reporting.Report
|
||||
import simulation.reporting.ReportBuilder
|
||||
import kotlin.test.Test
|
||||
|
||||
class MeleeAttackTest {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user