Skip to content
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

[BE] 게임 모드 선택 및 방 최대 인원 조절 기능 추가 #252

Merged
merged 1 commit into from
Dec 7, 2024
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
1 change: 1 addition & 0 deletions be/gameServer/src/modules/games/dto/turn-data.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ApiProperty } from '@nestjs/swagger';
export enum GameMode {
PRONUNCIATION = 'PRONUNCIATION',
CLEOPATRA = 'CLEOPATRA',
RANDOM = 'RANDOM',
}

export class TurnDataDto {
Expand Down
21 changes: 12 additions & 9 deletions be/gameServer/src/modules/games/games-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GameMode, TurnDataDto } from './dto/turn-data.dto';
import { RoomDataDto } from './../rooms/dto/room-data.dto';
import { RoomDataDto } from '../rooms/dto/room-data.dto';
import { GameDataDto } from './dto/game-data.dto';

const SAMPLE_DATA = [
Expand Down Expand Up @@ -30,15 +30,17 @@ const SAMPLE_DATA = [
export function createTurnData(
roomId: string,
gameData: GameDataDto,
roomData: RoomDataDto,
): TurnDataDto {
const gameModes = [
GameMode.PRONUNCIATION,
GameMode.CLEOPATRA,
GameMode.CLEOPATRA,
GameMode.CLEOPATRA,
GameMode.CLEOPATRA,
];
const gameMode = gameModes[Math.floor(Math.random() * gameModes.length)];
let gameMode = roomData.gameMode;

// 랜덤 모드인 경우 비율에 따라 게임 모드 결정
if (gameMode === GameMode.RANDOM) {
const ratio = roomData.randomModeRatio || 50;
const randomValue = Math.random() * 100;
gameMode =
randomValue < ratio ? GameMode.CLEOPATRA : GameMode.PRONUNCIATION;
}

if (gameMode === GameMode.CLEOPATRA) {
return {
Expand All @@ -49,6 +51,7 @@ export function createTurnData(
lyrics: '안녕! 클레오파트라! 세상에서 제일가는 포테이토 칩!',
};
}

const randomSentence =
SAMPLE_DATA[Math.floor(Math.random() * SAMPLE_DATA.length)];

Expand Down
15 changes: 12 additions & 3 deletions be/gameServer/src/modules/games/games.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class GamesGateway implements OnGatewayDisconnect {
);

if (!roomData) {
this.logger.warn(`Room not found: ${roomId}`);
this.logger.warn(`Room ${roomId} does not exist`);
client.emit('error', ErrorMessages.ROOM_NOT_FOUND);
return;
}
Expand Down Expand Up @@ -114,7 +114,8 @@ export class GamesGateway implements OnGatewayDisconnect {
};
await this.redisService.set(`game:${roomId}`, JSON.stringify(gameData));

const turnData: TurnDataDto = createTurnData(roomId, gameData);
// roomData를 추가로 전달하여 게임 모드 정보 전달
const turnData: TurnDataDto = createTurnData(roomId, gameData, roomData);

await new Promise<void>((resolve) => {
this.server.to(VOICE_SERVERS).emit('turnChanged', turnData, () => {
Expand Down Expand Up @@ -155,9 +156,17 @@ export class GamesGateway implements OnGatewayDisconnect {
}

const gameData: GameDataDto = JSON.parse(gameDataString);
const roomData = await this.redisService.hgetAll<RoomDataDto>(
`room:${roomId}`,
);

if (gameData.alivePlayers.length > 1) {
const turnData: TurnDataDto = createTurnData(roomId, gameData);
// roomData를 추가로 전달하여 게임 모드 정보 전달
const turnData: TurnDataDto = createTurnData(
roomId,
gameData,
roomData,
);

await new Promise<void>((resolve) => {
this.server.to(VOICE_SERVERS).emit('turnChanged', turnData, () => {
Expand Down
42 changes: 41 additions & 1 deletion be/gameServer/src/modules/rooms/dto/create-room.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, Length, Matches } from 'class-validator';
import {
IsString,
IsNotEmpty,
Length,
Matches,
IsEnum,
IsInt,
IsNumber,
Min,
Max,
ValidateIf,
} from 'class-validator';
import { GameMode } from '../../games/dto/turn-data.dto';

export class CreateRoomDto {
@IsString({ message: 'roomName은 문자열이어야 합니다.' })
Expand All @@ -19,4 +31,32 @@ export class CreateRoomDto {
description: 'hostNickname',
})
hostNickname: string;

@IsInt()
@Min(2)
@Max(10)
@ApiProperty({
example: 4,
description: '최대 플레이어 수 (2-10명)',
})
maxPlayers: number;

@IsEnum(GameMode)
@ApiProperty({
enum: GameMode,
example: GameMode.RANDOM,
description: '게임 모드',
})
gameMode: GameMode;

@ValidateIf((o) => o.gameMode === GameMode.RANDOM)
@IsNumber()
@Min(1)
@Max(99)
@ApiProperty({
example: 50,
description: '랜덤 모드에서 클레오파트라 모드의 비율 (1-99%)',
required: false,
})
randomModeRatio?: number;
}
21 changes: 21 additions & 0 deletions be/gameServer/src/modules/rooms/dto/room-data.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { PlayerDataDto } from '../../players/dto/player-data.dto';
import { GameMode } from '../../games/dto/turn-data.dto';

export class RoomDataDto {
@ApiProperty({
Expand Down Expand Up @@ -43,4 +44,24 @@ export class RoomDataDto {
description: '현재 방의 상태 (예: 대기 중, 게임 중)',
})
status: string;

@ApiProperty({
example: 4,
description: '최대 플레이어 수',
})
maxPlayers: number;

@ApiProperty({
enum: GameMode,
example: GameMode.RANDOM,
description: '게임 모드',
})
gameMode: GameMode;

@ApiProperty({
example: 50,
description: '랜덤 모드에서 클레오파트라 모드의 비율',
required: false,
})
randomModeRatio?: number;
}
19 changes: 16 additions & 3 deletions be/gameServer/src/modules/rooms/room-utils.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
import { RoomDataDto } from './dto/room-data.dto';
import { PlayerDataDto } from '../players/dto/player-data.dto';
import { RoomsConstant } from 'src/common/constant';

export const convertRoomDataToHash = (
roomData: RoomDataDto,
): Record<string, string> => {
const { roomId, roomName, hostNickname, players, status } = roomData;
const {
roomId,
roomName,
hostNickname,
players,
status,
maxPlayers,
gameMode,
randomModeRatio,
} = roomData;

return {
roomId,
roomName,
hostNickname,
players: JSON.stringify(players),
status,
maxPlayers: String(maxPlayers), // maxPlayers를 문자열로 변환하여 저장
gameMode,
...(randomModeRatio !== undefined && {
randomModeRatio: String(randomModeRatio),
}),
};
};

export const isRoomFull = (roomData: RoomDataDto): boolean => {
return roomData.players.length >= RoomsConstant.ROOMS_MAX_PLAYERS;
return roomData.players.length >= roomData.maxPlayers;
};

export const isNicknameTaken = (
Expand Down
66 changes: 16 additions & 50 deletions be/gameServer/src/modules/rooms/rooms.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,21 +185,6 @@ export class RoomController {
description: '조회할 게임 방의 ID',
example: '1234',
})
@ApiResponse({
description: '특정 roomId에 해당하는 게임 방이 성공적으로 반환됩니다.',
type: RoomDataDto,
})
@ApiResponse({
status: 404,
description: 'roomId에 해당하는 게임 방을 찾지 못했습니다.',
schema: {
example: {
statusCode: 404,
message: 'Room with ID 1234 not found',
error: 'Not Found',
},
},
})
async getRoomById(@Param('roomId') roomId: string): Promise<RoomDataDto> {
this.logger.log(`요청 시작 - Room 조회: roomId=${roomId}`);
const roomData = await this.redisService.hgetAll<RoomDataDto>(
Expand All @@ -211,15 +196,20 @@ export class RoomController {
throw new NotFoundException(`Room with ID ${roomId} not found`);
}

// 데이터 변환 확실히 하기
roomData.maxPlayers = Number(roomData.maxPlayers);
if (roomData.randomModeRatio) {
roomData.randomModeRatio = Number(roomData.randomModeRatio);
}

this.logger.log(`요청 완료 - Room 조회 성공: roomId=${roomId}`);
return roomData;
}

@Get()
@ApiOperation({
summary: '게임 방 목록 조회',
description:
'저장된 모든 게임 방 목록을 페이지네이션으로 조회합니다. 페이지는 1부터 시작하며, 한 페이지에 최대 ROOM_LIMIT개의 방 정보를 반환합니다.',
description: '저장된 모든 게임 방 목록을 페이지네이션으로 조회합니다.',
})
@ApiQuery({
name: 'page',
Expand All @@ -228,33 +218,6 @@ export class RoomController {
description: '조회할 페이지 번호 (기본값: 0)',
example: 1,
})
@ApiResponse({
status: 200,
description: '게임 방 목록이 성공적으로 반환됩니다.',
schema: {
example: {
rooms: [
{
roomId: '6f42377f-42ea-42cc-ac1a-b5d2b99d4ced',
roomName: '게임방123',
hostNickname: 'hostNickname123',
players: [
{ playerNickname: 'hostNic123', isReady: true, isMuted: false },
{ playerNickname: 'player1', isReady: false, isMuted: true },
],
status: 'waiting',
},
],
pagination: {
currentPage: 1,
totalPages: 5,
totalItems: 50,
hasNextPage: true,
hasPreviousPage: false,
},
},
},
})
async getRooms(@Query('page') page = 0): Promise<PaginatedRoomDto> {
page = Number(page);
this.logger.log(`게임 방 목록 조회 시작 (페이지: ${page})`);
Expand All @@ -279,16 +242,19 @@ export class RoomController {
return null;
}

// 데이터 변환 확실히 하기
return {
roomId: key,
roomName: roomData.roomName,
hostNickname: roomData.hostNickname,
players: roomData.players,
status: roomData.status,
...roomData,
maxPlayers: Number(roomData.maxPlayers),
randomModeRatio: roomData.randomModeRatio
? Number(roomData.randomModeRatio)
: undefined,
} as RoomDataDto;
}),
);
const validRooms = rooms.filter((room) => room !== null);
const validRooms = rooms.filter(
(room): room is RoomDataDto => room !== null,
);

this.logger.log(`게임 방 목록 조회 완료, ${validRooms.length}개 방 반환`);

Expand Down
20 changes: 11 additions & 9 deletions be/gameServer/src/modules/rooms/rooms.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { JoinRoomDto } from './dto/join-data.dto';
import { RoomsValidationPipe } from './rooms.validation.pipe';
import { WsExceptionsFilter } from '../../common/filters/ws-exceptions.filter';
import {
isRoomFull,
isNicknameTaken,
removePlayerFromRoom,
changeRoomHost,
Expand Down Expand Up @@ -47,9 +46,10 @@ export class RoomsGateway implements OnGatewayDisconnect {
@ConnectedSocket() client: Socket,
) {
try {
const { roomName, hostNickname } = createRoomDto;
const { roomName, hostNickname, maxPlayers, gameMode, randomModeRatio } =
createRoomDto;
this.logger.log(
`Room creation requested: ${roomName} by ${hostNickname}`,
`Room creation requested: ${roomName} by ${hostNickname} (max: ${maxPlayers})`,
);

const roomId = uuidv4();
Expand All @@ -67,6 +67,9 @@ export class RoomsGateway implements OnGatewayDisconnect {
},
],
status: 'waiting',
maxPlayers,
gameMode,
...(gameMode === 'RANDOM' && { randomModeRatio }),
};

await this.redisService.rpush(RedisKeys.ROOMS_LIST, roomId);
Expand Down Expand Up @@ -94,7 +97,6 @@ export class RoomsGateway implements OnGatewayDisconnect {
client.emit('roomCreated', roomData);
} catch (error) {
this.logger.error('Error creating room:', error.message);

client.emit('error', ErrorMessages.INTERNAL_ERROR);
}
}
Expand All @@ -118,8 +120,10 @@ export class RoomsGateway implements OnGatewayDisconnect {
return;
}

if (isRoomFull(roomData)) {
this.logger.log(`Room ${roomId} is full`);
if (roomData.players.length >= roomData.maxPlayers) {
this.logger.log(
`Room ${roomId} is full (${roomData.players.length}/${roomData.maxPlayers})`,
);
client.emit('error', ErrorMessages.ROOM_FULL);
return;
}
Expand Down Expand Up @@ -156,9 +160,7 @@ export class RoomsGateway implements OnGatewayDisconnect {
players: JSON.stringify(roomData.players),
},
RedisKeys.ROOMS_UPDATE_CHANNEL,
...(index === -1
? []
: [Math.floor(index / RoomsConstant.ROOMS_LIMIT)]),
index,
);

client.join(roomId);
Expand Down
Loading
Loading