Skip to content

Commit 931a184

Browse files
LimKangHyunsilver-eunjoo
authored andcommitted
✨ feat: GameSetting 변경 기능 추가 (#107)
* ✨ feat: 게임 설정 변경 인터페이스 및 구현체 추가 - GameSettingChanger 인터페이스 도입 * chore: Java 스타일 수정 * ✨ feat: 게임 설정 변경 기능 추가 * chore: Java 스타일 수정 * 🚚 move: 소켓 요청 DTO request 디렉토리로 이동 * 🚚 move: 소켓 요청 DTO 경로 변경 * chore: Java 스타일 수정 * ♻️ refactor: 게임 세팅 요청 후처리 분리 * chore: Java 스타일 수정 * ♻️ refactor: 코드 리뷰 반영 * chore: Java 스타일 수정 * 🗑️ remove: 불필요 코드 삭제 * chore: Java 스타일 수정 * ♻️ refactor: isHost 원상복구 * ♻️ refactor: QuizChangeRequest, RoundChangeRequest 타입 수정 * 🔧 chore: RoomUpdatedEventListener, RoomDeletedEventListener 빈 등록 * chore: Java 스타일 수정 * chore: Java 스타일 수정 * ♻️ refactor: player 조회 메서드 추가 --------- Co-authored-by: github-actions <>
1 parent e3beace commit 931a184

File tree

18 files changed

+316
-46
lines changed

18 files changed

+316
-46
lines changed

backend/src/main/java/io/f1/backend/domain/game/app/GameService.java

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@
22

33
import static io.f1.backend.domain.game.mapper.RoomMapper.*;
44
import static io.f1.backend.domain.game.websocket.WebSocketUtils.getDestination;
5+
import static io.f1.backend.domain.game.mapper.RoomMapper.toGameSettingResponse;
6+
import static io.f1.backend.domain.game.mapper.RoomMapper.toPlayerListResponse;
7+
import static io.f1.backend.domain.game.mapper.RoomMapper.toQuestionStartResponse;
58
import static io.f1.backend.domain.quiz.mapper.QuizMapper.toGameStartResponse;
69

710
import io.f1.backend.domain.game.dto.ChatMessage;
811
import io.f1.backend.domain.game.dto.MessageType;
912
import io.f1.backend.domain.game.dto.RoomEventType;
1013
import io.f1.backend.domain.game.event.GameCorrectAnswerEvent;
1114
import io.f1.backend.domain.game.event.GameTimeoutEvent;
15+
import io.f1.backend.domain.game.dto.request.GameSettingChanger;
16+
import io.f1.backend.domain.game.dto.response.PlayerListResponse;
1217
import io.f1.backend.domain.game.event.RoomUpdatedEvent;
1318
import io.f1.backend.domain.game.model.Player;
1419
import io.f1.backend.domain.game.model.Room;
@@ -23,16 +28,18 @@
2328
import io.f1.backend.global.exception.errorcode.GameErrorCode;
2429
import io.f1.backend.global.exception.errorcode.RoomErrorCode;
2530

31+
import java.util.Map;
2632
import lombok.RequiredArgsConstructor;
33+
import lombok.extern.slf4j.Slf4j;
2734

2835
import org.springframework.context.ApplicationEventPublisher;
2936
import org.springframework.context.event.EventListener;
3037
import org.springframework.stereotype.Service;
3138

3239
import java.util.List;
33-
import java.util.Map;
3440
import java.util.Objects;
3541

42+
@Slf4j
3643
@Service
3744
@RequiredArgsConstructor
3845
public class GameService {
@@ -175,19 +182,47 @@ public void gameEnd(Room room) {
175182
messageSender.send(destination, MessageType.ROOM_SETTING, toRoomSettingResponse(room));
176183
}
177184

178-
private boolean validateReadyStatus(Room room) {
185+
public void handlePlayerReady(Long roomId, String sessionId) {
179186

180-
Map<String, Player> playerSessionMap = room.getPlayerSessionMap();
187+
Room room = findRoom(roomId);
188+
189+
Player player = room.getPlayerBySessionId(sessionId);
190+
191+
toggleReadyIfPossible(room, player);
181192

182-
return playerSessionMap.values().stream().allMatch(Player::isReady);
193+
String destination = getDestination(roomId);
194+
195+
PlayerListResponse playerListResponse = toPlayerListResponse(room);
196+
log.info(playerListResponse.toString());
197+
messageSender.send(destination, MessageType.PLAYER_LIST, playerListResponse);
198+
}
199+
200+
public void changeGameSetting(
201+
Long roomId, UserPrincipal principal, GameSettingChanger request) {
202+
Room room = findRoom(roomId);
203+
validateHostAndState(room, principal);
204+
205+
if (!request.change(room, quizService)) {
206+
return;
207+
}
208+
request.afterChange(room, messageSender);
209+
210+
broadcastGameSetting(room);
211+
212+
RoomUpdatedEvent roomUpdatedEvent =
213+
new RoomUpdatedEvent(
214+
room,
215+
quizService.getQuizWithQuestionsById(room.getGameSetting().getQuizId()));
216+
217+
eventPublisher.publishEvent(roomUpdatedEvent);
183218
}
184219

185220
private void validateRoomStart(Room room, UserPrincipal principal) {
186221
if (!Objects.equals(principal.getUserId(), room.getHost().getId())) {
187222
throw new CustomException(RoomErrorCode.NOT_ROOM_OWNER);
188223
}
189224

190-
if (!validateReadyStatus(room)) {
225+
if (!room.validateReadyStatus()) {
191226
throw new CustomException(GameErrorCode.PLAYER_NOT_READY);
192227
}
193228

@@ -202,4 +237,37 @@ private List<Question> prepareQuestions(Room room, Quiz quiz) {
202237
Integer round = room.getGameSetting().getRound();
203238
return quizService.getRandomQuestionsWithoutAnswer(quizId, round);
204239
}
240+
241+
private Room findRoom(Long roomId) {
242+
return roomRepository
243+
.findRoom(roomId)
244+
.orElseThrow(() -> new CustomException(RoomErrorCode.ROOM_NOT_FOUND));
245+
}
246+
247+
private void validateHostAndState(Room room, UserPrincipal principal) {
248+
if (!room.isHost(principal.getUserId())) {
249+
throw new CustomException(RoomErrorCode.NOT_ROOM_OWNER);
250+
}
251+
if (room.isPlaying()) {
252+
throw new CustomException(RoomErrorCode.GAME_ALREADY_PLAYING);
253+
}
254+
}
255+
256+
private void toggleReadyIfPossible(Room room, Player player) {
257+
if (room.isPlaying()) {
258+
throw new CustomException(RoomErrorCode.GAME_ALREADY_PLAYING);
259+
}
260+
if (!room.isHost(player.getId())) {
261+
player.toggleReady();
262+
}
263+
}
264+
265+
private void broadcastGameSetting(Room room) {
266+
String destination = getDestination(room.getId());
267+
Quiz quiz = quizService.getQuizWithQuestionsById(room.getGameSetting().getQuizId());
268+
messageSender.send(
269+
destination,
270+
MessageType.GAME_SETTING,
271+
toGameSettingResponse(room.getGameSetting(), quiz));
272+
}
205273
}

backend/src/main/java/io/f1/backend/domain/game/app/RoomService.java

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -172,21 +172,6 @@ public void exitRoom(Long roomId, String sessionId, UserPrincipal principal) {
172172
}
173173
}
174174

175-
public void handlePlayerReady(Long roomId, String sessionId) {
176-
Player player =
177-
roomRepository
178-
.findPlayerInRoomBySessionId(roomId, sessionId)
179-
.orElseThrow(() -> new CustomException(RoomErrorCode.PLAYER_NOT_FOUND));
180-
181-
player.toggleReady();
182-
183-
Room room = findRoom(roomId);
184-
185-
String destination = getDestination(roomId);
186-
187-
messageSender.send(destination, MessageType.PLAYER_LIST, toPlayerListResponse(room));
188-
}
189-
190175
public RoomListResponse getAllRooms() {
191176
List<Room> rooms = roomRepository.findAll();
192177
List<RoomResponse> roomResponses =
@@ -260,7 +245,7 @@ public void exitIfNotPlaying(Long roomId, String sessionId, UserPrincipal princi
260245
private Player getRemovePlayer(Room room, String sessionId, UserPrincipal principal) {
261246
Player removePlayer = room.getPlayerSessionMap().get(sessionId);
262247
if (removePlayer == null) {
263-
room.removeUserId(principal.getUserId());
248+
room.removeValidatedUserId(principal.getUserId());
264249
throw new CustomException(RoomErrorCode.SOCKET_SESSION_NOT_FOUND);
265250
}
266251
return removePlayer;
@@ -307,7 +292,6 @@ private void changeHost(Room room, String hostSessionId) {
307292
}
308293

309294
private void removePlayer(Room room, String sessionId, Player removePlayer) {
310-
room.removeUserId(removePlayer.getId());
311295
room.removeSessionId(sessionId);
312296
room.removeValidatedUserId(removePlayer.getId());
313297
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.f1.backend.domain.game.dto.request;
2+
3+
import io.f1.backend.domain.game.model.Room;
4+
import io.f1.backend.domain.game.websocket.MessageSender;
5+
import io.f1.backend.domain.quiz.app.QuizService;
6+
7+
public interface GameSettingChanger {
8+
9+
boolean change(Room room, QuizService quizService);
10+
11+
void afterChange(Room room, MessageSender messageSender);
12+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.f1.backend.domain.game.dto.request;
2+
3+
import static io.f1.backend.domain.game.mapper.RoomMapper.toPlayerListResponse;
4+
import static io.f1.backend.domain.game.websocket.WebSocketUtils.getDestination;
5+
6+
import io.f1.backend.domain.game.dto.MessageType;
7+
import io.f1.backend.domain.game.dto.response.PlayerListResponse;
8+
import io.f1.backend.domain.game.model.Room;
9+
import io.f1.backend.domain.game.websocket.MessageSender;
10+
import io.f1.backend.domain.quiz.app.QuizService;
11+
import io.f1.backend.domain.quiz.entity.Quiz;
12+
13+
import lombok.extern.slf4j.Slf4j;
14+
15+
@Slf4j
16+
public record QuizChangeRequest(long quizId) implements GameSettingChanger {
17+
18+
@Override
19+
public boolean change(Room room, QuizService quizService) {
20+
if (room.getQuizId() == quizId) {
21+
return false; // 동일하면 무시
22+
}
23+
Quiz quiz = quizService.getQuizWithQuestionsById(quizId);
24+
int questionSize = quiz.getQuestions().size();
25+
room.changeQuiz(quiz);
26+
// 퀴즈의 문제 갯수로 변경
27+
room.changeRound(questionSize, questionSize);
28+
return true;
29+
}
30+
31+
@Override
32+
public void afterChange(Room room, MessageSender messageSender) {
33+
room.resetAllPlayerReadyStates();
34+
35+
String destination = getDestination(room.getId());
36+
PlayerListResponse response = toPlayerListResponse(room);
37+
38+
log.info(response.toString());
39+
messageSender.send(destination, MessageType.PLAYER_LIST, response);
40+
}
41+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.f1.backend.domain.game.dto.request;
2+
3+
import io.f1.backend.domain.game.model.Room;
4+
import io.f1.backend.domain.game.websocket.MessageSender;
5+
import io.f1.backend.domain.quiz.app.QuizService;
6+
import io.f1.backend.domain.quiz.entity.Quiz;
7+
8+
public record RoundChangeRequest(int round) implements GameSettingChanger {
9+
10+
@Override
11+
public boolean change(Room room, QuizService quizService) {
12+
if (room.getRound() == round) {
13+
return false; // 동일하면 무시
14+
}
15+
16+
Quiz quiz = quizService.findQuizById(room.getQuizId());
17+
int questionSize = quiz.getQuestions().size();
18+
19+
room.changeRound(round, questionSize);
20+
return true;
21+
}
22+
23+
@Override
24+
public void afterChange(Room room, MessageSender messageSender) {
25+
// 고유한 후처리 동작 없음
26+
}
27+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.f1.backend.domain.game.dto.request;
2+
3+
import io.f1.backend.global.exception.CustomException;
4+
import io.f1.backend.global.exception.errorcode.GameErrorCode;
5+
6+
import lombok.Getter;
7+
8+
import java.util.Arrays;
9+
10+
@Getter
11+
public enum TimeLimit {
12+
FIFTEEN(15),
13+
THIRTY(30),
14+
FORTY_FIVE(45),
15+
SIXTY(60);
16+
17+
private final int value;
18+
19+
TimeLimit(int value) {
20+
this.value = value;
21+
}
22+
23+
public static TimeLimit from(int value) {
24+
return Arrays.stream(values())
25+
.filter(t -> t.value == value)
26+
.findFirst()
27+
.orElseThrow(() -> new CustomException(GameErrorCode.GAME_SETTING_CONFLICT));
28+
}
29+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.f1.backend.domain.game.dto.request;
2+
3+
import io.f1.backend.domain.game.model.Room;
4+
import io.f1.backend.domain.game.websocket.MessageSender;
5+
import io.f1.backend.domain.quiz.app.QuizService;
6+
7+
public record TimeLimitChangeRequest(int timeLimit) implements GameSettingChanger {
8+
9+
@Override
10+
public boolean change(Room room, QuizService quizService) {
11+
if (room.getTimeLimit() == timeLimit) {
12+
return false; // 동일하면 무시
13+
}
14+
room.changeTimeLimit(TimeLimit.from(timeLimit));
15+
return true;
16+
}
17+
18+
@Override
19+
public void afterChange(Room room, MessageSender messageSender) {
20+
// 고유한 후처리 동작 없음
21+
}
22+
}

backend/src/main/java/io/f1/backend/domain/game/mapper/RoomMapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public static GameSettingResponse toGameSettingResponse(GameSetting gameSetting,
6060
public static PlayerListResponse toPlayerListResponse(Room room) {
6161
List<PlayerResponse> playerResponseList =
6262
room.getPlayerSessionMap().values().stream()
63-
.map(player -> new PlayerResponse(player.getNickname(), false))
63+
.map(player -> new PlayerResponse(player.getNickname(), player.isReady()))
6464
.toList();
6565

6666
return new PlayerListResponse(room.getHost().getNickname(), playerResponseList);
Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,34 @@
11
package io.f1.backend.domain.game.model;
22

3+
import io.f1.backend.domain.game.dto.request.TimeLimit;
4+
import io.f1.backend.domain.quiz.entity.Quiz;
5+
import io.f1.backend.global.exception.CustomException;
6+
import io.f1.backend.global.exception.errorcode.GameErrorCode;
7+
38
import lombok.AllArgsConstructor;
49
import lombok.Getter;
510

6-
import java.util.Objects;
7-
811
@Getter
912
@AllArgsConstructor
1013
public class GameSetting {
1114

1215
private Long quizId;
13-
private Integer round; // 게임 변경 시 해당 게임의 총 문제 수로 설정
16+
private Integer round;
1417
private int timeLimit;
1518

16-
public boolean validateQuizId(Long quizId) {
17-
return Objects.equals(this.quizId, quizId);
19+
public void changeQuiz(Quiz quiz) {
20+
quizId = quiz.getId();
21+
round = quiz.getQuestions().size(); // 라운드를 바꾼 퀴즈의 문제 수로 동기화
22+
}
23+
24+
public void changeTimeLimit(TimeLimit timeLimit) {
25+
this.timeLimit = timeLimit.getValue();
26+
}
27+
28+
public void changeRound(int round, int questionsCount) {
29+
if (round > questionsCount) {
30+
throw new CustomException(GameErrorCode.ROUND_EXCEEDS_QUESTION_COUNT);
31+
}
32+
this.round = round;
1833
}
1934
}

backend/src/main/java/io/f1/backend/domain/game/model/Player.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ public void toggleReady() {
2424
this.isReady = !this.isReady;
2525
}
2626

27+
public void setReadyFalse() {
28+
this.isReady = false;
29+
}
30+
2731
public void increaseCorrectCount() {
2832
correctCount++;
2933
}

0 commit comments

Comments
 (0)