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

refactor: 퀴즈게임 도메인로직의 input output 추상화 하기 #105

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
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,11 @@ class GameEngineService(
) {

@Async("GameEngineThreadPool")
fun start(
players: Map<Long, String>,
quizzes: List<QuizDto>,
roomId: Long,
categoryId: Long,
) {
fun start(players: Map<Long, String>, quizzes: List<QuizDto>, roomId: Long, categoryId: Long) {
logger.info { "[INFO] 게임 엔진 스레드 시작 - roomId : $roomId" }

quizGameService.play(players, quizzes, roomId, categoryId)

logger.info { "[INFO] 게임 엔진 스레드 종료 - roomId : $roomId" }
}
}
21 changes: 9 additions & 12 deletions src/main/kotlin/yjh/cstar/engine/application/QuizGameService.kt
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
package yjh.cstar.engine.application

import org.springframework.stereotype.Service
import yjh.cstar.engine.application.port.AnswerProvider
import yjh.cstar.engine.application.port.GameNotifier
import yjh.cstar.engine.application.port.RankingHandler
import yjh.cstar.engine.domain.game.GameInfo
import yjh.cstar.engine.domain.game.QuizGame
import yjh.cstar.engine.domain.io.InputHandler
import yjh.cstar.engine.domain.io.OutputHandler
import yjh.cstar.engine.domain.io.RankingHandler
import yjh.cstar.engine.domain.quiz.QuizDto
import yjh.cstar.game.application.GameResultService

@Service
class QuizGameService(
private val inputHandler: InputHandler,
private val outputHandler: OutputHandler,
private val answerProvider: AnswerProvider,
private val gameNotifier: GameNotifier,
private val rankingHandler: RankingHandler,
private val gameResultService: GameResultService,
) {

fun play(
players: Map<Long, String>,
quizzes: List<QuizDto>,
roomId: Long,
categoryId: Long,
) {
fun play(players: Map<Long, String>, quizzes: List<QuizDto>, roomId: Long, categoryId: Long) {
val gameInfo = GameInfo.of(players, quizzes, roomId, categoryId)
val quizGame = QuizGame(gameInfo, inputHandler, outputHandler, rankingHandler, gameResultService)

val quizGame = QuizGame(gameInfo, answerProvider, gameNotifier, rankingHandler, gameResultService)

quizGame.initialize()

quizGame.run()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package yjh.cstar.engine.application.port

import yjh.cstar.engine.domain.quiz.PlayerAnswer

interface AnswerProvider {

fun receivePlayerAnswer(roomId: Long, quizId: Long): PlayerAnswer?

fun initializePlayerAnswerToReceive(roomId: Long, quizId: Long)
}

This file was deleted.

22 changes: 22 additions & 0 deletions src/main/kotlin/yjh/cstar/engine/application/port/GameNotifier.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package yjh.cstar.engine.application.port

import yjh.cstar.engine.domain.player.Players
import yjh.cstar.engine.domain.quiz.Quiz
import yjh.cstar.engine.domain.ranking.Ranking

interface GameNotifier {

fun notifyGameStartComments(destination: String, roomId: Long)

fun notifyQuizQuestion(destination: String, quizNo: Int, quiz: Quiz)

fun notifyRoundResult(destination: String, playerId: Long, nickname: String)

fun notifyRanking(destination: String, players: Players, ranking: Ranking)

fun notifyGameResult(destination: String, playerId: Long, nickname: String)

fun notifyTimeOut(destination: String)

fun notifyCountdown(destination: String)
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package yjh.cstar.engine.domain.io
package yjh.cstar.engine.application.port

import yjh.cstar.engine.domain.player.Players
import yjh.cstar.engine.domain.ranking.Ranking

interface RankingHandler {

fun init(roomId: Long, players: Players)
fun initRankingBoard(roomId: Long, players: Players)

fun increaseScore(roomId: Long, playerId: Long)
fun assignScoreToPlayer(roomId: Long, playerId: Long)

fun getWinner(roomId: Long): Long

Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/yjh/cstar/engine/domain/game/GameInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ package yjh.cstar.engine.domain.game
import yjh.cstar.engine.domain.player.Players
import yjh.cstar.engine.domain.quiz.Quiz
import yjh.cstar.engine.domain.quiz.QuizDto
import yjh.cstar.engine.domain.quiz.Quizzes

data class GameInfo(
val players: Players,
val quizzes: List<Quiz>,
val quizzes: Quizzes,
val roomId: Long,
val categoryId: Long,
) {

companion object {
fun of(players: Map<Long, String>, quizzes: List<QuizDto>, roomId: Long, categoryId: Long): GameInfo {
val quizList = quizzes.map { quizDto -> Quiz.of(quizDto.id, quizDto.question, quizDto.answer) }
return GameInfo(Players.of(players), quizList, roomId, categoryId)
return GameInfo(Players.of(players), Quizzes.of(quizzes), roomId, categoryId)
}
}
}
101 changes: 57 additions & 44 deletions src/main/kotlin/yjh/cstar/engine/domain/game/QuizGame.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package yjh.cstar.engine.domain.game

import io.github.oshai.kotlinlogging.KotlinLogging
import yjh.cstar.common.BaseException
import yjh.cstar.engine.domain.io.InputHandler
import yjh.cstar.engine.domain.io.OutputHandler
import yjh.cstar.engine.domain.io.RankingHandler
import yjh.cstar.engine.application.port.AnswerProvider
import yjh.cstar.engine.application.port.GameNotifier
import yjh.cstar.engine.application.port.RankingHandler
import yjh.cstar.engine.domain.player.Players
import yjh.cstar.engine.domain.quiz.PlayerAnswer
import yjh.cstar.engine.domain.quiz.Quiz
import yjh.cstar.engine.domain.quiz.Quizzes
import yjh.cstar.engine.domain.ranking.Ranking
import yjh.cstar.game.application.GameResultService
import yjh.cstar.game.presentation.request.RankingCreateRequest
Expand All @@ -17,8 +16,8 @@ private val logger = KotlinLogging.logger {}

class QuizGame(
private val gameInfo: GameInfo,
private val inputHandler: InputHandler,
private val outputHandler: OutputHandler,
private val answerProvider: AnswerProvider,
private val gameNotifier: GameNotifier,
private val rankingHandler: RankingHandler,
private val gameResultService: GameResultService,
) : GameInitializable, GameRunnable {
Expand All @@ -34,87 +33,101 @@ class QuizGame(
}

override fun initialize() {
rankingHandler.init(roomId, players)
rankingHandler.initRankingBoard(roomId, players)
}

override fun run() {
val gameStartedAt = getCurrentAt()
outputHandler.sendGameStartComments(destination, gameInfo.roomId)
gameNotifier.notifyGameStartComments(destination, gameInfo.roomId)

try {
for (idx in quizzes.indices) {
val quiz = quizzes[idx]
val quizNo = idx + 1
for ((index, quiz) in quizzes.getQuizList().withIndex()) {
val quizNo = index + 1
val quizId = quiz.id

outputHandler.resetPlayerAnswer(roomId, quizId)
answerProvider.initializePlayerAnswerToReceive(roomId, quizId)

outputHandler.sendQuizQuestion(destination, quizNo, quiz)
gameNotifier.notifyQuizQuestion(destination, quizNo, quiz)

val roundStartTime = getCurrentTime()
while (true) {
logger.info { "[INFO] 정답 대기중..." }
outputHandler.sendCountdown(destination)

gameNotifier.notifyCountdown(destination)

if (isTimeOut(roundStartTime)) {
outputHandler.sendTimeOut(destination)
gameNotifier.notifyTimeOut(destination)
break
}

val playerAnswer: PlayerAnswer? = inputHandler.getPlayerAnswer(roomId, quizId)
if (playerAnswer == null) {
continue
}
/**
* 1초 동안 플레이어 응답을 대기합니다.(Blocking)
*/
val playerAnswer = answerProvider.receivePlayerAnswer(roomId, quizId)
?: continue

if (playerAnswer.isCorrect(quiz)) {
val roundWinnerId = playerAnswer.playerId

if (quiz.isCorrectAnswer(playerAnswer)) {
rankingHandler.increaseScore(roomId, playerAnswer.playerId)
val ranking = rankingHandler.getRanking(roomId)
outputHandler.sendRanking(destination, players, ranking)
rankingHandler.assignScoreToPlayer(roomId, roundWinnerId)
notifyRanking()

val playerId = playerAnswer.playerId
outputHandler.sendRoundResult(destination, playerId, players.getNickname(playerId))
notifyRoundResult(roundWinnerId)
break
}
}
}

findAndSendWinner(players)

val ranking = rankingHandler.getRanking(roomId)
saveGameResult(ranking, quizzes, gameStartedAt)
recordGameResult(gameStartedAt)
} catch (e: BaseException) {
logger.error { e }
} catch (e: Exception) {
logger.error { "[ERROR] 프로그램 내부에 문제가 생겼습니다." }
}
}

private fun isTimeOut(startTime: Long) = getDuration(startTime) >= TIME_LIMIT_MILLIS

private fun getCurrentTime() = System.currentTimeMillis()

private fun getCurrentAt() = LocalDateTime.now()

private fun getDuration(pastTime: Long) = getCurrentTime() - pastTime

private fun findWinner() = rankingHandler.getWinner(roomId)

private fun findAndSendWinner(players: Players) {
val winnerId = findWinner()
val winnerNickname = players.getNickname(winnerId)
outputHandler.sendGameResult(destination, winnerId, winnerNickname)
private fun recordGameResult(gameStartedAt: LocalDateTime) {
val ranking = rankingHandler.getRanking(roomId)
saveGameResult(ranking, quizzes, gameStartedAt)
}

private fun saveGameResult(ranking: Ranking, quizzes: List<Quiz>, gameStartedAt: LocalDateTime) {
private fun saveGameResult(ranking: Ranking, quizzes: Quizzes, gameStartedAt: LocalDateTime) {
val winnerId = findWinner()
val rankingCreateRequest = RankingCreateRequest(
ranking,
roomId,
winnerId,
quizzes.size,
quizzes.getSize(),
categoryId,
gameStartedAt
)
gameResultService.create(rankingCreateRequest)
}

private fun isTimeOut(startTime: Long) = getDuration(startTime) >= TIME_LIMIT_MILLIS

private fun findAndSendWinner(players: Players) {
val winnerId = findWinner()
val winnerNickname = players.getNickname(winnerId)
gameNotifier.notifyGameResult(destination, winnerId, winnerNickname)
}

private fun notifyRoundResult(roundWinnerId: Long) {
gameNotifier.notifyRoundResult(destination, roundWinnerId, players.getNickname(roundWinnerId))
}

private fun notifyRanking() {
val ranking = rankingHandler.getRanking(roomId)
gameNotifier.notifyRanking(destination, players, ranking)
}

private fun findWinner() = rankingHandler.getWinner(roomId)

private fun getCurrentTime() = System.currentTimeMillis()

private fun getCurrentAt() = LocalDateTime.now()

private fun getDuration(pastTime: Long) = getCurrentTime() - pastTime
}
16 changes: 0 additions & 16 deletions src/main/kotlin/yjh/cstar/engine/domain/io/ExternalInputHandler.kt

This file was deleted.

8 changes: 0 additions & 8 deletions src/main/kotlin/yjh/cstar/engine/domain/io/InputHandler.kt

This file was deleted.

24 changes: 0 additions & 24 deletions src/main/kotlin/yjh/cstar/engine/domain/io/OutputHandler.kt

This file was deleted.

4 changes: 4 additions & 0 deletions src/main/kotlin/yjh/cstar/engine/domain/quiz/PlayerAnswer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ package yjh.cstar.engine.domain.quiz
class PlayerAnswer(val roomId: Long, val playerId: Long, val quizId: Long, val playerAnswer: String) {

fun isMatch(quizAnswer: String) = playerAnswer.equals(quizAnswer)

fun isCorrect(quiz: Quiz): Boolean {
return quiz.isSameAnswer(playerAnswer)
}
}
4 changes: 1 addition & 3 deletions src/main/kotlin/yjh/cstar/engine/domain/quiz/Quiz.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package yjh.cstar.engine.domain.quiz

data class QuizDto(val id: Long, val question: String, val answer: String)

class Quiz(val id: Long, val question: String, private val answer: String) {

companion object {
fun of(id: Long, question: String, answer: String) = Quiz(id, question, answer)
}

fun isCorrectAnswer(playerAnswer: PlayerAnswer) = playerAnswer.isMatch(answer)
fun isSameAnswer(answer: String) = this.answer == answer
}
9 changes: 9 additions & 0 deletions src/main/kotlin/yjh/cstar/engine/domain/quiz/QuizDto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package yjh.cstar.engine.domain.quiz

data class QuizDto(val id: Long, val question: String, val answer: String)

fun QuizDto.toModel(): Quiz = Quiz(
id = this.id,
question = this.question,
answer = this.answer
)
Loading