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.AttackSimulatorModel
|
||||||
import simulation.fifthEd.MeleeAttackBuilder
|
import simulation.fifthEd.MeleeAttackBuilder
|
||||||
import simulation.fifthEd.SimpleMeleeAttackAction
|
import simulation.fifthEd.SimpleMeleeAttackAction
|
||||||
|
import simulation.reporting.MetricReport
|
||||||
|
import simulation.reporting.MetricResult
|
||||||
|
import simulation.reporting.Report
|
||||||
|
import simulation.reporting.ReportBuilder
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
val rounds = 1_000_000
|
val rounds = 1_000_000
|
||||||
@ -124,7 +128,7 @@ fun generateCombatSuggestions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun defaultReport(): ReportBuilder{
|
fun defaultReport(): ReportBuilder {
|
||||||
return ReportBuilder.getInstance()
|
return ReportBuilder.getInstance()
|
||||||
.addRateMetric("Accuracy") { it.rollSucceeded }
|
.addRateMetric("Accuracy") { it.rollSucceeded }
|
||||||
.addRateMetric("Crit Rate") { it.rollWasCritical }
|
.addRateMetric("Crit Rate") { it.rollWasCritical }
|
||||||
|
|||||||
@ -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.
|
* The isCrit() method checks if a roll result meets the crit threshold.
|
||||||
*/
|
*/
|
||||||
internal class AttackDiceImpl(
|
internal class CritDiceImpl(
|
||||||
override val rollString: String,
|
override val rollString: String,
|
||||||
override val modifiers: List<DiceModifier<Int>> = ArrayList()
|
override val modifiers: List<DiceModifier<Int>> = ArrayList()
|
||||||
) : CritDice {
|
) : CritDice {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ object DiceBag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun critDice(rollString: String, modifiers: List<DiceModifier<Int>> = ArrayList()): CritDice {
|
fun critDice(rollString: String, modifiers: List<DiceModifier<Int>> = ArrayList()): CritDice {
|
||||||
return AttackDiceImpl(rollString, modifiers)
|
return CritDiceImpl(rollString, modifiers)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rerollDice(
|
fun rerollDice(
|
||||||
|
|||||||
@ -5,35 +5,3 @@ import simulation.dice.Dice
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
data class ActionRollInfo(val actionRoll: CritDice, val damageRoll: Dice)
|
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.AttackResult
|
||||||
import simulation.fifthEd.AttackSimulatorModel
|
import simulation.fifthEd.AttackSimulatorModel
|
||||||
import simulation.fifthEd.SimpleMeleeAttackAction
|
import simulation.fifthEd.SimpleMeleeAttackAction
|
||||||
|
import simulation.reporting.Report
|
||||||
|
import simulation.reporting.ReportBuilder
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
|
||||||
class MeleeAttackTest {
|
class MeleeAttackTest {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user