diff options
13 files changed, 166 insertions, 78 deletions
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 5dda0292..6cc50f44 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 @@ -3,10 +3,10 @@ package org.luxons.sevenwonders.client import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.serializer import org.hildan.krossbow.stomp.StompClient import org.hildan.krossbow.stomp.config.HeartBeat @@ -19,9 +19,7 @@ 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.ConnectedPlayer -import org.luxons.sevenwonders.model.api.LobbyDTO -import org.luxons.sevenwonders.model.api.SEVEN_WONDERS_WS_ENDPOINT +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.cards.PreparedCard @@ -69,8 +67,8 @@ class SevenWondersSession(private val stompSession: StompSessionWithKxSerializat deserializer = ConnectedPlayer.serializer(), ) - suspend fun watchGames(): Flow<List<LobbyDTO>> = - stompSession.subscribe("/topic/games", ListSerializer(LobbyDTO.serializer())) + suspend fun watchGames(): Flow<GameListEvent> = + stompSession.subscribe("/topic/games", GameListEventWrapper.serializer()).map { it.event } suspend fun createGame(gameName: String) { stompSession.convertAndSend("/app/lobby/create", CreateGameAction(gameName), CreateGameAction.serializer()) @@ -87,6 +85,12 @@ class SevenWondersSession(private val stompSession: StompSessionWithKxSerializat stompSession.sendEmptyMsg("/app/lobby/leave") } + suspend fun disbandLobby() { + 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()) } @@ -164,4 +168,4 @@ suspend fun SevenWondersSession.joinGameAndWaitLobby(gameId: Long): LobbyDTO { val joinedLobbies = watchLobbyJoined() joinGame(gameId) return joinedLobbies.first() -}
\ No newline at end of file +} 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 e1e978f7..62bac3b1 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,10 +1,36 @@ package org.luxons.sevenwonders.model.api +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable 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() +} + +@Serializable enum class State { LOBBY, PLAYING, 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 587afc10..d6573f8f 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,8 +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.server.ApiMisuseException import org.luxons.sevenwonders.server.api.toDTO import org.luxons.sevenwonders.server.lobby.Lobby @@ -36,9 +39,9 @@ class GameBrowserController( * @return the current list of [Lobby]s */ @SubscribeMapping("/games") // prefix /topic not shown - fun listGames(principal: Principal): List<LobbyDTO> { + fun listGames(principal: Principal): GameListEventWrapper { logger.info("Player '{}' subscribed to /topic/games", principal.name) - return lobbyRepository.list().map { it.toDTO() } + return GameListEvent.ReplaceList(lobbyRepository.list().map { it.toDTO() }).wrap() } /** @@ -61,9 +64,7 @@ class GameBrowserController( // notify everyone that a new game exists val lobbyDto = lobby.toDTO() - // we need to pass a non-generic type (array is fine) so that Spring doesn't break when trying to find a - // serializer from Kotlinx Serialization - template.convertAndSend("/topic/games", listOf(lobbyDto).toTypedArray()) + template.convertAndSend("/topic/games", GameListEvent.CreateOrUpdate(lobbyDto).wrap()) return lobbyDto } 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 7757c9d9..6cc404e9 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 @@ -3,10 +3,12 @@ package org.luxons.sevenwonders.server.controllers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.luxons.sevenwonders.bot.SevenWondersBot +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.hideHandsAndWaitForReadiness import org.luxons.sevenwonders.server.api.toDTO import org.luxons.sevenwonders.server.lobby.Lobby @@ -44,12 +46,38 @@ class LobbyController( val player = principal.player val lobby = player.lobby lobby.removePlayer(principal.name) + logger.info("Player {} left game '{}'", player, lobby.name) + template.convertAndSendToUser(player.username, "/queue/lobby/left", lobby.id) + if (lobby.getPlayers().isEmpty()) { - lobbyRepository.remove(lobby.id) + deleteLobby(lobby) + } else { + sendLobbyUpdateToPlayers(lobby) } + } - logger.info("Player {} left game '{}'", player, lobby.name) - sendLobbyUpdateToPlayers(lobby) + /** + * Disbands the current group, making everyone leave the lobby. + * + * @param principal the connected user's information + */ + @MessageMapping("/lobby/disband") + fun disband(principal: Principal) { + val player = principal.player + val lobby = player.ownedLobby + + lobby.getPlayers().forEach { + it.leave() + template.convertAndSendToUser(it.username, "/queue/lobby/left", lobby.id) + } + logger.info("Player {} disbanded game '{}'", player, lobby.name) + deleteLobby(lobby) + } + + private fun deleteLobby(lobby: Lobby) { + lobbyRepository.remove(lobby.id) + template.convertAndSend("/topic/games", GameListEvent.Delete(lobby.id).wrap()) + logger.info("Game '{}' removed", lobby.name) } /** @@ -110,12 +138,11 @@ class LobbyController( } internal fun sendLobbyUpdateToPlayers(lobby: Lobby) { + val lobbyDto = lobby.toDTO() lobby.getPlayers().forEach { - template.convertAndSendToUser(it.username, "/queue/lobby/updated", lobby.toDTO()) + template.convertAndSendToUser(it.username, "/queue/lobby/updated", lobbyDto) } - // we need to pass a non-generic type (array is fine) so that Spring doesn't break when trying to find a - // serializer from Kotlinx Serialization - template.convertAndSend("/topic/games", listOf(lobby.toDTO()).toTypedArray()) + template.convertAndSend("/topic/games", GameListEvent.CreateOrUpdate(lobbyDto).wrap()) } /** diff --git a/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/lobby/Lobby.kt b/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/lobby/Lobby.kt index 6ebe39c1..4176c485 100644 --- a/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/lobby/Lobby.kt +++ b/sw-server/src/main/kotlin/org/luxons/sevenwonders/server/lobby/Lobby.kt @@ -101,7 +101,7 @@ class Lobby( @Synchronized fun removePlayer(username: String): Player { - val playerIndex = find(username) + val playerIndex = players.indexOfFirst { it.username == username } if (playerIndex < 0) { throw UnknownPlayerException(username) } @@ -115,8 +115,6 @@ class Lobby( return player } - private fun find(username: String): Int = players.indexOfFirst { it.username == username } - fun setEndOfGame() { state = State.FINISHED } 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 db50609a..ccc2e548 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 @@ -10,16 +10,14 @@ import org.junit.runner.RunWith import org.luxons.sevenwonders.client.SevenWondersClient import org.luxons.sevenwonders.client.SevenWondersSession import org.luxons.sevenwonders.client.joinGameAndWaitLobby +import org.luxons.sevenwonders.model.api.GameListEvent import org.luxons.sevenwonders.model.api.LobbyDTO import org.luxons.sevenwonders.server.test.runAsyncTest 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.assertNotNull -import kotlin.test.assertNull +import kotlin.test.* @OptIn(FlowPreview::class) @RunWith(SpringRunner::class) @@ -94,18 +92,17 @@ class SevenWondersTest { val otherSession = newPlayer("OtherPlayer") val games = otherSession.watchGames().produceIn(this) - var receivedLobbies = withTimeout(500) { games.receive() } - assertNotNull(receivedLobbies) - assertEquals(0, receivedLobbies.size) + val initialListEvent = withTimeout(500) { games.receive() } + assertTrue(initialListEvent is GameListEvent.ReplaceList) + assertEquals(0, initialListEvent.lobbies.size) val ownerSession = newPlayer("GameOwner") val gameName = "Test Game" val createdLobby = ownerSession.createGameAndWaitLobby(gameName) - receivedLobbies = withTimeout(500) { games.receive() } - assertNotNull(receivedLobbies) - assertEquals(1, receivedLobbies.size) - val receivedLobby = receivedLobbies[0] + val afterGameListEvent = withTimeout(500) { games.receive() } + assertTrue(afterGameListEvent is GameListEvent.CreateOrUpdate) + val receivedLobby = afterGameListEvent.lobby assertEquals(createdLobby.id, receivedLobby.id) assertEquals(createdLobby.name, receivedLobby.name) 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 648c924b..df2e02ee 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 @@ -2,6 +2,7 @@ package org.luxons.sevenwonders.server.controllers 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.server.controllers.GameBrowserController.UserAlreadyInGameException @@ -32,8 +33,8 @@ class GameBrowserControllerTest { @Test fun listGames_initiallyEmpty() { val principal = TestPrincipal("testuser") - val games = gameBrowserController.listGames(principal) - assertTrue(games.isEmpty()) + val gameListEvent = gameBrowserController.listGames(principal).event as GameListEvent.ReplaceList + assertTrue(gameListEvent.lobbies.isEmpty()) } @Test @@ -47,9 +48,9 @@ class GameBrowserControllerTest { assertEquals("Test Game", createdLobby.name) - val games = gameBrowserController.listGames(principal) - assertFalse(games.isEmpty()) - val lobby = games.iterator().next() + val gameListEvent = gameBrowserController.listGames(principal).event as GameListEvent.ReplaceList + assertFalse(gameListEvent.lobbies.isEmpty()) + val lobby = gameListEvent.lobbies.first() assertEquals(lobby, createdLobby) assertEquals(player.username, lobby.players[0].username) } 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 73a24ef0..097e4792 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,12 +14,8 @@ 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 java.util.HashMap -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertSame -import kotlin.test.assertTrue +import java.util.* +import kotlin.test.* class LobbyControllerTest { @@ -69,7 +65,7 @@ class LobbyControllerTest { } @Test - fun leave_succeedsWhenInALobby_asOwner() { + fun leave_ownerAloneInLobby_succeedsAndRemovesLobby() { val player = playerRepository.createOrUpdate("testuser", "Test User") val lobby = lobbyRepository.create("Test Game", player) @@ -82,7 +78,29 @@ class LobbyControllerTest { } @Test - fun leave_succeedsWhenInALobby_asPeasant() { + fun leave_ownerInLobbyWithOthers_succeedsAndTransfersOwnership() { + val player1 = playerRepository.createOrUpdate("testuser", "Test User") + val lobby = lobbyRepository.create("Test Game", player1) + val player2 = addPlayer(lobby, "testuser2") + + val principal = TestPrincipal("testuser") + lobbyController.leave(principal) + + assertTrue(lobbyRepository.list().contains(lobby)) + assertFalse(lobby.getPlayers().contains(player1)) + assertEquals(lobby.owner, player2) + + assertTrue(player2.isGameOwner) + assertTrue(player2.isInLobby) + assertFalse(player2.isInGame) + + assertFalse(player1.isGameOwner) + assertFalse(player1.isInLobby) + assertFalse(player1.isInGame) + } + + @Test + fun leave_succeedsWhenInALobby_asJoiner() { val player = playerRepository.createOrUpdate("testuser", "Test User") val lobby = lobbyRepository.create("Test Game", player) val player2 = addPlayer(lobby, "testuser2") @@ -118,7 +136,7 @@ class LobbyControllerTest { } @Test - fun reorderPlayers_failsForPeasant() { + fun reorderPlayers_failsForJoiner() { val player = playerRepository.createOrUpdate("testuser", "Test User") val lobby = lobbyRepository.create("Test Game", player) @@ -157,7 +175,7 @@ class LobbyControllerTest { } @Test - fun updateSettings_failsForPeasant() { + fun updateSettings_failsForJoiner() { val player = playerRepository.createOrUpdate("testuser", "Test User") val lobby = lobbyRepository.create("Test Game", player) @@ -189,7 +207,7 @@ class LobbyControllerTest { } @Test - fun startGame_failsForPeasant() { + fun startGame_failsForJoiner() { val player = playerRepository.createOrUpdate("testuser", "Test User") val lobby = lobbyRepository.create("Test Game", player) diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt index 3fa85b0a..478f4f4f 100644 --- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt +++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt @@ -1,33 +1,21 @@ package org.luxons.sevenwonders.ui.components.lobby -import com.palantir.blueprintjs.Elevation -import com.palantir.blueprintjs.Intent -import com.palantir.blueprintjs.bpButton -import com.palantir.blueprintjs.bpButtonGroup -import com.palantir.blueprintjs.bpCard -import com.palantir.blueprintjs.bpDivider -import com.palantir.blueprintjs.bpNonIdealState +import com.palantir.blueprintjs.* import kotlinx.css.* -import kotlinx.css.properties.* +import kotlinx.css.properties.transform +import kotlinx.css.properties.translate import org.luxons.sevenwonders.model.api.LobbyDTO import org.luxons.sevenwonders.model.api.PlayerDTO -import org.luxons.sevenwonders.model.wonders.AssignedWonder -import org.luxons.sevenwonders.model.wonders.WonderSide -import org.luxons.sevenwonders.model.wonders.deal -import org.luxons.sevenwonders.model.wonders.withRandomSide -import org.luxons.sevenwonders.model.wonders.withSide +import org.luxons.sevenwonders.model.wonders.* import org.luxons.sevenwonders.ui.components.GlobalStyles -import org.luxons.sevenwonders.ui.redux.RequestAddBot -import org.luxons.sevenwonders.ui.redux.RequestLeaveLobby -import org.luxons.sevenwonders.ui.redux.RequestReassignWonders -import org.luxons.sevenwonders.ui.redux.RequestReorderPlayers -import org.luxons.sevenwonders.ui.redux.RequestStartGame -import org.luxons.sevenwonders.ui.redux.connectStateAndDispatch +import org.luxons.sevenwonders.ui.redux.* import react.RBuilder import react.RComponent import react.RProps import react.RState -import react.dom.* +import react.dom.h2 +import react.dom.h3 +import react.dom.h4 import styled.css import styled.styledDiv import styled.styledH2 @@ -43,6 +31,7 @@ interface LobbyDispatchProps : RProps { var startGame: () -> Unit var addBot: (displayName: String) -> Unit var leaveLobby: () -> Unit + var disbandLobby: () -> Unit var reorderPlayers: (orderedPlayers: List<String>) -> Unit var reassignWonders: (wonders: List<AssignedWonder>) -> Unit } @@ -84,6 +73,8 @@ class LobbyPresenter(props: LobbyProps) : RComponent<LobbyProps, RState>(props) bpButtonGroup { startButton(currentGame, currentPlayer) addBotButton(currentGame) + leaveButton() + disbandButton() } } else { leaveButton() @@ -217,7 +208,7 @@ class LobbyPresenter(props: LobbyProps) : RComponent<LobbyProps, RState>(props) private fun RBuilder.leaveButton() { bpButton( large = true, - intent = Intent.DANGER, + intent = Intent.WARNING, icon = "arrow-left", title = "Leave the lobby and go back to the game browser", onClick = { props.leaveLobby() }, @@ -225,6 +216,18 @@ class LobbyPresenter(props: LobbyProps) : RComponent<LobbyProps, RState>(props) +"LEAVE" } } + + private fun RBuilder.disbandButton() { + bpButton( + large = true, + intent = Intent.DANGER, + icon = "delete", + title = "Disband the group and go back to the game browser", + onClick = { props.disbandLobby() }, + ) { + +"DISBAND" + } + } } fun RBuilder.lobby() = lobby {} @@ -239,6 +242,7 @@ private val lobby = connectStateAndDispatch<LobbyStateProps, LobbyDispatchProps, startGame = { dispatch(RequestStartGame()) } addBot = { name -> dispatch(RequestAddBot(name)) } leaveLobby = { dispatch(RequestLeaveLobby()) } + disbandLobby = { dispatch(RequestDisbandLobby()) } reorderPlayers = { orderedPlayers -> dispatch(RequestReorderPlayers(orderedPlayers)) } reassignWonders = { wonders -> dispatch(RequestReassignWonders(wonders)) } }, 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 cd9443df..c5ffafa0 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 @@ -3,6 +3,7 @@ package org.luxons.sevenwonders.ui.redux import org.luxons.sevenwonders.model.PlayerMove import org.luxons.sevenwonders.model.PlayerTurnInfo 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.cards.PreparedCard import redux.RAction @@ -11,12 +12,14 @@ data class FatalError(val message: String) : RAction data class SetCurrentPlayerAction(val player: ConnectedPlayer) : RAction -data class UpdateGameListAction(val games: List<LobbyDTO>) : RAction +data class UpdateGameListAction(val event: GameListEvent) : RAction data class UpdateLobbyAction(val lobby: LobbyDTO) : RAction data class EnterLobbyAction(val lobby: LobbyDTO) : RAction +data class LeaveLobbyAction(val lobbyId: Long) : RAction + data class EnterGameAction(val lobby: LobbyDTO, val turnInfo: PlayerTurnInfo) : RAction data class TurnInfoEvent(val turnInfo: PlayerTurnInfo) : RAction diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/ApiActions.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/ApiActions.kt index d259da81..87bacf62 100644 --- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/ApiActions.kt +++ b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/ApiActions.kt @@ -23,6 +23,8 @@ class RequestStartGame : RAction class RequestLeaveLobby : RAction +class RequestDisbandLobby : RAction + class RequestLeaveGame : RAction class RequestSayReady : 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 7789cabb..b1d730a0 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 @@ -4,9 +4,9 @@ import org.luxons.sevenwonders.model.MoveType import org.luxons.sevenwonders.model.PlayerMove import org.luxons.sevenwonders.model.PlayerTurnInfo 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.State import org.luxons.sevenwonders.model.cards.CardBack import org.luxons.sevenwonders.model.cards.HandCard import org.luxons.sevenwonders.model.resources.ResourceTransactionOptions @@ -53,7 +53,11 @@ fun rootReducer(state: SwState, action: RAction): SwState = state.copy( ) private fun gamesReducer(games: Map<Long, LobbyDTO>, action: RAction): Map<Long, LobbyDTO> = when (action) { - is UpdateGameListAction -> (games + action.games.associateBy { it.id }).filterValues { it.state != State.FINISHED } + is UpdateGameListAction -> when (action.event) { + is GameListEvent.ReplaceList -> action.event.lobbies.associateBy { it.id } + is GameListEvent.CreateOrUpdate -> games + (action.event.lobby.id to action.event.lobby) + is GameListEvent.Delete -> games - action.event.lobbyId + } else -> games } @@ -64,6 +68,7 @@ private fun currentPlayerReducer(currentPlayer: ConnectedPlayer?, action: RActio private fun currentLobbyReducer(currentLobby: LobbyDTO?, action: RAction): LobbyDTO? = when (action) { is EnterLobbyAction -> action.lobby + is LeaveLobbyAction -> null is UpdateLobbyAction -> action.lobby is PlayerReadyEvent -> currentLobby?.let { l -> l.copy(players = l.players.map { p -> if (p.username == action.username) p.copy(isReady = true) else p }) 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 c2d26e0f..c9f73111 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 @@ -80,6 +80,8 @@ private fun SwSagaContext.launchApiActionHandlersIn(scope: CoroutineScope, sessi scope.launchOnEach<RequestCreateGame> { session.createGame(it.gameName) } scope.launchOnEach<RequestJoinGame> { session.joinGame(it.gameId) } + scope.launchOnEach<RequestLeaveLobby> { session.leaveLobby() } + scope.launchOnEach<RequestDisbandLobby> { session.disbandLobby() } scope.launchOnEach<RequestAddBot> { session.addBot(it.botDisplayName) } scope.launchOnEach<RequestReorderPlayers> { session.reorderPlayers(it.orderedPlayers) } @@ -94,10 +96,11 @@ private fun SwSagaContext.launchApiActionHandlersIn(scope: CoroutineScope, sessi private fun SwSagaContext.launchNavigationHandlers(scope: CoroutineScope, session: SevenWondersSession) { - // FIXME map this actions like others and await server event instead - scope.launchOnEach<RequestLeaveLobby> { - session.leaveLobby() - dispatch(Navigate(Route.GAME_BROWSER)) + scope.launch { + session.watchLobbyLeft().collect { leftLobbyId -> + dispatch(LeaveLobbyAction(leftLobbyId)) + dispatch(Navigate(Route.GAME_BROWSER)) + } } // FIXME map this actions like others and await server event instead @@ -113,4 +116,3 @@ private fun SwSagaContext.launchNavigationHandlers(scope: CoroutineScope, sessio } } } - |