Skip to content

Commit

Permalink
1.實作遊戲結束功能 (#185)
Browse files Browse the repository at this point in the history
* 1.實作遊戲結束功能
2.實作遊戲結束推播

* 根據第一次code review修正

---------

Co-authored-by: Ted <[email protected]>
  • Loading branch information
ted791029 and Ted authored Nov 5, 2023
1 parent e808531 commit b1a2839
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package tw.waterballsa.gaas.application.usecases

import tw.waterballsa.gaas.application.eventbus.EventBus
import tw.waterballsa.gaas.application.repositories.RoomRepository
import tw.waterballsa.gaas.application.repositories.UserRepository
import tw.waterballsa.gaas.domain.Room
import tw.waterballsa.gaas.events.EndedGameEvent
import tw.waterballsa.gaas.events.EndedGameEvent.Data
import tw.waterballsa.gaas.events.enums.EventMessageType.GAME_ENDED
import javax.inject.Named

@Named
class EndGameUseCase(
roomRepository: RoomRepository,
userRepository: UserRepository,
private val eventBus: EventBus,
) : AbstractRoomUseCase(roomRepository, userRepository) {
fun execute(request: Request) {
val room = findRoomById(request.roomId)
room.endGame()
roomRepository.update(room)

val endedGameEvent = room.endGameByGameService()
eventBus.broadcast(endedGameEvent)
}

data class Request(
val roomId: String,
)
}

fun Room.endGameByGameService(): EndedGameEvent {
val type = GAME_ENDED
val data = Data(roomId!!.value)
return EndedGameEvent(type, data)
}
19 changes: 19 additions & 0 deletions domain/src/main/kotlin/tw/waterballsa/gaas/domain/Room.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import tw.waterballsa.gaas.exceptions.PlatformException
import tw.waterballsa.gaas.exceptions.enums.PlatformError.GAME_ALREADY_STARTED
import tw.waterballsa.gaas.exceptions.enums.PlatformError.PLAYER_NOT_FOUND
import tw.waterballsa.gaas.exceptions.enums.PlatformError.PLAYER_NOT_HOST
import tw.waterballsa.gaas.exceptions.enums.PlatformError.GAME_NOT_STARTED

class Room(
var roomId: Id? = null,
Expand Down Expand Up @@ -33,6 +34,8 @@ class Room(

fun isFull(): Boolean = players.size >= maxPlayers

fun isHost(playerId: Player.Id): Boolean = playerId == host.id

fun changePlayerReadiness(playerId: Player.Id, readiness: Boolean) {
val player =
findPlayer(playerId) ?: throw PlatformException(PLAYER_NOT_FOUND, "Player not joined")
Expand All @@ -43,6 +46,14 @@ class Room(
}
}

fun endGame() {
if (status != PLAYING) {
throw PlatformException(GAME_NOT_STARTED, "Game has not started yet")
}
status = WAITING
cancelReadyForNonHostPlayers()
}

fun hasPlayer(playerId: Player.Id): Boolean =
players.any { it.id == playerId }

Expand Down Expand Up @@ -83,6 +94,14 @@ class Room(

private fun findPlayer(playerId: Player.Id): Player? = players.find { it.id == playerId }

private fun cancelReadyForNonHostPlayers() {
players.forEach { player ->
if (!isHost(player.id)) {
player.cancelReady()
}
}
}

@JvmInline
value class Id(val value: String)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package tw.waterballsa.gaas.events

import tw.waterballsa.gaas.events.enums.EventMessageType

data class EndedGameEvent(
val type: EventMessageType,
val data: Data,
) : DomainEvent() {
data class Data(
val roomId: String,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ enum class EventMessageType(
USER_NOT_READY("USER_NOT_READY"),
USER_JOINED("USER_JOINED"),
USER_LEFT("USER_LEFT"),
GAME_ENDED("GAME_ENDED"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ enum class PlatformError(
GAME_CREATE_ERROR("G003"),
GAME_ALREADY_STARTED("G004"),
GAME_START_FAILED("G005"),
GAME_NOT_STARTED("G006"),

USER_NOT_FOUND("U001"),
USER_INPUT_INVALID("U002"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class SecurityConfig(
.antMatchers("/login", "/authenticate").permitAll()
.antMatchers("/health", "/walking-skeleton").permitAll()
.antMatchers("/swagger-ui/**", "/favicon.ico").permitAll()
.regexMatchers("/rooms/.*:endGame").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class RoomController(
private val getRoomUsecase: GetRoomUsecase,
private val startGameUseCase: StartGameUseCase,
private val fastJoinRoomUseCase: FastJoinRoomUseCase,
private val endGameUseCase: EndGameUseCase,
) {
@PostMapping("/rooms")
fun createRoom(
Expand Down Expand Up @@ -150,6 +151,14 @@ class RoomController(
return presenter.viewModel
}

@PostMapping("/rooms/{roomId}:endGame")
@ResponseStatus(NO_CONTENT)
fun endGame(
@PathVariable roomId: String,
) {
endGameUseCase.execute(EndGameUseCase.Request(roomId))
}

class CreateRoomRequest(
private val name: String,
private val gameId: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class SpringRoomRepository(
minPlayers = minPlayers,
name = name,
password = password,
status = status
)

private fun User.Id.toRoomPlayer(): Player =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import tw.waterballsa.gaas.application.repositories.UserRepository
import tw.waterballsa.gaas.domain.GameRegistration
import tw.waterballsa.gaas.domain.Room
import tw.waterballsa.gaas.domain.Room.Player
import tw.waterballsa.gaas.domain.Room.Status
import tw.waterballsa.gaas.domain.Room.Status.PLAYING
import tw.waterballsa.gaas.domain.Room.Status.WAITING
import tw.waterballsa.gaas.domain.User
import tw.waterballsa.gaas.exceptions.PlatformException
import tw.waterballsa.gaas.exceptions.enums.PlatformError.GAME_START_FAILED
Expand All @@ -40,7 +43,6 @@ import tw.waterballsa.gaas.spring.utils.Users.Companion.defaultUser
import java.util.UUID.randomUUID
import kotlin.reflect.KClass


class RoomControllerTest @Autowired constructor(
val userRepository: UserRepository,
val roomRepository: RoomRepository,
Expand Down Expand Up @@ -411,6 +413,28 @@ class RoomControllerTest @Autowired constructor(
.thenShouldFail("Player(${userB.id!!.value}) has joined another room.")
}

@Test
fun givenHostAndPlayerBArePlayingInRoomC_WhenEndGame_ThenRoomCAndPlayersStatusAreChanged() {
val userA = testUser
val host = userA.toRoomPlayer()
val playerB = defaultUser("2").createUser().toRoomPlayer()

givenPlayersArePlayingInRoom(host, playerB)
.wheEndGame()
.thenRoomAndPlayersStatusAreChanged()
}

@Test
fun givenHostAndPlayerBAreWaitingInRoomC_WhenEndGame_ThenShouldFailed() {
val userA = testUser
val host = userA.toRoomPlayer()
val playerB = defaultUser("2").createUser().toRoomPlayer()

givenHostAndPlayersJoinedTheRoom(host, playerB)
.wheEndGame()
.thenShouldFail("Game has not started yet")
}

private fun TestGetRoomsRequest.whenUserAVisitLobby(joinUser: User): ResultActions =
mockMvc.perform(
get("/rooms")
Expand Down Expand Up @@ -493,6 +517,13 @@ class RoomControllerTest @Autowired constructor(
return testRoom
}

private fun givenPlayersArePlayingInRoom(host: Player, vararg players: Player): Room {
players.forEach { it.ready() }
val allPlayers = mutableListOf(host, *players)
testRoom = createRoom(host = host, players = allPlayers, status = PLAYING)
return testRoom
}

private fun Room.whenUserJoinTheRoom(user: User, password: String? = null): ResultActions =
user.joinRoom(roomId!!.value, joinRoomRequest(password))

Expand All @@ -517,6 +548,11 @@ class RoomControllerTest @Autowired constructor(
.withJwt(user.toJwt())
)

private fun Room.wheEndGame(): ResultActions =
mockMvc.perform(
post("/rooms/${testRoom.roomId!!.value}:endGame")
)

private fun ResultActions.thenCreateRoomSuccessfully() {
val roomView = getBody(CreateRoomViewModel::class.java)
val room = roomRepository.findById(roomView.id)!!
Expand Down Expand Up @@ -655,19 +691,25 @@ class RoomControllerTest @Autowired constructor(
)
)

private fun createRoom(host: Player, players: MutableList<Player>, password: String? = null): Room =
roomRepository.createRoom(
private fun createRoom(
host: Player,
players: MutableList<Player>,
password: String? = null,
status: Status? = WAITING,
): Room {
return roomRepository.createRoom(
Room(
game = testGame,
host = host,
host = Player(Player.Id(host.id!!.value), host.nickname, true),
players = players,
maxPlayers = testGame.maxPlayers,
minPlayers = testGame.minPlayers,
name = "My Room",
status = Room.Status.WAITING,
status = status!!,
password = password
)
)
}

private fun createRoomRequest(password: String? = null): TestCreateRoomRequest =
TestCreateRoomRequest(
Expand Down Expand Up @@ -754,4 +796,19 @@ class RoomControllerTest @Autowired constructor(
andExpect(status().isBadRequest)
.andExpect(jsonPath("$.message").value(message))
}

private fun ResultActions.thenRoomAndPlayersStatusAreChanged() {
andExpect(status().isNoContent)
val room = roomRepository.findById(testRoom.roomId!!)!!
room.let {
assertEquals(WAITING, it.status)
assertFalse(it.isEmpty())
assertTrue(it.host.readiness)
it.players.forEach { player ->
if (!it.isHost(player.id)) {
assertFalse(player.readiness)
}
}
}
}
}

0 comments on commit b1a2839

Please sign in to comment.