-
Notifications
You must be signed in to change notification settings - Fork 3
[feat] 게임 시작 후 게임 진행 + 타이머 기능 추가 #94
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
Changes from 5 commits
aee9991
e9a5a38
0b2689e
de8b3b2
9c4a793
e090bd8
56e0010
a59060d
37c7a65
d8f0d62
f33974b
4031963
65629e2
6444663
d4e8f43
257f09c
d046325
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,12 +56,18 @@ | |
| @RequiredArgsConstructor | ||
| public class RoomService { | ||
|
|
||
| private final TimerService timerService; | ||
| private final QuizService quizService; | ||
| private final RoomRepository roomRepository; | ||
| private final AtomicLong roomIdGenerator = new AtomicLong(0); | ||
| private final ApplicationEventPublisher eventPublisher; | ||
| private final Map<Long, Object> roomLocks = new ConcurrentHashMap<>(); | ||
|
|
||
| private final MessageSender messageSender; | ||
|
|
||
| private static final int START_DELAY = 5; | ||
| private static final int CONTINUE_DELAY = 3; | ||
|
|
||
| private static final String PENDING_SESSION_ID = "PENDING_SESSION_ID"; | ||
|
|
||
| public RoomCreateResponse saveRoom(RoomCreateRequest request) { | ||
|
|
@@ -244,13 +250,26 @@ public void chat(Long roomId, String sessionId, ChatMessage chatMessage) { | |
| messageSender.send( | ||
| destination, | ||
| MessageType.QUESTION_RESULT, | ||
| toQuestionResultResponse(currentQuestion.getId(), chatMessage, answer)); | ||
| toQuestionResultResponse(chatMessage.nickname(), answer)); | ||
| messageSender.send(destination, MessageType.RANK_UPDATE, toRankUpdateResponse(room)); | ||
| messageSender.send( | ||
| destination, | ||
| MessageType.SYSTEM_NOTICE, | ||
| ofPlayerEvent(chatMessage.nickname(), RoomEventType.ENTER)); | ||
| ofPlayerEvent(chatMessage.nickname(), RoomEventType.CORRECT_ANSWER)); | ||
| } | ||
|
|
||
| timerService.cancelTimer(room); | ||
|
|
||
| // TODO : 게임 종료 로직 추가 | ||
| if (!timerService.validateCurrentRound(room)) { | ||
| // 게임 종료 로직 | ||
| return; | ||
| } | ||
|
|
||
| room.increaseCurrentRound(); | ||
|
|
||
| // 타이머 추가하기 | ||
| timerService.startTimer(room, CONTINUE_DELAY); | ||
|
||
| } | ||
|
|
||
| private Player getRemovePlayer(Room room, String sessionId, UserPrincipal principal) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| package io.f1.backend.domain.game.app; | ||
|
|
||
| import static io.f1.backend.domain.game.mapper.RoomMapper.ofPlayerEvent; | ||
| import static io.f1.backend.domain.game.mapper.RoomMapper.toQuestionResultResponse; | ||
| import static io.f1.backend.domain.game.mapper.RoomMapper.toQuestionStartResponse; | ||
|
|
||
| import io.f1.backend.domain.game.dto.MessageType; | ||
| import io.f1.backend.domain.game.dto.RoomEventType; | ||
| import io.f1.backend.domain.game.model.Room; | ||
| import io.f1.backend.domain.game.websocket.MessageSender; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import java.util.concurrent.ScheduledFuture; | ||
| import java.util.concurrent.TimeUnit; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class TimerService { | ||
|
|
||
| private final MessageSender messageSender; | ||
|
|
||
| private static final String NONE_CORRECT_USER = ""; | ||
| private static final int CONTINUE_DELAY = 3; | ||
|
|
||
| public void startTimer(Room room, int delaySec) { | ||
| cancelTimer(room); | ||
|
|
||
| ScheduledFuture<?> timer = | ||
| room.getScheduler() | ||
| .schedule( | ||
| () -> { | ||
| handleTimeout(room); | ||
| }, | ||
| delaySec + room.getGameSetting().getTimeLimit(), | ||
| TimeUnit.SECONDS); | ||
|
Collaborator
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. 오홍.. 재연결 유예시간 로직 구현할 때 참고해서 구현해봐야겠군요 👀 |
||
|
|
||
| room.updateTimer(timer); | ||
| } | ||
|
|
||
| private void handleTimeout(Room room) { | ||
| String destination = getDestination(room.getId()); | ||
|
|
||
| messageSender.send( | ||
| destination, | ||
| MessageType.QUESTION_RESULT, | ||
| toQuestionResultResponse(NONE_CORRECT_USER, room.getCurrentQuestion().getAnswer())); | ||
| messageSender.send( | ||
| destination, | ||
| MessageType.SYSTEM_NOTICE, | ||
| ofPlayerEvent(NONE_CORRECT_USER, RoomEventType.TIMEOUT)); | ||
|
|
||
| // TODO : 게임 종료 로직 | ||
| if (!validateCurrentRound(room)) { | ||
| // 게임 종료 로직 | ||
| // GAME_SETTING, PLAYER_LIST, GAME_RESULT, ROOM_SETTING | ||
| return; | ||
| } | ||
|
|
||
| // 다음 문제 출제 | ||
| room.increaseCurrentRound(); | ||
|
|
||
| startTimer(room, CONTINUE_DELAY); | ||
| messageSender.send( | ||
| destination, | ||
| MessageType.QUESTION_START, | ||
| toQuestionStartResponse(room, CONTINUE_DELAY)); | ||
| } | ||
|
|
||
| public boolean validateCurrentRound(Room room) { | ||
| if (room.getGameSetting().getRound() != room.getCurrentRound()) { | ||
| return true; | ||
| } | ||
| cancelTimer(room); | ||
| room.getScheduler().shutdown(); | ||
| return false; | ||
| } | ||
|
|
||
| public void cancelTimer(Room room) { | ||
| // 정답 맞혔어요 ~ 타이머 캔슬 부탁 | ||
| ScheduledFuture<?> timer = room.getTimer(); | ||
| if (timer != null && !timer.isDone()) { | ||
| timer.cancel(false); | ||
| } | ||
| } | ||
|
|
||
| private String getDestination(Long roomId) { | ||
| return "/sub/room/" + roomId; | ||
| } | ||
|
Collaborator
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. 여기도 있군요. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package io.f1.backend.domain.game.dto; | ||
|
|
||
| public enum GameEventType { | ||
| START, | ||
| CONTINUE | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package io.f1.backend.domain.game.dto; | ||
|
|
||
| import io.f1.backend.domain.game.dto.response.GameStartResponse; | ||
| import io.f1.backend.domain.game.dto.response.QuestionStartResponse; | ||
|
|
||
| public record GameStartData( | ||
|
||
| GameStartResponse gameStartResponse, QuestionStartResponse questionStartResponse) {} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,4 +9,5 @@ public enum MessageType { | |
| CHAT, | ||
| QUESTION_RESULT, | ||
| RANK_UPDATE, | ||
| QUESTION_START | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,4 +5,6 @@ public enum RoomEventType { | |
| EXIT, | ||
| START, | ||
| END, | ||
| CORRECT_ANSWER, | ||
| TIMEOUT | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| package io.f1.backend.domain.game.dto.response; | ||
|
|
||
| public record QuestionResultResponse(Long questionId, String correctUser, String answer) {} | ||
| public record QuestionResultResponse(String correctUser, String answer) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package io.f1.backend.domain.game.dto.response; | ||
|
|
||
| import java.time.Instant; | ||
|
|
||
| public record QuestionStartResponse(Long questionId, int round, Instant timestamp) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,13 @@ | ||
| package io.f1.backend.domain.game.mapper; | ||
|
|
||
| import io.f1.backend.domain.game.dto.ChatMessage; | ||
| import io.f1.backend.domain.game.dto.Rank; | ||
| import io.f1.backend.domain.game.dto.RoomEventType; | ||
| import io.f1.backend.domain.game.dto.request.RoomCreateRequest; | ||
| import io.f1.backend.domain.game.dto.response.GameSettingResponse; | ||
| import io.f1.backend.domain.game.dto.response.PlayerListResponse; | ||
| import io.f1.backend.domain.game.dto.response.PlayerResponse; | ||
| import io.f1.backend.domain.game.dto.response.QuestionResultResponse; | ||
| import io.f1.backend.domain.game.dto.response.QuestionStartResponse; | ||
| import io.f1.backend.domain.game.dto.response.QuizResponse; | ||
| import io.f1.backend.domain.game.dto.response.RankUpdateResponse; | ||
| import io.f1.backend.domain.game.dto.response.RoomResponse; | ||
|
|
@@ -91,13 +91,17 @@ public static SystemNoticeResponse ofPlayerEvent(String nickname, RoomEventType | |
| message = " 님이 입장하셨습니다"; | ||
| } else if (roomEventType == RoomEventType.EXIT) { | ||
| message = " 님이 퇴장하셨습니다"; | ||
| } else if (roomEventType == RoomEventType.CORRECT_ANSWER) { | ||
| message = " 님 정답입니다 !"; | ||
| } else if (roomEventType == RoomEventType.TIMEOUT) { | ||
| message = "땡 ~ ⏰ 제한 시간 초과!"; | ||
|
||
| } | ||
| return new SystemNoticeResponse(nickname + message, Instant.now()); | ||
| } | ||
|
|
||
| public static QuestionResultResponse toQuestionResultResponse( | ||
| Long questionId, ChatMessage chatMessage, String answer) { | ||
| return new QuestionResultResponse(questionId, chatMessage.nickname(), answer); | ||
| String correctUser, String answer) { | ||
| return new QuestionResultResponse(correctUser, answer); | ||
| } | ||
|
|
||
| public static RankUpdateResponse toRankUpdateResponse(Room room) { | ||
|
|
@@ -107,4 +111,11 @@ public static RankUpdateResponse toRankUpdateResponse(Room room) { | |
| .map(player -> new Rank(player.getNickname(), player.getCorrectCount())) | ||
| .toList()); | ||
| } | ||
|
|
||
| public static QuestionStartResponse toQuestionStartResponse(Room room, int delay) { | ||
| return new QuestionStartResponse( | ||
| room.getCurrentQuestion().getId(), | ||
| room.getCurrentRound(), | ||
| Instant.now().plusSeconds(delay)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,7 @@ public class GameSetting { | |
|
|
||
| private Long quizId; | ||
| private Integer round; // 게임 변경 시 해당 게임의 총 문제 수로 설정 | ||
| private int timeLimit = 60; | ||
|
Collaborator
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. 이부분 전 캐치 못하고있었는뎁 감사합니다 ~! |
||
| private int timeLimit; | ||
|
|
||
| public boolean validateQuizId(Long quizId) { | ||
| return Objects.equals(this.quizId, quizId); | ||
|
|
||
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.
getDestinatnion을public static으로 변경해서util로 분리하는건 어떨까요?현재
roomService,gameService에서 중복인 메소드라서요!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.
util 분리가 좋을 것 같습니다 ! 수정하겠습니다 !
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.
흠.. 얘도 WebSocketUtils에서 관리하면 될까요?
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.
우선은 WebSocketUtils로 분리해두었습니다 !