Add support for Hp grow bonus and Leaning bonus.

This commit is contained in:
Thomas Andres Gomez 2024-11-30 18:05:55 +01:00
parent 3419afbe59
commit ca20078ffa
18 changed files with 526 additions and 402 deletions

View file

@ -42,6 +42,8 @@
<string name="character_sheet_edit__sub_characteristics__power_point">Points de pouvoir</string> <string name="character_sheet_edit__sub_characteristics__power_point">Points de pouvoir</string>
<string name="character_sheet_edit__sub_characteristics__damage_bonus">Bonus aux dégats</string> <string name="character_sheet_edit__sub_characteristics__damage_bonus">Bonus aux dégats</string>
<string name="character_sheet_edit__sub_characteristics__armor">Armure</string> <string name="character_sheet_edit__sub_characteristics__armor">Armure</string>
<string name="character_sheet_edit__sub_characteristics__learning">Bonus d'apprentissage</string>
<string name="character_sheet_edit__sub_characteristics__hp_grow">Bonus de PV</string>
<string name="character_sheet_edit__skills__common_title">Compétences communes</string> <string name="character_sheet_edit__skills__common_title">Compétences communes</string>
<string name="character_sheet_edit__skills__special_title">Compétences spéciales</string> <string name="character_sheet_edit__skills__special_title">Compétences spéciales</string>
<string name="character_sheet_edit__skills__special_action">Ajouter une compétence spéciale</string> <string name="character_sheet_edit__skills__special_action">Ajouter une compétence spéciale</string>
@ -94,6 +96,8 @@
<string name="character_sheet__sub_characteristics__power_point">Points de pouvoir</string> <string name="character_sheet__sub_characteristics__power_point">Points de pouvoir</string>
<string name="character_sheet__sub_characteristics__damage_bonus">Bonus aux dégats</string> <string name="character_sheet__sub_characteristics__damage_bonus">Bonus aux dégats</string>
<string name="character_sheet__sub_characteristics__armor">Armure</string> <string name="character_sheet__sub_characteristics__armor">Armure</string>
<string name="character_sheet__sub_characteristics__learning">Bonus d'apprentissage</string>
<string name="character_sheet__sub_characteristics__hp_grow">Bonus de PV</string>
<string name="character_sheet__skills__common_title">Compétences communes</string> <string name="character_sheet__skills__common_title">Compétences communes</string>
<string name="character_sheet__skills__special_title">Compétences spéciales</string> <string name="character_sheet__skills__special_title">Compétences spéciales</string>
<string name="character_sheet__skills__magic_title">Compétences magiques</string> <string name="character_sheet__skills__magic_title">Compétences magiques</string>
@ -113,7 +117,8 @@
<string name="tooltip__sub_characteristics__power_point">les points de pouvoir sont égaux au POU et sont dépensés pour utiliser la magie ou dautres pouvoirs. Tous les points de pouvoir régénèrent naturellement après une nuit de repos.</string> <string name="tooltip__sub_characteristics__power_point">les points de pouvoir sont égaux au POU et sont dépensés pour utiliser la magie ou dautres pouvoirs. Tous les points de pouvoir régénèrent naturellement après une nuit de repos.</string>
<string name="tooltip__sub_characteristics__bonus_damage">Les personnages plus massifs ou plus forts infligent plus de dégâts quand ils frappent leurs ennemis en combat au corps à corps. Le modificateur sapplique aux dégâts infligés par toute attaque portée par les personnages avec des armes de mêlée (BDC). La moitié de ce bonus s'applique aux attaques de lancer (BDD).</string> <string name="tooltip__sub_characteristics__bonus_damage">Les personnages plus massifs ou plus forts infligent plus de dégâts quand ils frappent leurs ennemis en combat au corps à corps. Le modificateur sapplique aux dégâts infligés par toute attaque portée par les personnages avec des armes de mêlée (BDC). La moitié de ce bonus s'applique aux attaques de lancer (BDD).</string>
<string name="tooltip__sub_characteristics__armor">Une armure protège son porteur des blessures. Lorsquun personnage est touché en combat par une attaque non magique, soustrayez les points darmure aux points de dégâts infligés. Les dommages au-delà de la protection de larmure surpassent celle-ci et sont infligés au personnage, réduisant ses points de vie actuels.</string> <string name="tooltip__sub_characteristics__armor">Une armure protège son porteur des blessures. Lorsquun personnage est touché en combat par une attaque non magique, soustrayez les points darmure aux points de dégâts infligés. Les dommages au-delà de la protection de larmure surpassent celle-ci et sont infligés au personnage, réduisant ses points de vie actuels.</string>
<string name="tooltip__sub_characteristics__learning">Plus un personnage est intelligent, plus il assimile rapidement les connaissances et plus il digère son expérience efficacement. Ce modificateur s'applique au score des compétences nouvellement acquises et aux jets d'expériences. Il est égal à (INT - 10) x 2 avec une valeur minimale de zéro.</string>
<string name="tooltip__sub_characteristics__hp_grow">Plus un personnage est de bonne constitution, plus son corps se renforce rapidement. Ce modificateur indique le nombre de "PV" maximum que le personnage gagne à chaque progression. Il est égal à CON / 3, arrondi à l'inférieur.</string>
<string name="tooltip__skills__combat">Attaque en combat à mains nues. Une attaque réussie inflige 1D3 + BDGT.</string> <string name="tooltip__skills__combat">Attaque en combat à mains nues. Une attaque réussie inflige 1D3 + BDGT.</string>
<string name="tooltip__skills__dodge">Éviter une attaque, un projectile, etc.</string> <string name="tooltip__skills__dodge">Éviter une attaque, un projectile, etc.</string>
<string name="tooltip__skills__grab">Maitriser/immobiliser un adversaire.</string> <string name="tooltip__skills__grab">Maitriser/immobiliser un adversaire.</string>

View file

@ -1,8 +1,7 @@
package com.pixelized.desktop.lwa package com.pixelized.desktop.lwa
import com.pixelized.desktop.lwa.business.DamageBonusUseCase import com.pixelized.desktop.lwa.business.CharacterSheetUseCase
import com.pixelized.desktop.lwa.business.RollUseCase import com.pixelized.desktop.lwa.business.RollUseCase
import com.pixelized.desktop.lwa.business.SkillNormalizerUseCase
import com.pixelized.desktop.lwa.business.SkillStepUseCase import com.pixelized.desktop.lwa.business.SkillStepUseCase
import com.pixelized.desktop.lwa.business.SkillValueComputationUseCase import com.pixelized.desktop.lwa.business.SkillValueComputationUseCase
import com.pixelized.desktop.lwa.parser.arithmetic.ArithmeticParser import com.pixelized.desktop.lwa.parser.arithmetic.ArithmeticParser
@ -71,9 +70,8 @@ val parserDependencies
val useCaseDependencies val useCaseDependencies
get() = module { get() = module {
factoryOf(::DamageBonusUseCase)
factoryOf(::SkillStepUseCase) factoryOf(::SkillStepUseCase)
factoryOf(::SkillNormalizerUseCase)
factoryOf(::RollUseCase) factoryOf(::RollUseCase)
factoryOf(::SkillValueComputationUseCase) factoryOf(::SkillValueComputationUseCase)
factoryOf(::CharacterSheetUseCase)
} }

