Skip to content
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
2 changes: 2 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ dependencies {
testRuntimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'com.github.database-rider:rider-spring:1.44.0'
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation "com.redis:testcontainers-redis:2.2.4"

/* ETC */
implementation 'org.apache.commons:commons-lang3:3.12.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package io.f1.backend.domain.game.app;

import static io.f1.backend.domain.game.mapper.RoomMapper.toGameSettingResponse;
import static io.f1.backend.domain.game.mapper.RoomMapper.toPlayerListResponse;
import static io.f1.backend.domain.game.mapper.RoomMapper.toQuestionStartResponse;
import static io.f1.backend.domain.game.websocket.WebSocketUtils.getDestination;
import static io.f1.backend.domain.quiz.mapper.QuizMapper.toGameStartResponse;

import io.f1.backend.domain.game.dto.MessageType;
import io.f1.backend.domain.game.dto.request.GameSettingChanger;
import io.f1.backend.domain.game.dto.response.PlayerListResponse;
import io.f1.backend.domain.game.event.RoomUpdatedEvent;
import io.f1.backend.domain.game.model.Player;
import io.f1.backend.domain.game.model.Room;
Expand All @@ -20,14 +23,15 @@
import io.f1.backend.global.exception.errorcode.RoomErrorCode;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.Objects;

@Slf4j
@Service
@RequiredArgsConstructor
public class GameService {
Expand Down Expand Up @@ -70,19 +74,47 @@ public void gameStart(Long roomId, UserPrincipal principal) {
toQuestionStartResponse(room, START_DELAY));
}

private boolean validateReadyStatus(Room room) {
public void handlePlayerReady(Long roomId, String sessionId) {

Map<String, Player> playerSessionMap = room.getPlayerSessionMap();
Room room = findRoom(roomId);

return playerSessionMap.values().stream().allMatch(Player::isReady);
Player player = room.getPlayerBySessionId(sessionId);

toggleReadyIfPossible(room, player);

String destination = getDestination(roomId);

PlayerListResponse playerListResponse = toPlayerListResponse(room);
log.info(playerListResponse.toString());
messageSender.send(destination, MessageType.PLAYER_LIST, playerListResponse);
}

public void changeGameSetting(
Long roomId, UserPrincipal principal, GameSettingChanger request) {
Room room = findRoom(roomId);
validateHostAndState(room, principal);

if (!request.change(room, quizService)) {
return;
}
request.afterChange(room, messageSender);

broadcastGameSetting(room);

RoomUpdatedEvent roomUpdatedEvent =
new RoomUpdatedEvent(
room,
quizService.getQuizWithQuestionsById(room.getGameSetting().getQuizId()));

eventPublisher.publishEvent(roomUpdatedEvent);
}

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

if (!validateReadyStatus(room)) {
if (!room.validateReadyStatus()) {
throw new CustomException(GameErrorCode.PLAYER_NOT_READY);
}

Expand All @@ -97,4 +129,41 @@ private List<Question> prepareQuestions(Room room, Quiz quiz) {
Integer round = room.getGameSetting().getRound();
return quizService.getRandomQuestionsWithoutAnswer(quizId, round);
}

private Room findRoom(Long roomId) {
return roomRepository
.findRoom(roomId)
.orElseThrow(() -> new CustomException(RoomErrorCode.ROOM_NOT_FOUND));
}

private String getDestination(Long roomId) {
return "/sub/room/" + roomId;
}

private void validateHostAndState(Room room, UserPrincipal principal) {
if (!room.isHost(principal.getUserId())) {
throw new CustomException(RoomErrorCode.NOT_ROOM_OWNER);
}
if (room.isPlaying()) {
throw new CustomException(RoomErrorCode.GAME_ALREADY_PLAYING);
}
}

private void toggleReadyIfPossible(Room room, Player player) {
if (room.isPlaying()) {
throw new CustomException(RoomErrorCode.GAME_ALREADY_PLAYING);
}
if (!room.isHost(player.getId())) {
player.toggleReady();
}
}

private void broadcastGameSetting(Room room) {
String destination = getDestination(room.getId());
Quiz quiz = quizService.getQuizWithQuestionsById(room.getGameSetting().getQuizId());
messageSender.send(
destination,
MessageType.GAME_SETTING,
toGameSettingResponse(room.getGameSetting(), quiz));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -190,21 +190,6 @@ public void exitRoom(Long roomId, String sessionId, UserPrincipal principal) {
}
}

public void handlePlayerReady(Long roomId, String sessionId) {
Player player =
roomRepository
.findPlayerInRoomBySessionId(roomId, sessionId)
.orElseThrow(() -> new CustomException(RoomErrorCode.PLAYER_NOT_FOUND));

player.toggleReady();

Room room = findRoom(roomId);

String destination = getDestination(roomId);

messageSender.send(destination, MessageType.PLAYER_LIST, toPlayerListResponse(room));
}

public RoomListResponse getAllRooms() {
List<Room> rooms = roomRepository.findAll();
List<RoomResponse> roomResponses =
Expand Down Expand Up @@ -327,7 +312,7 @@ public void exitIfNotPlaying(Long roomId, String sessionId, UserPrincipal princi
private Player getRemovePlayer(Room room, String sessionId, UserPrincipal principal) {
Player removePlayer = room.getPlayerSessionMap().get(sessionId);
if (removePlayer == null) {
room.removeUserId(principal.getUserId());
room.removeValidatedUserId(principal.getUserId());
throw new CustomException(RoomErrorCode.SOCKET_SESSION_NOT_FOUND);
}
return removePlayer;
Expand Down Expand Up @@ -374,7 +359,6 @@ private void changeHost(Room room, String hostSessionId) {
}

private void removePlayer(Room room, String sessionId, Player removePlayer) {
room.removeUserId(removePlayer.getId());
room.removeSessionId(sessionId);
room.removeValidatedUserId(removePlayer.getId());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.f1.backend.domain.game.dto.request;

import io.f1.backend.domain.game.model.Room;
import io.f1.backend.domain.game.websocket.MessageSender;
import io.f1.backend.domain.quiz.app.QuizService;

public interface GameSettingChanger {

boolean change(Room room, QuizService quizService);

void afterChange(Room room, MessageSender messageSender);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.f1.backend.domain.game.dto.request;

import static io.f1.backend.domain.game.mapper.RoomMapper.toPlayerListResponse;
import static io.f1.backend.domain.game.websocket.WebSocketUtils.getDestination;

import io.f1.backend.domain.game.dto.MessageType;
import io.f1.backend.domain.game.dto.response.PlayerListResponse;
import io.f1.backend.domain.game.model.Room;
import io.f1.backend.domain.game.websocket.MessageSender;
import io.f1.backend.domain.quiz.app.QuizService;
import io.f1.backend.domain.quiz.entity.Quiz;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public record QuizChangeRequest(long quizId) implements GameSettingChanger {

@Override
public boolean change(Room room, QuizService quizService) {
if (room.getQuizId() == quizId) {
return false; // 동일하면 무시
}
Quiz quiz = quizService.getQuizWithQuestionsById(quizId);
int questionSize = quiz.getQuestions().size();
room.changeQuiz(quiz);
// 퀴즈의 문제 갯수로 변경
room.changeRound(questionSize, questionSize);
return true;
}

@Override
public void afterChange(Room room, MessageSender messageSender) {
room.resetAllPlayerReadyStates();

String destination = getDestination(room.getId());
PlayerListResponse response = toPlayerListResponse(room);

log.info(response.toString());
messageSender.send(destination, MessageType.PLAYER_LIST, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.f1.backend.domain.game.dto.request;

import io.f1.backend.domain.game.model.Room;
import io.f1.backend.domain.game.websocket.MessageSender;
import io.f1.backend.domain.quiz.app.QuizService;
import io.f1.backend.domain.quiz.entity.Quiz;

public record RoundChangeRequest(int round) implements GameSettingChanger {

@Override
public boolean change(Room room, QuizService quizService) {
if (room.getRound() == round) {
return false; // 동일하면 무시
}

Quiz quiz = quizService.findQuizById(room.getQuizId());
int questionSize = quiz.getQuestions().size();

room.changeRound(round, questionSize);
return true;
}

@Override
public void afterChange(Room room, MessageSender messageSender) {
// 고유한 후처리 동작 없음
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.f1.backend.domain.game.dto.request;

import io.f1.backend.global.exception.CustomException;
import io.f1.backend.global.exception.errorcode.GameErrorCode;

import lombok.Getter;

import java.util.Arrays;

@Getter
public enum TimeLimit {
FIFTEEN(15),
THIRTY(30),
FORTY_FIVE(45),
SIXTY(60);

private final int value;

TimeLimit(int value) {
this.value = value;
}

public static TimeLimit from(int value) {
return Arrays.stream(values())
.filter(t -> t.value == value)
.findFirst()
.orElseThrow(() -> new CustomException(GameErrorCode.GAME_SETTING_CONFLICT));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.f1.backend.domain.game.dto.request;

import io.f1.backend.domain.game.model.Room;
import io.f1.backend.domain.game.websocket.MessageSender;
import io.f1.backend.domain.quiz.app.QuizService;

public record TimeLimitChangeRequest(int timeLimit) implements GameSettingChanger {

@Override
public boolean change(Room room, QuizService quizService) {
if (room.getTimeLimit() == timeLimit) {
return false; // 동일하면 무시
}
room.changeTimeLimit(TimeLimit.from(timeLimit));
return true;
}

@Override
public void afterChange(Room room, MessageSender messageSender) {
// 고유한 후처리 동작 없음
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public static GameSettingResponse toGameSettingResponse(GameSetting gameSetting,
public static PlayerListResponse toPlayerListResponse(Room room) {
List<PlayerResponse> playerResponseList =
room.getPlayerSessionMap().values().stream()
.map(player -> new PlayerResponse(player.getNickname(), false))
.map(player -> new PlayerResponse(player.getNickname(), player.isReady()))
.toList();

return new PlayerListResponse(room.getHost().getNickname(), playerResponseList);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
package io.f1.backend.domain.game.model;

import io.f1.backend.domain.game.dto.request.TimeLimit;
import io.f1.backend.domain.quiz.entity.Quiz;
import io.f1.backend.global.exception.CustomException;
import io.f1.backend.global.exception.errorcode.GameErrorCode;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Objects;

@Getter
@AllArgsConstructor
public class GameSetting {

private Long quizId;
private Integer round; // 게임 변경 시 해당 게임의 총 문제 수로 설정
private Integer round;
private int timeLimit;

public boolean validateQuizId(Long quizId) {
return Objects.equals(this.quizId, quizId);
public void changeQuiz(Quiz quiz) {
quizId = quiz.getId();
round = quiz.getQuestions().size(); // 라운드를 바꾼 퀴즈의 문제 수로 동기화
}

public void changeTimeLimit(TimeLimit timeLimit) {
this.timeLimit = timeLimit.getValue();
}

public void changeRound(int round, int questionsCount) {
if (round > questionsCount) {
throw new CustomException(GameErrorCode.ROUND_EXCEEDS_QUESTION_COUNT);
}
this.round = round;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ public void toggleReady() {
this.isReady = !this.isReady;
}

public void setReadyFalse() {
this.isReady = false;
}

public void increaseCorrectCount() {
correctCount++;
}
Expand Down
Loading