Skip to content

Commit

Permalink
fix: 시간 초과된 게임 초대 데이터 처리
Browse files Browse the repository at this point in the history
  • Loading branch information
tomatoziyun committed Dec 14, 2023
1 parent 018532f commit 85128a5
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 134 deletions.
2 changes: 2 additions & 0 deletions src/common/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export const EVENT_USER_STATUS = 'userStatus'; // 유저의 상태 변경 이벤
export const EVENT_GAME_INVITATION = 'gameInvitation';

export const EVENT_GAME_INVITATION_REPLY = 'gameInvitationReply';

export const EVENT_GAME_READY = 'gameReady';
6 changes: 6 additions & 0 deletions src/game/dto/accept-game-param.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { User } from '../../users/entities/user.entity';

export class acceptGameParamDto {
invitationId: number;
invitedUser: User;
}
37 changes: 35 additions & 2 deletions src/game/dto/create-game-param.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,37 @@
import { GameStatus, GameType } from '../../common/enum';

export class CreateGameParamDto {
invitationId: number;
invitedUserId: number;
winnerId: number;
loserId: number;
gameType: GameType;
winnerScore: number;
loserScore: number;
gameStatus: GameStatus;
ballSpeed: number;
racketSize: number;

constructor(player1Id: number, player2Id: number, gameType: GameType) {
this.winnerId = player1Id;
this.loserId = player2Id;
this.gameType = gameType;
this.winnerScore = 0;
this.loserScore = 0;
this.gameStatus = GameStatus.WAITING;
if (
this.gameType === GameType.SPECIAL_INVITE ||
this.gameType === GameType.SPECIAL_MATCHING
) {
this.ballSpeed = this.getRandomNumber(1, 3);
this.racketSize = this.getRandomNumber(1, 3);
} else {
// Set to 1 for NORMAL
this.ballSpeed = 1;
this.racketSize = 1;
}
}

private getRandomNumber(min: number, max: number): number {
// Generate a random integer between min and max (inclusive)
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}
37 changes: 0 additions & 37 deletions src/game/dto/game-param.dto.ts

This file was deleted.

34 changes: 34 additions & 0 deletions src/game/dto/game.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { GameStatus } from '../../common/enum';
import { Game } from '../entities/game.entity';
import { ViewMapDto } from './view-map.dto';

export class GameDto {
private gameId: number;
player1Id: number;
player2Id: number;
winnerId: number | null;
loserId: number | null;
score1: number;
score2: number;
gameStatus: GameStatus;
viewMap: ViewMapDto;

constructor(game: Game) {
this.setGameId(game.id);
this.player1Id = game.winnerId;
this.player2Id = game.loserId;
this.winnerId = null;
this.loserId = null;
this.score1 = game.winnerScore;
this.score2 = game.loserScore;
this.gameStatus = game.gameStatus;
this.viewMap = new ViewMapDto(game.ballSpeed, game.racketSize);
}
getGameId() {
return this.gameId;
}

private setGameId(gameId: number) {
this.gameId = gameId;
}
}
1 change: 1 addition & 0 deletions src/game/dto/gateway-send-invitation-reaponse.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export class GatewaySendInvitationReplyDto {
targetUserId: number;
targetUserChannelSocketId: string;
isAccepted: boolean;
gameId: number | null;
Expand Down
43 changes: 43 additions & 0 deletions src/game/dto/view-map.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// ball 변하는 값
type ball = {
x: number;
y: number;
speed: number;
};

// racket 변하는 값
type racket = {
y: number;
size: number;
};

export class ViewMapDto {
ball: ball;
racket1: racket;
racket2: racket;

constructor(
ballSpeed: number,
racketSize: number,

// 고정값
private readonly canvasWidth = 1400,
private readonly canvasHeight = 1000,

private readonly ballRadius = 2,

private readonly racketWidth = canvasWidth * 0.1,
private readonly racketHeight = canvasHeight * 0.4,
private readonly racket1X = 0,
private readonly racket2X = canvasWidth - racketWidth,
) {
this.ball.x = canvasWidth / 2;
this.ball.y = canvasHeight / 2;
this.ball.speed = ballSpeed;

this.racket1.y = canvasHeight / 2 - racketHeight / 2;
this.racket2.y = canvasHeight / 2 - racketHeight / 2;
this.racket1.size = racketSize;
this.racket2.size = racketSize;
}
}
25 changes: 0 additions & 25 deletions src/game/game-invitation.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { GameInvitation } from './entities/game-invitation.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { CreateGameInvitationParamDto } from './dto/create-invitation-param.dto';
import { DBUpdateFailureException } from '../common/exception/custom-exception';
import { DeleteGameInvitationsParamDto } from './dto/delete-invitations-param.dto';

export class GameInvitationRepository extends Repository<GameInvitation> {
constructor(
Expand All @@ -28,28 +27,4 @@ export class GameInvitationRepository extends Repository<GameInvitation> {

return result;
}

async deleteGameInvitaitons(
deleteInvitationsDto: DeleteGameInvitationsParamDto,
) {
const invitingUserId = deleteInvitationsDto.invitingUserId;
const invitedUserId = deleteInvitationsDto.invitedUserId;

const result = await this.dataSource.query(
`
UPDATE game_invitation
SET "deletedAt" = CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
WHERE "invitingUserId" = $1
AND "invitedUserId" = $2
AND "deletedAt" IS NULL
`,
[invitingUserId, invitedUserId],
);
// AND now() - "createdAt" >= INTERVAL '10 seconds'

if (result[1] === 0)
throw DBUpdateFailureException(
'soft delete game invitations failed',
);
}
}
7 changes: 3 additions & 4 deletions src/game/game.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ import { User } from '../users/entities/user.entity';
import { ApiTags } from '@nestjs/swagger';
import { CreateGameInvitationParamDto } from './dto/create-invitation-param.dto';
import { PositiveIntPipe } from '../common/pipes/positiveInt.pipe';
import { GameParamDto } from './dto/game-param.dto';
import { AuthGuard } from '@nestjs/passport';
import { DeleteGameInvitationParamDto } from './dto/delete-invitation-param.dto';
import { CreateGameParamDto } from './dto/create-game-param.dto';
import { acceptGameParamDto } from './dto/accept-game-param.dto';

@Controller('game')
@ApiTags('game')
Expand Down Expand Up @@ -51,8 +50,8 @@ export class GameController {
@Body('gameInvitationId', ParseIntPipe, PositiveIntPipe)
invitationId: number,
) {
const createGameParamDto: CreateGameParamDto = {
invitedUserId: user.id,
const createGameParamDto: acceptGameParamDto = {
invitedUser: user,
invitationId: invitationId,
};
await this.gameService.createGame(createGameParamDto);
Expand Down
58 changes: 36 additions & 22 deletions src/game/game.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ import {
import { UsersRepository } from '../users/users.repository';
import { Server, Socket } from 'socket.io';
import { AuthService } from '../auth/auth.service';
import { ParseIntPipe, UseFilters } from '@nestjs/common';
import { UseFilters } from '@nestjs/common';
import { WsExceptionFilter } from '../common/exception/custom-ws-exception.filter';
import { GatewayCreateGameInvitationParamDto } from './dto/gateway-create-invitation-param.dto';
import {
EVENT_GAME_INVITATION,
EVENT_GAME_INVITATION_REPLY,
EVENT_GAME_READY,
} from '../common/events';
import { ChannelsGateway } from '../channels/channels.gateway';
import { GatewaySendInvitationReplyDto } from './dto/gateway-send-invitation-reaponse.dto';
import { PositiveIntPipe } from '../common/pipes/positiveInt.pipe';
import { GameRepository } from './game.repository';
import { WSBadRequestException } from '../common/exception/custom-exception';
import { GameDto } from './dto/game.dto';

@WebSocketGateway({ namespace: 'game' })
@UseFilters(WsExceptionFilter)
Expand All @@ -35,7 +36,9 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {

@WebSocketServer()
server: Server;
private connectedClients: Map<number, Socket> = new Map();
private userIdToClient: Map<number, Socket> = new Map();
private userIdToGameId: Map<number, number> = new Map();
private gameIdToGameDto: Map<number, GameDto> = new Map();

async handleConnection(@ConnectedSocket() client: Socket) {
const user = await this.authService.getUserFromSocket(client);
Expand All @@ -48,7 +51,7 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
await this.usersRepository.update(user.id, {
gameSocketId: client.id,
});
this.connectedClients.set(user.id, client);
this.userIdToClient.set(user.id, client);
}

async handleDisconnect(@ConnectedSocket() client: Socket) {
Expand All @@ -58,7 +61,7 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
await this.usersRepository.update(user.id, {
gameSocketId: null,
});
this.connectedClients.delete(user.id);
this.userIdToClient.delete(user.id);
client.rooms.clear();
}

Expand All @@ -84,10 +87,13 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
async sendInvitationReply(
sendInvitationReplyDto: GatewaySendInvitationReplyDto,
) {
const targetUserId = sendInvitationReplyDto.targetUserId;
const targetUserChannelSocketId =
sendInvitationReplyDto.targetUserChannelSocketId;
const isAccepted = sendInvitationReplyDto.isAccepted;
const gameId = sendInvitationReplyDto.gameId;
// gameId 저장하기
if (isAccepted && gameId) this.userIdToGameId.set(targetUserId, gameId);

this.channelsGateway.server
.to(targetUserChannelSocketId)
Expand All @@ -98,35 +104,43 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
}

@SubscribeMessage('gameRequest')
async prepareGame(
@ConnectedSocket() client: Socket,
@MessageBody() data: { gameId: number },
) {
async prepareGame(@ConnectedSocket() client: Socket) {
/* TODO: Game 세팅
* 1. game DB 만들기
* 1. game Data 만들기
*
* 2. game room join하기 ✅
* 3. game start event emit ✅*/
* 2. map 준비하기
* 3. game room join하기 ✅
* 4. game start event emit ✅*/
const user = await this.authService.getUserFromSocket(client);
if (!user) return client.disconnect();
const gameId = this.userIdToGameId.get(user.id);

const game = await this.gameRepository.findOne({
where: { id: data.gameId },
where: { id: gameId },
});
if (!game)
throw WSBadRequestException(
`해당하는 game id ${data.gameId}없습니다`,
`gameId ${gameId}유효하지 않습니다`,
);

const player1Socket = this.connectedClients.get(game.winnerId);
if (!player1Socket)
throw WSBadRequestException(
`player1 ${game.winnerId} 소켓이 없습니다`,
);
const player2Socket = this.connectedClients.get(game.winnerId);
if (!player2Socket)
const gameDto = new GameDto(game);
if (!this.gameIdToGameDto.get(game.id))
this.gameIdToGameDto.set(game.id, gameDto);

const player1Socket = this.userIdToClient.get(gameDto.player1Id);
const player2Socket = this.userIdToClient.get(gameDto.player2Id);
if (!player1Socket || !player2Socket)
throw WSBadRequestException(
`player2 ${game.loserId} 소켓이 없습니다`,
`두 플레이어의 게임 소켓이 모두 필요합니다. 게임 불가`,
);
player1Socket.join(gameDto.getGameId().toString());
player2Socket.join(gameDto.getGameId().toString());

// 프론트 무슨 데이터 필요한지 ?
this.server.to(player1Socket.id).emit(EVENT_GAME_READY, {});
this.server.to(player2Socket.id).emit(EVENT_GAME_READY, {});
}

@SubscribeMessage('gameStart')
async gameStart(@ConnectedSocket() client: Socket) {}
}
9 changes: 6 additions & 3 deletions src/game/game.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@ import { InjectRepository } from '@nestjs/typeorm';
import { GAME_DEFAULT_PAGE_SIZE } from 'src/common/constants';
import { DataSource, Repository } from 'typeorm';
import { Game } from './entities/game.entity';
import { CreateGameInvitationParamDto } from './dto/create-invitation-param.dto';
import { DBUpdateFailureException } from '../common/exception/custom-exception';
import { GameParamDto } from './dto/game-param.dto';
import { CreateGameParamDto } from './dto/create-game-param.dto';
export class GameRepository extends Repository<Game> {
constructor(@InjectRepository(Game) private dataSource: DataSource) {
super(Game, dataSource.manager);
}

async createGame(gameParamDto: GameParamDto) {
async createGame(gameParamDto: CreateGameParamDto) {
const game = this.create(gameParamDto);
// console.log(JSON.stringify(game)); // {"winnerId":8,"loserId":7,"gameType":"NORMAL_INVITE","winnerScore":0,"loserScore":0,"gameStatus":"WAITING","ballSpeed":1,"racketSize":1}
// console.log(game.id); // undefined

const result = await this.save(game);
// console.log(JSON.stringify(result)); // {"winnerId":8,"loserId":7,"gameType":"NORMAL_INVITE","winnerScore":0,"loserScore":0,"gameStatus":"WAITING","ballSpeed":1,"racketSize":1,"deletedAt":null,"id":2,"createdAt":"2023-12-14T04:38:55.989Z","updatedAt":"2023-12-14T04:38:55.989Z"}
// console.log(result.id); // 2

if (!result)
throw DBUpdateFailureException('create game invitation failed');
Expand Down
Loading

0 comments on commit 85128a5

Please sign in to comment.