diff --git a/src/game/game.gateway.ts b/src/game/game.gateway.ts index 97c2382..bc75d50 100644 --- a/src/game/game.gateway.ts +++ b/src/game/game.gateway.ts @@ -46,7 +46,6 @@ export class GameGateway if (!Number.isFinite(userId)) throw new Error('Invalid userId'); client.data.userId = userId; this.userSockets.set(userId, client.id); - console.log(`User connected: ${userId}`); } catch (err) { console.error(err.message); client.disconnect(); @@ -61,11 +60,13 @@ export class GameGateway const roomId = await this.gameRoomService.getRoomIdByClient( userId.toString(), ); + + const userNickname = await this.gameService.getUserNickname(userId); if (roomId) { await this.gameRoomService.leaveRoom(roomId, userId); this.server.to(roomId.toString()).emit('message', { sender: 'System', - message: `User ${userId} disconnected.`, + message: `${userNickname} 유저가 퇴장했습니다.`, }); } } @@ -79,10 +80,7 @@ export class GameGateway const { roomId } = payload; const userId = client.data.userId; const userNickname = await this.gameService.getUserNickname(userId); - if (!userNickname) { - client.emit('error', { message: 'User not found.' }); - return; - } + const inRoom = await this.gameRoomService.isUserInRoom(userId, roomId); if (!inRoom) { await this.gameRoomService.joinRoom(roomId, userId); @@ -100,16 +98,13 @@ export class GameGateway const { roomId } = payload; const userId = client.data.userId; const userNickname = await this.gameService.getUserNickname(userId); - if (!userNickname) { - client.emit('error', { message: 'User not found.' }); - return; - } + if (roomId) { await this.gameRoomService.leaveRoom(roomId, userId); this.server.to(roomId.toString()).emit('leave', { sender: 'System', userNickname, - message: `${userId}유저가 게임방을 나갔습니다.`, // TODO userNickname 추가: userNickname DB와 연결해서 가져오기 + message: `${userNickname}유저가 게임방을 나갔습니다.`, // TODO userNickname 추가: userNickname DB와 연결해서 가져오기 }); } } @@ -125,10 +120,6 @@ export class GameGateway const { roomId, message } = payload; const userId = client.data.userId; const userNickname = await this.gameService.getUserNickname(userId); - if (!userNickname) { - client.emit('error', { message: 'User not found.' }); - return; - } const inRoom = await this.gameRoomService.isUserInRoom(userId, roomId); if (!inRoom) { @@ -200,14 +191,26 @@ export class GameGateway gameState.whiteDeck = whiteDeck; await this.saveGameState(roomId, gameState); + const firstPlayerNickname = + await this.gameService.getUserNickname(firstPlayerId); //& 첫번째 게임 유저 this.server.to(roomId.toString()).emit('gameStart', { - starterUserId: firstPlayerId, - message: `게임을 시작합니다. 첫번째 턴은 ${firstPlayerId}의 시작입니다.`, + starterUserId: firstPlayerNickname, + message: `게임을 시작합니다. 첫번째 턴은 ${firstPlayerNickname}의 시작입니다.`, }); } + @SubscribeMessage('unReady') + async handleUnReady(client: Socket, payload: { roomId: number }) { + const { roomId } = payload; + const userId = client.data.userId; + + await this.redisService.set(`room:${roomId}:user:${userId}:ready`, 'false'); + + this.server.to(roomId.toString()).emit('ready', { userId, ready: false }); + } + // ───────────────────────────────────────── // (2) chooseInitialCards // ───────────────────────────────────────── @@ -254,13 +257,20 @@ export class GameGateway const pid = Number(pidStr); const pState = st.players[pid]; const arr: ICard[] = []; + let hasJoker = false; // 흑 카드 배분 for (let i = 0; i < pState.blackCount; i++) { const c = st.blackDeck.pop(); - if (!c) { - client.emit('error', { message: 'No more black cards left.' }); - return; + if (c.num === -1) { + if (hasJoker) { + const c1 = st.blackDeck.pop(); + st.blackDeck.push(c); + arr.push(c1); + continue; + } else { + hasJoker = true; + } } arr.push(c); } @@ -268,18 +278,23 @@ export class GameGateway // 백 카드 배분 for (let i = 0; i < pState.whiteCount; i++) { const c = st.whiteDeck.pop(); - if (!c) { - client.emit('error', { message: 'No more white cards left.' }); - return; + if (c.num === -1) { + if (hasJoker) { + const c1 = st.whiteDeck.pop(); + st.whiteDeck.push(c); + arr.push(c1); + continue; + } else { + hasJoker = true; + } } arr.push(c); } - // 카드 정렬 (조커는 자동으로 뒤로 정렬되지 않음) + // 카드 정렬 (조커는 자동으로 뒤로 정렬) arr.sort((a, b) => this.gameService.compareCard(a, b)); pState.finalHand = arr; - const hasJoker = arr.some((x) => x.num === -1); if (!hasJoker) { pState.arrangementDone = true; } @@ -297,22 +312,27 @@ export class GameGateway // 조커 카드 존재 여부 확인 const hasJoker = arr.some((x) => x.num === -1); + let possiblePositions: number[] = []; if (hasJoker) { // 조커를 배치할 수 있는 모든 가능한 위치 계산 (1부터 n+1까지) const n = arr.length; - possiblePositions = Array.from({ length: n + 1 }, (_, i) => i + 1); + const jokerCard = arr.pop(); + possiblePositions = Array.from({ length: n }, (_, i) => i); // 클라이언트에게 `handDeck` 이벤트 전송 this.server.to(sockId).emit('arrangeCard', { message: `조커카드의 위치를 정해주세요.`, + arrangeCard: true, finalHand: arr, + jokerCard, possiblePositions, }); } else { this.server.to(sockId).emit('handDeck', { message: '당신이 뽑은 최종 카드덱입니다.', + arrangeCard: false, finalHand: arr, }); const timeout = Math.floor(Math.random() * (10000 - 5000 + 1)) + 5000; @@ -337,23 +357,25 @@ export class GameGateway const st = await this.getGameState(roomId); if (!st) { - client.emit('error', { message: 'No game state found.' }); + client.emit('error', { message: '게임방을 찾을 수 없습니다.' }); return; } if (!st.players[userId]) { - client.emit('error', { message: 'Invalid user or room.' }); + client.emit('error', { message: '유저가 게임방에 존재하지 않습니다.' }); return; } const pState = st.players[userId]; const oldArr = [...pState.finalHand]; if (newOrder.length !== oldArr.length) { - client.emit('error', { message: 'Invalid newOrder length.' }); + client.emit('error', { message: '카드덱의 카드 게수가 옳지 않습니다.' }); return; } for (const c of newOrder) { if (!oldArr.some((x) => x.color === c.color && x.num === c.num)) { - client.emit('error', { message: 'newOrder has unknown card.' }); + client.emit('error', { + message: '유효하지 않은 카드가 추가되었습니다.', + }); return; } } @@ -401,11 +423,11 @@ export class GameGateway const st = await this.getGameState(roomId); if (!st) { - client.emit('error', { message: 'No game state found.' }); + client.emit('error', { message: '게임이 진행중이 아닙니다.' }); return; } if (st.turn !== userId) { - client.emit('error', { message: 'Not your turn.' }); + client.emit('error', { message: '당신의 차례가 아닙니다.' }); return; } @@ -417,7 +439,9 @@ export class GameGateway card = st.whiteDeck.pop(); } if (!card) { - client.emit('error', { message: `No more ${color} cards left.` }); + client.emit('error', { + message: `더이상 ${color} 색상의 카드가 없습니다.`, + }); return; } @@ -435,7 +459,7 @@ export class GameGateway const sockId = this.userSockets.get(userId); if (sockId) { this.server.to(sockId).emit('drawCard', { - message: `${card.color} / ${card.num}를 뽑았습니다. 삽입 가능한 위치를 정해주세요`, + message: `[${card.color}색 ${card.num}번] 카드를 뽑았습니다. 삽입 가능한 위치를 정해주세요`, possiblePositions, // 삽입 가능 위치 전달 currentHand: pState.finalHand, newlyDrawn: card, @@ -459,7 +483,7 @@ export class GameGateway const sockId = this.userSockets.get(userId); if (sockId) { this.server.to(sockId).emit('drawCard', { - message: `${card.color} / ${card.num}를 뽑았습니다. 자동으로 정렬되었습니다.`, + message: `[${card.color} 색 ${card.num}번] 카드를 뽑았습니다. 자동으로 정렬되었습니다.`, finalHand: pState.finalHand, newPosition: possiblePositions[0], newlyDrawn: card, @@ -487,7 +511,7 @@ export class GameGateway if (sockId) { this.server.to(sockId).emit('drawCard', { - message: `${card.color} / ${card.num}를 뽑았습니다. 삽입 가능한 위치를 정해주세요`, + message: `[${card.color}색 ${card.num}번] 카드를 뽑았습니다. 삽입 가능한 위치를 정해주세요`, possiblePositions, // 삽입 가능 위치 전달 currentHand: pState.finalHand, newlyDrawn: card, @@ -510,11 +534,11 @@ export class GameGateway const st = await this.getGameState(roomId); if (!st) { - client.emit('error', { message: 'No game state found.' }); + client.emit('error', { message: '게임이 진행중이 아닙니다.' }); return; } if (!st.players[userId]) { - client.emit('error', { message: 'Invalid user or room.' }); + client.emit('error', { message: '유저가 게임방에 존재하지 않습니다.' }); return; } const pState = st.players[userId]; @@ -524,12 +548,14 @@ export class GameGateway // 검증 if (newOrder.length !== oldArr.length) { - client.emit('error', { message: 'newOrder length mismatch.' }); + client.emit('error', { + message: '새로운 카드덱 갯수가 옳바르지 않습니다.', + }); return; } for (const c of newOrder) { if (!oldArr.some((o) => o.color === c.color && o.num === c.num)) { - client.emit('error', { message: 'newOrder has invalid card.' }); + client.emit('error', { message: '유효하지 않은 카드를 뽑았습니다.' }); return; } } @@ -647,11 +673,11 @@ export class GameGateway ? { ...card } : { color: card.color, isFlipped: card.isFlipped }, ); - + const userNickname = await this.gameService.getUserNickname(userId); // Guessing User에게 'correctGuess' 이벤트 전송 if (guessingUserSocketId) { this.server.to(guessingUserSocketId).emit('correctGuess', { - message: `${await this.gameService.getUserNickname(userId)}님이 ${opponentId}님의 카드 ${cardIndex + 1}번을 맞추셨습니다!`, + message: `${userNickname}님이 ${opponentId}님의 카드 ${cardIndex + 1}번을 맞추셨습니다!`, cardIndex: cardIndex, cardNumber: cardNumber, opponentFinalHand: cardInfo, @@ -661,7 +687,7 @@ export class GameGateway // Opponent User에게 'cardFlipped' 이벤트 전송 if (opponentUserSocketId) { this.server.to(opponentUserSocketId).emit('yourCardFlipped', { - message: `${await this.gameService.getUserNickname(userId)}님이 당신의 카드 ${cardIndex + 1}번을 맞추셨습니다!`, + message: `${userNickname}님이 당신의 카드 ${cardIndex + 1}번을 맞추셨습니다!`, cardIndex: cardIndex, cardNumber: cardNumber, finalHand: updateOpponentHand, @@ -735,11 +761,7 @@ export class GameGateway await this.saveGameState(roomId, st); // 모든 플레이어에게 턴 변경 알림 - const userNickname = await this.gameService.getUserNickname(st.turn); - if (!userNickname) { - client.emit('error', { message: 'User not found.' }); - return; - } + const userNickname = await this.gameService.getUserNickname(userId); this.server.to(roomId.toString()).emit('nowTurn', { turnUserId: st.turn, @@ -770,14 +792,14 @@ export class GameGateway if (winnerSocketId) { this.server.to(winnerSocketId).emit('gameOver', { result: 'win', - message: `축하합니다! ${loserNickname}님이 패배하셨습니다.`, + message: `축하합니다! ${winnerNickname}님이 승리하셨습니다.`, }); } if (loserSocketId) { this.server.to(loserSocketId).emit('gameOver', { result: 'lose', - message: `아쉽습니다. ${winnerNickname}님이 승리하셨습니다.`, + message: `아쉽습니다. ${loserNickname}님이 패배하셨습니다.`, }); } @@ -801,7 +823,7 @@ export class GameGateway }); console.log( - `게임 방 ${roomId}이 종료되었습니다. 승리자: ${winnerId}, 패배자: ${loserId}`, + `게임 방 ${roomId}이 종료되었습니다. 승리자: ${winnerNickname}, 패배자: ${loserNickname}`, ); } @@ -817,7 +839,7 @@ export class GameGateway if (!st) return; if (st.turn !== userId) { - client.emit('error', { message: 'Not your turn to end.' }); + client.emit('error', { message: '당신은 현재 턴이 아닙니다.' }); return; } @@ -875,14 +897,14 @@ export class GameGateway const s1 = this.userSockets.get(Number(p1)); if (s1) { this.server.to(s1).emit('opponentColorArrayRevealed', { - message: '상대방 색상 배열 공개 (numbers hidden).', + message: '상대방 카드덱 공개.', opponentColorArray: arr2, }); } const s2 = this.userSockets.get(Number(p2)); if (s2) { this.server.to(s2).emit('opponentColorArrayRevealed', { - message: '상대방 색상 배열 공개 (numbers hidden).', + message: '상대방 카드덱 공개.', opponentColorArray: arr1, }); }