summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sw-bot/src/main/kotlin/org/luxons/sevenwonders/bot/SevenWondersBot.kt62
-rw-r--r--sw-client/src/commonMain/kotlin/org/luxons/sevenwonders/client/SevenWondersClient.kt93
-rw-r--r--sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/Lobby.kt25
-rw-r--r--sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/events/GameEvents.kt (renamed from sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/events/Events.kt)23
-rw-r--r--sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/events/GameListEvents.kt29
-rw-r--r--sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/ControllerUtils.kt19
-rw-r--r--sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/GameBrowserController.kt25
-rw-r--r--sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/GameController.kt33
-rw-r--r--sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/HomeController.kt10
-rw-r--r--sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/LobbyController.kt22
-rw-r--r--sw-server/src/test/kotlin/org/luxons/sevenwonders/server/SevenWondersTest.kt107
-rw-r--r--sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/GameBrowserControllerTest.kt55
-rw-r--r--sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/HomeControllerTest.kt14
-rw-r--r--sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/LobbyControllerTest.kt7
-rw-r--r--sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/ClientEventsAsserts.kt48
-rw-r--r--sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/MockMessageChannel.kt53
-rw-r--r--sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/TestUtils.kt10
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Actions.kt2
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Reducers.kt2
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/RouteBasedSagas.kt40
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/Sagas.kt58
21 files changed, 441 insertions, 296 deletions
diff --git a/sw-bot/src/main/kotlin/org/luxons/sevenwonders/bot/SevenWondersBot.kt b/sw-bot/src/main/kotlin/org/luxons/sevenwonders/bot/SevenWondersBot.kt
index 7530b40e..4442ad32 100644
--- a/sw-bot/src/main/kotlin/org/luxons/sevenwonders/bot/SevenWondersBot.kt
+++ b/sw-bot/src/main/kotlin/org/luxons/sevenwonders/bot/SevenWondersBot.kt
@@ -7,6 +7,7 @@ import org.luxons.sevenwonders.model.*
import org.luxons.sevenwonders.model.api.ConnectedPlayer
import org.luxons.sevenwonders.model.api.actions.BotConfig
import org.luxons.sevenwonders.model.api.actions.Icon
+import org.luxons.sevenwonders.model.api.events.GameEvent
import org.luxons.sevenwonders.model.resources.noTransactions
import org.luxons.sevenwonders.model.wonders.AssignedWonder
import org.slf4j.LoggerFactory
@@ -21,7 +22,7 @@ suspend fun SevenWondersClient.connectBot(
): SevenWondersBot {
logger.info("Connecting new bot '$name' to $serverUrl")
val session = connect(serverUrl)
- val player = session.chooseName(name, Icon("desktop"), isHuman = false)
+ val player = session.chooseNameAndAwait(name, Icon("desktop"), isHuman = false)
return SevenWondersBot(player, config, session)
}
@@ -62,40 +63,51 @@ class SevenWondersBot(
return withContext(Dispatchers.Default) {
otherBots.forEach {
- launch {
- val turn = it.session.watchGameStarted().first()
- it.autoPlayUntilEnd(turn)
- }
+ launch { it.autoPlayUntilEnd() }
}
- val firstTurn = session.startGameAndAwaitFirstTurn()
- autoPlayUntilEnd(firstTurn)
+ val endTurn = async { autoPlayUntilEnd() }
+ session.startGame()
+ endTurn.await()
}
}
- suspend fun joinAndAutoPlay(gameId: Long): PlayerTurnInfo<*> {
- val firstTurn = session.joinGameAndAwaitFirstTurn(gameId)
- return autoPlayUntilEnd(firstTurn)
+ suspend fun joinAndAutoPlay(gameId: Long) = coroutineScope {
+ launch { autoPlayUntilEnd() }
+ session.joinGame(gameId)
}
- private suspend fun autoPlayUntilEnd(currentTurn: PlayerTurnInfo<*>) = coroutineScope {
+ private suspend fun autoPlayUntilEnd(): PlayerTurnInfo<TurnAction.WatchScore> = coroutineScope {
val endGameTurnInfo = async {
- session.watchTurns().filter { it.action is TurnAction.WatchScore }.first()
+ @Suppress("UNCHECKED_CAST")
+ session.watchTurns()
+ .filter { it.action is TurnAction.WatchScore }
+ .first() as PlayerTurnInfo<TurnAction.WatchScore>
}
- session.watchTurns()
- .onStart {
- session.sayReady()
- emit(currentTurn)
- }
- .takeWhile { it.action !is TurnAction.WatchScore }
- .catch { e -> logger.error("BOT $player: error in turnInfo flow", e) }
- .collect { turn ->
- botDelay()
- logger.info("BOT $player: playing turn (action ${turn.action})")
- session.autoPlayTurn(turn)
+ session.watchGameEvents()
+ .catch { e -> logger.error("BOT $player: error in game events flow", e) }
+ .takeWhile { it !is GameEvent.LobbyLeft }
+ .collect { event ->
+ when (event) {
+ is GameEvent.NameChosen -> error("Unexpected name chosen event in bot")
+ is GameEvent.GameStarted -> session.sayReady()
+ is GameEvent.NewTurnStarted -> if (event.turnInfo.action is TurnAction.WatchScore) {
+ logger.info("BOT $player: leaving the game")
+ session.leaveGame()
+ } else {
+ botDelay()
+ logger.info("BOT $player: playing turn (action ${event.turnInfo.action})")
+ session.autoPlayTurn(event.turnInfo)
+ }
+ is GameEvent.LobbyJoined,
+ is GameEvent.LobbyUpdated,
+ is GameEvent.PlayerIsReady,
+ is GameEvent.MovePrepared,
+ GameEvent.MoveUnprepared,
+ is GameEvent.CardPrepared -> Unit // ignore those
+ GameEvent.LobbyLeft -> error("Unexpected lobby left event in bot") // collect should have ended
+ }
}
val lastTurn = endGameTurnInfo.await()
- logger.info("BOT $player: leaving the game")
- session.leaveGameAndAwaitEnd()
session.disconnect()
logger.info("BOT $player: disconnected")
lastTurn
diff --git a/sw-client/src/commonMain/kotlin/org/luxons/sevenwonders/client/SevenWondersClient.kt b/sw-client/src/commonMain/kotlin/org/luxons/sevenwonders/client/SevenWondersClient.kt
index d14b07b7..f11439ef 100644
--- a/sw-client/src/commonMain/kotlin/org/luxons/sevenwonders/client/SevenWondersClient.kt
+++ b/sw-client/src/commonMain/kotlin/org/luxons/sevenwonders/client/SevenWondersClient.kt
@@ -4,8 +4,10 @@ import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.*
-import kotlinx.serialization.builtins.serializer
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
import org.hildan.krossbow.stomp.StompClient
import org.hildan.krossbow.stomp.config.HeartBeat
import org.hildan.krossbow.stomp.config.HeartBeatTolerance
@@ -15,13 +17,11 @@ import org.hildan.krossbow.stomp.conversions.kxserialization.subscribe
import org.hildan.krossbow.stomp.conversions.kxserialization.withJsonConversions
import org.hildan.krossbow.stomp.sendEmptyMsg
import org.luxons.sevenwonders.model.PlayerMove
-import org.luxons.sevenwonders.model.PlayerTurnInfo
import org.luxons.sevenwonders.model.Settings
import org.luxons.sevenwonders.model.api.*
import org.luxons.sevenwonders.model.api.actions.*
import org.luxons.sevenwonders.model.api.errors.ErrorDTO
-import org.luxons.sevenwonders.model.api.events.GameEvent
-import org.luxons.sevenwonders.model.api.events.GameEventWrapper
+import org.luxons.sevenwonders.model.api.events.*
import org.luxons.sevenwonders.model.wonders.AssignedWonder
class SevenWondersClient {
@@ -43,27 +43,20 @@ class SevenWondersSession(private val stompSession: StompSessionWithKxSerializat
suspend fun watchErrors(): Flow<ErrorDTO> = stompSession.subscribe("/user/queue/errors", ErrorDTO.serializer())
- suspend fun chooseName(displayName: String, icon: Icon? = null, isHuman: Boolean = true): ConnectedPlayer {
- return doAndWaitForEvent(
- send = {
- stompSession.convertAndSend(
- destination = "/app/chooseName",
- body = ChooseNameAction(displayName, icon, isHuman),
- serializer = ChooseNameAction.serializer(),
- )
- },
- subscribe = {
- stompSession.subscribe(
- destination = "/user/queue/nameChoice",
- deserializer = ConnectedPlayer.serializer(),
- )
- }
- )
- }
-
suspend fun watchGames(): Flow<GameListEvent> =
stompSession.subscribe("/topic/games", GameListEventWrapper.serializer()).map { it.event }
+ suspend fun watchGameEvents(): Flow<GameEvent> =
+ stompSession.subscribe("/user/queue/game/events", GameEventWrapper.serializer()).map { it.event }
+
+ suspend fun chooseName(displayName: String, icon: Icon? = null, isHuman: Boolean = true) {
+ stompSession.convertAndSend(
+ destination = "/app/chooseName",
+ body = ChooseNameAction(displayName, icon, isHuman),
+ serializer = ChooseNameAction.serializer(),
+ )
+ }
+
suspend fun createGame(gameName: String) {
stompSession.convertAndSend("/app/lobby/create", CreateGameAction(gameName), CreateGameAction.serializer())
}
@@ -72,9 +65,6 @@ class SevenWondersSession(private val stompSession: StompSessionWithKxSerializat
stompSession.convertAndSend("/app/lobby/join", JoinGameAction(gameId), JoinGameAction.serializer())
}
- suspend fun watchLobbyJoined(): Flow<LobbyDTO> =
- stompSession.subscribe("/user/queue/lobby/joined", LobbyDTO.serializer())
-
suspend fun leaveLobby() {
stompSession.sendEmptyMsg("/app/lobby/leave")
}
@@ -83,8 +73,6 @@ class SevenWondersSession(private val stompSession: StompSessionWithKxSerializat
stompSession.sendEmptyMsg("/app/lobby/disband")
}
- suspend fun watchLobbyLeft(): Flow<Long> = stompSession.subscribe("/user/queue/lobby/left", Long.serializer())
-
suspend fun addBot(displayName: String) {
stompSession.convertAndSend("/app/lobby/addBot", AddBotAction(displayName), AddBotAction.serializer())
}
@@ -113,29 +101,10 @@ class SevenWondersSession(private val stompSession: StompSessionWithKxSerializat
)
}
- suspend fun watchLobbyUpdates(): Flow<LobbyDTO> =
- stompSession.subscribe("/user/queue/lobby/updated", LobbyDTO.serializer())
-
- suspend fun watchGameStarted(): Flow<PlayerTurnInfo> =
- stompSession.subscribe("/user/queue/lobby/started", PlayerTurnInfo.serializer())
-
suspend fun startGame() {
stompSession.sendEmptyMsg("/app/lobby/startGame")
}
- @OptIn(ExperimentalCoroutinesApi::class)
- suspend fun watchGameEvents(gameId: Long): Flow<GameEvent> {
- val private = watchPublicGameEvents()
- val public = watchPrivateGameEvents(gameId)
- return merge(private, public)
- }
-
- private suspend fun watchPrivateGameEvents(gameId: Long) =
- stompSession.subscribe("/topic/game/$gameId/events", GameEventWrapper.serializer()).map { it.event }
-
- suspend fun watchPublicGameEvents() =
- stompSession.subscribe("/user/queue/game/events", GameEventWrapper.serializer()).map { it.event }
-
suspend fun sayReady() {
stompSession.sendEmptyMsg("/app/game/sayReady")
}
@@ -157,6 +126,13 @@ class SevenWondersSession(private val stompSession: StompSessionWithKxSerializat
}
}
+suspend fun SevenWondersSession.chooseNameAndAwait(displayName: String, icon: Icon? = null, isHuman: Boolean = true): ConnectedPlayer {
+ return doAndWaitForEvent(
+ send = { chooseName(displayName, icon, isHuman) },
+ subscribe = { watchNameChoice() }
+ )
+}
+
suspend fun SevenWondersSession.createGameAndAwaitLobby(gameName: String): LobbyDTO = doAndWaitForEvent(
send = { createGame(gameName) },
subscribe = { watchLobbyJoined() },
@@ -167,21 +143,6 @@ suspend fun SevenWondersSession.joinGameAndAwaitLobby(gameId: Long): LobbyDTO =
subscribe = { watchLobbyJoined() },
)
-suspend fun SevenWondersSession.startGameAndAwaitFirstTurn(): PlayerTurnInfo = doAndWaitForEvent(
- send = { startGame() },
- subscribe = { watchGameStarted() },
-)
-
-suspend fun SevenWondersSession.joinGameAndAwaitFirstTurn(gameId: Long): PlayerTurnInfo = doAndWaitForEvent(
- send = { joinGame(gameId) },
- subscribe = { watchGameStarted() },
-)
-
-suspend fun SevenWondersSession.leaveGameAndAwaitEnd() = doAndWaitForEvent(
- send = { leaveGame() },
- subscribe = { watchLobbyLeft() },
-)
-
@OptIn(ExperimentalCoroutinesApi::class)
private suspend fun <T> doAndWaitForEvent(send: suspend () -> Unit, subscribe: suspend () -> Flow<T>): T =
coroutineScope {
@@ -192,4 +153,10 @@ private suspend fun <T> doAndWaitForEvent(send: suspend () -> Unit, subscribe: s
}
suspend fun SevenWondersSession.watchTurns() =
- watchPublicGameEvents().filterIsInstance<GameEvent.NewTurnStarted>().map { it.turnInfo }
+ watchGameEvents().filterIsInstance<GameEvent.NewTurnStarted>().map { it.turnInfo }
+
+suspend fun SevenWondersSession.watchLobbyJoined(): Flow<LobbyDTO> =
+ watchGameEvents().filterIsInstance<GameEvent.LobbyJoined>().map { it.lobby }
+
+suspend fun SevenWondersSession.watchNameChoice(): Flow<ConnectedPlayer> =
+ watchGameEvents().filterIsInstance<GameEvent.NameChosen>().map { it.player }
diff --git a/sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/Lobby.kt b/sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/Lobby.kt
index 59515057..2c7e2c5c 100644
--- a/sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/Lobby.kt
+++ b/sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/Lobby.kt
@@ -1,36 +1,11 @@
package org.luxons.sevenwonders.model.api
-import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.luxons.sevenwonders.model.Settings
import org.luxons.sevenwonders.model.wonders.PreGameWonder
const val SEVEN_WONDERS_WS_ENDPOINT = "/seven-wonders-websocket"
-// workaround for https://github.com/Kotlin/kotlinx.serialization/issues/1194
-@Serializable
-data class GameListEventWrapper(
- val event: GameListEvent
-)
-
-fun GameListEvent.wrap(): GameListEventWrapper = GameListEventWrapper(this)
-
-@Serializable
-sealed class GameListEvent {
-
- @SerialName("ReplaceList")
- @Serializable
- data class ReplaceList(val lobbies: List<LobbyDTO>) : GameListEvent()
-
- @SerialName("CreateOrUpdate")
- @Serializable
- data class CreateOrUpdate(val lobby: LobbyDTO) : GameListEvent()
-
- @SerialName("Delete")
- @Serializable
- data class Delete(val lobbyId: Long) : GameListEvent()
-}
-
enum class State {
LOBBY,
PLAYING,
diff --git a/sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/events/Events.kt b/sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/events/GameEvents.kt
index d8c05e91..c978fb96 100644
--- a/sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/events/Events.kt
+++ b/sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/events/GameEvents.kt
@@ -3,6 +3,9 @@ package org.luxons.sevenwonders.model.api.events
import kotlinx.serialization.Serializable
import org.luxons.sevenwonders.model.PlayerMove
import org.luxons.sevenwonders.model.PlayerTurnInfo
+import org.luxons.sevenwonders.model.TurnAction
+import org.luxons.sevenwonders.model.api.ConnectedPlayer
+import org.luxons.sevenwonders.model.api.LobbyDTO
import org.luxons.sevenwonders.model.cards.PreparedCard
// workaround for https://github.com/Kotlin/kotlinx.serialization/issues/1194
@@ -17,12 +20,30 @@ fun GameEvent.wrap() = GameEventWrapper(this)
sealed class GameEvent {
@Serializable
- data class NewTurnStarted(val turnInfo: PlayerTurnInfo) : GameEvent()
+ data class NameChosen(val player: ConnectedPlayer) : GameEvent()
+
+ @Serializable
+ data class LobbyJoined(val lobby: LobbyDTO) : GameEvent()
+
+ @Serializable
+ data class LobbyUpdated(val lobby: LobbyDTO) : GameEvent()
+
+ @Serializable
+ object LobbyLeft : GameEvent()
+
+ @Serializable
+ data class GameStarted(val turnInfo: PlayerTurnInfo<TurnAction.SayReady>) : GameEvent()
+
+ @Serializable
+ data class NewTurnStarted(val turnInfo: PlayerTurnInfo<*>) : GameEvent()
@Serializable
data class MovePrepared(val move: PlayerMove) : GameEvent()
@Serializable
+ object MoveUnprepared : GameEvent()
+
+ @Serializable
data class CardPrepared(val preparedCard: PreparedCard) : GameEvent()
@Serializable
diff --git a/sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/events/GameListEvents.kt b/sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/events/GameListEvents.kt
new file mode 100644
index 00000000..2dd8f551
--- /dev/null
+++ b/sw-common-model/src/commonMain/kotlin/org/luxons/sevenwonders/model/api/events/GameListEvents.kt
@@ -0,0 +1,29 @@
+package org.luxons.sevenwonders.model.api.events
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import org.luxons.sevenwonders.model.api.LobbyDTO
+
+// workaround for https://github.com/Kotlin/kotlinx.serialization/issues/1194
+@Serializable
+data class GameListEventWrapper(
+ val event: GameListEvent
+)
+
+fun GameListEvent.wrap(): GameListEventWrapper = GameListEventWrapper(this)
+
+@Serializable
+sealed class GameListEvent {
+
+ @SerialName("ReplaceList")
+ @Serializable
+ data class ReplaceList(val lobbies: List<LobbyDTO>) : GameListEvent()
+
+ @SerialName("CreateOrUpdate")
+ @Serializable
+ data class CreateOrUpdate(val lobby: LobbyDTO) : GameListEvent()
+
+ @SerialName("Delete")
+ @Serializable
+ data class Delete(val lobbyId: Long) : GameListEvent()
+}
diff --git a/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/ControllerUtils.kt b/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/ControllerUtils.kt
new file mode 100644
index 00000000..41796bb6
--- /dev/null
+++ b/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/ControllerUtils.kt
@@ -0,0 +1,19 @@
+package org.luxons.sevenwonders.server.controllers
+
+import org.luxons.sevenwonders.model.api.events.GameEvent
+import org.luxons.sevenwonders.model.api.events.GameListEvent
+import org.luxons.sevenwonders.model.api.events.wrap
+import org.luxons.sevenwonders.server.lobby.Player
+import org.springframework.messaging.simp.SimpMessageSendingOperations
+
+internal fun SimpMessageSendingOperations.sendGameEvent(player: Player, event: GameEvent) {
+ convertAndSendToUser(player.username, "/queue/game/events", event.wrap())
+}
+
+internal fun SimpMessageSendingOperations.sendGameEvent(players: List<Player>, event: GameEvent) {
+ players.forEach { sendGameEvent(it, event) }
+}
+
+internal fun SimpMessageSendingOperations.sendGameListEvent(event: GameListEvent) {
+ convertAndSend("/topic/games", event.wrap())
+}
diff --git a/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/GameBrowserController.kt b/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/GameBrowserController.kt
index 8e90eb27..d3382c62 100644
--- a/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/GameBrowserController.kt
+++ b/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/GameBrowserController.kt
@@ -1,11 +1,11 @@
package org.luxons.sevenwonders.server.controllers
-import org.luxons.sevenwonders.model.api.GameListEvent
-import org.luxons.sevenwonders.model.api.GameListEventWrapper
-import org.luxons.sevenwonders.model.api.LobbyDTO
import org.luxons.sevenwonders.model.api.actions.CreateGameAction
import org.luxons.sevenwonders.model.api.actions.JoinGameAction
-import org.luxons.sevenwonders.model.api.wrap
+import org.luxons.sevenwonders.model.api.events.GameEvent
+import org.luxons.sevenwonders.model.api.events.GameListEvent
+import org.luxons.sevenwonders.model.api.events.GameListEventWrapper
+import org.luxons.sevenwonders.model.api.events.wrap
import org.luxons.sevenwonders.server.ApiMisuseException
import org.luxons.sevenwonders.server.api.toDTO
import org.luxons.sevenwonders.server.lobby.Lobby
@@ -14,8 +14,7 @@ import org.luxons.sevenwonders.server.repositories.LobbyRepository
import org.luxons.sevenwonders.server.repositories.PlayerRepository
import org.slf4j.LoggerFactory
import org.springframework.messaging.handler.annotation.MessageMapping
-import org.springframework.messaging.simp.SimpMessagingTemplate
-import org.springframework.messaging.simp.annotation.SendToUser
+import org.springframework.messaging.simp.SimpMessageSendingOperations
import org.springframework.messaging.simp.annotation.SubscribeMapping
import org.springframework.stereotype.Controller
import org.springframework.validation.annotation.Validated
@@ -26,10 +25,10 @@ import java.security.Principal
*/
@Controller
class GameBrowserController(
+ private val messenger: SimpMessageSendingOperations,
private val lobbyController: LobbyController,
private val lobbyRepository: LobbyRepository,
private val playerRepository: PlayerRepository,
- private val template: SimpMessagingTemplate,
) {
/**
* Gets the created or updated games. The list of existing games is received on this topic at once upon
@@ -54,8 +53,7 @@ class GameBrowserController(
* @return the newly created [Lobby]
*/
@MessageMapping("/lobby/create")
- @SendToUser("/queue/lobby/joined")
- fun createGame(@Validated action: CreateGameAction, principal: Principal): LobbyDTO {
+ fun createGame(@Validated action: CreateGameAction, principal: Principal) {
checkThatUserIsNotInAGame(principal, "cannot create another game")
val player = playerRepository.get(principal.name)
@@ -65,8 +63,8 @@ class GameBrowserController(
// notify everyone that a new game exists
val lobbyDto = lobby.toDTO()
- template.convertAndSend("/topic/games", GameListEvent.CreateOrUpdate(lobbyDto).wrap())
- return lobbyDto
+ messenger.sendGameListEvent(GameListEvent.CreateOrUpdate(lobbyDto))
+ messenger.sendGameEvent(player, GameEvent.LobbyJoined(lobbyDto))
}
/**
@@ -78,8 +76,7 @@ class GameBrowserController(
* @return the [Lobby] that has just been joined
*/
@MessageMapping("/lobby/join")
- @SendToUser("/queue/lobby/joined")
- fun joinGame(@Validated action: JoinGameAction, principal: Principal): LobbyDTO {
+ fun joinGame(@Validated action: JoinGameAction, principal: Principal) {
checkThatUserIsNotInAGame(principal, "cannot join another game")
val lobby = lobbyRepository.get(action.gameId)
@@ -90,7 +87,7 @@ class GameBrowserController(
logger.info("Player '{}' ({}) joined game {}", player.displayName, player.username, lobby.name)
lobbyController.sendLobbyUpdateToPlayers(lobby)
}
- return lobby.toDTO()
+ messenger.sendGameEvent(player, GameEvent.LobbyJoined(lobby.toDTO()))
}
private fun checkThatUserIsNotInAGame(principal: Principal, impossibleActionDescription: String) {
diff --git a/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/GameController.kt b/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/GameController.kt
index 70fb3220..17687e8b 100644
--- a/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/GameController.kt
+++ b/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/GameController.kt
@@ -2,11 +2,9 @@ package org.luxons.sevenwonders.server.controllers
import io.micrometer.core.instrument.MeterRegistry
import org.luxons.sevenwonders.engine.Game
-import org.luxons.sevenwonders.model.api.GameListEvent
import org.luxons.sevenwonders.model.api.actions.PrepareMoveAction
import org.luxons.sevenwonders.model.api.events.GameEvent
-import org.luxons.sevenwonders.model.api.events.wrap
-import org.luxons.sevenwonders.model.api.wrap
+import org.luxons.sevenwonders.model.api.events.GameListEvent
import org.luxons.sevenwonders.model.cards.PreparedCard
import org.luxons.sevenwonders.model.hideHandsAndWaitForReadiness
import org.luxons.sevenwonders.server.api.toDTO
@@ -16,7 +14,7 @@ import org.luxons.sevenwonders.server.repositories.LobbyRepository
import org.luxons.sevenwonders.server.repositories.PlayerRepository
import org.slf4j.LoggerFactory
import org.springframework.messaging.handler.annotation.MessageMapping
-import org.springframework.messaging.simp.SimpMessagingTemplate
+import org.springframework.messaging.simp.SimpMessageSendingOperations
import org.springframework.stereotype.Controller
import java.security.Principal
@@ -25,7 +23,7 @@ import java.security.Principal
*/
@Controller
class GameController(
- private val template: SimpMessagingTemplate,
+ private val messenger: SimpMessageSendingOperations,
private val playerRepository: PlayerRepository,
private val lobbyRepository: LobbyRepository,
private val meterRegistry: MeterRegistry,
@@ -49,7 +47,7 @@ class GameController(
synchronized(game) {
player.isReady = true
if (lobby.settings.askForReadiness) {
- sendPlayerReady(game.id, player)
+ messenger.sendGameEvent(lobby.getPlayers(), GameEvent.PlayerIsReady(player.username))
}
logger.info("Game {}: player {} is ready for the next turn", game.id, player)
@@ -94,7 +92,7 @@ class GameController(
logger.info("Game {}: player {} preparing move {}", game.id, player, action.move)
val preparedCardBack = game.prepareMove(player.index, action.move)
val preparedCard = PreparedCard(player.username, preparedCardBack)
- sendPreparedCard(game.id, preparedCard)
+ messenger.sendGameEvent(lobby.getPlayers(), GameEvent.CardPrepared(preparedCard))
if (game.allPlayersPreparedTheirMove()) {
logger.info("Game {}: all players have prepared their move, executing turn...", game.id)
@@ -104,7 +102,7 @@ class GameController(
handleEndOfGame(game, player, lobby)
}
} else {
- template.convertAndSendToUser(player.username, "/queue/game/events", GameEvent.MovePrepared(action.move).wrap())
+ messenger.sendGameEvent(player, GameEvent.MovePrepared(action.move))
}
}
}
@@ -113,7 +111,7 @@ class GameController(
meterRegistry.counter("games.finished").increment()
logger.info("Game {}: end of game, displaying score board", game.id)
player.lobby.setEndOfGame()
- template.convertAndSend("/topic/games", GameListEvent.CreateOrUpdate(lobby.toDTO()).wrap())
+ messenger.sendGameListEvent(GameListEvent.CreateOrUpdate(lobby.toDTO()))
}
@MessageMapping("/game/unprepareMove")
@@ -128,24 +126,17 @@ class GameController(
game.unprepareMove(player.index)
val preparedCard = PreparedCard(player.username, null)
logger.info("Game {}: player {} unprepared his move", game.id, player)
- sendPreparedCard(game.id, preparedCard)
+ messenger.sendGameEvent(player.lobby.getPlayers(), GameEvent.CardPrepared(preparedCard))
+ messenger.sendGameEvent(player, GameEvent.MoveUnprepared)
}
}
- private fun sendPlayerReady(gameId: Long, player: Player) {
- template.convertAndSend("/topic/game/$gameId/events", GameEvent.PlayerIsReady(player.username).wrap())
- }
-
- private fun sendPreparedCard(gameId: Long, preparedCard: PreparedCard) {
- template.convertAndSend("/topic/game/$gameId/events", GameEvent.CardPrepared(preparedCard).wrap())
- }
-
private fun sendTurnInfo(players: List<Player>, game: Game, hideHands: Boolean) {
val turns = game.getCurrentTurnInfo()
val turnsToSend = if (hideHands) turns.hideHandsAndWaitForReadiness() else turns
for (turnInfo in turnsToSend) {
val player = players[turnInfo.playerIndex]
- template.convertAndSendToUser(player.username, "/queue/game/events", GameEvent.NewTurnStarted(turnInfo).wrap())
+ messenger.sendGameEvent(player, GameEvent.NewTurnStarted(turnInfo))
}
}
@@ -162,13 +153,13 @@ class GameController(
synchronized(game) {
lobby.removePlayer(player.username)
logger.info("Game {}: player {} left the game", game.id, player)
- template.convertAndSendToUser(player.username, "/queue/lobby/left", lobby.id)
+ messenger.sendGameEvent(player, GameEvent.LobbyLeft)
// This could cause problems if the humans are faster than bots to leave a finished game,
// but this case should be quite rare, so it does not matter much
if (lobby.getPlayers().none { it.isHuman }) {
lobbyRepository.remove(lobby.id)
- template.convertAndSend("/topic/games", GameListEvent.Delete(lobby.id).wrap())
+ messenger.sendGameListEvent(GameListEvent.Delete(lobby.id))
logger.info("Game {}: game deleted", game.id)
}
}
diff --git a/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/HomeController.kt b/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/HomeController.kt
index 230623d8..59258e44 100644
--- a/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/HomeController.kt
+++ b/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/HomeController.kt
@@ -4,10 +4,11 @@ import io.micrometer.core.instrument.MeterRegistry
import org.luxons.sevenwonders.model.api.ConnectedPlayer
import org.luxons.sevenwonders.model.api.PlayerDTO
import org.luxons.sevenwonders.model.api.actions.ChooseNameAction
+import org.luxons.sevenwonders.model.api.events.GameEvent
import org.luxons.sevenwonders.server.repositories.PlayerRepository
import org.slf4j.LoggerFactory
import org.springframework.messaging.handler.annotation.MessageMapping
-import org.springframework.messaging.simp.annotation.SendToUser
+import org.springframework.messaging.simp.SimpMessageSendingOperations
import org.springframework.stereotype.Controller
import org.springframework.validation.annotation.Validated
import java.security.Principal
@@ -17,6 +18,7 @@ import java.security.Principal
*/
@Controller
class HomeController(
+ private val messenger: SimpMessageSendingOperations,
private val playerRepository: PlayerRepository,
private val meterRegistry: MeterRegistry,
) {
@@ -29,14 +31,14 @@ class HomeController(
* @return the created [PlayerDTO] object
*/
@MessageMapping("/chooseName")
- @SendToUser("/queue/nameChoice")
- fun chooseName(@Validated action: ChooseNameAction, principal: Principal): ConnectedPlayer {
+ fun chooseName(@Validated action: ChooseNameAction, principal: Principal) {
val username = principal.name
val player = playerRepository.createOrUpdate(username, action.playerName, action.isHuman, action.icon)
meterRegistry.counter("players.connections").increment()
logger.info("Player '{}' chose the name '{}'", username, player.displayName)
- return ConnectedPlayer(username, player.displayName, player.isHuman, player.icon)
+ val connectedPlayer = ConnectedPlayer(username, player.displayName, player.isHuman, player.icon)
+ messenger.sendGameEvent(player, GameEvent.NameChosen(connectedPlayer))
}
companion object {
diff --git a/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/LobbyController.kt b/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/LobbyController.kt
index 6a696d25..56e94493 100644
--- a/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/LobbyController.kt
+++ b/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/controllers/LobbyController.kt
@@ -7,12 +7,12 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull
import org.luxons.sevenwonders.bot.connectBot
import org.luxons.sevenwonders.client.SevenWondersClient
-import org.luxons.sevenwonders.model.api.GameListEvent
import org.luxons.sevenwonders.model.api.actions.AddBotAction
import org.luxons.sevenwonders.model.api.actions.ReassignWondersAction
import org.luxons.sevenwonders.model.api.actions.ReorderPlayersAction
import org.luxons.sevenwonders.model.api.actions.UpdateSettingsAction
-import org.luxons.sevenwonders.model.api.wrap
+import org.luxons.sevenwonders.model.api.events.GameEvent
+import org.luxons.sevenwonders.model.api.events.GameListEvent
import org.luxons.sevenwonders.model.hideHandsAndWaitForReadiness
import org.luxons.sevenwonders.server.api.toDTO
import org.luxons.sevenwonders.server.lobby.Lobby
@@ -23,7 +23,7 @@ import org.luxons.sevenwonders.server.repositories.PlayerRepository
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.messaging.handler.annotation.MessageMapping
-import org.springframework.messaging.simp.SimpMessagingTemplate
+import org.springframework.messaging.simp.SimpMessageSendingOperations
import org.springframework.stereotype.Controller
import org.springframework.validation.annotation.Validated
import java.security.Principal
@@ -34,9 +34,9 @@ import kotlin.time.milliseconds
*/
@Controller
class LobbyController(
+ private val messenger: SimpMessageSendingOperations,
private val lobbyRepository: LobbyRepository,
private val playerRepository: PlayerRepository,
- private val template: SimpMessagingTemplate,
@Value("\${server.port}") private val serverPort: String,
private val meterRegistry: MeterRegistry,
) {
@@ -56,7 +56,7 @@ class LobbyController(
synchronized(lobby) {
lobby.removePlayer(principal.name)
logger.info("Player {} left the lobby of game '{}'", player, lobby.name)
- template.convertAndSendToUser(player.username, "/queue/lobby/left", lobby.id)
+ messenger.sendGameEvent(player, GameEvent.LobbyLeft)
if (lobby.getPlayers().none { it.isHuman }) {
deleteLobby(lobby)
@@ -79,7 +79,7 @@ class LobbyController(
synchronized(lobby) {
lobby.getPlayers().forEach {
it.leave()
- template.convertAndSendToUser(it.username, "/queue/lobby/left", lobby.id)
+ messenger.sendGameEvent(it, GameEvent.LobbyLeft)
}
logger.info("Player {} disbanded game '{}'", player, lobby.name)
deleteLobby(lobby)
@@ -89,7 +89,7 @@ class LobbyController(
private fun deleteLobby(lobby: Lobby) {
lobbyRepository.remove(lobby.id)
- template.convertAndSend("/topic/games", GameListEvent.Delete(lobby.id).wrap())
+ messenger.sendGameListEvent(GameListEvent.Delete(lobby.id))
logger.info("Game '{}' removed", lobby.name)
}
@@ -147,9 +147,9 @@ class LobbyController(
internal fun sendLobbyUpdateToPlayers(lobby: Lobby) {
val lobbyDto = lobby.toDTO()
lobby.getPlayers().forEach {
- template.convertAndSendToUser(it.username, "/queue/lobby/updated", lobbyDto)
+ messenger.sendGameEvent(it, GameEvent.LobbyUpdated(lobbyDto))
}
- template.convertAndSend("/topic/games", GameListEvent.CreateOrUpdate(lobbyDto).wrap())
+ messenger.sendGameListEvent(GameListEvent.CreateOrUpdate(lobbyDto))
}
@MessageMapping("/lobby/addBot")
@@ -191,9 +191,9 @@ class LobbyController(
currentTurnInfo.forEach {
val player = lobby.getPlayers()[it.playerIndex]
- template.convertAndSendToUser(player.username, "/queue/lobby/started", it)
+ messenger.sendGameEvent(player, GameEvent.GameStarted(it))
}
- template.convertAndSend("/topic/games", GameListEvent.CreateOrUpdate(lobby.toDTO()).wrap())
+ messenger.sendGameListEvent(GameListEvent.CreateOrUpdate(lobby.toDTO()))
}
private fun Lobby.resetPlayersReadyState() {
diff --git a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/SevenWondersTest.kt b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/SevenWondersTest.kt
index 7f05688b..549ef267 100644
--- a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/SevenWondersTest.kt
+++ b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/SevenWondersTest.kt
@@ -1,23 +1,18 @@
package org.luxons.sevenwonders.server
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.produceIn
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withTimeout
-import kotlinx.coroutines.withTimeoutOrNull
+import kotlinx.coroutines.*
import org.junit.runner.RunWith
import org.luxons.sevenwonders.client.*
import org.luxons.sevenwonders.model.TurnAction
-import org.luxons.sevenwonders.model.api.GameListEvent
-import org.luxons.sevenwonders.model.api.LobbyDTO
-import org.luxons.sevenwonders.server.test.runAsyncTest
+import org.luxons.sevenwonders.model.api.events.GameEvent
+import org.luxons.sevenwonders.model.api.events.GameListEvent
+import org.luxons.sevenwonders.server.test.*
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.web.server.LocalServerPort
import org.springframework.test.context.junit4.SpringRunner
import kotlin.test.Test
import kotlin.test.assertEquals
-import kotlin.test.assertNull
import kotlin.test.assertTrue
@RunWith(SpringRunner::class)
@@ -43,13 +38,13 @@ class SevenWondersTest {
fun chooseName_succeedsWithCorrectDisplayName() = runAsyncTest {
val session = connectNewClient()
val playerName = "Test User"
- val player = session.chooseName(playerName)
+ val player = session.chooseNameAndAwait(playerName)
assertEquals(playerName, player.displayName)
session.disconnect()
}
private suspend fun newPlayer(name: String): SevenWondersSession = connectNewClient().apply {
- chooseName(name)
+ chooseNameAndAwait(name)
}
@Test
@@ -59,17 +54,18 @@ class SevenWondersTest {
val session2 = newPlayer("Player2")
val gameName = "Test Game"
- val lobby = ownerSession.createGameWithLegacySettingsAndWaitLobby(gameName)
+ val lobby = ownerSession.createGameAndAwaitLobby(gameName)
session1.joinGameAndAwaitLobby(lobby.id)
session2.joinGameAndAwaitLobby(lobby.id)
val outsiderSession = newPlayer("Outsider")
- val gameStartedEvents = outsiderSession.watchGameStarted()
- ownerSession.startGameAndAwaitFirstTurn()
+ val outsiderAsserter = outsiderSession.eventAsserter(scope = this)
- val nullForTimeout = withTimeoutOrNull(50) { gameStartedEvents.first() }
- assertNull(nullForTimeout, "outsider should not receive the game start event of this game")
+ val ownerAsserter = ownerSession.eventAsserter(scope = this)
+ ownerSession.startGame()
+ ownerAsserter.expectGameEvent<GameEvent.GameStarted>()
+ outsiderAsserter.expectNoGameEvent("outsider should not receive the game start event of this game")
disconnect(ownerSession, session1, session2, outsiderSession)
}
@@ -79,7 +75,7 @@ class SevenWondersTest {
val ownerSession = newPlayer("GameOwner")
val gameName = "Test Game"
- val lobby = ownerSession.createGameWithLegacySettingsAndWaitLobby(gameName)
+ val lobby = ownerSession.createGameAndAwaitLobby(gameName)
assertEquals(gameName, lobby.name)
disconnect(ownerSession)
@@ -88,18 +84,16 @@ class SevenWondersTest {
@Test
fun createGame_seenByConnectedPlayers() = runAsyncTest {
val otherSession = newPlayer("OtherPlayer")
- val games = otherSession.watchGames().produceIn(this)
+ val asserter = otherSession.eventAsserter(scope = this)
- val initialListEvent = withTimeout(500) { games.receive() }
- assertTrue(initialListEvent is GameListEvent.ReplaceList)
+ val initialListEvent = asserter.expectGameListEvent<GameListEvent.ReplaceList>()
assertEquals(0, initialListEvent.lobbies.size)
val ownerSession = newPlayer("GameOwner")
val gameName = "Test Game"
- val createdLobby = ownerSession.createGameWithLegacySettingsAndWaitLobby(gameName)
+ val createdLobby = ownerSession.createGameAndAwaitLobby(gameName)
- val afterGameListEvent = withTimeout(500) { games.receive() }
- assertTrue(afterGameListEvent is GameListEvent.CreateOrUpdate)
+ val afterGameListEvent = asserter.expectGameListEvent<GameListEvent.CreateOrUpdate>()
val receivedLobby = afterGameListEvent.lobby
assertEquals(createdLobby.id, receivedLobby.id)
assertEquals(createdLobby.name, receivedLobby.name)
@@ -107,46 +101,57 @@ class SevenWondersTest {
disconnect(ownerSession, otherSession)
}
+ @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
@Test
fun startGame_3players() = runAsyncTest {
val session1 = newPlayer("Player1")
val session2 = newPlayer("Player2")
- val startEvents1 = session1.watchGameStarted()
- val lobby = session1.createGameWithLegacySettingsAndWaitLobby("Test Game")
+ val asserter1 = session1.eventAsserter(scope = this)
+ val lobby = session1.createGameAndAwaitLobby("Test Game")
+ asserter1.expectGameEvent<GameEvent.LobbyJoined>()
- val startEvents2 = session2.watchGameStarted()
- session2.joinGameAndAwaitLobby(lobby.id)
+ val asserter2 = session2.eventAsserter(scope = this)
+ session2.joinGame(lobby.id)
+ asserter1.expectGameEvent<GameEvent.LobbyUpdated>()
+ asserter2.expectGameEvent<GameEvent.LobbyUpdated>()
+ asserter2.expectGameEvent<GameEvent.LobbyJoined>()
// player 3 connects after game creation (on purpose)
val session3 = newPlayer("Player3")
- val startEvents3 = session3.watchGameStarted()
- session3.joinGameAndAwaitLobby(lobby.id)
+ val asserter3 = session3.eventAsserter(scope = this)
+ session3.joinGame(lobby.id)
+ asserter1.expectGameEvent<GameEvent.LobbyUpdated>()
+ asserter2.expectGameEvent<GameEvent.LobbyUpdated>()
+ asserter3.expectGameEvent<GameEvent.LobbyUpdated>()
+ asserter3.expectGameEvent<GameEvent.LobbyJoined>()
session1.startGame()
-
- listOf(
- session1 to startEvents1,
- session2 to startEvents2,
- session3 to startEvents3,
- ).forEach { (session, startEvents) ->
- launch {
- val initialReadyTurn = startEvents.first()
- assertTrue(initialReadyTurn.action is TurnAction.SayReady)
- val turns = session.watchTurns()
- session.sayReady()
-
- val firstActualTurn = turns.first()
- val action = firstActualTurn.action
- assertTrue(action is TurnAction.PlayFromHand)
- session.disconnect()
- }
- }
+ asserter1.expectGameEvent<GameEvent.GameStarted>()
+ asserter2.expectGameEvent<GameEvent.GameStarted>()
+ asserter3.expectGameEvent<GameEvent.GameStarted>()
+
+ session2.sayReady()
+ asserter2.expectNoGameEvent("nothing should happen while other players are not ready for game start")
+ asserter1.expectNoGameEvent("nothing should happen while other players are not ready for game start")
+ asserter3.expectNoGameEvent("nothing should happen while other players are not ready for game start")
+
+ session1.sayReady()
+ asserter1.expectNoGameEvent("nothing should happen while other players are not ready for game start")
+ session3.sayReady()
+
+ asserter1.expectPlayFromHandTurn()
+ asserter2.expectPlayFromHandTurn()
+ asserter3.expectPlayFromHandTurn()
+
+ session1.disconnect()
+ session2.disconnect()
+ session3.disconnect()
}
}
-private suspend fun SevenWondersSession.createGameWithLegacySettingsAndWaitLobby(gameName: String): LobbyDTO {
- val lobby = createGameAndAwaitLobby(gameName)
- updateSettings(lobby.settings.copy(askForReadiness = true))
- return lobby
+private suspend fun EventAsserter.expectPlayFromHandTurn() {
+ val firstTurn = expectGameEvent<GameEvent.NewTurnStarted>()
+ val action = firstTurn.turnInfo.action
+ assertTrue(action is TurnAction.PlayFromHand)
}
diff --git a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/GameBrowserControllerTest.kt b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/GameBrowserControllerTest.kt
index 0f6e031b..70369a59 100644
--- a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/GameBrowserControllerTest.kt
+++ b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/GameBrowserControllerTest.kt
@@ -3,14 +3,18 @@ package org.luxons.sevenwonders.server.controllers
import io.micrometer.core.instrument.simple.SimpleMeterRegistry
import org.junit.Before
import org.junit.Test
-import org.luxons.sevenwonders.model.api.GameListEvent
import org.luxons.sevenwonders.model.api.actions.CreateGameAction
import org.luxons.sevenwonders.model.api.actions.JoinGameAction
+import org.luxons.sevenwonders.model.api.events.GameEvent
+import org.luxons.sevenwonders.model.api.events.GameListEvent
import org.luxons.sevenwonders.server.controllers.GameBrowserController.UserAlreadyInGameException
import org.luxons.sevenwonders.server.repositories.LobbyRepository
import org.luxons.sevenwonders.server.repositories.PlayerNotFoundException
import org.luxons.sevenwonders.server.repositories.PlayerRepository
-import org.luxons.sevenwonders.server.test.mockSimpMessagingTemplate
+import org.luxons.sevenwonders.server.test.MockMessageChannel
+import org.luxons.sevenwonders.server.test.expectSentGameEventTo
+import org.luxons.sevenwonders.server.test.expectSentGameListEvent
+import org.springframework.messaging.simp.SimpMessagingTemplate
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
@@ -18,18 +22,21 @@ import kotlin.test.assertTrue
class GameBrowserControllerTest {
+ private lateinit var messageChannel: MockMessageChannel
+
private lateinit var playerRepository: PlayerRepository
private lateinit var gameBrowserController: GameBrowserController
@Before
fun setUp() {
+ messageChannel = MockMessageChannel()
+ val messenger = SimpMessagingTemplate(messageChannel)
val meterRegistry = SimpleMeterRegistry()
- playerRepository = PlayerRepository(meterRegistry)
val lobbyRepository = LobbyRepository(meterRegistry)
- val template = mockSimpMessagingTemplate()
- val lobbyController = LobbyController(lobbyRepository, playerRepository, template, "UNUSED", meterRegistry)
- gameBrowserController = GameBrowserController(lobbyController, lobbyRepository, playerRepository, template)
+ playerRepository = PlayerRepository(meterRegistry)
+ val lobbyController = LobbyController(messenger, lobbyRepository, playerRepository, "UNUSED", meterRegistry)
+ gameBrowserController = GameBrowserController(messenger, lobbyController, lobbyRepository, playerRepository)
}
@Test
@@ -46,9 +53,16 @@ class GameBrowserControllerTest {
val action = CreateGameAction("Test Game")
- val createdLobby = gameBrowserController.createGame(action, principal)
+ gameBrowserController.createGame(action, principal)
+
+ val createdEvent = messageChannel.expectSentGameListEvent<GameListEvent.CreateOrUpdate>()
+ val lobbyJoinedEvent = messageChannel.expectSentGameEventTo<GameEvent.LobbyJoined>("testuser")
+ val createdLobby = createdEvent.lobby
assertEquals("Test Game", createdLobby.name)
+ assertEquals(createdLobby, lobbyJoinedEvent.lobby)
+
+ messageChannel.expectNoMoreMessages()
val gameListEvent = gameBrowserController.listGames(principal).event as GameListEvent.ReplaceList
assertFalse(gameListEvent.lobbies.isEmpty())
@@ -91,17 +105,34 @@ class GameBrowserControllerTest {
val ownerPrincipal = TestPrincipal("testowner")
val createGameAction = CreateGameAction("Test Game")
- val createdLobby = gameBrowserController.createGame(createGameAction, ownerPrincipal)
+ gameBrowserController.createGame(createGameAction, ownerPrincipal)
+
+ val createdEvent = messageChannel.expectSentGameListEvent<GameListEvent.CreateOrUpdate>()
+ messageChannel.expectSentGameEventTo<GameEvent.LobbyJoined>("testowner")
+ val createdLobby = createdEvent.lobby
assertEquals(owner.username, createdLobby.players[0].username)
+ messageChannel.expectNoMoreMessages()
+
val joiner = playerRepository.createOrUpdate("testjoiner", "Test User Joiner")
val joinerPrincipal = TestPrincipal("testjoiner")
val joinGameAction = JoinGameAction(createdLobby.id)
- val joinedLobby = gameBrowserController.joinGame(joinGameAction, joinerPrincipal)
+ gameBrowserController.joinGame(joinGameAction, joinerPrincipal)
+
+ // lobby update for existing players
+ messageChannel.expectSentGameEventTo<GameEvent.LobbyUpdated>("testowner")
+ messageChannel.expectSentGameEventTo<GameEvent.LobbyUpdated>("testjoiner")
+ // lobby update for people on game browser page
+ messageChannel.expectSentGameListEvent<GameListEvent.CreateOrUpdate>()
+ // lobby joined for the player who joined
+ val joinedLobbyEvent = messageChannel.expectSentGameEventTo<GameEvent.LobbyJoined>("testjoiner")
+ val joinedLobby = joinedLobbyEvent.lobby
assertEquals(owner.username, joinedLobby.players[0].username)
assertEquals(joiner.username, joinedLobby.players[1].username)
+
+ messageChannel.expectNoMoreMessages()
}
@Test
@@ -110,7 +141,11 @@ class GameBrowserControllerTest {
val ownerPrincipal = TestPrincipal("testowner")
val createGameAction = CreateGameAction("Test Game")
- val createdLobby = gameBrowserController.createGame(createGameAction, ownerPrincipal)
+ gameBrowserController.createGame(createGameAction, ownerPrincipal)
+
+ val createdEvent = messageChannel.expectSentGameListEvent<GameListEvent.CreateOrUpdate>()
+ messageChannel.expectSentGameEventTo<GameEvent.LobbyJoined>("testowner")
+ val createdLobby = createdEvent.lobby
playerRepository.createOrUpdate("testjoiner", "Test User Joiner")
val joinerPrincipal = TestPrincipal("testjoiner")
diff --git a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/HomeControllerTest.kt b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/HomeControllerTest.kt
index c73fba10..4976e2de 100644
--- a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/HomeControllerTest.kt
+++ b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/HomeControllerTest.kt
@@ -4,7 +4,11 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry
import org.junit.Test
import org.luxons.sevenwonders.model.api.actions.ChooseNameAction
import org.luxons.sevenwonders.model.api.actions.Icon
+import org.luxons.sevenwonders.model.api.events.GameEvent
import org.luxons.sevenwonders.server.repositories.PlayerRepository
+import org.luxons.sevenwonders.server.test.MockMessageChannel
+import org.luxons.sevenwonders.server.test.expectSentGameEventTo
+import org.springframework.messaging.simp.SimpMessagingTemplate
import kotlin.test.assertEquals
class HomeControllerTest {
@@ -13,15 +17,21 @@ class HomeControllerTest {
fun chooseName() {
val meterRegistry = SimpleMeterRegistry()
val playerRepository = PlayerRepository(meterRegistry)
- val homeController = HomeController(playerRepository, meterRegistry)
+ val messageChannel = MockMessageChannel()
+ val messenger = SimpMessagingTemplate(messageChannel)
+ val homeController = HomeController(messenger, playerRepository, meterRegistry)
val action = ChooseNameAction("Test User", Icon("person"), isHuman = true)
val principal = TestPrincipal("testuser")
- val player = homeController.chooseName(action, principal)
+ homeController.chooseName(action, principal)
+ val payload = messageChannel.expectSentGameEventTo<GameEvent.NameChosen>("testuser")
+ val player = payload.player
assertEquals("testuser", player.username)
assertEquals("Test User", player.displayName)
assertEquals("person", player.icon?.name)
+
+ messageChannel.expectNoMoreMessages()
}
}
diff --git a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/LobbyControllerTest.kt b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/LobbyControllerTest.kt
index 7d9db01d..38f6ffb3 100644
--- a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/LobbyControllerTest.kt
+++ b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/controllers/LobbyControllerTest.kt
@@ -14,7 +14,8 @@ import org.luxons.sevenwonders.server.lobby.PlayerNotInLobbyException
import org.luxons.sevenwonders.server.repositories.LobbyRepository
import org.luxons.sevenwonders.server.repositories.PlayerNotFoundException
import org.luxons.sevenwonders.server.repositories.PlayerRepository
-import org.luxons.sevenwonders.server.test.mockSimpMessagingTemplate
+import org.luxons.sevenwonders.server.test.MockMessageChannel
+import org.springframework.messaging.simp.SimpMessagingTemplate
import java.util.*
import kotlin.test.*
@@ -29,10 +30,10 @@ class LobbyControllerTest {
@Before
fun setUp() {
val meterRegistry = SimpleMeterRegistry()
- val template = mockSimpMessagingTemplate()
+ val template = SimpMessagingTemplate(MockMessageChannel())
playerRepository = PlayerRepository(meterRegistry)
lobbyRepository = LobbyRepository(meterRegistry)
- lobbyController = LobbyController(lobbyRepository, playerRepository, template, "UNUSED", meterRegistry)
+ lobbyController = LobbyController(template, lobbyRepository, playerRepository, "UNUSED", meterRegistry)
}
@Test
diff --git a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/ClientEventsAsserts.kt b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/ClientEventsAsserts.kt
new file mode 100644
index 00000000..d87c2122
--- /dev/null
+++ b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/ClientEventsAsserts.kt
@@ -0,0 +1,48 @@
+package org.luxons.sevenwonders.server.test
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.flow.produceIn
+import kotlinx.coroutines.withTimeoutOrNull
+import org.luxons.sevenwonders.client.SevenWondersSession
+import org.luxons.sevenwonders.model.api.events.GameEvent
+import org.luxons.sevenwonders.model.api.events.GameListEvent
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.time.Duration
+import kotlin.time.milliseconds
+import kotlin.time.seconds
+
+class EventAsserter(
+ val gameListEvents: ReceiveChannel<GameListEvent>,
+ val gameEvents: ReceiveChannel<GameEvent>,
+)
+
+@OptIn(FlowPreview::class)
+suspend fun SevenWondersSession.eventAsserter(scope: CoroutineScope): EventAsserter {
+ val gameListEvents = watchGames().produceIn(scope)
+ val gameEvents = watchGameEvents().produceIn(scope)
+ return EventAsserter(gameListEvents, gameEvents)
+}
+
+suspend inline fun EventAsserter.expectNoGameEvent(message: String? = null, timeout: Duration = 50.milliseconds) {
+ val event = withTimeoutOrNull(timeout) { gameEvents.receive() }
+ val extraMessage = message?.let { " ($it)" } ?: ""
+ assertNull(event, "Expected no game event$extraMessage, but received $event")
+}
+
+suspend inline fun <reified T : GameEvent> EventAsserter.expectGameEvent(timeout: Duration = 1.seconds): T {
+ val event = withTimeoutOrNull(timeout) { gameEvents.receive() }
+ assertNotNull(event, "Expected event of type ${T::class.simpleName}, received nothing in $timeout")
+ assertTrue(event is T, "Expected event of type ${T::class.simpleName}, received $event")
+ return event
+}
+
+suspend inline fun <reified T : GameListEvent> EventAsserter.expectGameListEvent(timeout: Duration = 1.seconds): T {
+ val event = withTimeoutOrNull(timeout) { gameListEvents.receive() }
+ assertNotNull(event, "Expected event of type ${T::class.simpleName}, received nothing in $timeout")
+ assertTrue(event is T, "Expected event of type ${T::class.simpleName}, received $event")
+ return event
+}
diff --git a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/MockMessageChannel.kt b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/MockMessageChannel.kt
new file mode 100644
index 00000000..e27a2550
--- /dev/null
+++ b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/MockMessageChannel.kt
@@ -0,0 +1,53 @@
+package org.luxons.sevenwonders.server.test
+
+import org.luxons.sevenwonders.model.api.events.GameEvent
+import org.luxons.sevenwonders.model.api.events.GameEventWrapper
+import org.luxons.sevenwonders.model.api.events.GameListEvent
+import org.luxons.sevenwonders.model.api.events.GameListEventWrapper
+import org.springframework.messaging.Message
+import org.springframework.messaging.MessageChannel
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+class MockMessageChannel : MessageChannel {
+ private val _messages: MutableList<Message<*>> = mutableListOf()
+ val messages: List<Message<*>> get() = _messages
+
+ override fun send(message: Message<*>, timeout: Long): Boolean = _messages.add(message)
+
+ fun expectSentMessageTo(expectedDestination: String): Message<*> {
+ val m = _messages.removeFirstOrNull()
+ assertNotNull(m, "Expected sent message, but no messages found")
+ assertEquals(expectedDestination, m.headers["simpDestination"], "Incorrect message destination")
+ return m
+ }
+
+ fun expectNoMoreMessages() {
+ assertTrue(
+ actual = messages.isEmpty(),
+ message = "No more messages should have been sent, but ${messages.size} were found: $messages",
+ )
+ }
+}
+
+inline fun <reified T> MockMessageChannel.expectSentMessageWithPayload(expectedDestination: String): T {
+ val m = expectSentMessageTo(expectedDestination)
+ val payload = m.payload
+ assertTrue(payload is T, "Message payload should be of type ${T::class.simpleName}")
+ return payload
+}
+
+inline fun <reified T : GameListEvent> MockMessageChannel.expectSentGameListEvent(): T {
+ val wrappedEvent = expectSentMessageWithPayload<GameListEventWrapper>("/topic/games")
+ val event = wrappedEvent.event
+ assertTrue(event is T, "Expected game list event of type ${T::class.simpleName}, got $event")
+ return event
+}
+
+inline fun <reified T : GameEvent> MockMessageChannel.expectSentGameEventTo(username: String): T {
+ val wrappedEvent = expectSentMessageWithPayload<GameEventWrapper>("/user/$username/queue/game/events")
+ val event = wrappedEvent.event
+ assertTrue(event is T, "Expected game event of type ${T::class.simpleName}, got $event")
+ return event
+}
diff --git a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/TestUtils.kt b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/TestUtils.kt
index efd40a6d..6e64e9da 100644
--- a/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/TestUtils.kt
+++ b/sw-server/src/test/kotlin/org/luxons/sevenwonders/server/test/TestUtils.kt
@@ -3,18 +3,8 @@ package org.luxons.sevenwonders.server.test
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull
-import org.springframework.messaging.Message
-import org.springframework.messaging.MessageChannel
-import org.springframework.messaging.simp.SimpMessagingTemplate
import kotlin.test.assertNotNull
-fun mockSimpMessagingTemplate(): SimpMessagingTemplate = SimpMessagingTemplate(
- object : MessageChannel {
- override fun send(message: Message<*>): Boolean = true
- override fun send(message: Message<*>, timeout: Long): Boolean = true
- },
-)
-
fun runAsyncTest(timeoutMillis: Long = 10000, block: suspend CoroutineScope.() -> Unit) = runBlocking {
val result = withTimeoutOrNull(timeoutMillis, block)
assertNotNull(result, "Test timed out, exceeded ${timeoutMillis}ms")
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Actions.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Actions.kt
index 6ef52ec6..b0c56a79 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Actions.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Actions.kt
@@ -4,8 +4,8 @@ import org.luxons.sevenwonders.model.PlayerMove
import org.luxons.sevenwonders.model.PlayerTurnInfo
import org.luxons.sevenwonders.model.TurnAction
import org.luxons.sevenwonders.model.api.ConnectedPlayer
-import org.luxons.sevenwonders.model.api.GameListEvent
import org.luxons.sevenwonders.model.api.LobbyDTO
+import org.luxons.sevenwonders.model.api.events.GameListEvent
import org.luxons.sevenwonders.model.cards.PreparedCard
import redux.RAction
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Reducers.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Reducers.kt
index 3fb72904..e79b063e 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Reducers.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Reducers.kt
@@ -2,9 +2,9 @@ package org.luxons.sevenwonders.ui.redux
import org.luxons.sevenwonders.client.GameState
import org.luxons.sevenwonders.model.api.ConnectedPlayer
-import org.luxons.sevenwonders.model.api.GameListEvent
import org.luxons.sevenwonders.model.api.LobbyDTO
import org.luxons.sevenwonders.model.api.PlayerDTO
+import org.luxons.sevenwonders.model.api.events.GameListEvent
import redux.RAction
data class SwState(
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/RouteBasedSagas.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/RouteBasedSagas.kt
index b2fad7e1..a014a318 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/RouteBasedSagas.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/RouteBasedSagas.kt
@@ -1,20 +1,11 @@
package org.luxons.sevenwonders.ui.redux.sagas
-import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.map
import org.luxons.sevenwonders.client.SevenWondersSession
-import org.luxons.sevenwonders.model.api.events.GameEvent
import org.luxons.sevenwonders.ui.redux.*
import org.luxons.sevenwonders.ui.router.Navigate
import org.luxons.sevenwonders.ui.router.Route
-suspend fun SwSagaContext.homeSaga(session: SevenWondersSession) {
- val action = next<RequestChooseName>()
- val player = session.chooseName(action.playerName)
- dispatch(SetCurrentPlayerAction(player))
- dispatch(Navigate(Route.GAME_BROWSER))
-}
-
suspend fun SwSagaContext.gameBrowserSaga(session: SevenWondersSession) {
// browser navigation could have brought us here: we should leave the game/lobby
ensureNoCurrentGameNorLobby(session)
@@ -23,42 +14,29 @@ suspend fun SwSagaContext.gameBrowserSaga(session: SevenWondersSession) {
private suspend fun SwSagaContext.ensureNoCurrentGameNorLobby(session: SevenWondersSession) {
if (reduxState.gameState != null) {
- console.warn("User left a game via browser navigation, cleaning up...")
+ console.warn("User left a game via browser navigation, telling the server...")
session.leaveGame()
} else if (reduxState.currentLobby != null) {
- console.warn("User left the lobby via browser navigation, cleaning up...")
+ console.warn("User left the lobby via browser navigation, telling the server...")
session.leaveLobby()
}
}
suspend fun SwSagaContext.lobbySaga(session: SevenWondersSession) {
- // browser navigation could have brought us here: we should leave the current game in that case
if (reduxState.gameState != null) {
console.warn("User left a game via browser navigation, telling the server...")
session.leaveGame()
- return
- }
- // browser navigation could have brought us here: we should go back to game browser if no lobby
- if (reduxState.currentLobby == null) {
- console.warn("User went to lobby via browser navigation, cleaning up...")
+ } else if (reduxState.currentLobby == null) {
+ console.warn("User went to lobby page via browser navigation, redirecting to game browser...")
dispatch(Navigate(Route.GAME_BROWSER))
- return
}
- session.watchLobbyUpdates().map { UpdateLobbyAction(it) }.dispatchAll()
}
suspend fun SwSagaContext.gameSaga(session: SevenWondersSession) {
- val game = reduxState.gameState ?: error("Game saga run without a current game")
- coroutineScope {
- session.watchGameEvents(game.gameId).map {
- when (it) {
- is GameEvent.NewTurnStarted -> TurnInfoEvent(it.turnInfo)
- is GameEvent.MovePrepared -> PreparedMoveEvent(it.move)
- is GameEvent.CardPrepared -> PreparedCardEvent(it.preparedCard)
- is GameEvent.PlayerIsReady -> PlayerReadyEvent(it.username)
- }
- }.dispatchAllIn(this)
- session.sayReady()
+ if (reduxState.gameState == null) {
+ // TODO properly redirect somewhere
+ error("Game saga run without a current game")
}
- console.log("End of game saga")
+ // notifies the server that the client is ready to receive the first hand
+ session.sayReady()
}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/Sagas.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/Sagas.kt
index 13407247..ba3949cc 100644
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/Sagas.kt
+++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/Sagas.kt
@@ -6,8 +6,8 @@ import kotlinx.coroutines.flow.collect
import org.hildan.krossbow.stomp.ConnectionException
import org.hildan.krossbow.stomp.MissingHeartBeatException
import org.hildan.krossbow.stomp.WebSocketClosedUnexpectedly
-import org.luxons.sevenwonders.client.SevenWondersClient
-import org.luxons.sevenwonders.client.SevenWondersSession
+import org.luxons.sevenwonders.client.*
+import org.luxons.sevenwonders.model.api.events.GameEvent
import org.luxons.sevenwonders.ui.redux.*
import org.luxons.sevenwonders.ui.router.Navigate
import org.luxons.sevenwonders.ui.router.Route
@@ -34,12 +34,12 @@ suspend fun SwSagaContext.rootSaga() = try {
launchApiActionHandlersIn(this, session)
launchApiEventHandlersIn(this, session)
- val player = session.chooseName(action.playerName)
+ val player = session.chooseNameAndAwait(action.playerName)
dispatch(SetCurrentPlayerAction(player))
routerSaga(Route.GAME_BROWSER) {
when (it) {
- Route.HOME -> homeSaga(session)
+ Route.HOME -> Unit
Route.LOBBY -> lobbySaga(session)
Route.GAME_BROWSER -> gameBrowserSaga(session)
Route.GAME -> gameSaga(session)
@@ -78,6 +78,7 @@ private suspend fun serverErrorSaga(session: SevenWondersSession) {
}
private fun SwSagaContext.launchApiActionHandlersIn(scope: CoroutineScope, session: SevenWondersSession) {
+ scope.launchOnEach<RequestChooseName> { session.chooseName(it.playerName) }
scope.launchOnEach<RequestCreateGame> { session.createGame(it.gameName) }
scope.launchOnEach<RequestJoinGame> { session.joinGame(it.gameId) }
@@ -96,26 +97,37 @@ private fun SwSagaContext.launchApiActionHandlersIn(scope: CoroutineScope, sessi
}
private fun SwSagaContext.launchApiEventHandlersIn(scope: CoroutineScope, session: SevenWondersSession) {
-
- scope.launch {
- session.watchLobbyJoined().collect { lobby ->
- dispatch(EnterLobbyAction(lobby))
- dispatch(Navigate(Route.LOBBY))
- }
- }
-
scope.launch {
- session.watchLobbyLeft().collect {
- dispatch(LeaveLobbyAction)
- dispatch(Navigate(Route.GAME_BROWSER))
- }
- }
-
- scope.launch {
- session.watchGameStarted().collect { turnInfo ->
- val currentLobby = reduxState.currentLobby ?: error("Received game started event without being in a lobby")
- dispatch(EnterGameAction(currentLobby, turnInfo))
- dispatch(Navigate(Route.GAME))
+ session.watchGameEvents().collect { event ->
+ when (event) {
+ is GameEvent.NameChosen -> {
+ dispatch(SetCurrentPlayerAction(event.player))
+ dispatch(Navigate(Route.GAME_BROWSER))
+ }
+ is GameEvent.LobbyJoined -> {
+ dispatch(EnterLobbyAction(event.lobby))
+ dispatch(Navigate(Route.LOBBY))
+ }
+ is GameEvent.LobbyUpdated -> {
+ dispatch(UpdateLobbyAction(event.lobby))
+ }
+ GameEvent.LobbyLeft -> {
+ dispatch(LeaveLobbyAction)
+ dispatch(Navigate(Route.GAME_BROWSER))
+ }
+ is GameEvent.GameStarted -> {
+ val currentLobby = reduxState.currentLobby ?: error("Received game started event without being in a lobby")
+ dispatch(EnterGameAction(currentLobby, event.turnInfo))
+ dispatch(Navigate(Route.GAME))
+ }
+ is GameEvent.NewTurnStarted -> dispatch(TurnInfoEvent(event.turnInfo))
+ is GameEvent.MovePrepared -> dispatch(PreparedMoveEvent(event.move))
+ is GameEvent.CardPrepared -> dispatch(PreparedCardEvent(event.preparedCard))
+ is GameEvent.PlayerIsReady -> dispatch(PlayerReadyEvent(event.username))
+ // Currently the move is already unprepared when launching the unprepare request
+ // TODO add a "unpreparing" state and only update redux when the move is successfully unprepared
+ GameEvent.MoveUnprepared -> {}
+ }
}
}
}
bgstack15