View file

@ -0,0 +1,56 @@
package com.pixelized.desktop.lwa.business
import kotlin.math.ceil
import kotlin.math.max
class CharacterSheetUseCase {
fun normalize(value: Int): Int {
return value - value % 5 // (truncate(value.toFloat() / 5f) * 5f).toInt()
}
fun defaultMovement(): Int = 10
fun defaultMaxHp(
constitution: Int,
height: Int,
): Int {
return (ceil((constitution + height) / 2f).toInt())
}
fun defaultMaxPower(
power: Int,
): Int {
return power
}
fun defaultDamageBonus(
strength: Int,
height: Int,
): String {
return defaultDamageBonus(sum = strength + height)
}
fun defaultDamageBonus(
sum: Int,
): String {
return when {
sum < 12 -> "-1d6"
sum in 12..17 -> "-1d4"
sum in 18..22 -> "+0"
sum in 23..29 -> "+1d4"
sum in 30..39 -> "+1d6"
else -> "+2d6"
}
}
fun defaultArmor(): Int = 0
fun defaultLearning(intelligence: Int): Int {
return max(0, (intelligence - 10) * 2)
}
fun defaultHpGrow(constitution: Int): Int {
return (constitution / 3)
}
}

View file

@ -1,19 +0,0 @@
package com.pixelized.desktop.lwa.business
class DamageBonusUseCase {
fun bonusDamage(strength: Int, height: Int): String {
return bonusDamage(stat = strength + height)
}
fun bonusDamage(stat: Int): String {
return when {
stat < 12 -> "-1d6"
stat in 12..17 -> "-1d4"
stat in 18..22 -> "+0"
stat in 23..29 -> "+1d4"
stat in 30..39 -> "+1d6"
else -> "+2d6"
}
}
}

View file

