Skip to content

Commit fc1b85d

Browse files
authored
๐Ÿ”– release: v0.0.6
2 parents 518c8f5 + 2d23468 commit fc1b85d

35 files changed

+1059
-112
lines changed

โ€Žbackend/build.gradleโ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ dependencies {
4343
testRuntimeOnly 'com.h2database:h2'
4444
testImplementation 'org.springframework.security:spring-security-test'
4545
testImplementation 'com.github.database-rider:rider-spring:1.44.0'
46+
testImplementation 'org.testcontainers:junit-jupiter'
47+
testImplementation "com.redis:testcontainers-redis:2.2.4"
4648

4749
/* ETC */
4850
implementation 'org.apache.commons:commons-lang3:3.12.0'

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

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package io.f1.backend.domain.game.app;
22

3+
import static io.f1.backend.domain.game.mapper.RoomMapper.toGameSettingResponse;
4+
import static io.f1.backend.domain.game.mapper.RoomMapper.toPlayerListResponse;
35
import static io.f1.backend.domain.game.mapper.RoomMapper.toQuestionStartResponse;
4-
import static io.f1.backend.domain.game.websocket.WebSocketUtils.getDestination;
56
import static io.f1.backend.domain.quiz.mapper.QuizMapper.toGameStartResponse;
67

78
import io.f1.backend.domain.game.dto.MessageType;
9+
import io.f1.backend.domain.game.dto.request.GameSettingChanger;
10+
import io.f1.backend.domain.game.dto.response.PlayerListResponse;
811
import io.f1.backend.domain.game.event.RoomUpdatedEvent;
912
import io.f1.backend.domain.game.model.Player;
1013
import io.f1.backend.domain.game.model.Room;
@@ -20,14 +23,15 @@
2023
import io.f1.backend.global.exception.errorcode.RoomErrorCode;
2124

2225
import lombok.RequiredArgsConstructor;
26+
import lombok.extern.slf4j.Slf4j;
2327

2428
import org.springframework.context.ApplicationEventPublisher;
2529
import org.springframework.stereotype.Service;
2630

2731
import java.util.List;
28-
import java.util.Map;
2932
import java.util.Objects;
3033

34+
@Slf4j
3135
@Service
3236
@RequiredArgsConstructor
3337
public class GameService {
@@ -70,19 +74,47 @@ public void gameStart(Long roomId, UserPrincipal principal) {
7074
toQuestionStartResponse(room, START_DELAY));
7175
}
7276

73-
private boolean validateReadyStatus(Room room) {
77+
public void handlePlayerReady(Long roomId, String sessionId) {
7478

75-
Map<String, Player> playerSessionMap = room.getPlayerSessionMap();
79+
Room room = findRoom(roomId);
7680

77-
return playerSessionMap.values().stream().allMatch(Player::isReady);
81+
Player player = room.getPlayerBySessionId(sessionId);
82+
83+
toggleReadyIfPossible(room, player);
84+
85+
String destination = getDestination(roomId);
86+
87+
PlayerListResponse playerListResponse = toPlayerListResponse(room);
88+
log.info(playerListResponse.toString());
89+
messageSender.send(destination, MessageType.PLAYER_LIST, playerListResponse);
90+
}
91+
92+
public void changeGameSetting(
93+
Long roomId, UserPrincipal principal, GameSettingChanger request) {
94+
Room room = findRoom(roomId);
95+
validateHostAndState(room, principal);
96+
97+
if (!request.change(room, quizService)) {
98+
return;
99+
}
100+
request.afterChange(room, messageSender);
101+
102+
broadcastGameSetting(room);
103+
104+
RoomUpdatedEvent roomUpdatedEvent =
105+
new RoomUpdatedEvent(
106+
room,
107+
quizService.getQuizWithQuestionsById(room.getGameSetting().getQuizId()));
108+
109+
eventPublisher.publishEvent(roomUpdatedEvent);
78110
}
79111

80112
private void validateRoomStart(Room room, UserPrincipal principal) {
81113
if (!Objects.equals(principal.getUserId(), room.getHost().getId())) {
82114
throw new CustomException(RoomErrorCode.NOT_ROOM_OWNER);
83115
}
84116

85-
if (!validateReadyStatus(room)) {
117+
if (!room.validateReadyStatus()) {
86118
throw new CustomException(GameErrorCode.PLAYER_NOT_READY);
87119
}
88120

@@ -97,4 +129,41 @@ private List<Question> prepareQuestions(Room room, Quiz quiz) {
97129
Integer round = room.getGameSetting().getRound();
98130
return quizService.getRandomQuestionsWithoutAnswer(quizId, round);
99131
}
132+
133+
private Room findRoom(Long roomId) {
134+
return roomRepository
135+
.findRoom(roomId)
136+
.orElseThrow(() -> new CustomException(RoomErrorCode.ROOM_NOT_FOUND));
137+
}
138+
139+
private String getDestination(Long roomId) {
140+
return "/sub/room/" + roomId;
141+
}
142+
143+
private void validateHostAndState(Room room, UserPrincipal principal) {
144+
if (!room.isHost(principal.getUserId())) {
145+
throw new CustomException(RoomErrorCode.NOT_ROOM_OWNER);
146+
}
147+
if (room.isPlaying()) {
148+
throw new CustomException(RoomErrorCode.GAME_ALREADY_PLAYING);
149+
}
150+
}
151+
152+
private void toggleReadyIfPossible(Room room, Player player) {
153+
if (room.isPlaying()) {
154+
throw new CustomException(RoomErrorCode.GAME_ALREADY_PLAYING);
155+
}
156+
if (!room.isHost(player.getId())) {
157+
player.toggleReady();
158+
}
159+
}
160+
161+
private void broadcastGameSetting(Room room) {
162+
String destination = getDestination(room.getId());
163+
Quiz quiz = quizService.getQuizWithQuestionsById(room.getGameSetting().getQuizId());
164+
messageSender.send(
165+
destination,
166+
MessageType.GAME_SETTING,
167+
toGameSettingResponse(room.getGameSetting(), quiz));
168+
}
100169
}

โ€Ž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
@@ -190,21 +190,6 @@ public void exitRoom(Long roomId, String sessionId, UserPrincipal principal) {
190190
}
191191
}
192192

193-
public void handlePlayerReady(Long roomId, String sessionId) {
194-
Player player =
195-
roomRepository
196-
.findPlayerInRoomBySessionId(roomId, sessionId)
197-
.orElseThrow(() -> new CustomException(RoomErrorCode.PLAYER_NOT_FOUND));
198-
199-
player.toggleReady();
200-
201-
Room room = findRoom(roomId);
202-
203-
String destination = getDestination(roomId);
204-
205-
messageSender.send(destination, MessageType.PLAYER_LIST, toPlayerListResponse(room));
206-
}
207-
208193
public RoomListResponse getAllRooms() {
209194
List<Room> rooms = roomRepository.findAll();
210195
List<RoomResponse> roomResponses =
@@ -327,7 +312,7 @@ public void exitIfNotPlaying(Long roomId, String sessionId, UserPrincipal princi
327312
private Player getRemovePlayer(Room room, String sessionId, UserPrincipal principal) {
328313
Player removePlayer = room.getPlayerSessionMap().get(sessionId);
329314
if (removePlayer == null) {
330-
room.removeUserId(principal.getUserId());
315+
room.removeValidatedUserId(principal.getUserId());
331316
throw new CustomException(RoomErrorCode.SOCKET_SESSION_NOT_FOUND);
332317
}
333318
return removePlayer;
@@ -374,7 +359,6 @@ private void changeHost(Room room, String hostSessionId) {
374359
}
375360

376361
private void removePlayer(Room room, String sessionId, Player removePlayer) {
377-
room.removeUserId(removePlayer.getId());
378362
room.removeSessionId(sessionId);
379363
room.removeValidatedUserId(removePlayer.getId());
380364
}
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
@@ -56,7 +56,7 @@ public static GameSettingResponse toGameSettingResponse(GameSetting gameSetting,
5656
public static PlayerListResponse toPlayerListResponse(Room room) {
5757
List<PlayerResponse> playerResponseList =
5858
room.getPlayerSessionMap().values().stream()
59-
.map(player -> new PlayerResponse(player.getNickname(), false))
59+
.map(player -> new PlayerResponse(player.getNickname(), player.isReady()))
6060
.toList();
6161

6262
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
}

0 commit comments

Comments
ย (0)