Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.實作遊戲結束功能 #185

Merged
merged 2 commits into from
Nov 5, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
playersCancelReady()
}

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 playersCancelReady() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancelReadyForNonHostPlayers

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修正

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,15 @@ class RoomControllerTest @Autowired constructor(
return testRoom
}

private fun givenPlayersArePlayingInRoom(host: Player, vararg players: Player): Room {
val combinedPlayers = (listOf(host) + players).toMutableList()
players.forEach { player ->
player.ready()
}
testRoom = createRoom(host = host, players = combinedPlayers, status = PLAYING)
return testRoom
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    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
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修正

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

Expand All @@ -517,6 +550,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 +693,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 +798,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)
}
}
}
}
}