Add string roll support

This commit is contained in:
Thomas Andres Gomez 2024-11-05 17:17:33 +01:00
parent b71645a7a2
commit 1b9a2f48ca
10 changed files with 241 additions and 66 deletions

View file

@ -1,12 +1,48 @@
package com.pixelized.desktop.lwa.business
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
object RollUseCase {
private val d100 = (1..100)
/**
* (Math.random() * 100 + 1).toInt()
*/
private val diceParser = Regex(
"""(?<sign>[+-])?\h*(?<modifier>[ade])?(?<quantity>\d+)d(?<face>\d+)"""
)
private val flatParser = Regex(
"""(?<sign>[+-])?\h(?<value>\d+)\b"""
)
private val paramParser = Regex(
"""(?<sign>[+-])?\h(?<param>BDGT)\b"""
)
fun rollD100(): Int {
return d100.random()
}
fun roll(characterSheet: CharacterSheet, roll: String): Int {
println(roll)
return diceParser.findAll(roll).sumOf {
val (sign, modifier, quantity, faces) = it.destructured
((if (sign == "-") -1 else 1) * quantity.toInt() * (Math.random() * faces.toDouble() + 1).toInt()).also {
println("roll ${sign}${quantity}d${faces} -> $it")
}
} + flatParser.findAll(roll).sumOf {
val (sign, value) = it.destructured
((if (sign == "-") -1 else 1) * value.toInt()).also {
println("flat: ${sign}${value} -> $it")
}
} + paramParser.findAll(roll).sumOf {
val (sign, param) = it.destructured
(if (sign == "-") -1 else 1) * when (param) {
"BDGT" -> diceParser.findAll(characterSheet.damageBonus).sumOf {
val (sign, modifier, quantity, faces) = it.destructured
((if (sign == "-") -1 else 1) * quantity.toInt() * (Math.random() * faces.toDouble() + 1).toInt()).also {
println("param: ${sign}${param} -> $it")
}
}
else -> 0
}
}
}
}

View file

