diff --git a/back/volume/src/main.ts b/back/volume/src/main.ts index 03c0cf0..458e476 100644 --- a/back/volume/src/main.ts +++ b/back/volume/src/main.ts @@ -12,7 +12,7 @@ async function bootstrap (): Promise { const logger = new Logger() const app = await NestFactory.create(AppModule) const port = - process.env.BACK_PORT && process.env.BACK_PORT !== '' + process.env.BACK_PORT !== undefined && process.env.BACK_PORT !== '' ? +process.env.BACK_PORT : 3001 const cors = { @@ -28,7 +28,7 @@ async function bootstrap (): Promise { resave: false, saveUninitialized: false, secret: - process.env.JWT_SECRET && process.env.JWT_SECRET !== '' + process.env.JWT_SECRET !== undefined && process.env.JWT_SECRET !== '' ? process.env.JWT_SECRET : 'secret' }) diff --git a/back/volume/src/pong/dtos/GameInfo.ts b/back/volume/src/pong/dtos/GameInfo.ts index ad86391..97734ce 100644 --- a/back/volume/src/pong/dtos/GameInfo.ts +++ b/back/volume/src/pong/dtos/GameInfo.ts @@ -9,4 +9,5 @@ export class GameInfo { playerXOffset!: number ballSize!: Point winScore!: number + ranked!: boolean } diff --git a/back/volume/src/pong/game/Game.ts b/back/volume/src/pong/game/Game.ts index 7b54ada..890d08c 100644 --- a/back/volume/src/pong/game/Game.ts +++ b/back/volume/src/pong/game/Game.ts @@ -17,38 +17,6 @@ import { type GameUpdate } from '../dtos/GameUpdate' import { type GameInfo } from '../dtos/GameInfo' import { type PongService } from '../pong.service' -function gameLoop (game: Game): void { - const canvasRect: Rect = new Rect( - new Point(game.map.size.x / 2, game.map.size.y / 2), - new Point(game.map.size.x, game.map.size.y) - ) - - game.ball.update( - canvasRect, - game.players.map((p) => p.paddle), - game.map - ) - const indexPlayerScored: number = game.ball.getIndexPlayerScored() - if (indexPlayerScored !== -1) { - game.players[indexPlayerScored].score += 1 - if (game.players[indexPlayerScored].score >= DEFAULT_WIN_SCORE) { - console.log(`${game.players[indexPlayerScored].name} won!`) - void game.stop() - } - } - - const data: GameUpdate = { - paddlesPositions: game.players.map((p) => p.paddle.rect.center), - ballPosition: game.ball.rect.center, - scores: game.players.map((p) => p.score) - } - const websocketData: string = formatWebsocketData( - GAME_EVENTS.GAME_TICK, - data - ) - game.broadcastGame(websocketData) -} - export class Game { id: string timer: NodeJS.Timer | null @@ -57,6 +25,7 @@ export class Game { players: Player[] = [] spectators: Spectator[] = [] playing: boolean + ranked: boolean gameStoppedCallback: (name: string) => void constructor ( @@ -65,11 +34,13 @@ export class Game { names: string[], map: MapDtoValidated, gameStoppedCallback: (name: string) => void, - private readonly pongService: PongService + private readonly pongService: PongService, + ranked: boolean ) { this.id = randomUUID() this.timer = null this.playing = false + this.ranked = ranked this.map = map this.gameStoppedCallback = gameStoppedCallback this.ball = new Ball(new Point(this.map.size.x / 2, this.map.size.y / 2)) @@ -88,7 +59,8 @@ export class Game { paddleSize: DEFAULT_PADDLE_SIZE, playerXOffset: DEFAULT_PLAYER_X_OFFSET, ballSize: DEFAULT_BALL_SIZE, - winScore: DEFAULT_WIN_SCORE + winScore: DEFAULT_WIN_SCORE, + ranked: this.ranked } } @@ -108,47 +80,41 @@ export class Game { this.players.push( new Player(socket, uuid, name, paddleCoords, this.map.size) ) - } - - removePlayer (name: string): void { - const playerIndex: number = this.players.findIndex((p) => p.name === name) - if (playerIndex !== -1) { - this.players.splice(playerIndex, 1) - if (this.players.length < 2) { - void this.stop() - } + if (this.ranked) { + this.ready(name) } } ready (name: string): void { const playerIndex: number = this.players.findIndex((p) => p.name === name) - if (playerIndex !== -1) { + if (playerIndex !== -1 && !this.players[playerIndex].ready) { this.players[playerIndex].ready = true - console.log(`${this.players[playerIndex].name} is ready!`) - if (this.players.every((p) => p.ready)) { + console.log(`${this.players[playerIndex].name} is ready`) + if (this.players.length === 2 && this.players.every((p) => p.ready)) { this.start() } } } - private start (): boolean { + private start (): void { if (this.timer === null && this.players.length === 2) { this.ball = new Ball(new Point(this.map.size.x / 2, this.map.size.y / 2)) this.players.forEach((p) => { p.newGame() }) - this.timer = setInterval(gameLoop, 1000 / GAME_TICKS, this) - this.broadcastGame(formatWebsocketData(GAME_EVENTS.START_GAME)) this.playing = true - return true + this.broadcastGame(formatWebsocketData(GAME_EVENTS.START_GAME)) + this.timer = setInterval(this.gameLoop.bind(this), 1000 / GAME_TICKS) + console.log(`Game ${this.id} started`) } - return false } async stop (): Promise { if (this.timer !== null) { await this.pongService.saveResult(this.players) - this.gameStoppedCallback(this.players[0].name) + if (this.players.length !== 0) { + this.gameStoppedCallback(this.players[0].name) + } clearInterval(this.timer) this.timer = null @@ -165,7 +131,7 @@ export class Game { } } - broadcastGame (data: string): void { + private broadcastGame (data: string): void { this.players.forEach((p) => { p.socket.send(data) }) @@ -177,4 +143,36 @@ export class Game { isPlaying (): boolean { return this.playing } + + private gameLoop (): void { + const canvasRect: Rect = new Rect( + new Point(this.map.size.x / 2, this.map.size.y / 2), + new Point(this.map.size.x, this.map.size.y) + ) + + this.ball.update( + canvasRect, + this.players.map((p) => p.paddle), + this.map + ) + const indexPlayerScored: number = this.ball.getIndexPlayerScored() + if (indexPlayerScored !== -1) { + this.players[indexPlayerScored].score += 1 + if (this.players[indexPlayerScored].score >= DEFAULT_WIN_SCORE) { + console.log(`${this.players[indexPlayerScored].name} won`) + void this.stop() + } + } + + const data: GameUpdate = { + paddlesPositions: this.players.map((p) => p.paddle.rect.center), + ballPosition: this.ball.rect.center, + scores: this.players.map((p) => p.score) + } + const websocketData: string = formatWebsocketData( + GAME_EVENTS.GAME_TICK, + data + ) + this.broadcastGame(websocketData) + } } diff --git a/back/volume/src/pong/game/Games.ts b/back/volume/src/pong/game/Games.ts index 87a41f6..8ce0bb3 100644 --- a/back/volume/src/pong/game/Games.ts +++ b/back/volume/src/pong/game/Games.ts @@ -21,7 +21,8 @@ export class Games { newGame ( sockets: WebSocket[], uuids: string[], - gameCreationDto: GameCreationDtoValidated + gameCreationDto: GameCreationDtoValidated, + ranked: boolean ): void { const names: string[] = gameCreationDto.playerNames const map: GameMap = { @@ -35,8 +36,9 @@ export class Games { uuids, names, map, - this.gameStopped.bind(this, names[0]), - this.pongService + this.deleteGame.bind(this, names[0]), + this.pongService, + ranked ) ) this.playerNameToGameIndex.set(names[0], this.games.length - 1) @@ -49,13 +51,6 @@ export class Games { } } - removePlayer (name: string): void { - const game: Game | undefined = this.playerGame(name) - if (game !== undefined) { - game.removePlayer(name) - } - } - ready (name: string): void { const game: Game | undefined = this.playerGame(name) if (game !== undefined) { @@ -63,7 +58,7 @@ export class Games { } } - private gameStopped (name: string): void { + private deleteGame (name: string): void { const game: Game | undefined = this.playerGame(name) if (game !== undefined) { this.games.splice(this.games.indexOf(game), 1) @@ -87,7 +82,8 @@ export class Games { paddleSize: DEFAULT_PADDLE_SIZE, playerXOffset: DEFAULT_PLAYER_X_OFFSET, ballSize: DEFAULT_BALL_SIZE, - winScore: DEFAULT_WIN_SCORE + winScore: DEFAULT_WIN_SCORE, + ranked: false } } @@ -123,4 +119,12 @@ export class Games { this.games[gameIndex].addSpectator(socket, uuid, name) } } + + async leaveGame (name: string): Promise { + const game: Game | undefined = this.playerGame(name) + if (game !== undefined && !game.ranked) { + await game.stop() + this.deleteGame(name) + } + } } diff --git a/back/volume/src/pong/game/MatchmakingQueue.ts b/back/volume/src/pong/game/MatchmakingQueue.ts index a937edb..12932d1 100644 --- a/back/volume/src/pong/game/MatchmakingQueue.ts +++ b/back/volume/src/pong/game/MatchmakingQueue.ts @@ -49,11 +49,13 @@ export class MatchmakingQueue { walls: [] } } + const ranked = true this.games.newGame( [player1.socket, player2.socket], [player1.uuid, player2.uuid], - gameCreationDto + gameCreationDto, + ranked ) } } diff --git a/back/volume/src/pong/game/constants.ts b/back/volume/src/pong/game/constants.ts index ff44063..6c7daba 100644 --- a/back/volume/src/pong/game/constants.ts +++ b/back/volume/src/pong/game/constants.ts @@ -9,7 +9,8 @@ export const GAME_EVENTS = { CREATE_GAME: 'CREATE_GAME', REGISTER_PLAYER: 'REGISTER_PLAYER', SPECTATE: 'SPECTATE', - MATCHMAKING: 'MATCHMAKING' + MATCHMAKING: 'MATCHMAKING', + LEAVE_GAME: 'LEAVE_GAME' } 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 5d2c92a..c30ddbe 100644 --- a/back/volume/src/pong/pong.gateway.ts +++ b/back/volume/src/pong/pong.gateway.ts @@ -57,7 +57,7 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect { ): void { const name: string | undefined = this.socketToPlayerName.get(client) const game: Game | undefined = this.games.playerGame(name) - if (game !== undefined && game.isPlaying()) { + if (game?.isPlaying() !== undefined) { void game.stop() } if (name !== undefined) { @@ -152,10 +152,12 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect { (client.id === player1Socket.id || client.id === player2Socket.id) && player1Socket.id !== player2Socket.id ) { + const ranked = false this.games.newGame( [player1Socket, player2Socket], [player1Socket.id, player2Socket.id], - realGameCreationDto + realGameCreationDto, + ranked ) return { event: GAME_EVENTS.CREATE_GAME, data: true } } @@ -167,11 +169,14 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect { ready ( @ConnectedSocket() client: WebSocketWithId - ): void { + ): { event: string, data: boolean } { + let succeeded: boolean = false const name: string | undefined = this.socketToPlayerName.get(client) if (name !== undefined) { this.games.ready(name) + succeeded = true } + return { event: GAME_EVENTS.READY, data: succeeded } } @UsePipes(new ValidationPipe({ whitelist: true })) @@ -213,4 +218,16 @@ export class PongGateway implements OnGatewayConnection, OnGatewayDisconnect { data: { matchmaking: isMatchmaking } } } + + @UsePipes(new ValidationPipe({ whitelist: true })) + @SubscribeMessage(GAME_EVENTS.LEAVE_GAME) + leaveGame ( + @ConnectedSocket() + client: WebSocketWithId + ): void { + const name: string | undefined = this.socketToPlayerName.get(client) + if (name !== undefined) { + void this.games.leaveGame(name) + } + } } diff --git a/front/volume/src/components/MatchHistory.svelte b/front/volume/src/components/MatchHistory.svelte index c068ee7..c2c9e12 100644 --- a/front/volume/src/components/MatchHistory.svelte +++ b/front/volume/src/components/MatchHistory.svelte @@ -11,7 +11,7 @@ export let matches: Array = []; function displayDate(str: string) { const splitT = str.split("T"); - const splitDate = splitT[0].split('-') + const splitDate = splitT[0].split("-"); const splitDot = splitT[1].split("."); return `${splitDate[1]}/${splitDate[2]}-${splitDot[0]}`; } @@ -21,27 +21,30 @@
{#if matches.length > 0} - - - - - - - - - - - - - {#each matches.slice(0, 10) as match} - - - - - - {/each} - -
Last 10 monkey games
DatePlayersScores
{displayDate(match.date.toString())}{match.players[0].username}
{match.players[1].username}
{match.score[0]}
{match.score[1]}
+ + + + + + + + + + + + + {#each matches.slice(0, 10) as match} + + + + + + {/each} + +
Last 10 monkey games
DatePlayersScores
{displayDate(match.date.toString())}{match.players[0].username}
{match.players[1] + .username}
{match.score[0]}
{match.score[1]}
{:else}

No matches to display

{/if} @@ -69,15 +72,14 @@ border-radius: 5px; padding: 1rem; width: 300px; - display:flex; + display: flex; justify-content: center; } - + td { - border:1px solid #111; + border: 1px solid #111; text-align: center; max-width: 15ch; overflow: hidden; } - diff --git a/front/volume/src/components/NavBar.svelte b/front/volume/src/components/NavBar.svelte index b2866ff..001ceeb 100644 --- a/front/volume/src/components/NavBar.svelte +++ b/front/volume/src/components/NavBar.svelte @@ -26,30 +26,22 @@ {#each links as link} {#if link.text === "Leaderboard"}
  • - +
  • {/if} {#if link.text === "Spectate"}
  • - +
  • {/if} {#if link.text === "Channels"}
  • - +
  • {/if} {#if link.text === "Friends"}
  • - +
  • {/if} {#if link.text === "Profile"} @@ -61,9 +53,7 @@ {/if} {#if link.text === "History"}
  • - +
  • {/if} {#if link.text === "Home"} diff --git a/front/volume/src/components/Pong/Game.ts b/front/volume/src/components/Pong/Game.ts index 377c341..ce835c7 100644 --- a/front/volume/src/components/Pong/Game.ts +++ b/front/volume/src/components/Pong/Game.ts @@ -20,6 +20,8 @@ export class Game { drawInterval: NodeJS.Timer; elementsColor: string; backgroundColor: string; + ranked: boolean; + youAreReady: boolean; constructor( renderCanvas: HTMLCanvasElement, @@ -33,10 +35,13 @@ export class Game { this.context = context; this.players = []; this.my_paddle = null; + this.id = ""; this.walls = []; this.drawInterval = null; this.elementsColor = elementsColor; this.backgroundColor = backgroundColor; + this.ranked = false; + this.youAreReady = false; } setInfo(data: GameInfo) { @@ -44,6 +49,8 @@ export class Game { this.renderCanvas.height = data.mapSize.y; this.canvas.width = data.mapSize.x; this.canvas.height = data.mapSize.y; + this.ranked = data.ranked; + this.youAreReady = false; this.ball = new Ball( new Point(this.canvas.width / 2, this.canvas.height / 2), data.ballSize @@ -76,26 +83,28 @@ export class Game { } start(socket: WebSocket) { - if (this.my_paddle) { - this.renderCanvas.addEventListener("pointermove", (e) => { - this.my_paddle.move(e); - const data: Point = this.my_paddle.rect.center; - socket.send(formatWebsocketData(GAME_EVENTS.PLAYER_MOVE, data)); - }); - console.log("Game started!"); - } + // if (this.my_paddle) { + this.renderCanvas.addEventListener("pointermove", (e) => { + this.my_paddle.move(e); + const data: Point = this.my_paddle.rect.center; + socket.send(formatWebsocketData(GAME_EVENTS.PLAYER_MOVE, data)); + }); + console.log("Game started!"); + // } } update(data: GameUpdate) { - if (this.players[0].paddle != this.my_paddle) { - this.players[0].paddle.rect.center = data.paddlesPositions[0]; - } - if (this.players[1].paddle != this.my_paddle) { - this.players[1].paddle.rect.center = data.paddlesPositions[1]; - } - this.ball.rect.center = data.ballPosition; - for (let i = 0; i < data.scores.length; i++) { - this.players[i].score = data.scores[i]; + if (this.id !== "") { + if (this.players[0].paddle != this.my_paddle) { + this.players[0].paddle.rect.center = data.paddlesPositions[0]; + } + if (this.players[1].paddle != this.my_paddle) { + this.players[1].paddle.rect.center = data.paddlesPositions[1]; + } + this.ball.rect.center = data.ballPosition; + for (let i = 0; i < data.scores.length; i++) { + this.players[i].score = data.scores[i]; + } } } diff --git a/front/volume/src/components/Pong/GameComponent.svelte b/front/volume/src/components/Pong/GameComponent.svelte index a4917bd..d2fc438 100644 --- a/front/volume/src/components/Pong/GameComponent.svelte +++ b/front/volume/src/components/Pong/GameComponent.svelte @@ -1,6 +1,7 @@ diff --git a/front/volume/src/components/Pong/GameCreation.svelte b/front/volume/src/components/Pong/GameCreation.svelte index 4619f24..ac042f4 100644 --- a/front/volume/src/components/Pong/GameCreation.svelte +++ b/front/volume/src/components/Pong/GameCreation.svelte @@ -7,6 +7,7 @@ export let username: string; export let socket: WebSocket; + let map: Map = new Map(DEFAULT_MAP_SIZE.clone(), []); let otherUsername: string = "Garfield"; diff --git a/front/volume/src/components/Pong/MapCustomization.svelte b/front/volume/src/components/Pong/MapCustomization.svelte index 4a94656..604fb69 100644 --- a/front/volume/src/components/Pong/MapCustomization.svelte +++ b/front/volume/src/components/Pong/MapCustomization.svelte @@ -50,10 +50,7 @@ function addWall(e: MouseEvent) { const rect: any = gameCanvas.getBoundingClientRect(); - const wall = new Rect( - getMapXY(e), - new Point(wallWidth, wallHeight) - ); + const wall = new Rect(getMapXY(e), new Point(wallWidth, wallHeight)); const ballSpawnArea = new Rect( new Point(map.size.x / 2, map.size.y / 2), new Point(DEFAULT_BALL_SIZE.x * 5, DEFAULT_BALL_SIZE.y * 5) @@ -78,8 +75,8 @@ function getMapXY(e: MouseEvent): Point { const canvasPoint: Point = getCanvasXY(new Point(e.pageX, e.pageY)); - const x = canvasPoint.x * gameCanvas.width / gameCanvas.clientWidth; - const y = canvasPoint.y * gameCanvas.height / gameCanvas.clientHeight; + const x = (canvasPoint.x * gameCanvas.width) / gameCanvas.clientWidth; + const y = (canvasPoint.y * gameCanvas.height) / gameCanvas.clientHeight; return new Point(x, y); } diff --git a/front/volume/src/components/Pong/Pong.svelte b/front/volume/src/components/Pong/Pong.svelte index 716b571..2a76ee2 100644 --- a/front/volume/src/components/Pong/Pong.svelte +++ b/front/volume/src/components/Pong/Pong.svelte @@ -39,7 +39,13 @@ renderCanvas = _renderCanvas; canvas = _canvas; context = _context; - game = new Game(_renderCanvas, canvas, context, elementsColor, backgroundColor); + game = new Game( + _renderCanvas, + canvas, + context, + elementsColor, + backgroundColor + ); socket.onmessage = function (e) { const event_json = JSON.parse(e.data); @@ -52,7 +58,7 @@ game.update(data); } else if (event == GAME_EVENTS.GET_GAME_INFO) { if (data && data.gameId != game.id) { - if (gamePlaying && data.gameId == '') { + if (gamePlaying && data.gameId == "") { resetMenus(); gamePlaying = false; } @@ -81,26 +87,28 @@ updateGameInfo(); }, 1000); } + } else if (event == GAME_EVENTS.READY) { + game.youAreReady = true; } else { console.log( "Unknown event from server: " + event + " with data " + data ); } }; - socket.onopen = onSocketOpen - socket.onclose = onSocketClose + socket.onopen = onSocketOpen; + socket.onclose = onSocketClose; } async function onSocketOpen() { await getUser(); void logIn(); connected = true; - }; + } async function onSocketClose() { connected = false; setupSocket(renderCanvas, canvas, context); - }; + } function updateGameInfo() { socket.send(formatWebsocketData(GAME_EVENTS.GET_GAME_INFO)); @@ -128,6 +136,7 @@ createMatchWindow = false; spectateWindow = false; matchmaking = false; + game.youAreReady = false; } $: { @@ -138,7 +147,7 @@
    - + {#if gamePlaying}
    {:else if loggedIn} diff --git a/front/volume/src/components/Pong/constants.ts b/front/volume/src/components/Pong/constants.ts index 8938625..62bc9f2 100644 --- a/front/volume/src/components/Pong/constants.ts +++ b/front/volume/src/components/Pong/constants.ts @@ -10,6 +10,7 @@ export const GAME_EVENTS = { REGISTER_PLAYER: "REGISTER_PLAYER", SPECTATE: "SPECTATE", MATCHMAKING: "MATCHMAKING", + LEAVE_GAME: "LEAVE_GAME", }; export const DEFAULT_MAP_SIZE = new Point(600, 400); @@ -17,3 +18,4 @@ export const DEFAULT_PADDLE_SIZE = new Point(6, 50); export const DEFAULT_BALL_SIZE = new Point(20, 20); export const DEFAULT_PLAYER_X_OFFSET = 50; export const DEFAULT_WIN_SCORE = 5; +export const GAME_TICKS = 30; diff --git a/front/volume/src/components/Pong/dtos/GameInfo.ts b/front/volume/src/components/Pong/dtos/GameInfo.ts index 657b340..1c510f6 100644 --- a/front/volume/src/components/Pong/dtos/GameInfo.ts +++ b/front/volume/src/components/Pong/dtos/GameInfo.ts @@ -9,4 +9,5 @@ export class GameInfo { playerXOffset!: number; ballSize!: Point; winScore!: number; + ranked!: boolean; }