Skip to content
Merged
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
130 changes: 76 additions & 54 deletions src/game/game.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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} 유저가 퇴장했습니다.`,
});
}
}
Expand All @@ -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);
Expand All @@ -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와 연결해서 가져오기
});
}
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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
// ─────────────────────────────────────────
Expand Down Expand Up @@ -254,32 +257,44 @@ 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);
}

// 백 카드 배분
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;
}
Expand All @@ -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;
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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];
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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}님이 패배하셨습니다.`,
});
}

Expand All @@ -801,7 +823,7 @@ export class GameGateway
});

console.log(
`게임 방 ${roomId}이 종료되었습니다. 승리자: ${winnerId}, 패배자: ${loserId}`,
`게임 방 ${roomId}이 종료되었습니다. 승리자: ${winnerNickname}, 패배자: ${loserNickname}`,
);
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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,
});
}
Expand Down
Loading