diff --git a/apps/backend/src/play/entities/quiz-summary.entity.ts b/apps/backend/src/play/entities/quiz-summary.entity.ts new file mode 100644 index 000000000..1edc0de4d --- /dev/null +++ b/apps/backend/src/play/entities/quiz-summary.entity.ts @@ -0,0 +1,6 @@ +import { Rank } from './rank.entity'; + +export interface QuizSummary { + readonly ranks: Rank[]; + readonly endSocketTime?: number; +} \ No newline at end of file diff --git a/apps/backend/src/play/entities/rank.entity.ts b/apps/backend/src/play/entities/rank.entity.ts new file mode 100644 index 000000000..7854840df --- /dev/null +++ b/apps/backend/src/play/entities/rank.entity.ts @@ -0,0 +1,6 @@ +export interface Rank { + readonly id: string; + readonly nickname: string; + readonly score: number; + readonly ranking: number; +} \ No newline at end of file diff --git a/apps/backend/src/play/play.gateway.ts b/apps/backend/src/play/play.gateway.ts index d83309b3e..5cf7a7c39 100644 --- a/apps/backend/src/play/play.gateway.ts +++ b/apps/backend/src/play/play.gateway.ts @@ -15,11 +15,10 @@ import { SendEventMessage } from './entities/send-event.entity'; import { ClientInfo } from './entities/client-info.entity'; import { WebSocketWithSession } from '../core/SessionWsAdapter'; import { RuntimeException } from '@nestjs/core/errors/exceptions'; -import { CLOSE_CODE } from '../common/constants'; import { SubmitResponseDto } from './dto/submit-response.dto'; -import { clearTimeout } from 'node:timers'; import { ChatMessage } from 'src/chat/entities/chat-message.entity'; -import { ChatService } from '../chat/chat.service'; // 경로 수정 +import { ChatService } from '../chat/chat.service'; +import { CLOSE_CODE } from '../common/constants'; // 경로 수정 /** * 퀴즈 게임에 대한 WebSocket 연결을 관리하는 Gateway입니다. @@ -248,7 +247,7 @@ export class PlayGateway implements OnGatewayInit { const clientsIds = summaries.map(({ id }) => id); - this.clearQuizZone(clientsIds, quizZoneId, endSocketTime); + this.clearQuizZone(clientsIds, quizZoneId, endSocketTime - Date.now()); } /** @@ -258,16 +257,16 @@ export class PlayGateway implements OnGatewayInit { * - 일반 플레이어가 나가면 퀴즈 존에서 나가고 다른 플레이어에게 나갔다고 알립니다. * @param clientIds - 퀴즈존에 참여하고 있는 클라이언트 id 리스트 * @param quizZoneId - 퀴즈가 끝난 퀴즈존 id - * @param endSocketTime - 소켓 연결 종료 시간 종료 시간 + * @param time - 소켓 연결 종료 시간 종료 시간 */ - private clearQuizZone(clientIds: string[], quizZoneId: string, endSocketTime: number) { + private clearQuizZone(clientIds: string[], quizZoneId: string, time: number) { setTimeout(() => { clientIds.forEach((id) => { this.clearClient(id, 'finish'); }); this.playService.clearQuizZone(quizZoneId); this.chatService.delete(quizZoneId); - }, endSocketTime - Date.now()); + }, time); } /** @@ -286,7 +285,7 @@ export class PlayGateway implements OnGatewayInit { if (isHost) { this.broadcast(playerIds, 'close'); - playerIds.forEach((id) => this.clearClient(id, 'Host leave.')); + this.clearQuizZone(playerIds, quizZoneId, 0); } else { this.broadcast(playerIds, 'someone_leave', clientId); this.clearClient(clientId, 'Client leave'); diff --git a/apps/backend/src/play/play.service.ts b/apps/backend/src/play/play.service.ts index 2badf6b3a..fd033c338 100644 --- a/apps/backend/src/play/play.service.ts +++ b/apps/backend/src/play/play.service.ts @@ -304,7 +304,8 @@ export class PlayService { const now = Date.now(); const endSocketTime = now + socketConnectTime; - return [...players.values()].map(({ id, score, submits }) => ({ + + const summaries = [...players.values()].map(({ id, score, submits }) => ({ id, score, submits, @@ -312,6 +313,9 @@ export class PlayService { ranks, endSocketTime })); + + quizZone.summaries = {ranks, endSocketTime}; + return summaries; } public clearQuizZone(quizZoneId: string) { diff --git a/apps/backend/src/quiz-zone/dto/find-quiz-zone.dto.ts b/apps/backend/src/quiz-zone/dto/find-quiz-zone.dto.ts index c5f708f85..37e3b3da1 100644 --- a/apps/backend/src/quiz-zone/dto/find-quiz-zone.dto.ts +++ b/apps/backend/src/quiz-zone/dto/find-quiz-zone.dto.ts @@ -2,6 +2,8 @@ import { PLAYER_STATE, QUIZ_ZONE_STAGE } from '../../common/constants'; import { CurrentQuizDto } from '../../play/dto/current-quiz.dto'; import { SubmittedQuiz } from '../entities/submitted-quiz.entity'; import { ChatMessage } from 'src/chat/entities/chat-message.entity'; +import { Rank } from '../../play/entities/rank.entity'; +import { Quiz } from '../entities/quiz.entity'; /** * 퀴즈 게임에 참여하는 플레이어 엔티티 @@ -42,4 +44,10 @@ export interface FindQuizZoneDto { readonly currentQuiz?: CurrentQuizDto; readonly maxPlayers?: number; readonly chatMessages?: ChatMessage[]; + + readonly ranks?: Rank[]; + readonly endSocketTime?: number; + readonly score?: number; + readonly quizzes?: Quiz[]; + readonly submits?: SubmittedQuiz[]; } diff --git a/apps/backend/src/quiz-zone/entities/quiz-zone.entity.ts b/apps/backend/src/quiz-zone/entities/quiz-zone.entity.ts index 59c762e5e..4e6bbbffc 100644 --- a/apps/backend/src/quiz-zone/entities/quiz-zone.entity.ts +++ b/apps/backend/src/quiz-zone/entities/quiz-zone.entity.ts @@ -1,6 +1,7 @@ import { Quiz } from './quiz.entity'; import { Player } from './player.entity'; import { QUIZ_ZONE_STAGE } from '../../common/constants'; +import { QuizSummary } from '../../play/entities/quiz-summary.entity'; /** * 퀴즈 게임을 진행하는 공간을 나타내는 퀴즈존 인터페이스 * @@ -28,4 +29,5 @@ export interface QuizZone { currentQuizStartTime: number; currentQuizDeadlineTime: number; intervalTime: number; + summaries?: QuizSummary; } diff --git a/apps/backend/src/quiz-zone/quiz-zone.service.spec.ts b/apps/backend/src/quiz-zone/quiz-zone.service.spec.ts index a2e8b9281..847367989 100644 --- a/apps/backend/src/quiz-zone/quiz-zone.service.spec.ts +++ b/apps/backend/src/quiz-zone/quiz-zone.service.spec.ts @@ -6,7 +6,6 @@ import { IQuizZoneRepository } from './repository/quiz-zone.repository.interface import { Quiz } from './entities/quiz.entity'; import { PLAYER_STATE, QUIZ_TYPE, QUIZ_ZONE_STAGE } from '../common/constants'; import { QuizService } from '../quiz/quiz.service'; -import { max } from 'class-validator'; import { ChatService } from '../chat/chat.service'; const nickNames: string[] = [ @@ -571,6 +570,13 @@ describe('QuizZoneService', () => { currentQuizStartTime: Date.now(), currentQuizDeadlineTime: Date.now() + playTime, intervalTime: 5000, + summaries: { + ranks: [ + {id: "player1", nickname: "미친투사", score: 0, ranking: 1}, + {id: "player2", nickname: "미친투사", score: 0, ranking: 1} + ], + endSocketTime: Date.now(), + } }; mockQuizZoneRepository.get.mockResolvedValue(mockQuizZone); @@ -579,7 +585,7 @@ describe('QuizZoneService', () => { const result = await service.getQuizZoneInfo(clientId, quizZoneId); // then - expect(result).toEqual({ + expect(result).toMatchObject({ currentPlayer: { id: clientId, nickname: '닉네임', @@ -591,8 +597,12 @@ describe('QuizZoneService', () => { description: '테스트 퀴즈입니다', quizCount: quizzes.length, stage: QUIZ_ZONE_STAGE.RESULT, - maxPlayers: 10, hostId: 'adminId', + ranks: [ + {id: "player1", nickname: "미친투사", score: 0, ranking: 1}, + {id: "player2", nickname: "미친투사", score: 0, ranking: 1} + ], + endSocketTime: Date.now(), }); }); diff --git a/apps/backend/src/quiz-zone/quiz-zone.service.ts b/apps/backend/src/quiz-zone/quiz-zone.service.ts index 135b6914f..d2aeda3e2 100644 --- a/apps/backend/src/quiz-zone/quiz-zone.service.ts +++ b/apps/backend/src/quiz-zone/quiz-zone.service.ts @@ -172,7 +172,7 @@ export class QuizZoneService { } private async getResultInfo(clientId: string, quizZoneId: string): Promise { - const { players, stage, title, description, hostId, quizzes, maxPlayers } = + const { players, stage, title, description, hostId, quizzes, summaries } = await this.findOne(quizZoneId); const { id, nickname, state, submits, score } = players.get(clientId); const chatMessages = await this.chatService.get(quizZoneId); @@ -181,11 +181,15 @@ export class QuizZoneService { currentPlayer: { id, nickname, state, score, submits }, title, description, - maxPlayers: maxPlayers, quizCount: quizzes.length, stage: stage, hostId, - chatMessages: chatMessages, + chatMessages, + ranks: summaries.ranks, + endSocketTime: summaries.endSocketTime, + quizzes, + score, + submits }; } diff --git a/apps/frontend/src/blocks/CreateQuizZone/CreateQuizZoneBasic.tsx b/apps/frontend/src/blocks/CreateQuizZone/CreateQuizZoneBasic.tsx index b433f259d..8df873058 100644 --- a/apps/frontend/src/blocks/CreateQuizZone/CreateQuizZoneBasic.tsx +++ b/apps/frontend/src/blocks/CreateQuizZone/CreateQuizZoneBasic.tsx @@ -129,9 +129,9 @@ const CreateQuizZoneBasic = ({ onChange={(e) => handleChangeQuizZoneBasic(e, 'DESC')} isBorder={true} placeholder="퀴즈존 설명을 입력하세요" - error={validationError == '500자 이하로 입력해주세요.' ? validationError : ''} + error={validationError == '300자 이하로 입력해주세요.' ? validationError : ''} isShowCount={true} - max={500} + max={300} /> diff --git a/apps/frontend/src/components/common/Input.tsx b/apps/frontend/src/components/common/Input.tsx index a9e0e137e..7c96b645d 100644 --- a/apps/frontend/src/components/common/Input.tsx +++ b/apps/frontend/src/components/common/Input.tsx @@ -148,7 +148,7 @@ const Input = forwardRef( )} {typeof value === 'string' && isShowCount && ( -
+
= (state, action) => { ...state.currentPlayer, state: 'SUBMIT', }, - chatMessages: payload.chatMessages, currentQuizResult: { + ...payload, fastestPlayers: payload.fastestPlayerIds .map((id) => state.players?.find((p) => p.id === id)) .filter((p) => !!p), - submittedCount: payload.submittedCount, - totalPlayerCount: payload.totalPlayerCount, }, }; case 'someone_submit': @@ -115,9 +113,8 @@ const quizZoneReducer: Reducer = (state, action) => { }, currentQuiz: { ...state.currentQuiz, + ...nextQuiz, question: atob(nextQuiz.question), - currentIndex: nextQuiz.currentIndex, - playTime: nextQuiz.playTime, startTime: nextQuiz.startTime - state.offset, deadlineTime: nextQuiz.deadlineTime - state.offset, quizType: 'SHORT', @@ -154,11 +151,8 @@ const quizZoneReducer: Reducer = (state, action) => { case 'summary': return { ...state, + ...payload, stage: 'RESULT', - score: payload.score, - submits: payload.submits, - quizzes: payload.quizzes, - ranks: payload.ranks, endSocketTime: payload.endSocketTime - state.offset, }; case 'chat': diff --git a/apps/frontend/src/utils/validators.ts b/apps/frontend/src/utils/validators.ts index 0c274a5a5..4ab4fbe18 100644 --- a/apps/frontend/src/utils/validators.ts +++ b/apps/frontend/src/utils/validators.ts @@ -32,7 +32,7 @@ export const validateQuizZoneSetName = (name: string) => { //퀴즈존 설명 유효성 검사 export const validateQuizZoneSetDescription = (description: string) => { - if (description.length > 500) return '500자 이하로 입력해주세요.'; + if (description.length > 300) return '300자 이하로 입력해주세요.'; }; //퀴즈존 입장 코드 유효성 검사