Migrate FightSystem modes and countdowns to GameCore

- Move shared arena-mode abstractions into GameCore
- Adapt fight countdowns and APIs to the shared model
- Add SteamWar issue audit workflow and SQL language settings
This commit is contained in:
2026-07-24 12:18:49 +02:00
parent badff1c70f
commit 867ca6f5dc
133 changed files with 2155 additions and 1076 deletions
@@ -0,0 +1,38 @@
package de.steamwar.gamecore
import de.steamwar.gamecore.config.GameRuntimeProperties
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.Test
import java.util.Properties
class ArenaModeTest {
@Test
fun resolvesEveryFightSystemArenaMode() {
assertThat(mode(), equalTo(ArenaMode.NORMAL))
assertThat(mode(GameRuntimeProperties.FIGHT_ID to "42"), equalTo(ArenaMode.EVENT))
assertThat(mode(GameRuntimeProperties.FIGHT_ID to "-1"), equalTo(ArenaMode.TEST))
assertThat(mode(GameRuntimeProperties.REPLAY_ID to "42"), equalTo(ArenaMode.REPLAY))
assertThat(mode(GameRuntimeProperties.PREPARE_SCHEMATIC_ID to "42"), equalTo(ArenaMode.PREPARE))
assertThat(mode(GameRuntimeProperties.CHECK_SCHEMATIC_ID to "42"), equalTo(ArenaMode.CHECK))
}
@Test
fun keepsFightSystemResolutionPrecedence() {
val resolved = mode(
GameRuntimeProperties.CHECK_SCHEMATIC_ID to "1",
GameRuntimeProperties.PREPARE_SCHEMATIC_ID to "2",
GameRuntimeProperties.FIGHT_ID to "3",
GameRuntimeProperties.REPLAY_ID to "4",
)
assertThat(resolved, equalTo(ArenaMode.CHECK))
}
private fun mode(vararg values: Pair<String, String>): ArenaMode {
val properties = Properties().apply {
values.forEach { (name, value) -> setProperty(name, value) }
}
return ArenaMode.from(GameRuntimeProperties.load(properties))
}
}
@@ -0,0 +1,50 @@
package de.steamwar.gamecore.config
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.nullValue
import org.junit.Test
import java.util.Properties
import java.util.UUID
class GameRuntimePropertiesTest {
@Test
fun loadsEveryFightSystemRuntimeProperty() {
val blueLeader = UUID.randomUUID()
val redLeader = UUID.randomUUID()
val properties = Properties().apply {
setProperty(GameRuntimeProperties.CHECK_SCHEMATIC_ID, "11")
setProperty(GameRuntimeProperties.PREPARE_SCHEMATIC_ID, "12")
setProperty(GameRuntimeProperties.REPLAY_ID, "-13")
setProperty(GameRuntimeProperties.CONFIG_FILE, "event.yml")
setProperty(GameRuntimeProperties.FIGHT_ID, "14")
setProperty(GameRuntimeProperties.BLUE_LEADER, blueLeader.toString())
setProperty(GameRuntimeProperties.RED_LEADER, redLeader.toString())
}
val runtime = GameRuntimeProperties.load(properties)
assertThat(runtime.checkSchematicId, equalTo(11))
assertThat(runtime.prepareSchematicId, equalTo(12))
assertThat(runtime.replayId, equalTo(-13))
assertThat(runtime.configFile, equalTo("event.yml"))
assertThat(runtime.fightId, equalTo(14))
assertThat(runtime.blueLeader, equalTo(blueLeader))
assertThat(runtime.redLeader, equalTo(redLeader))
assertThat(runtime.isReplayServer, equalTo(true))
}
@Test
fun usesFightSystemDefaults() {
val runtime = GameRuntimeProperties.load(Properties())
assertThat(runtime.checkSchematicId, equalTo(0))
assertThat(runtime.prepareSchematicId, equalTo(0))
assertThat(runtime.replayId, equalTo(0))
assertThat(runtime.configFile, equalTo("config.yml"))
assertThat(runtime.fightId, equalTo(0))
assertThat(runtime.blueLeader, nullValue())
assertThat(runtime.redLeader, nullValue())
assertThat(runtime.isReplayServer, equalTo(false))
}
}
@@ -0,0 +1,56 @@
package de.steamwar.gamecore.countdown
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.contains
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.nullValue
import org.junit.Test
import java.util.function.IntConsumer
class CountdownMilestonesTest {
@Test
fun preservesFightSystemMilestones() {
assertThat(
CountdownMilestones.at(900),
equalTo(CountdownMilestone(CountdownMessage.MINUTES, 15)),
)
assertThat(
CountdownMilestones.at(60),
equalTo(CountdownMilestone(CountdownMessage.SECONDS, 60)),
)
assertThat(
CountdownMilestones.at(1),
equalTo(CountdownMilestone(CountdownMessage.SECOND, 1)),
)
assertThat(CountdownMilestones.at(0), nullValue())
assertThat(CountdownMilestones.at(59), nullValue())
}
@Test
fun presentationOwnsMessagesSoundsAndLevelUpdates() {
val messages = mutableListOf<CountdownMilestone>()
val pitches = mutableListOf<Float>()
val levels = mutableListOf<Int>()
val presentation = CountdownPresentation(
CountdownMessageSink { message, displayTime ->
messages += CountdownMilestone(message, displayTime)
},
CountdownSoundSink(pitches::add),
IntConsumer(levels::add),
)
presentation.update(120)
presentation.update(10)
presentation.finished()
assertThat(
messages,
contains(
CountdownMilestone(CountdownMessage.MINUTES, 2),
CountdownMilestone(CountdownMessage.SECONDS, 10),
),
)
assertThat(pitches, contains(1.0f, 2.0f))
assertThat(levels, contains(120, 10))
}
}
@@ -0,0 +1,53 @@
package de.steamwar.gamecore.state
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.contains
import org.hamcrest.Matchers.equalTo
import org.junit.Test
class GameStateMachineTest {
private enum class ConsumerState {
WAITING,
RUNNING,
END,
}
@Test
fun transitionsNotifyListenersOnce() {
val state = GameStateMachine(ConsumerState.WAITING)
val transitions = mutableListOf<Pair<ConsumerState, ConsumerState>>()
state.addListener { oldState, newState -> transitions.add(oldState to newState) }
state.transitionTo(ConsumerState.RUNNING)
state.transitionTo(ConsumerState.RUNNING)
assertThat(state.currentState, equalTo(ConsumerState.RUNNING))
assertThat(transitions, contains(ConsumerState.WAITING to ConsumerState.RUNNING))
}
@Test
fun consumerEnumDrivesRegisteredStateDependents() {
val lifecycle = GameStateMachine(ConsumerState.WAITING)
var enables = 0
var disables = 0
val feature = object : IStateDependent<ConsumerState> {
override fun enabled() = setOf(ConsumerState.RUNNING)
override fun enable() {
enables++
}
override fun disable() {
disables++
}
}
lifecycle.registerStateDependent(feature)
lifecycle.transitionTo(ConsumerState.RUNNING)
lifecycle.transitionTo(ConsumerState.END)
assertThat(enables, equalTo(1))
assertThat(disables, equalTo(1))
assertThat(lifecycle.currentState, equalTo(ConsumerState.END))
}
}
@@ -0,0 +1,25 @@
package de.steamwar.gamecore.team
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.Test
class TeamRegistryTest {
private data class Team(
private val id: String,
private val players: Collection<String>,
) : GameTeam<String> {
override fun teamId() = id
override fun members() = players
}
@Test
fun findsMembersAndOppositeTeam() {
val red = Team("red", listOf("Ada"))
val blue = Team("blue", listOf("Linus"))
val registry = TeamRegistry(listOf(red, blue))
assertThat(registry.findTeam("Ada"), equalTo(red))
assertThat(registry.opposite(red), equalTo(blue))
}
}
@@ -0,0 +1,28 @@
package de.steamwar.gamecore.wincondition
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.Test
import java.util.function.ToDoubleFunction
class WinConditionsTest {
@Test
fun selectsUniqueHighestScore() {
val result = WinConditions.winnerByScore(
listOf("red" to 4.0, "blue" to 7.0),
ToDoubleFunction { it.second },
)
assertThat(result, equalTo(GameResult.Winner("blue" to 7.0)))
}
@Test
fun equalScoresAreADraw() {
val result = WinConditions.winnerByScore(
listOf("red" to 7.0, "blue" to 7.0),
ToDoubleFunction { it.second },
)
assertThat(result, equalTo(GameResult.Draw))
}
}