1.0.0
This commit is contained in:
@@ -14,7 +14,7 @@ android {
|
||||
minSdk = 35
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "0.2.0"
|
||||
versionName = "1.0.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.atridad.magiccounter.ui
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -10,12 +8,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.History
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.AlertDialog
|
||||
@@ -36,6 +32,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.atridad.magiccounter.ui.screens.GameScreen
|
||||
import com.atridad.magiccounter.ui.screens.SetupScreen
|
||||
@@ -45,15 +42,12 @@ import com.atridad.magiccounter.ui.settings.AppSettingsViewModel
|
||||
import com.atridad.magiccounter.ui.settings.ThemeMode
|
||||
import com.atridad.magiccounter.ui.settings.MatchRecord
|
||||
import com.atridad.magiccounter.ui.theme.MagicCounterTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
// no-op
|
||||
|
||||
// Top-level navigation destinations using a simple view stack
|
||||
private sealed class Screen {
|
||||
data object Home : Screen()
|
||||
data object Setup : Screen()
|
||||
@@ -61,7 +55,6 @@ private sealed class Screen {
|
||||
data class Game(val matchId: String, val bootState: GameState? = null) : Screen()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MagicCounterApp() {
|
||||
@@ -79,7 +72,7 @@ fun MagicCounterApp() {
|
||||
navigationIcon = {
|
||||
if (screenStack.size > 1) {
|
||||
IconButton(onClick = { screenStack.removeLast() }) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -132,8 +125,8 @@ fun MagicCounterApp() {
|
||||
}
|
||||
)
|
||||
is Screen.Game -> {
|
||||
val id = (currentScreen as Screen.Game).matchId
|
||||
val bootState = (currentScreen as Screen.Game).bootState
|
||||
val id = currentScreen.matchId
|
||||
val bootState = currentScreen.bootState
|
||||
val record = historyState.value.firstOrNull { it.id == id }
|
||||
val stateForGame = record?.state ?: bootState
|
||||
if (stateForGame == null) {
|
||||
@@ -144,7 +137,6 @@ fun MagicCounterApp() {
|
||||
.padding(paddingValues)
|
||||
.fillMaxSize(),
|
||||
state = stateForGame,
|
||||
onEnd = { screenStack.removeLast() },
|
||||
onProgress = { updated ->
|
||||
val current = historyState.value
|
||||
val idx = current.indexOfFirst { it.id == id }
|
||||
@@ -188,7 +180,7 @@ private fun SettingsContent(modifier: Modifier, current: ThemeMode, onSelect: (T
|
||||
Column(modifier = modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text("Theme")
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
ThemeMode.values().forEach { mode ->
|
||||
ThemeMode.entries.forEach { mode ->
|
||||
FilterChip(
|
||||
selected = current == mode,
|
||||
onClick = { onSelect(mode) },
|
||||
@@ -199,8 +191,6 @@ private fun SettingsContent(modifier: Modifier, current: ThemeMode, onSelect: (T
|
||||
}
|
||||
}
|
||||
|
||||
// History sheet removed; history presented on HomeScreen
|
||||
|
||||
@Composable
|
||||
private fun HomeScreen(
|
||||
modifier: Modifier,
|
||||
@@ -218,6 +208,18 @@ private fun HomeScreen(
|
||||
Button(onClick = { showClearConfirm = true }) { Text("Clear history") }
|
||||
}
|
||||
|
||||
if (history.isEmpty()) {
|
||||
androidx.compose.foundation.layout.Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Icon(Icons.Default.History, contentDescription = "Empty history")
|
||||
Text("Nothing to see here")
|
||||
Text("Start a new game to begin.")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
item {
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(top = 4.dp, bottom = 4.dp)) {
|
||||
@@ -226,7 +228,7 @@ private fun HomeScreen(
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Divider()
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
items(history.filter { it.ongoing }, key = { it.id }) { rec ->
|
||||
@@ -249,7 +251,7 @@ private fun HomeScreen(
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Divider()
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
items(history.filter { !it.ongoing }, key = { it.id }) { rec ->
|
||||
@@ -266,6 +268,7 @@ private fun HomeScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm delete dialog
|
||||
val pending = history.firstOrNull { it.id == pendingDeleteId }
|
||||
|
||||
@@ -21,14 +21,15 @@ import androidx.compose.material.icons.filled.Flag
|
||||
import androidx.compose.material.icons.filled.Remove
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.FilledTonalIconButton
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -44,28 +45,32 @@ import com.atridad.magiccounter.ui.state.PlayerState
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
/**
|
||||
* Game screen hosting all player panels.
|
||||
*
|
||||
* - state: immutable state used to bootstrap the in-memory counters
|
||||
* - onProgress: called with a snapshot of the latest state after any user interaction
|
||||
* - onWinner: called once when a winner is determined, with (winnerId, finalState)
|
||||
*/
|
||||
fun GameScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
state: GameState,
|
||||
onEnd: () -> Unit,
|
||||
onProgress: ((GameState) -> Unit)? = null,
|
||||
onWinner: ((Int, GameState) -> Unit)? = null
|
||||
) {
|
||||
// Local editable state per player
|
||||
val lifeTotals = remember { mutableStateMapOf<Int, Int>() }
|
||||
val poisonTotals = remember { mutableStateMapOf<Int, Int>() }
|
||||
val energyTotals = remember { mutableStateMapOf<Int, Int>() }
|
||||
val experienceTotals = remember { mutableStateMapOf<Int, Int>() }
|
||||
|
||||
val commanderDamages = remember { mutableStateMapOf<Int, MutableMap<Int, Int>>() }
|
||||
val eliminated = remember { mutableStateMapOf<Int, Boolean>() }
|
||||
val gameLocked = remember { mutableStateMapOf<String, Boolean>() } // single key used to freeze when winner determined
|
||||
// Tracks whether the game has ended. When true, inputs are frozen and the winner is highlighted.
|
||||
val gameLocked = remember { mutableStateOf(false) }
|
||||
|
||||
// Initialize
|
||||
state.players.forEach { p ->
|
||||
lifeTotals.putIfAbsent(p.id, p.life)
|
||||
poisonTotals.putIfAbsent(p.id, p.poison)
|
||||
energyTotals.putIfAbsent(p.id, p.energy)
|
||||
experienceTotals.putIfAbsent(p.id, p.experience)
|
||||
commanderDamages.putIfAbsent(p.id, p.commanderDamages.toMutableMap())
|
||||
eliminated.putIfAbsent(p.id, p.scooped)
|
||||
}
|
||||
@@ -89,8 +94,6 @@ fun GameScreen(
|
||||
name = p.name,
|
||||
life = lifeTotals[p.id] ?: p.life,
|
||||
poison = poisonTotals[p.id] ?: p.poison,
|
||||
energy = energyTotals[p.id] ?: p.energy,
|
||||
experience = experienceTotals[p.id] ?: p.experience,
|
||||
commanderDamages = commanderDamages[p.id]?.toMap() ?: p.commanderDamages,
|
||||
scooped = eliminated[p.id] == true
|
||||
)
|
||||
@@ -104,12 +107,10 @@ fun GameScreen(
|
||||
state.players.first { eliminated[it.id] != true }.id
|
||||
} else null
|
||||
|
||||
if (currentWinnerId != null) {
|
||||
if (gameLocked["locked"] != true) {
|
||||
gameLocked["locked"] = true
|
||||
if (currentWinnerId != null && !gameLocked.value) {
|
||||
gameLocked.value = true
|
||||
onWinner?.invoke(currentWinnerId, snapshotState())
|
||||
}
|
||||
}
|
||||
val displayPlayers = state.players.sortedBy { eliminated[it.id] == true }
|
||||
|
||||
LazyColumn(
|
||||
@@ -125,39 +126,23 @@ fun GameScreen(
|
||||
opponents = state.players.map { it.id }.filter { it != player.id },
|
||||
life = lifeTotals[player.id] ?: state.startingLife,
|
||||
onLifeChange = { new ->
|
||||
if (gameLocked["locked"] == true) return@PlayerCard
|
||||
if (gameLocked.value) return@PlayerCard
|
||||
lifeTotals[player.id] = new
|
||||
checkElimination(player.id)
|
||||
onProgress?.invoke(snapshotState())
|
||||
},
|
||||
poison = poisonTotals[player.id] ?: 0,
|
||||
onPoisonChange = {
|
||||
if (gameLocked["locked"] != true) {
|
||||
if (!gameLocked.value) {
|
||||
poisonTotals[player.id] = it
|
||||
onProgress?.invoke(snapshotState())
|
||||
}
|
||||
},
|
||||
energy = energyTotals[player.id] ?: 0,
|
||||
onEnergyChange = {
|
||||
if (gameLocked["locked"] != true) {
|
||||
energyTotals[player.id] = it
|
||||
onProgress?.invoke(snapshotState())
|
||||
}
|
||||
},
|
||||
experience = experienceTotals[player.id] ?: 0,
|
||||
onExperienceChange = {
|
||||
if (gameLocked["locked"] != true) {
|
||||
experienceTotals[player.id] = it
|
||||
onProgress?.invoke(snapshotState())
|
||||
}
|
||||
},
|
||||
trackPoison = state.trackPoison,
|
||||
trackEnergy = state.trackEnergy,
|
||||
trackExperience = state.trackExperience,
|
||||
trackCommanderDamage = state.trackCommanderDamage,
|
||||
commanderDamages = perPlayerCommander,
|
||||
onCommanderDamageChange = { fromId, dmg ->
|
||||
if (gameLocked["locked"] != true) {
|
||||
if (!gameLocked.value) {
|
||||
val newMap = (commanderDamages[player.id] ?: mutableMapOf()).toMutableMap()
|
||||
newMap[fromId] = dmg
|
||||
commanderDamages[player.id] = newMap
|
||||
@@ -170,7 +155,7 @@ fun GameScreen(
|
||||
isEliminated = eliminated[player.id] == true,
|
||||
isWinner = currentWinnerId == player.id,
|
||||
onScoop = {
|
||||
if (gameLocked["locked"] != true) {
|
||||
if (!gameLocked.value) {
|
||||
eliminated[player.id] = true
|
||||
onProgress?.invoke(snapshotState())
|
||||
}
|
||||
@@ -190,6 +175,22 @@ private fun seatAccentColor(index: Int, scheme: androidx.compose.material3.Color
|
||||
else -> scheme.tertiaryContainer
|
||||
}
|
||||
|
||||
/**
|
||||
* Player panel with counters and actions.
|
||||
*
|
||||
* - player: immutable player data snapshot.
|
||||
* - opponents: list of opponent player ids for commander damage rows.
|
||||
* - life/poison: current counter values for this player.
|
||||
* - onLifeChange/onPoisonChange: invoked with the new value when a counter is adjusted.
|
||||
* - trackPoison/trackCommanderDamage: toggles for which rows are shown.
|
||||
* - commanderDamages: map of damage received from opponent id -> damage.
|
||||
* - onCommanderDamageChange: callback with (fromId, newDamage).
|
||||
* - rotation: visual rotation of the card in degrees.
|
||||
* - accentColor: seat accent color used for text and default border.
|
||||
* - isEliminated: when true, card is dimmed and inputs disabled.
|
||||
* - isWinner: when true, card is highlighted in green and inputs disabled.
|
||||
* - onScoop: invoked when the player taps Scoop.
|
||||
*/
|
||||
@Composable
|
||||
private fun PlayerCard(
|
||||
player: PlayerState,
|
||||
@@ -198,13 +199,7 @@ private fun PlayerCard(
|
||||
onLifeChange: (Int) -> Unit,
|
||||
poison: Int,
|
||||
onPoisonChange: (Int) -> Unit,
|
||||
energy: Int,
|
||||
onEnergyChange: (Int) -> Unit,
|
||||
experience: Int,
|
||||
onExperienceChange: (Int) -> Unit,
|
||||
trackPoison: Boolean,
|
||||
trackEnergy: Boolean,
|
||||
trackExperience: Boolean,
|
||||
trackCommanderDamage: Boolean,
|
||||
commanderDamages: Map<Int, Int>,
|
||||
onCommanderDamageChange: (fromId: Int, damage: Int) -> Unit,
|
||||
@@ -241,11 +236,9 @@ private fun PlayerCard(
|
||||
BigLifeRow(value = life, onChange = onLifeChange, enabled = controlsEnabled)
|
||||
|
||||
if (trackPoison) ChipRow(label = "Poison", value = poison, onChange = onPoisonChange, enabled = controlsEnabled)
|
||||
if (trackEnergy) ChipRow(label = "Energy", value = energy, onChange = onEnergyChange, enabled = controlsEnabled)
|
||||
if (trackExperience) ChipRow(label = "Experience", value = experience, onChange = onExperienceChange, enabled = controlsEnabled)
|
||||
|
||||
if (trackCommanderDamage) {
|
||||
Divider()
|
||||
HorizontalDivider()
|
||||
Text("Commander damage", style = MaterialTheme.typography.titleSmall)
|
||||
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
opponents.forEach { fromId ->
|
||||
@@ -269,15 +262,19 @@ private fun PlayerCard(
|
||||
}
|
||||
}
|
||||
|
||||
private fun playerOpponents(selfId: Int, ids: Collection<Int>): List<Int> =
|
||||
ids.filter { it != selfId }.sorted()
|
||||
|
||||
/**
|
||||
* Counter row used for poison and commander damage.
|
||||
*
|
||||
* - label: row label text
|
||||
* - value: current integer value
|
||||
* - onChange: callback with the new value after +/- is pressed
|
||||
* - enabled: when false, the +/- buttons are disabled
|
||||
*/
|
||||
@Composable
|
||||
private fun ChipRow(
|
||||
label: String,
|
||||
value: Int,
|
||||
onChange: (Int) -> Unit,
|
||||
emphasized: Boolean = false,
|
||||
enabled: Boolean = true
|
||||
) {
|
||||
Row(
|
||||
@@ -299,6 +296,13 @@ private fun ChipRow(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Life total row with number and +/- buttons.
|
||||
*
|
||||
* - value: current life total
|
||||
* - onChange: callback with new life total
|
||||
* - enabled: when false, buttons are disabled
|
||||
*/
|
||||
@Composable
|
||||
private fun BigLifeRow(value: Int, onChange: (Int) -> Unit, enabled: Boolean = true) {
|
||||
Row(
|
||||
@@ -320,6 +324,13 @@ private fun BigLifeRow(value: Int, onChange: (Int) -> Unit, enabled: Boolean = t
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon button used for counters.
|
||||
* - icon: vector asset to display
|
||||
* - contentDescription: a11y description
|
||||
* - enabled: controls click availability
|
||||
* - onClick: invoked on press
|
||||
*/
|
||||
@Composable
|
||||
private fun CounterIconButton(icon: ImageVector, contentDescription: String, enabled: Boolean = true, onClick: () -> Unit) {
|
||||
FilledTonalIconButton(onClick = onClick, enabled = enabled, modifier = Modifier.size(40.dp)) {
|
||||
|
||||
@@ -38,8 +38,6 @@ fun SetupScreen(
|
||||
var playerCount by remember { mutableIntStateOf(4) }
|
||||
var startingLife by remember { mutableIntStateOf(40) }
|
||||
var trackPoison by remember { mutableStateOf(true) }
|
||||
var trackEnergy by remember { mutableStateOf(false) }
|
||||
var trackExperience by remember { mutableStateOf(false) }
|
||||
var trackCommander by remember { mutableStateOf(true) }
|
||||
var matchName by remember { mutableStateOf("") }
|
||||
|
||||
@@ -78,14 +76,6 @@ fun SetupScreen(
|
||||
Checkbox(checked = trackPoison, onCheckedChange = { trackPoison = it })
|
||||
Text("Track poison")
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = trackEnergy, onCheckedChange = { trackEnergy = it })
|
||||
Text("Track energy")
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = trackExperience, onCheckedChange = { trackExperience = it })
|
||||
Text("Track experience")
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -114,8 +104,6 @@ fun SetupScreen(
|
||||
name = name.ifBlank { defaultPlayerName(index) },
|
||||
life = startingLife,
|
||||
poison = 0,
|
||||
energy = 0,
|
||||
experience = 0,
|
||||
commanderDamages = emptyMap()
|
||||
)
|
||||
}
|
||||
@@ -125,8 +113,6 @@ fun SetupScreen(
|
||||
players = players,
|
||||
startingLife = startingLife,
|
||||
trackPoison = trackPoison,
|
||||
trackEnergy = trackEnergy,
|
||||
trackExperience = trackExperience,
|
||||
trackCommanderDamage = trackCommander
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package com.atridad.magiccounter.ui.settings
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -29,6 +27,8 @@ enum class ThemeMode { System, Light, Dark }
|
||||
private val Context.dataStore by preferencesDataStore(name = "app_settings")
|
||||
|
||||
object AppSettingsRepository {
|
||||
// Use a tolerant JSON instance to handle backward/forward compatible schema changes
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
private val THEME_MODE = intPreferencesKey("theme_mode")
|
||||
private val MATCH_HISTORY = stringPreferencesKey("match_history_json")
|
||||
|
||||
@@ -51,18 +51,18 @@ object AppSettingsRepository {
|
||||
}
|
||||
}
|
||||
|
||||
// Very small JSON blob to persist match history
|
||||
// JSON blob to persist match history
|
||||
fun readMatchHistory(context: Context): Flow<List<MatchRecord>> =
|
||||
context.dataStore.data.map { prefs ->
|
||||
prefs[MATCH_HISTORY]?.let {
|
||||
runCatching { Json.decodeFromString<List<MatchRecord>>(it) }.getOrDefault(emptyList())
|
||||
runCatching { json.decodeFromString<List<MatchRecord>>(it) }.getOrDefault(emptyList())
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
suspend fun writeMatchHistory(context: Context, history: List<MatchRecord>) {
|
||||
val json = Json.encodeToString(history)
|
||||
val jsonStr = json.encodeToString(history)
|
||||
context.dataStore.edit { prefs ->
|
||||
prefs[MATCH_HISTORY] = json
|
||||
prefs[MATCH_HISTORY] = jsonStr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.atridad.magiccounter.ui.settings
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
@@ -2,15 +2,6 @@ package com.atridad.magiccounter.ui.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.builtins.MapSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
|
||||
@Immutable
|
||||
@Serializable
|
||||
data class CommanderDamage(
|
||||
val fromPlayerId: Int,
|
||||
val damage: Int
|
||||
)
|
||||
|
||||
@Immutable
|
||||
@Serializable
|
||||
@@ -19,8 +10,6 @@ data class PlayerState(
|
||||
val name: String,
|
||||
val life: Int,
|
||||
val poison: Int,
|
||||
val energy: Int,
|
||||
val experience: Int,
|
||||
val commanderDamages: Map<Int, Int>,
|
||||
val scooped: Boolean = false
|
||||
)
|
||||
@@ -31,8 +20,6 @@ data class GameState(
|
||||
val players: List<PlayerState>,
|
||||
val startingLife: Int,
|
||||
val trackPoison: Boolean,
|
||||
val trackEnergy: Boolean,
|
||||
val trackExperience: Boolean,
|
||||
val trackCommanderDamage: Boolean
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
|
||||
Reference in New Issue
Block a user