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

feat(snackgame): 스낵게임 Biz를 추가한다 #177

Merged
merged 13 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 4 additions & 3 deletions src/main/java/com/snackgame/server/game/metadata/Metadata.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ enum class Metadata(
val localizedName: String
) {

APPLE_GAME(1L, "사과게임"),
SNACK_GAME(2L, "스낵게임"),
SNACK_GAME_INFINITE(3L, "스낵게임 무한모드")
APPLE_GAME(1, "사과게임"),
SNACK_GAME(2, "스낵게임"),
SNACK_GAME_INFINITE(3, "스낵게임 무한모드"),
SNACK_GAME_BIZ(4, "스낵게임 Biz"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ abstract class Session(
val currentState: SessionStateType
get() = sessionState.current

abstract fun getMetadata(): Metadata
abstract val metadata: Metadata

Copy link
Collaborator

Choose a reason for hiding this comment

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

변경을 하게 된 이유가 궁금합니다! 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

게임의 입장에서 Metadata는 함수(행위)보다는 속성(property)의 성질이 더 강한데요.

그래서 처음부터 property로 표현하고 싶었는데, 당시엔 문법이 익숙하지 않아 함수로 표현하게 되었었네요 ㅎㅎ;

그러다가 오늘 이걸 발견해서 마침내 적절한 형태로 변경하게 된 것이었던 것이었다...입니다

Copy link
Collaborator

Choose a reason for hiding this comment

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

답변 감사합니다 ㅎㅎ
저도 이번기회에 덕분에 코틀린 문법 하나 배워갑니다!

fun pause() = sessionState.pause()
fun resume() = sessionState.resume()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ data class SessionEndEvent(
companion object {
fun of(session: Session): SessionEndEvent {
return SessionEndEvent(
session.getMetadata(),
session.metadata,
session.ownerId,
session.sessionId,
session.score
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ abstract class SessionResponse(
session: Session
) : Serializable {

val metadata = MetadataResponse.of(session.getMetadata())
val metadata = MetadataResponse.of(session.metadata)
val ownerId = session.ownerId
val sessionId = session.sessionId
val state = session.currentState
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.snackgame.server.game.snackgame.biz.controller

import com.snackgame.server.auth.token.support.Authenticated
import com.snackgame.server.game.snackgame.biz.service.SnackgameBizService
import com.snackgame.server.game.snackgame.core.service.dto.SnackgameEndResponse
import com.snackgame.server.game.snackgame.core.service.dto.SnackgameResponse
import com.snackgame.server.game.snackgame.core.service.dto.SnackgameUpdateRequest
import com.snackgame.server.game.snackgame.core.service.dto.StreaksRequest
import com.snackgame.server.member.domain.Member
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import javax.validation.Valid

@Tag(name = "🍿 스낵게임 Biz")
@RequestMapping("/games/4")
@RestController
class SnackgameBizController(
private val snackgameBizService: SnackgameBizService
) {

@Operation(
summary = "스낵게임 세션 시작",
description = """
스낵게임 세션을 시작한다.

게임 보드는 추후 제공 예정"""
)
@PostMapping
fun startSessionFor(@Authenticated member: Member): SnackgameResponse =
snackgameBizService.startSessionFor(member.id)

@Operation(
summary = "[임시] 스낵게임 세션 수정",
description = """
세션을 수정한다.

현재는 점수 수정만 가능하며, 기존 점수가 덮어쓰기된다."""
)
@PutMapping("/{sessionId}")
fun update(
@Authenticated member: Member,
@PathVariable sessionId: Long,
@RequestBody @Valid request: SnackgameUpdateRequest,
): SnackgameResponse = snackgameBizService.update(member.id, sessionId, request)

@Operation(
summary = "스트릭 추가",
description = """
스트릭을 순서대로 전달하여 게임을 검증한다.
황금 스낵을 제거한 경우 세션 정보가 함께 응답된다.
"""
)
@PostMapping("/{sessionId}/streaks")
fun removeStreaks(
@Authenticated member: Member,
@PathVariable sessionId: Long,
@RequestBody streaksRequest: StreaksRequest
): ResponseEntity<SnackgameResponse?> {
val game = snackgameBizService.removeStreaks(member.id, sessionId, streaksRequest)
return game.let {
Comment on lines +60 to +68
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

한번의 호출로 여러 개의 스트릭을 받을 수 있도록 API 개선했습니다

ResponseEntity
.status(HttpStatus.CREATED)
.body(it)
} ?: ResponseEntity.ok().build()
}

@Operation(
summary = "스낵게임 세션 일시정지",
description = """
해당 세션이 일시정지된다.

일시정지된 세션은 별도로 종료하지 않아도 **7일** 후 자동으로 만료된다."""
)
@PostMapping("/{sessionId}/pause")
fun pause(@Authenticated member: Member, @PathVariable sessionId: Long): SnackgameResponse =
snackgameBizService.pause(member.id, sessionId)

@Operation(summary = "스낵게임 세션 재개", description = "해당 세션을 재개한다")
@PostMapping("/{sessionId}/resume")
fun resume(@Authenticated member: Member, @PathVariable sessionId: Long): SnackgameResponse =
snackgameBizService.resume(member.id, sessionId)

@Operation(summary = "스낵게임 세션 종료", description = "세션을 종료한다")
@PostMapping("/{sessionId}/end")
fun end(@Authenticated member: Member, @PathVariable sessionId: Long): SnackgameEndResponse =
snackgameBizService.end(member.id, sessionId)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.snackgame.server.game.snackgame.biz.domain

import com.snackgame.server.game.metadata.Metadata.SNACK_GAME_BIZ
import com.snackgame.server.game.session.domain.Session
import com.snackgame.server.game.snackgame.core.domain.Board
import com.snackgame.server.game.snackgame.core.domain.BoardConverter
import com.snackgame.server.game.snackgame.core.domain.Snackgame.Companion.DEFAULT_HEIGHT
import com.snackgame.server.game.snackgame.core.domain.Snackgame.Companion.DEFAULT_WIDTH
import com.snackgame.server.game.snackgame.core.domain.Snackgame.Companion.SESSION_TIME
import com.snackgame.server.game.snackgame.core.domain.Snackgame.Companion.SPARE_TIME
import com.snackgame.server.game.snackgame.core.domain.Streak
import com.snackgame.server.game.snackgame.core.domain.snack.Snack
import java.time.Duration
import javax.persistence.Convert
import javax.persistence.Entity
import javax.persistence.Lob

@Entity
open class SnackgameBiz(
ownerId: Long,
board: Board = Board(DEFAULT_HEIGHT, DEFAULT_WIDTH),
timeLimit: Duration = SESSION_TIME + SPARE_TIME,
score: Int = 0
) : Session(ownerId, timeLimit, score) {

@Lob
@Convert(converter = BoardConverter::class)
var board = board
private set

@Deprecated("스트릭 구현 시 제거 예정")
fun setScoreUnsafely(score: Int) {
this.score = score
}

fun remove(streak: Streak) {
val removedSnacks = board.removeSnacksIn(streak)
this.score += removedSnacks.size
if (removedSnacks.any(Snack::isGolden)) {
this.board = board.reset()
}
}
Comment on lines +36 to +42
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

황금스낵을 제거한 경우에도 보드를 교체하지 않는 문제가 있었습니다

Copy link
Collaborator

Choose a reason for hiding this comment

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

혹시 어떤부분이 문제였는지 말씀해주시면 감사하겠습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

지금은 this.board = board.reset()으로 표현이 되어있는데요,
기존엔 board.reset()만 있어 리셋한 보드가 저장되지 못하는 상황이었습니다.

추가로 board.reset의 반환형도 Board가 아닌, List<List> 으로 되어있어 살~짝 고쳤습니다!

Copy link
Collaborator

Choose a reason for hiding this comment

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

아 그렇네요 더 신경쓰겠습니다..!


override val metadata = SNACK_GAME_BIZ
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.snackgame.server.game.snackgame.biz.domain

import com.snackgame.server.game.session.exception.NoSuchSessionException
import com.snackgame.server.game.snackgame.core.domain.Percentile
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Modifying
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository

@Repository
interface SnackgameBizRepository : JpaRepository<SnackgameBiz, Long> {

fun findByOwnerIdAndSessionId(ownerId: Long, sessionId: Long): SnackgameBiz?

@Query(
value = """
with scores as (
select percent_rank() over (order by score desc) as percentile, session_id, score
from snackgame_biz where TIMESTAMPDIFF(SECOND, now(), expires_at) <=0
)
select percentile from scores where session_id = :sessionId""",
nativeQuery = true
)
fun findPercentileOf(sessionId: Long): Double?

@Modifying
@Query("update SnackgameBiz set ownerId = :toMemberId where ownerId = :fromMemberId")
fun transferSessions(fromMemberId: Long, toMemberId: Long): Int

fun deleteAllByOwnerId(memberId: Long)
}

fun SnackgameBizRepository.getBy(ownerId: Long, sessionId: Long): SnackgameBiz =
findByOwnerIdAndSessionId(ownerId, sessionId) ?: throw NoSuchSessionException()

fun SnackgameBizRepository.ratePercentileOf(sessionId: Long): Percentile {
with(findPercentileOf(sessionId)) {
this ?: throw NoSuchSessionException()
return Percentile(this)
}
}
Loading
Loading