Skip to content

Commit

Permalink
Merge pull request #105 from FreakPeople/refactor/#104-퀴즈게임-도메인로직의-in…
Browse files Browse the repository at this point in the history
…put-output-추상화-하기

refactor: 퀴즈게임 도메인로직의 input output 추상화 하기
  • Loading branch information
youjungHwang authored Sep 23, 2024
2 parents 597e620 + 9f088ff commit ad09ed4
Show file tree
Hide file tree
Showing 21 changed files with 195 additions and 176 deletions.
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

0 comments on commit ad09ed4

Please sign in to comment.