@ -28,7 +28,7 @@ data class CharacterSheet(
// magic skill
val magics: List<Skill>,
// attack
val attacks: List<Roll>,
val rolls: List<Roll>,
) : Serializable {
data class Skill(

View file

@ -35,7 +35,6 @@ class CharacterSheetFactory {
skills = model.skills.mapNotNull {
if (it.value > 0) {
Node(
type = Node.Type.SKILLS,
label = it.label,
value = it.value,
)
@ -46,7 +45,6 @@ class CharacterSheetFactory {
occupations = model.occupations.mapNotNull {
if (it.value > 0) {
Node(
type = Node.Type.OCCUPATIONS,
label = it.label,
value = it.value,
)
@ -57,7 +55,6 @@ class CharacterSheetFactory {
magics = model.magics.mapNotNull {
if (it.value > 0) {
Node(
type = Node.Type.MAGICS,
label = it.label,
value = it.value,
)
@ -65,6 +62,16 @@ class CharacterSheetFactory {
null
}
},
rolls = model.rolls.mapNotNull {
if (it.roll.isNotEmpty()) {
CharacterSheetPageUio.Roll(
label = it.label,
value = it.roll,
)
} else {
null
}
}
)
}
}

View file

@ -48,6 +48,9 @@ import com.pixelized.desktop.lwa.navigation.destination.navigateToCharacterSheet
import com.pixelized.desktop.lwa.screen.roll.RollPage
import com.pixelized.desktop.lwa.screen.roll.RollViewModel
import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_d20_32dp
import org.jetbrains.compose.resources.painterResource
@Stable
data class CharacterSheetPageUio(
@ -58,6 +61,7 @@ data class CharacterSheetPageUio(
val skills: List<Node>,
val occupations: List<Node>,
val magics: List<Node>,
val rolls: List<Roll>,
) {
@Stable
data class Characteristic(
@ -67,15 +71,15 @@ data class CharacterSheetPageUio(
@Stable
data class Node(
val type: Type,
val label: String,
val value: Int,
) {
@Stable
enum class Type {
SKILLS, OCCUPATIONS, MAGICS,
}
}
)
@Stable
data class Roll(
val label: String,
val value: String,
)
}
@Composable
@ -101,28 +105,32 @@ fun CharacterSheetPage(
)
},
content = {
viewModel.sheet.value?.let {
viewModel.sheet.value?.let { sheet ->
CharacterSheetPageContent(
modifier = Modifier.fillMaxSize(),
characterSheet = it,
characterSheet = sheet,
onBack = {
screen.popBackStack()
},
onEdit = {
screen.navigateToCharacterSheetEdit(id = it.id)
screen.navigateToCharacterSheetEdit(id = sheet.id)
},
onDelete = {
scope.launch {
viewModel.deleteCharacter(id = it.id)
viewModel.deleteCharacter(id = sheet.id)
screen.popBackStack()
}
},
onCharacteristic = { characteristic ->
rollViewModel.prepareRoll(characteristic = characteristic)
rollViewModel.prepareRoll(sheet = sheet, characteristic = characteristic)
overlayViewModel.show()
},
onSkill = { node ->
rollViewModel.prepareRoll(node = node)
rollViewModel.prepareRoll(sheet = sheet, node = node)
overlayViewModel.show()
},
onRoll = { roll ->
rollViewModel.prepareRoll(sheet = sheet, roll = roll)
overlayViewModel.show()
},
)
@ -144,6 +152,7 @@ fun CharacterSheetPageContent(
onDelete: () -> Unit,
onCharacteristic: (characteristic: CharacterSheetPageUio.Characteristic) -> Unit,
onSkill: (skill: CharacterSheetPageUio.Node) -> Unit,
onRoll: (roll: CharacterSheetPageUio.Roll) -> Unit,
) {
Scaffold(
topBar = {
@ -289,6 +298,13 @@ fun CharacterSheetPageContent(
}
}
}
characterSheet.rolls.forEach {
Roll(
modifier = Modifier.fillMaxWidth(),
label = it.label,
onClick = { onRoll(it) },
)
}
}
}
)
@ -346,6 +362,35 @@ private fun Characteristics(
}
}
@Composable
private fun Roll(
modifier: Modifier = Modifier,
paddingValues: PaddingValues = PaddingValues(horizontal = 8.dp),
label: String,
onClick: () -> Unit,
) {
Row(
modifier = Modifier
.clickable(onClick = onClick)
.padding(paddingValues = paddingValues)
.then(other = modifier),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.body1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = label
)
Icon(
painter = painterResource(Res.drawable.ic_d20_32dp),
contentDescription = null,
)
}
}
@Composable
private fun Skill(
modifier: Modifier = Modifier,
@ -355,7 +400,9 @@ private fun Skill(
onClick: () -> Unit,
) {
Row(
modifier = Modifier.clickable(onClick = onClick).padding(paddingValues = paddingValues)
modifier = Modifier
.clickable(onClick = onClick)
.padding(paddingValues = paddingValues)
.then(other = modifier),
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
verticalAlignment = Alignment.CenterVertically,

View file

@ -40,6 +40,7 @@ data class CharacterSheetEditPageUio(
val id: String,
val name: FieldUio,
val skills: List<SkillGroup>,
val rolls: List<FieldUio>,
) {
@Stable
data class SkillGroup(
@ -55,7 +56,7 @@ data class CharacterSheetEditPageUio(
SKILLS,
OCCUPATIONS,
MAGICS,
OTHER,
OTHERS,
}
}
}
@ -77,8 +78,9 @@ fun CharacterSheetEditPage(
) {
CharacterSheetEdit(
form = viewModel.characterSheet.value,
onSkill = viewModel::onSkill,
onBack = { screen.popBackStack() },
onNewSkill = viewModel::onSkill,
onNewCategory = viewModel::onNewRoll,
onSave = {
scope.launch {
viewModel.save()
@ -92,8 +94,9 @@ fun CharacterSheetEditPage(
@Composable
fun CharacterSheetEdit(
form: CharacterSheetEditPageUio,
onSkill: (CharacterSheetEditPageUio.SkillGroup) -> Unit,
onBack: () -> Unit,
onNewSkill: (CharacterSheetEditPageUio.SkillGroup) -> Unit,
onNewCategory: () -> Unit,
onSave: () -> Unit,
) {
Scaffold(
@ -159,7 +162,7 @@ fun CharacterSheetEdit(
)
) {
TextButton(
onClick = { onSkill(it) },
onClick = { onNewSkill(it) },
) {
Row(
verticalAlignment = Alignment.CenterVertically,
@ -180,6 +183,24 @@ fun CharacterSheetEdit(
}
}
form.rolls.forEach {
Form(
modifier = Modifier.fillMaxWidth(),
field = it,
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
TextButton(
onClick = onNewCategory,
) {
Text(text = "Ajouter un lancé")
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,

View file

@ -24,7 +24,6 @@ class CharacterSheetEditViewModel(
fun onSkill(skill: SkillGroup) {
val sheet = _characterSheet.value
_characterSheet.value = sheet.copy(
skills = sheet.skills.map { group ->
if (skill.title == group.title) {
@ -42,7 +41,7 @@ class CharacterSheetEditViewModel(
SkillGroup.Type.SKILLS -> "0"
SkillGroup.Type.OCCUPATIONS -> "40"
SkillGroup.Type.MAGICS -> "0"
SkillGroup.Type.OTHER -> ""
SkillGroup.Type.OTHERS -> ""
}
},
)
@ -56,6 +55,21 @@ class CharacterSheetEditViewModel(
)
}
fun onNewRoll() {
val sheet = _characterSheet.value
_characterSheet.value = sheet.copy(
rolls = sheet.rolls.toMutableList().apply {
add(
FieldUio.create(
label = "",
isLabelEditable = true,
valuePlaceHolder = { "" },
)
)
}
)
}
suspend fun save() {
val sheet = _characterSheet.value
val model = factory.convertToModel(sheet = sheet)

View file

@ -5,6 +5,7 @@ import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEditPageUio.SkillGroup
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.FieldUio
import java.util.UUID
import kotlin.math.ceil
import kotlin.math.max
class CharacterSheetFactory {
@ -48,7 +49,12 @@ class CharacterSheetFactory {
used = false,
)
},
attacks = emptyList(),
rolls = sheet.rolls.map {
CharacterSheet.Roll(
label = it.label.value,
roll = it.unpack(),
)
},
)
}
@ -123,7 +129,7 @@ class CharacterSheetFactory {
),
FieldUio.create(
label = "Points de vie",
valuePlaceHolder = { "${(con() + hei()) / 2}" },
valuePlaceHolder = { "${ceil((con() + hei()) / 2f).toInt()}" },
initialValue = sheet?.maxHp?.toString() ?: ""
),
FieldUio.create(
@ -160,7 +166,6 @@ class CharacterSheetFactory {
fields = sheet?.skills?.map {
FieldUio.create(
label = it.label,
valuePlaceHolder = { "" },
initialValue = it.value.toString(),
)
} ?: listOf(
@ -249,12 +254,18 @@ class CharacterSheetFactory {
fields = sheet?.magics?.map {
FieldUio.create(
label = it.label,
valuePlaceHolder = { "" },
initialValue = it.value.toString()
)
} ?: emptyList(),
),
)
),
rolls = sheet?.rolls?.map {
FieldUio.create(
label = it.label,
isLabelEditable = true,
initialValue = it.roll,
)
} ?: emptyList()
)
}

View file

@ -42,7 +42,7 @@ import org.jetbrains.compose.resources.painterResource
@Stable
data class RollUio(
val label: String,
val value: Int,
val value: Int?,
)
@Stable
@ -80,12 +80,14 @@ fun RollPage(
overflow = TextOverflow.Ellipsis,
text = viewModel.roll.value.label,
)
Text(
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
text = "Réussite en dessous de : ${viewModel.roll.value.value}",
)
viewModel.roll.value.value?.let {
Text(
style = MaterialTheme.typography.caption,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
text = "Réussite en dessous de : ${it}",
)
}
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(

View file

@ -8,6 +8,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.pixelized.desktop.lwa.business.RollUseCase
import com.pixelized.desktop.lwa.business.SkillStepUseCase
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@ -20,42 +22,71 @@ class RollViewModel : ViewModel() {
val roll: State<RollUio> get() = _roll
private var rollJob: Job? = null
private lateinit var rollStep: SkillStepUseCase.SkillStep
private var rollStep: SkillStepUseCase.SkillStep? = null
private lateinit var rollAction: String
private lateinit var sheet: CharacterSheet
val rollRotation = Animatable(0f)
private val _result = mutableStateOf<RollResultUio?>(null)
val result: State<RollResultUio?> get() = _result
fun prepareRoll(node: CharacterSheetPageUio.Node) {
val step = SkillStepUseCase.computeSkillStep(
skill = node.value,
)
prepareRoll(
label = node.label,
step = step,
)
}
fun prepareRoll(characteristic: CharacterSheetPageUio.Characteristic) {
fun prepareRoll(
sheet: CharacterSheetPageUio,
characteristic: CharacterSheetPageUio.Characteristic,
) {
val step = SkillStepUseCase.computeSkillStep(
skill = (characteristic.value.toIntOrNull() ?: 0) * 5
)
prepareRoll(
label = characteristic.label,
step = step,
rollAction = "1d100",
sheet = sheet,
rollStep = step,
)
}
fun prepareRoll(
sheet: CharacterSheetPageUio,
node: CharacterSheetPageUio.Node,
) {
val step = SkillStepUseCase.computeSkillStep(
skill = node.value,
)
prepareRoll(
label = node.label,
rollAction = "1d100",
sheet = sheet,
rollStep = step,
)
}
fun prepareRoll(
sheet: CharacterSheetPageUio,
roll: CharacterSheetPageUio.Roll,
) {
prepareRoll(
label = roll.label,
rollAction = roll.value,
sheet = sheet,
rollStep = null,
)
}
private fun prepareRoll(
label: String,
step: SkillStepUseCase.SkillStep,
rollAction: String,
sheet: CharacterSheetPageUio,
rollStep: SkillStepUseCase.SkillStep?,
) {
runBlocking { rollRotation.snapTo(0f) }
rollStep = step
this.rollStep = rollStep
this.rollAction = rollAction
this.sheet = CharacterSheetRepository.characterSheetFlow(id = sheet.id).value!!
_result.value = null
_roll.value = RollUio(
label = label,
value = step.successRange.last
value = rollStep?.successRange?.last
)
}
@ -77,19 +108,25 @@ class RollViewModel : ViewModel() {
launch {
delay(500)
val d100 = RollUseCase.rollD100()
val roll = if (rollAction == "1d100") {
RollUseCase.rollD100()
} else {
RollUseCase.roll(characterSheet = sheet, roll = rollAction)
}
_result.value = RollResultUio(
label = when (d100) {
// TODO wording
in rollStep.criticalSuccessRange -> "Réussite critique"
in rollStep.specialSuccessRange -> "Réussite spéciale"
in rollStep.successRange -> "Réussite"
in rollStep.failureRange -> "Échec"
in rollStep.criticalFailureRange -> "Échec critique"
else -> ""
},
value = d100,
label = rollStep?.let { rollStep ->
when (roll) {
// TODO wording
in rollStep.criticalSuccessRange -> "Réussite critique"
in rollStep.specialSuccessRange -> "Réussite spéciale"
in rollStep.successRange -> "Réussite"
in rollStep.failureRange -> "Échec"
in rollStep.criticalFailureRange -> "Échec critique"
else -> ""
}
} ?: "",
value = roll,
)
}
}