diff --git a/back/volume/src/pong/dtos/MatchmakingDto.ts b/back/volume/src/pong/dtos/MatchmakingDto.ts new file mode 100644 index 0000000..580c222 --- /dev/null +++ b/back/volume/src/pong/dtos/MatchmakingDto.ts @@ -0,0 +1,3 @@ +export class MatchmakingDto { + matchmaking!: boolean +} diff --git a/back/volume/src/pong/dtos/MatchmakingDtoValidated.ts b/back/volume/src/pong/dtos/MatchmakingDtoValidated.ts new file mode 100644 index 0000000..d83286c --- /dev/null +++ b/back/volume/src/pong/dtos/MatchmakingDtoValidated.ts @@ -0,0 +1,7 @@ +import { IsBoolean } from 'class-validator' +import { MatchmakingDto } from './MatchmakingDto' + +export class MatchmakingDtoValidated extends MatchmakingDto { + @IsBoolean() + matchmaking!: boolean +} diff --git a/back/volume/src/pong/game/Games.ts b/back/volume/src/pong/game/Games.ts index da9ab59..6734c0e 100644 --- a/back/volume/src/pong/game/Games.ts +++ b/back/volume/src/pong/game/Games.ts @@ -73,7 +73,7 @@ export class Games { return game.getGameInfo(name) } return { - yourPaddleIndex: 0, + yourPaddleIndex: -2, gameId: '', mapSize: new Point(0, 0), walls: [], diff --git a/back/volume/src/pong/game/MatchmakingQueue.ts b/back/volume/src/pong/game/MatchmakingQueue.ts new file mode 100644 index 0000000..a937edb --- /dev/null +++ b/back/volume/src/pong/game/MatchmakingQueue.ts @@ -0,0 +1,59 @@ +import { type WebSocket } from 'ws' +import { type GameCreationDtoValidated } from '../dtos/GameCreationDtoValidated' +import { DEFAULT_MAP_SIZE } from './constants' +import { type Games } from './Games' + +export class MatchmakingQueue { + games: Games + queue: Array<{ name: string, socket: WebSocket, uuid: string }> + + constructor (games: Games) { + this.games = games + this.queue = [] + } + + addPlayer (name: string, socket: WebSocket, uuid: string): boolean { + let succeeded: boolean = false + if (!this.alreadyInQueue(name)) { + this.queue.push({ name, socket, uuid }) + if (this.canCreateGame()) { + this.createGame() + } + succeeded = true + } + return succeeded + } + + removePlayer (name: string): void { + this.queue = this.queue.filter((player) => player.name !== name) + } + + alreadyInQueue (name: string): boolean { + return this.queue.some((player) => player.name === name) + } + + canCreateGame (): boolean { + return this.queue.length >= 2 + } + + createGame (): void { + const player1 = this.queue.shift() + const player2 = this.queue.shift() + if (player1 === undefined || player2 === undefined) { + return + } + const gameCreationDto: GameCreationDtoValidated = { + playerNames: [player1.name, player2.name], + map: { + size: DEFAULT_MAP_SIZE, + walls: [] + } + } + + this.games.newGame( + [player1.socket, player2.socket], + [player1.uuid, player2.uuid], + gameCreationDto + ) + } +} diff --git a/back/volume/src/pong/game/constants.ts b/back/volume/src/pong/game/constants.ts index fe2a3df..bab1b53 100644 --- a/back/volume/src/pong/game/constants.ts +++ b/back/volume/src/pong/game/constants.ts @@ -8,7 +8,8 @@ export const GAME_EVENTS = { GET_GAME_INFO: 'GET_GAME_INFO', CREATE_GAME: 'CREATE_GAME', REGISTER_PLAYER: 'REGISTER_PLAYER', - SPECTATE: 'SPECTATE' + SPECTATE: 'SPECTATE', + MATCHMAKING: 'MATCHMAKING' } export const DEFAULT_MAP_SIZE = new Point(600, 400) diff --git a/back/volume/src/pong/pong.gateway.ts b/back/volume/src/pong/pong.gateway.ts index 4439db1..b2ef6b9 100644 --- a/back/volume/src/pong/pong.gateway.ts +++ b/back/volume/src/pong/pong.gateway.ts @@ -18,6 +18,8 @@ import { type Game } from './game/Game' import { plainToClass } from 'class-transformer' import { PointDtoValidated } from './dtos/PointDtoValidated' import { StringDtoValidated } from './dtos/StringDtoValidated' +import { MatchmakingQueue } from './game/MatchmakingQueue' +import { MatchmakingDtoValidated } from './dtos/MatchmakingDtoValidated' interface WebSocketWithId extends WebSocket { id: string @@ -27,6 +29,7 @@ interface WebSocketWithId extends WebSocket { export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect { private readonly games: Games = new Games() private readonly socketToPlayerName = new Map() + private readonly matchmakingQueue = new MatchmakingQueue(this.games) handleConnection (client: WebSocketWithId): void { const uuid = randomUUID() @@ -93,7 +96,7 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect { @ConnectedSocket() client: WebSocketWithId, @MessageBody() gameCreationDto: GameCreationDtoValidated - ): void { + ): { event: string, data: boolean } { const realGameCreationDto: GameCreationDtoValidated = plainToClass( GameCreationDtoValidated, gameCreationDto @@ -126,8 +129,10 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect { [player1Socket.id, player2Socket.id], realGameCreationDto ) + return { event: GAME_EVENTS.CREATE_GAME, data: true } } } + return { event: GAME_EVENTS.CREATE_GAME, data: false } } @SubscribeMessage(GAME_EVENTS.READY) @@ -147,10 +152,37 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect { @ConnectedSocket() client: WebSocketWithId, @MessageBody() playerToSpectate: StringDtoValidated - ): void { + ): { event: string, data: boolean } { + let succeeded: boolean = false const name: string | undefined = this.socketToPlayerName.get(client) if (name !== undefined) { this.games.spectateGame(playerToSpectate.value, client, client.id, name) + succeeded = true + } + return { event: GAME_EVENTS.SPECTATE, data: succeeded } + } + + @UsePipes(new ValidationPipe({ whitelist: true })) + @SubscribeMessage(GAME_EVENTS.MATCHMAKING) + updateMatchmaking ( + @ConnectedSocket() + client: WebSocketWithId, + @MessageBody() matchmakingUpdateData: MatchmakingDtoValidated + ): { event: string, data: MatchmakingDtoValidated } { + let isMatchmaking: boolean = false + const name: string | undefined = this.socketToPlayerName.get(client) + if (name !== undefined) { + if (matchmakingUpdateData.matchmaking) { + if (this.matchmakingQueue.addPlayer(name, client, client.id)) { + isMatchmaking = true + } + } else { + this.matchmakingQueue.removePlayer(name) + } + } + return { + event: GAME_EVENTS.MATCHMAKING, + data: { matchmaking: isMatchmaking } } } } diff --git a/front/volume/src/components/Play.svelte b/front/volume/src/components/Play.svelte index a5ef1cf..093c9f4 100644 --- a/front/volume/src/components/Play.svelte +++ b/front/volume/src/components/Play.svelte @@ -1,10 +1,15 @@

Choose a gamemode

- + + + {#if creatingMatch} + + {/if}
diff --git a/front/volume/src/components/Pong/MapCustomization.svelte b/front/volume/src/components/Pong/MapCustomization.svelte index f1b0f81..d3f21cf 100644 --- a/front/volume/src/components/Pong/MapCustomization.svelte +++ b/front/volume/src/components/Pong/MapCustomization.svelte @@ -65,7 +65,7 @@
-

Map Customization:

+

Map Customization:

Width: diff --git a/front/volume/src/components/Pong/Matchmaking.svelte b/front/volume/src/components/Pong/Matchmaking.svelte new file mode 100644 index 0000000..66ce9be --- /dev/null +++ b/front/volume/src/components/Pong/Matchmaking.svelte @@ -0,0 +1,32 @@ + + +
+
+

Matchmaking...

+ +
+
+ + diff --git a/front/volume/src/components/Pong/Pong.svelte b/front/volume/src/components/Pong/Pong.svelte index 4257ae0..8c58ffb 100644 --- a/front/volume/src/components/Pong/Pong.svelte +++ b/front/volume/src/components/Pong/Pong.svelte @@ -1,38 +1,27 @@
-
- {#if connected} - Your name: - -
- -
- Other player name: - -
- -
- -
- - -
- {:else} - Connecting to game server... - {/if} - + {#if !loggedIn} + Log in: + + +
+ {/if} + -
+ + diff --git a/front/volume/src/components/Pong/SpectateFriend.svelte b/front/volume/src/components/Pong/SpectateFriend.svelte new file mode 100644 index 0000000..54eaebb --- /dev/null +++ b/front/volume/src/components/Pong/SpectateFriend.svelte @@ -0,0 +1,44 @@ + + +
+
+ + +
+
+ + diff --git a/front/volume/src/components/Pong/constants.ts b/front/volume/src/components/Pong/constants.ts index 546fa84..8938625 100644 --- a/front/volume/src/components/Pong/constants.ts +++ b/front/volume/src/components/Pong/constants.ts @@ -9,6 +9,7 @@ export const GAME_EVENTS = { CREATE_GAME: "CREATE_GAME", REGISTER_PLAYER: "REGISTER_PLAYER", SPECTATE: "SPECTATE", + MATCHMAKING: "MATCHMAKING", }; export const DEFAULT_MAP_SIZE = new Point(600, 400); diff --git a/front/volume/src/components/Pong/dtos/MatchmakingDto.ts b/front/volume/src/components/Pong/dtos/MatchmakingDto.ts new file mode 100644 index 0000000..0d8b6ea --- /dev/null +++ b/front/volume/src/components/Pong/dtos/MatchmakingDto.ts @@ -0,0 +1,3 @@ +export class MatchmakingDto { + matchmaking!: boolean; +}