-
Notifications
You must be signed in to change notification settings - Fork 0
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
Changes from 9 commits
bed5807
7b172e9
08d0b84
9b8f679
b6929b6
32b7f54
dd5fa0e
475fefe
ee765dc
216be1e
5cfdbb1
4b3a5ad
4f52a54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 황금스낵을 제거한 경우에도 보드를 교체하지 않는 문제가 있었습니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시 어떤부분이 문제였는지 말씀해주시면 감사하겠습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금은 추가로 board.reset의 반환형도 Board가 아닌, List<List> 으로 되어있어 살~짝 고쳤습니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
변경을 하게 된 이유가 궁금합니다! 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
게임의 입장에서 Metadata는 함수(행위)보다는 속성(property)의 성질이 더 강한데요.
그래서 처음부터 property로 표현하고 싶었는데, 당시엔 문법이 익숙하지 않아 함수로 표현하게 되었었네요 ㅎㅎ;
그러다가 오늘 이걸 발견해서 마침내 적절한 형태로 변경하게 된 것이었던 것이었다...입니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
답변 감사합니다 ㅎㅎ
저도 이번기회에 덕분에 코틀린 문법 하나 배워갑니다!