@ -7,10 +7,6 @@ import kotlin.math.min
class RollUseCase { class RollUseCase {
fun rollD100(): Int {
return roll(modifier = null, quantity = 1, faces = 100)
}
fun roll(dice: Instruction.Dice): Int { fun roll(dice: Instruction.Dice): Int {
return roll( return roll(
modifier = dice.modifier, modifier = dice.modifier,
@ -19,9 +15,13 @@ class RollUseCase {
) )
} }
fun roll(modifier: Instruction.Dice.Modifier?, quantity: Int, faces: Int): Int { fun roll(
modifier: Instruction.Dice.Modifier? = null,
quantity: Int = 1,
faces: Int,
): Int {
print("{") print("{")
return sum(count = quantity) { return sum(count = quantity) { left ->
when (modifier) { when (modifier) {
Instruction.Dice.Modifier.ADVANTAGE -> { Instruction.Dice.Modifier.ADVANTAGE -> {
val roll1 = roll(faces = faces) val roll1 = roll(faces = faces)
@ -54,6 +54,8 @@ class RollUseCase {
null -> { null -> {
roll(faces = faces).also { print("$it") } roll(faces = faces).also { print("$it") }
} }
}.also {
if (quantity > 1 && left != 1) print(",")
} }
}.also { }.also {
print("}:") print("}:")
@ -61,14 +63,14 @@ class RollUseCase {
} }
private fun roll(faces: Int): Int { private fun roll(faces: Int): Int {
return (Math.random() * faces.toDouble() + 1).toInt() return (Math.random() * faces.toDouble() + 1.0).toInt()
} }
private fun sum(count: Int, block: () -> Int): Int { private fun sum(count: Int, block: (Int) -> Int): Int {
return if (count > 1) { return if (count > 1) {
block() + sum(count - 1, block) block(count) + sum(count - 1, block)
} else { } else {
block() block(count)
} }
} }
} }

View file

@ -1,9 +0,0 @@
package com.pixelized.desktop.lwa.business
import kotlin.math.truncate
class SkillNormalizerUseCase {
fun normalize(value: Int): Int {
return (truncate(value.toFloat() / 5f) * 5f).toInt()
}
}

View file

@ -1,16 +1,15 @@
package com.pixelized.desktop.lwa.parser.arithmetic package com.pixelized.desktop.lwa.parser.arithmetic
sealed class Instruction( sealed class Instruction {
val sign: Int, abstract val sign: Int
) {
class Dice( data class Dice(
sign: Int, override val sign: Int,
val modifier: Modifier?, val modifier: Modifier?,
val quantity: Int, val quantity: Int,
val faces: Int, val faces: Int,
) : Instruction( ) : Instruction() {
sign = sign,
) {
enum class Modifier { enum class Modifier {
ADVANTAGE, ADVANTAGE,
DISADVANTAGE, DISADVANTAGE,
@ -29,23 +28,20 @@ sealed class Instruction(
} }
} }
class Flat( data class Flat(
sign: Int, override val sign: Int,
val value: Int, val value: Int,
) : Instruction( ) : Instruction() {
sign = sign,
) {
override fun toString(): String { override fun toString(): String {
return "${sign.sign}${value}" return "${sign.sign}${value}"
} }
} }
class Word( data class Word(
sign: Int, override val sign: Int,
val type: Type, val type: Type,
) : Instruction( ) : Instruction() {
sign = sign
) {
enum class Type { enum class Type {
BDC, // Damages bonus for melee BDC, // Damages bonus for melee
BDD, // Damages bonus for range BDD, // Damages bonus for range

View file

@ -1,34 +1,13 @@
package com.pixelized.desktop.lwa.repository.characterSheet package com.pixelized.desktop.lwa.repository.characterSheet
import com.pixelized.desktop.lwa.business.DamageBonusUseCase import com.pixelized.desktop.lwa.business.CharacterSheetUseCase
import com.pixelized.desktop.lwa.parser.arithmetic.ArithmeticParser
import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet
import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheetJson import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheetJson
import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheetJsonV1 import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheetJsonV1
import kotlinx.coroutines.runBlocking
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__acrobatics
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__aid
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__athletics
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__bargain
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__combat
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__discretion
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__dodge
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__empathy
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__grab
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__intimidation
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__perception
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__persuasion
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__search
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__sleight_of_hand
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__spiel
import lwacharactersheet.composeapp.generated.resources.tooltip__skills__throw
import org.jetbrains.compose.resources.getString
import kotlin.math.ceil
class CharacterSheetJsonFactory( class CharacterSheetJsonFactory(
private val bonusDamageUseCase: DamageBonusUseCase,
private val skillDescriptionFactory: SkillDescriptionFactory, private val skillDescriptionFactory: SkillDescriptionFactory,
private val characterSheetUseCase: CharacterSheetUseCase,
) { ) {
fun convertToJson( fun convertToJson(
@ -51,6 +30,8 @@ class CharacterSheetJsonFactory(
maxPP = if (sheet.overrideMaxPP) sheet.maxPp else null, maxPP = if (sheet.overrideMaxPP) sheet.maxPp else null,
damageBonus = if (sheet.overrideDamageBonus) sheet.damageBonus else null, damageBonus = if (sheet.overrideDamageBonus) sheet.damageBonus else null,
armor = if (sheet.overrideArmor) sheet.armor else null, armor = if (sheet.overrideArmor) sheet.armor else null,
learning = if (sheet.overrideLearning) sheet.learning else null,
hpGrowf = if (sheet.overrideHpGrow) sheet.hpGrow else null,
skills = sheet.commonSkills.map { skills = sheet.commonSkills.map {
CharacterSheetJsonV1.Skill( CharacterSheetJsonV1.Skill(
id = it.id, id = it.id,
@ -108,8 +89,8 @@ class CharacterSheetJsonFactory(
private suspend fun convertFromV1( private suspend fun convertFromV1(
json: CharacterSheetJsonV1, json: CharacterSheetJsonV1,
): CharacterSheet { ): CharacterSheet = characterSheetUseCase.run {
val sheet = CharacterSheet( CharacterSheet(
id = json.id, id = json.id,
name = json.name, name = json.name,
strength = json.strength, strength = json.strength,
@ -120,21 +101,27 @@ class CharacterSheetJsonFactory(
power = json.power, power = json.power,
charisma = json.charisma, charisma = json.charisma,
overrideMovement = json.movement != null, overrideMovement = json.movement != null,
movement = json.movement ?: 10, movement = json.movement ?: defaultMovement(),
currentHp = json.currentHp, currentHp = json.currentHp,
overrideMaxHp = json.maxHp != null, overrideMaxHp = json.maxHp != null,
maxHp = json.maxHp ?: (ceil((json.constitution + json.height) / 2f).toInt()), maxHp = json.maxHp ?: defaultMaxHp(
constitution = json.constitution,
height = json.height,
),
currentPp = json.currentPP, currentPp = json.currentPP,
overrideMaxPP = json.maxPP != null, overrideMaxPP = json.maxPP != null,
maxPp = json.maxPP ?: json.power, maxPp = json.maxPP ?: defaultMaxPower(power = json.power),
overrideDamageBonus = json.damageBonus != null, overrideDamageBonus = json.damageBonus != null,
damageBonus = json.damageBonus damageBonus = json.damageBonus ?: defaultDamageBonus(
?: bonusDamageUseCase.bonusDamage( strength = json.strength,
strength = json.strength, height = json.height,
height = json.height ),
),
overrideArmor = json.armor != null, overrideArmor = json.armor != null,
armor = json.armor ?: 0, armor = json.armor ?: defaultArmor(),
overrideLearning = json.learning != null,
learning = json.learning ?: defaultLearning(intelligence = json.intelligence),
overrideHpGrow = json.hpGrowf != null,
hpGrow = json.hpGrowf ?: defaultHpGrow(constitution = json.constitution),
commonSkills = json.skills.map { commonSkills = json.skills.map {
CharacterSheet.Skill( CharacterSheet.Skill(
id = it.id, id = it.id,
@ -179,6 +166,5 @@ class CharacterSheetJsonFactory(
) )
}, },
) )
return sheet
} }
} }

View file

@ -24,6 +24,10 @@ data class CharacterSheet(
val damageBonus: String, val damageBonus: String,
val overrideArmor: Boolean, val overrideArmor: Boolean,
val armor: Int, val armor: Int,
val overrideLearning: Boolean,
val learning: Int,
val overrideHpGrow: Boolean,
val hpGrow: Int,
// skills // skills
val commonSkills: List<Skill>, val commonSkills: List<Skill>,
val specialSkills: List<Skill>, val specialSkills: List<Skill>,
@ -80,5 +84,7 @@ data class CharacterSheet(
const val PP = "PP" const val PP = "PP"
const val DMG = "DMG" const val DMG = "DMG"
const val ARMOR = "ARMOR" const val ARMOR = "ARMOR"
const val LB = "LEARNING"
const val GHP = "HP_GROW"
} }
} }

View file

@ -22,6 +22,8 @@ data class CharacterSheetJsonV1(
val maxPP: Int?, val maxPP: Int?,
val damageBonus: String?, val damageBonus: String?,
val armor: Int?, val armor: Int?,
val learning: Int?,
val hpGrowf: Int?,
// skills // skills
val skills: List<Skill>, val skills: List<Skill>,
// occupations // occupations

View file

@ -18,6 +18,8 @@ import lwacharactersheet.composeapp.generated.resources.character_sheet__charact
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__armor import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__armor
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__damage_bonus import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__damage_bonus
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__hit_point import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__hit_point
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__hp_grow
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__learning
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__movement import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__movement
import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__power_point import lwacharactersheet.composeapp.generated.resources.character_sheet__sub_characteristics__power_point
import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__charisma import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics__charisma
@ -30,6 +32,8 @@ import lwacharactersheet.composeapp.generated.resources.tooltip__characteristics
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__armor import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__armor
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__bonus_damage import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__bonus_damage
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__hit_point import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__hit_point
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__hp_grow
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__learning
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__movement import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__movement
import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__power_point import lwacharactersheet.composeapp.generated.resources.tooltip__sub_characteristics__power_point
import org.jetbrains.compose.resources.getString import org.jetbrains.compose.resources.getString
@ -171,6 +175,26 @@ class CharacterSheetFactory(
), ),
editable = false, editable = false,
), ),
Characteristic(
id = CharacteristicId.LB,
label = getString(Res.string.character_sheet__sub_characteristics__learning),
value = "${sheet.learning}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__learning),
description = getString(Res.string.tooltip__sub_characteristics__learning),
),
editable = false,
),
Characteristic(
id = CharacteristicId.GHP,
label = getString(Res.string.character_sheet__sub_characteristics__hp_grow),
value = "${sheet.hpGrow}",
tooltips = TooltipUio(
title = getString(Res.string.character_sheet__sub_characteristics__hp_grow),
description = getString(Res.string.tooltip__sub_characteristics__hp_grow),
),
editable = false,
),
), ),
commonSkills = sheet.commonSkills.map { skill -> commonSkills = sheet.commonSkills.map { skill ->
Node( Node(

View file

@ -3,8 +3,7 @@ package com.pixelized.desktop.lwa.screen.characterSheet.edit
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import com.pixelized.desktop.lwa.business.DamageBonusUseCase import com.pixelized.desktop.lwa.business.CharacterSheetUseCase
import com.pixelized.desktop.lwa.business.SkillNormalizerUseCase
import com.pixelized.desktop.lwa.repository.characterSheet.SkillDescriptionFactory import com.pixelized.desktop.lwa.repository.characterSheet.SkillDescriptionFactory
import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet import com.pixelized.desktop.lwa.repository.characterSheet.model.CharacterSheet
import com.pixelized.desktop.lwa.screen.characterSheet.edit.common.SkillFieldFactory import com.pixelized.desktop.lwa.screen.characterSheet.edit.common.SkillFieldFactory
@ -45,27 +44,26 @@ import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sk
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__throw import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__skills__throw
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__armor import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__armor
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__damage_bonus import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__damage_bonus
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__hit_point import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__hp_grow
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__learning
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__max_hit_point import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__max_hit_point
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__max_power_point import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__max_power_point
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__movement import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__movement
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__sub_characteristics__power_point
import org.jetbrains.compose.resources.getString import org.jetbrains.compose.resources.getString
import java.util.UUID import java.util.UUID
import kotlin.math.ceil
import kotlin.math.max import kotlin.math.max
import kotlin.math.min
class CharacterSheetEditFactory( class CharacterSheetEditFactory(
private val bonusDamageUseCase: DamageBonusUseCase, private val characterSheetUseCase: CharacterSheetUseCase,
private val skillFieldFactory: SkillFieldFactory, private val skillFieldFactory: SkillFieldFactory,
private val skillDescriptionFactory: SkillDescriptionFactory, private val skillDescriptionFactory: SkillDescriptionFactory,
private val normalizer: SkillNormalizerUseCase,
) { ) {
suspend fun updateCharacterSheet( suspend fun updateCharacterSheet(
currentSheet: CharacterSheet?, currentSheet: CharacterSheet?,
editedSheet: CharacterSheetEditPageUio, editedSheet: CharacterSheetEditPageUio,
): CharacterSheet { ): CharacterSheet {
val editedMaxHp = editedSheet.maxHp.unpack()?.toIntOrNull() ?: currentSheet?.maxHp ?: 0
val editedMaxPp = editedSheet.maxPp.unpack()?.toIntOrNull() ?: currentSheet?.maxPp ?: 0
return CharacterSheet( return CharacterSheet(
id = editedSheet.id, id = editedSheet.id,
name = editedSheet.name.value.value, name = editedSheet.name.value.value,
@ -95,19 +93,11 @@ class CharacterSheetEditFactory(
?: currentSheet?.movement ?: currentSheet?.movement
?: 10, ?: 10,
overrideMaxHp = editedSheet.maxHp.value.value.value.isNotBlank(), overrideMaxHp = editedSheet.maxHp.value.value.value.isNotBlank(),
maxHp = editedSheet.maxHp.unpack()?.toIntOrNull() maxHp = editedMaxHp,
?: currentSheet?.maxHp currentHp = currentSheet?.currentHp?.coerceAtMost(editedMaxHp) ?: editedMaxHp,
?: 0,
currentHp = editedSheet.currentHp.unpack()?.toIntOrNull()
?: currentSheet?.currentHp
?: 0,
overrideMaxPP = editedSheet.maxPp.value.value.value.isNotBlank(), overrideMaxPP = editedSheet.maxPp.value.value.value.isNotBlank(),
maxPp = editedSheet.maxPp.unpack()?.toIntOrNull() maxPp = editedMaxPp,
?: currentSheet?.maxPp currentPp = currentSheet?.currentPp?.coerceAtMost(editedMaxPp) ?: editedMaxPp,
?: 0,
currentPp = editedSheet.currentPp.unpack()?.toIntOrNull()
?: currentSheet?.currentPp
?: 0,
overrideDamageBonus = editedSheet.damageBonus.value.value.value.isNotBlank(), overrideDamageBonus = editedSheet.damageBonus.value.value.value.isNotBlank(),
damageBonus = editedSheet.damageBonus.unpack() damageBonus = editedSheet.damageBonus.unpack()
?: currentSheet?.damageBonus ?: currentSheet?.damageBonus
@ -116,6 +106,10 @@ class CharacterSheetEditFactory(
armor = editedSheet.armor.unpack()?.toIntOrNull() armor = editedSheet.armor.unpack()?.toIntOrNull()
?: currentSheet?.armor ?: currentSheet?.armor
?: 0, ?: 0,
overrideLearning = editedSheet.learning.value.value.value.isNotBlank(),
learning = editedSheet.learning.unpack()?.toIntOrNull() ?: 0,
overrideHpGrow = editedSheet.hpGrow.value.value.value.isNotBlank(),
hpGrow = editedSheet.hpGrow.unpack()?.toIntOrNull() ?: 0,
commonSkills = editedSheet.commonSkills.map { editedSkill -> commonSkills = editedSheet.commonSkills.map { editedSkill ->
val currentSkill = currentSheet?.commonSkills?.firstOrNull { val currentSkill = currentSheet?.commonSkills?.firstOrNull {
it.id == editedSkill.id it.id == editedSkill.id
@ -236,239 +230,243 @@ class CharacterSheetEditFactory(
val specialSkillsLabel = getString(Res.string.character_sheet_edit__skills__special_title) val specialSkillsLabel = getString(Res.string.character_sheet_edit__skills__special_title)
val magicSkillsLabel = getString(Res.string.character_sheet_edit__skills__magic_title) val magicSkillsLabel = getString(Res.string.character_sheet_edit__skills__magic_title)
val maxHitPoint = SimpleFieldUio( return with(characterSheetUseCase) {
label = getString(Res.string.character_sheet_edit__sub_characteristics__max_hit_point), CharacterSheetEditPageUio(
value = skillFieldFactory.createWrapper( id = sheet?.id ?: UUID.randomUUID().toString(),
value = if (sheet?.overrideMaxHp == true) "${sheet.maxHp}" else "", name = skillFieldFactory.createWrapper(
placeholder = derivedStateOf { "${ceil((con() + hei()) / 2f).toInt()}" }, label = mutableStateOf(getString(Res.string.character_sheet_edit__name_label)),
) value = sheet?.name ?: ""
)
val maxPowerPoint = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__sub_characteristics__max_power_point),
value = skillFieldFactory.createWrapper(
value = if (sheet?.overrideMaxPP == true) "${sheet.maxPp}" else "",
placeholder = derivedStateOf { "${pow()}" },
)
)
return CharacterSheetEditPageUio(
id = sheet?.id ?: UUID.randomUUID().toString(),
name = skillFieldFactory.createWrapper(
label = mutableStateOf(getString(Res.string.character_sheet_edit__name_label)),
value = sheet?.name ?: ""
),
strength = str,
dexterity = dex,
constitution = con,
height = hei,
intelligence = int,
power = pow,
charisma = cha,
movement = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__sub_characteristics__movement),
value = skillFieldFactory.createWrapper(
value = if (sheet?.overrideMovement == true) "${sheet.movement}" else "",
placeholder = mutableStateOf("10"),
)
),
maxHp = maxHitPoint,
currentHp = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__sub_characteristics__hit_point),
value = skillFieldFactory.createWrapper(
enable = false,
placeholder = derivedStateOf {
val min = min(
sheet?.currentHp ?: Int.MAX_VALUE,
maxHitPoint.unpack()?.toIntOrNull() ?: Int.MAX_VALUE,
)
if (min != Int.MAX_VALUE) "$min" else ""
},
)
),
maxPp = maxPowerPoint,
currentPp = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__sub_characteristics__power_point),
value = skillFieldFactory.createWrapper(
enable = false,
placeholder = derivedStateOf {
val min = min(
sheet?.currentPp ?: Int.MAX_VALUE,
maxPowerPoint.unpack()?.toIntOrNull() ?: Int.MAX_VALUE,
)
if (min != Int.MAX_VALUE) "$min" else ""
},
)
),
damageBonus = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__sub_characteristics__damage_bonus),
value = skillFieldFactory.createWrapper(
value = if (sheet?.overrideDamageBonus == true) sheet.damageBonus else "",
placeholder = derivedStateOf {
bonusDamageUseCase.bonusDamage(
strength = str(),
height = hei()
)
},
)
),
armor = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__sub_characteristics__armor),
value = skillFieldFactory.createWrapper(
value = if (sheet?.overrideArmor == true) "${sheet.armor}" else "",
placeholder = mutableStateOf("0"),
)
),
commonSkills = listOf(
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.COMBAT_ID,
label = getString(Res.string.character_sheet_edit__skills__combat),
base = derivedStateOf { normalizer.normalize(dex() * 2) },
), ),
createBaseSkill( strength = str,
sheet = sheet, dexterity = dex,
id = CharacterSheet.CommonSkillId.DODGE_ID, constitution = con,
label = getString(Res.string.character_sheet_edit__skills__dodge), height = hei,
base = derivedStateOf { normalizer.normalize(dex() * 2) }, intelligence = int,
power = pow,
charisma = cha,
movement = SimpleFieldUio(
label = getString(Res.string.character_sheet_edit__sub_characteristics__movement),
value = skillFieldFactory.createWrapper(
value = if (sheet?.overrideMovement == true) "${sheet.movement}" else "",
placeholder = derivedStateOf {
"${characterSheetUseCase.defaultMovement()}"
},
)
), ),
createBaseSkill( maxHp = SimpleFieldUio(
sheet = sheet, label = getString(Res.string.character_sheet_edit__sub_characteristics__max_hit_point),
id = CharacterSheet.CommonSkillId.GRAB_ID, value = skillFieldFactory.createWrapper(
label = getString(Res.string.character_sheet_edit__skills__grab), value = if (sheet?.overrideMaxHp == true) "${sheet.maxHp}" else "",
base = derivedStateOf { normalizer.normalize(str() + hei()) }, placeholder = derivedStateOf {
"${
characterSheetUseCase.defaultMaxHp(
constitution = con(),
height = hei()
)
}"
},
)
), ),
createBaseSkill( maxPp = SimpleFieldUio(
sheet = sheet, label = getString(Res.string.character_sheet_edit__sub_characteristics__max_power_point),
id = CharacterSheet.CommonSkillId.THROW_ID, value = skillFieldFactory.createWrapper(
label = getString(Res.string.character_sheet_edit__skills__throw), value = if (sheet?.overrideMaxPP == true) "${sheet.maxPp}" else "",
base = derivedStateOf { normalizer.normalize(str() + dex()) }, placeholder = derivedStateOf {
"${characterSheetUseCase.defaultMaxPower(power = pow())}"
},
)
), ),
createBaseSkill( damageBonus = SimpleFieldUio(
sheet = sheet, label = getString(Res.string.character_sheet_edit__sub_characteristics__damage_bonus),
id = CharacterSheet.CommonSkillId.ATHLETICS_ID, value = skillFieldFactory.createWrapper(
label = getString(Res.string.character_sheet_edit__skills__athletics), value = if (sheet?.overrideDamageBonus == true) sheet.damageBonus else "",
base = derivedStateOf { normalizer.normalize(str() + con() * 2) }, placeholder = derivedStateOf {
characterSheetUseCase.defaultDamageBonus(
strength = str(),
height = hei()
)
},
)
), ),
createBaseSkill( armor = SimpleFieldUio(
sheet = sheet, label = getString(Res.string.character_sheet_edit__sub_characteristics__armor),
id = CharacterSheet.CommonSkillId.ACROBATICS_ID, value = skillFieldFactory.createWrapper(
label = getString(Res.string.character_sheet_edit__skills__acrobatics), value = if (sheet?.overrideArmor == true) "${sheet.armor}" else "",
base = derivedStateOf { normalizer.normalize(dex() + con() * 2) }, placeholder = derivedStateOf {
"${characterSheetUseCase.defaultArmor()}"
},
)
), ),
createBaseSkill( learning = SimpleFieldUio(
sheet = sheet, label = getString(Res.string.character_sheet_edit__sub_characteristics__learning),
id = CharacterSheet.CommonSkillId.PERCEPTION_ID, value = skillFieldFactory.createWrapper(
label = getString(Res.string.character_sheet_edit__skills__perception), value = if (sheet?.overrideLearning == true) "${sheet.learning}" else "",
base = derivedStateOf { normalizer.normalize(10 + int() * 2) }, placeholder = derivedStateOf {
"${characterSheetUseCase.defaultLearning(intelligence = int())}"
},
)
), ),
createBaseSkill( hpGrow = SimpleFieldUio(
sheet = sheet, label = getString(Res.string.character_sheet_edit__sub_characteristics__hp_grow),
id = CharacterSheet.CommonSkillId.SEARCH_ID, value = skillFieldFactory.createWrapper(
label = getString(Res.string.character_sheet_edit__skills__search), value = if (sheet?.overrideHpGrow == true) "${sheet.hpGrow}" else "",
base = derivedStateOf { normalizer.normalize(10 + int() * 2) }, placeholder = derivedStateOf {
"${characterSheetUseCase.defaultHpGrow(constitution = con())}"
},
)
), ),
createBaseSkill( commonSkills = listOf(
sheet = sheet, createBaseSkill(
id = CharacterSheet.CommonSkillId.EMPATHY_ID, sheet = sheet,
label = getString(Res.string.character_sheet_edit__skills__empathy), id = CharacterSheet.CommonSkillId.COMBAT_ID,
base = derivedStateOf { normalizer.normalize(cha() + int()) }, label = getString(Res.string.character_sheet_edit__skills__combat),
), base = derivedStateOf { normalize(dex() * 2) },
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.PERSUASION_ID,
label = getString(Res.string.character_sheet_edit__skills__persuasion),
base = derivedStateOf { normalizer.normalize(cha() * 3) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.INTIMIDATION_ID,
label = getString(Res.string.character_sheet_edit__skills__intimidation),
base = derivedStateOf { normalizer.normalize(cha() + max(pow(), hei()) * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.SPIEL_ID,
label = getString(Res.string.character_sheet_edit__skills__spiel),
base = derivedStateOf { normalizer.normalize(cha() * 2 + int()) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.BARGAIN_ID,
label = getString(Res.string.character_sheet_edit__skills__bargain),
base = derivedStateOf { normalizer.normalize(cha() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.DISCRETION_ID,
label = getString(Res.string.character_sheet_edit__skills__discretion),
base = derivedStateOf { normalizer.normalize(cha() + dex() * 2 - hei()) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.SLEIGHT_OF_HAND_ID,
label = getString(Res.string.character_sheet_edit__skills__sleight_of_hand),
base = derivedStateOf { normalizer.normalize(dex() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.AID_ID,
label = getString(Res.string.character_sheet_edit__skills__aid),
base = derivedStateOf { normalizer.normalize(int() + dex()) },
),
),
specialSkills = sheet?.specialSkills?.map { skill ->
skillFieldFactory.createSkill(
id = skill.id,
label = specialSkillsLabel,
descriptionValue = skill.description ?: "",
labelValue = skill.label,
baseValue = skill.base,
bonusValue = skill.bonus ?: "",
levelValue = skill.level ?: "",
options = run {
val current = sheet.specialSkills.firstOrNull { it.id == skill.id }
listOf(
skillFieldFactory.occupationOption(checked = current?.occupation ?: false),
skillFieldFactory.deleteOption { onDeleteSkill(skill.id) },
)
},
)
} ?: emptyList(),
magicSkills = sheet?.magicSkills?.map { skill ->
skillFieldFactory.createSkill(
id = skill.id,
label = magicSkillsLabel,
descriptionValue = skill.description ?: "",
labelValue = skill.label,
baseValue = skill.base,
bonusValue = skill.bonus ?: "",
levelValue = skill.level ?: "",
options = run {
val current = sheet.magicSkills.firstOrNull { it.id == skill.id }
listOf(
skillFieldFactory.occupationOption(
checked = current?.occupation ?: false
),
skillFieldFactory.deleteOption { onDeleteSkill(skill.id) },
)
},
)
} ?: emptyList(),
actions = sheet?.actions?.map { action ->
ActionFieldUio(
id = action.id,
label = skillFieldFactory.createWrapper(
label = mutableStateOf(getString(Res.string.character_sheet_edit__actions__name_label)),
value = action.label,
), ),
action = skillFieldFactory.createWrapper( createBaseSkill(
label = mutableStateOf(getString(Res.string.character_sheet_edit__actions__action_label)), sheet = sheet,
value = action.roll, id = CharacterSheet.CommonSkillId.DODGE_ID,
label = getString(Res.string.character_sheet_edit__skills__dodge),
base = derivedStateOf { normalize(dex() * 2) },
), ),
option = skillFieldFactory.deleteOption { onDeleteSkill(action.id) }, createBaseSkill(
) sheet = sheet,
} ?: emptyList(), id = CharacterSheet.CommonSkillId.GRAB_ID,
) label = getString(Res.string.character_sheet_edit__skills__grab),
base = derivedStateOf { normalize(str() + hei()) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.THROW_ID,
label = getString(Res.string.character_sheet_edit__skills__throw),
base = derivedStateOf { normalize(str() + dex()) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.ATHLETICS_ID,
label = getString(Res.string.character_sheet_edit__skills__athletics),
base = derivedStateOf { normalize(str() + con() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.ACROBATICS_ID,
label = getString(Res.string.character_sheet_edit__skills__acrobatics),
base = derivedStateOf { normalize(dex() + con() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.PERCEPTION_ID,
label = getString(Res.string.character_sheet_edit__skills__perception),
base = derivedStateOf { normalize(10 + int() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.SEARCH_ID,
label = getString(Res.string.character_sheet_edit__skills__search),
base = derivedStateOf { normalize(10 + int() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.EMPATHY_ID,
label = getString(Res.string.character_sheet_edit__skills__empathy),
base = derivedStateOf { normalize(cha() + int()) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.PERSUASION_ID,
label = getString(Res.string.character_sheet_edit__skills__persuasion),
base = derivedStateOf { normalize(cha() * 3) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.INTIMIDATION_ID,
label = getString(Res.string.character_sheet_edit__skills__intimidation),
base = derivedStateOf { normalize(cha() + max(pow(), hei()) * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.SPIEL_ID,
label = getString(Res.string.character_sheet_edit__skills__spiel),
base = derivedStateOf { normalize(cha() * 2 + int()) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.BARGAIN_ID,
label = getString(Res.string.character_sheet_edit__skills__bargain),
base = derivedStateOf { normalize(cha() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.DISCRETION_ID,
label = getString(Res.string.character_sheet_edit__skills__discretion),
base = derivedStateOf { normalize(cha() + dex() * 2 - hei()) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.SLEIGHT_OF_HAND_ID,
label = getString(Res.string.character_sheet_edit__skills__sleight_of_hand),
base = derivedStateOf { normalize(dex() * 2) },
),
createBaseSkill(
sheet = sheet,
id = CharacterSheet.CommonSkillId.AID_ID,
label = getString(Res.string.character_sheet_edit__skills__aid),
base = derivedStateOf { normalize(int() + dex()) },
),
),
specialSkills = sheet?.specialSkills?.map { skill ->
skillFieldFactory.createSkill(
id = skill.id,
label = specialSkillsLabel,
descriptionValue = skill.description ?: "",
labelValue = skill.label,
baseValue = skill.base,
bonusValue = skill.bonus ?: "",
levelValue = skill.level ?: "",
options = run {
val current = sheet.specialSkills.firstOrNull { it.id == skill.id }
listOf(
skillFieldFactory.occupationOption(current?.occupation ?: false),
skillFieldFactory.deleteOption { onDeleteSkill(skill.id) },
)
},
)
} ?: emptyList(),
magicSkills = sheet?.magicSkills?.map { skill ->
skillFieldFactory.createSkill(
id = skill.id,
label = magicSkillsLabel,
descriptionValue = skill.description ?: "",
labelValue = skill.label,
baseValue = skill.base,
bonusValue = skill.bonus ?: "",
levelValue = skill.level ?: "",
options = run {
val current = sheet.magicSkills.firstOrNull { it.id == skill.id }
listOf(
skillFieldFactory.occupationOption(
checked = current?.occupation ?: false
),
skillFieldFactory.deleteOption { onDeleteSkill(skill.id) },
)
},
)
} ?: emptyList(),
actions = sheet?.actions?.map { action ->
ActionFieldUio(
id = action.id,
label = skillFieldFactory.createWrapper(
label = mutableStateOf(getString(Res.string.character_sheet_edit__actions__name_label)),
value = action.label,
),
action = skillFieldFactory.createWrapper(
label = mutableStateOf(getString(Res.string.character_sheet_edit__actions__action_label)),
value = action.roll,
),
option = skillFieldFactory.deleteOption { onDeleteSkill(action.id) },
)
} ?: emptyList(),
)
}
} }
private suspend fun createBaseSkill( private suspend fun createBaseSkill(

View file

@ -70,10 +70,10 @@ data class CharacterSheetEditPageUio(
val movement: SimpleFieldUio, val movement: SimpleFieldUio,
val maxHp: SimpleFieldUio, val maxHp: SimpleFieldUio,
val maxPp: SimpleFieldUio, val maxPp: SimpleFieldUio,
val currentHp: SimpleFieldUio,
val currentPp: SimpleFieldUio,
val damageBonus: SimpleFieldUio, val damageBonus: SimpleFieldUio,
val armor: SimpleFieldUio, val armor: SimpleFieldUio,
val learning: SimpleFieldUio,
val hpGrow: SimpleFieldUio,
val commonSkills: List<BaseSkillFieldUio>, val commonSkills: List<BaseSkillFieldUio>,
val specialSkills: List<SkillFieldUio>, val specialSkills: List<SkillFieldUio>,
val magicSkills: List<SkillFieldUio>, val magicSkills: List<SkillFieldUio>,
@ -94,11 +94,11 @@ data class CharacterSheetEditPageUio(
get() = listOf( get() = listOf(
movement, movement,
maxHp, maxHp,
currentHp,
maxPp, maxPp,
currentPp,
damageBonus, damageBonus,
armor, armor,
learning,
hpGrow,
) )
} }

View file

@ -6,43 +6,45 @@ class DamageBonusUseCaseTest {
@Test @Test
fun testBonusDamage() { fun testBonusDamage() {
val userCase = CharacterSheetUseCase()
(0 until 12).forEach { (0 until 12).forEach {
val result = DamageBonusUseCase.bonusDamage(stat = it) val result = userCase.defaultDamageBonus(sum = it)
val expected = "-1d6" val expected = "-1d6"
assert(result == expected) { assert(result == expected) {
"Expected:'$expected' bonus damage for stat:'$it' but was:'$result'" "Expected:'$expected' bonus damage for stat:'$it' but was:'$result'"
} }
} }
(12 until 18).forEach { (12 until 18).forEach {
val result = DamageBonusUseCase.bonusDamage(stat = it) val result = userCase.defaultDamageBonus(sum = it)
val expected = "-1d4" val expected = "-1d4"
assert(result == expected) { assert(result == expected) {
"Expected:'$expected' bonus damage for stat:'$it' but was:'$result'" "Expected:'$expected' bonus damage for stat:'$it' but was:'$result'"
} }
} }
(18 until 23).forEach { (18 until 23).forEach {
val result = DamageBonusUseCase.bonusDamage(stat = it) val result = userCase.defaultDamageBonus(sum = it)
val expected = "+0" val expected = "+0"
assert(result == expected) { assert(result == expected) {
"Expected:'$expected' bonus damage for stat:'$it' but was:'$result'" "Expected:'$expected' bonus damage for stat:'$it' but was:'$result'"
} }
} }
(23 until 30).forEach { (23 until 30).forEach {
val result = DamageBonusUseCase.bonusDamage(stat = it) val result = userCase.defaultDamageBonus(sum = it)
val expected = "+1d4" val expected = "+1d4"
assert(result == expected) { assert(result == expected) {
"Expected:'$expected' bonus damage for stat:'$it' but was:'$result'" "Expected:'$expected' bonus damage for stat:'$it' but was:'$result'"
} }
} }
(30 until 40).forEach { (30 until 40).forEach {
val result = DamageBonusUseCase.bonusDamage(stat = it) val result = userCase.defaultDamageBonus(sum = it)
val expected = "+1d6" val expected = "+1d6"
assert(result == expected) { assert(result == expected) {
"Expected:'$expected' bonus damage for stat:'$it' but was:'$result'" "Expected:'$expected' bonus damage for stat:'$it' but was:'$result'"
} }
} }
(40 until 100).forEach { (40 until 100).forEach {
val result = DamageBonusUseCase.bonusDamage(stat = it) val result = userCase.defaultDamageBonus(sum = it)
val expected = "+2d6" val expected = "+2d6"
assert(result == expected) { assert(result == expected) {
"Expected:'$expected' bonus damage for stat:'$it' but was:'$result'" "Expected:'$expected' bonus damage for stat:'$it' but was:'$result'"

View file

@ -5,7 +5,7 @@ import org.junit.Test
class RollUseCaseTest { class RollUseCaseTest {
companion object { companion object {
private const val ROLL_COUNT = 1000000000 private const val ROLL_COUNT = 100000000 // 100*10^6
} }
@Test @Test
@ -20,16 +20,18 @@ class RollUseCaseTest {
@Test @Test
fun testRoll1D100_() { fun testRoll1D100_() {
val result = build1D100ResultSet() val results = build1D100ResultSet()
val delta = 0.001f val delta = 0.005f
val median = (ROLL_COUNT / 100).let { val median = (ROLL_COUNT / 100).let {
(it * (1f - delta)).toInt()..(it * (1f + delta)).toInt() (it * (1f - delta)).toInt()..(it * (1f + delta)).toInt()
} }
println("Testing if with $ROLL_COUNT rolls we have at least all results in $median.") println("Testing if with $ROLL_COUNT rolls we have at least all results in $median.")
assert(result.all { it in median }) { results.map { it in median }.forEachIndexed { index, result ->
"Maybe a false negative, we expected that all values should be in $median a result.\nroll amount: $ROLL_COUNT - result:$result" assert(result) {
"Maybe a false negative, we expected that all values should be in $median a result.\nroll amount: $ROLL_COUNT - index:$index result:${results[index]}"
}
} }
} }
@ -37,7 +39,7 @@ class RollUseCaseTest {
val useCase = RollUseCase() val useCase = RollUseCase()
val result = MutableList(100) { 0 } val result = MutableList(100) { 0 }
repeat(count) { repeat(count) {
val roll = useCase.rollD100() val roll = useCase.roll(faces = 100)
result[roll - 1] += 1 result[roll - 1] += 1
} }
return result return result

View file

@ -6,7 +6,8 @@ class SkillNormalizerUseCaseText {
@Test @Test
fun testNormalization() { fun testNormalization() {
val useCase = SkillNormalizerUseCase() val useCase = CharacterSheetUseCase()
val samples = listOf( val samples = listOf(
0 to 0, 0 to 0,
1 to 0, 1 to 0,
@ -19,6 +20,96 @@ class SkillNormalizerUseCaseText {
8 to 5, 8 to 5,
9 to 5, 9 to 5,
10 to 10, 10 to 10,
11 to 10,
12 to 10,
13 to 10,
14 to 10,
15 to 15,
16 to 15,
17 to 15,
18 to 15,
19 to 15,
20 to 20,
21 to 20,
22 to 20,
23 to 20,
24 to 20,
25 to 25,
26 to 25,
27 to 25,
28 to 25,
29 to 25,
30 to 30,
31 to 30,
32 to 30,
33 to 30,
34 to 30,
35 to 35,
36 to 35,
37 to 35,
38 to 35,
39 to 35,
40 to 40,
41 to 40,
42 to 40,
43 to 40,
44 to 40,
45 to 45,
46 to 45,
47 to 45,
48 to 45,
49 to 45,
50 to 50,
51 to 50,
52 to 50,
53 to 50,
54 to 50,
55 to 55,
56 to 55,
57 to 55,
58 to 55,
59 to 55,
60 to 60,
61 to 60,
62 to 60,
63 to 60,
64 to 60,
65 to 65,
66 to 65,
67 to 65,
68 to 65,
69 to 65,
70 to 70,
71 to 70,
72 to 70,
73 to 70,
74 to 70,
75 to 75,
76 to 75,
77 to 75,
78 to 75,
79 to 75,
80 to 80,
81 to 80,
82 to 80,
83 to 80,
84 to 80,
85 to 85,
86 to 85,
87 to 85,
88 to 85,
89 to 85,
90 to 90,
91 to 90,
92 to 90,
93 to 90,
94 to 90,
95 to 95,
96 to 95,
97 to 95,
98 to 95,
99 to 95,
100 to 100,
) )
samples.forEach { (value, expected) -> samples.forEach { (value, expected) ->
assert(useCase.normalize(value) == expected) { assert(useCase.normalize(value) == expected) {

View file

@ -7,8 +7,9 @@ class SkillStepUseCaseTest {
@Test @Test
fun testStepForSkill() { fun testStepForSkill() {
val useCase = SkillStepUseCase()
(1..500).forEach { skill -> (1..500).forEach { skill ->
val step = SkillStepUseCase.computeSkillStep( val step = useCase.computeSkillStep(
skill = skill, skill = skill,
) )
val expected = expected[skill] ?: error( val expected = expected[skill] ?: error(

View file

@ -13,16 +13,16 @@ class InstructionParserTest {
fun test( fun test(
instruction: String, instruction: String,
expectedModifier: Instruction.Dice.Modifier.Dice.Modifier?, expectedModifier: Instruction.Dice.Modifier?,
expectedQuantity: Int, expectedQuantity: Int,
expectedFaces: Int, expectedFaces: Int,
) { ) {
val dice = parser.parseInstruction(instruction = instruction) val dice = parser.parse(value = instruction).first()
assert(dice is Instruction.Dice.Dice) { assert(dice is Instruction.Dice) {
"Instruction should be ArithmeticInstruction.Dice but was: ${dice::class.java.simpleName}" "Instruction should be ArithmeticInstruction.Dice but was: ${dice::class.java.simpleName}"
} }
(dice as? Instruction.Dice.Dice)?.let { (dice as? Instruction.Dice)?.let {
assert(dice.modifier == expectedModifier) { assert(dice.modifier == expectedModifier) {
"$instruction modifier should be:\"$expectedModifier\", but was: ${dice.modifier}" "$instruction modifier should be:\"$expectedModifier\", but was: ${dice.modifier}"
} }
@ -66,14 +66,14 @@ class InstructionParserTest {
val parser = ArithmeticParser() val parser = ArithmeticParser()
ArithmeticParser.words.map { instruction -> ArithmeticParser.words.map { instruction ->
val word = parser.parseInstruction(instruction = instruction) val word = parser.parse(value = instruction).first()
assert(word is Instruction.Word.Word) { assert(word is Instruction.Word) {
"Instruction should be ArithmeticInstruction.Word but was: ${word::class.java.simpleName}" "Instruction should be ArithmeticInstruction.Word but was: ${word::class.java.simpleName}"
} }
(word as? Instruction.Word.Word)?.let { (word as? Instruction.Word)?.let {
assert(it.name == instruction) { assert(it.type.name == instruction) {
"Instruction should be $instruction, but was ${it.name}" "Instruction should be $instruction, but was ${it.type.name}"
} }
} }
} }
@ -84,12 +84,12 @@ class InstructionParserTest {
val parser = ArithmeticParser() val parser = ArithmeticParser()
"100".let { instruction -> "100".let { instruction ->
val flat = parser.parseInstruction(instruction = instruction) val flat = parser.parse(value = instruction).first()
assert(flat is Instruction.Flat.Flat) { assert(flat is Instruction.Flat) {
"Instruction should be ArithmeticInstruction.Flat but was: ${flat::class.java.simpleName}" "Instruction should be ArithmeticInstruction.Flat but was: ${flat::class.java.simpleName}"
} }
(flat as? Instruction.Flat.Flat)?.let { (flat as? Instruction.Flat)?.let {
assert("${it.value}" == instruction) { assert("${it.value}" == instruction) {
"Instruction should be $instruction, but was ${it.value}" "Instruction should be $instruction, but was ${it.value}"
} }
@ -97,59 +97,42 @@ class InstructionParserTest {
} }
} }
@Test
fun testFailedInstructionParse() {
val parser = ArithmeticParser()
assertFails(
message = "Instruction parse should failed with UnknownInstruction",
) {
parser.parseInstruction(instruction = "a110")
}
}
@Test @Test
fun testRollParse() { fun testRollParse() {
val parser = ArithmeticParser() val parser = ArithmeticParser()
fun test( fun test(
arithmetics: Instruction, instruction: Instruction,
expectedSign: Int,
expectedInstruction: Instruction, expectedInstruction: Instruction,
) { ) {
assert(arithmetics.sign == expectedSign) { assert(instruction == expectedInstruction) {
"Arithmetic sign should be $expectedSign but was: ${arithmetics.sign}" "Arithmetic instruction should be $expectedInstruction but was: $instruction"
}
assert(arithmetics.instruction == expectedInstruction) {
"Arithmetic instruction should be $expectedInstruction but was: ${arithmetics.instruction}"
} }
} }
val instructions = parser.parse(value = "1+1d6+2-BDC+BDD") val instructions = parser.parse(
value = "1+1d6+2-BDC+BDD",
)
test( test(
arithmetics = instructions[0], instruction = instructions[0],
expectedSign = 1, expectedInstruction = Instruction.Flat(sign = 1, value = 1),
expectedInstruction = Instruction.Flat(value = 1),
) )
test( test(
arithmetics = instructions[1], instruction = instructions[1],
expectedSign = 1, expectedInstruction = Instruction.Dice(sign = 1, modifier = null, quantity = 1, faces = 6),
expectedInstruction = Instruction.Dice(quantity = 1, faces = 6, modifier = null),
) )
test( test(
arithmetics = instructions[2], instruction = instructions[2],
expectedSign = 1, expectedInstruction = Instruction.Flat(sign = 1, value = 2),
expectedInstruction = Instruction.Flat(value = 2),
) )
test( test(
arithmetics = instructions[3], instruction = instructions[3],
expectedSign = -1, expectedInstruction = Instruction.Word(sign = -1, type = Instruction.Word.Type.BDC),
expectedInstruction = Instruction.Word.BDC,
) )
test( test(
arithmetics = instructions[4], instruction = instructions[4],
expectedSign = 1, expectedInstruction = Instruction.Word(sign = 1, type = Instruction.Word.Type.BDD),
expectedInstruction = Instruction.Word.BDD,
) )
} }
} }