From 6e572f2fb5a23edd0715c8c672f9b186a4304036 Mon Sep 17 00:00:00 2001 From: LuckyLaszlo Date: Thu, 1 Dec 2022 05:50:31 +0100 Subject: [PATCH] matchmaking rework for multiples game modes --- src/client/constants.ts | 4 ++ src/client/ws.ts | 4 +- src/server/class/Client.ts | 10 ++--- src/server/class/GameSession.ts | 14 +++---- src/server/wsServer.ts | 72 ++++++++++++++++++--------------- src/shared_js/class/Event.ts | 4 +- src/shared_js/enums.ts | 8 +++- 7 files changed, 67 insertions(+), 49 deletions(-) diff --git a/src/client/constants.ts b/src/client/constants.ts index 5a3f4d9f..325ea7af 100644 --- a/src/client/constants.ts +++ b/src/client/constants.ts @@ -17,3 +17,7 @@ export const fixedDeltaTime = gameLoopIntervalMS/1000; // second export const soundMutedFlag = true; export const soundRobloxVolume = 0.3; // between 0 and 1 export const soundPongVolume = 0.3; // between 0 and 1 + +// TODO: replace by a selector on the website +import * as en from "../shared_js/enums.js" +export const optionsPLACEHOLDER = en.MatchOptions.noOption; diff --git a/src/client/ws.ts b/src/client/ws.ts index e80c640f..63dd4d92 100644 --- a/src/client/ws.ts +++ b/src/client/ws.ts @@ -25,7 +25,7 @@ class ClientInfo { export const clientInfo = new ClientInfo(); socket.addEventListener("open", (event) => { - socket.send(JSON.stringify( new ev.ClientAnnounce(en.ClientRole.player, clientInfo.id) )); + socket.send(JSON.stringify( new ev.ClientAnnounce(en.ClientRole.player, c.optionsPLACEHOLDER, clientInfo.id) )); }); // socket.addEventListener("message", logListener); // for testing purpose @@ -58,7 +58,7 @@ function preMatchListener(this: WebSocket, event: MessageEvent) { } clientInfo.opponentNextPos = new VectorInteger(clientInfo.opponent.pos.x, clientInfo.opponent.pos.y); clientInfo.racket.color = "darkgreen"; // for testing purpose - socket.send(JSON.stringify( new ev.ClientEvent(en.EventTypes.clientPlayerReady) )); + socket.send(JSON.stringify( new ev.ClientEvent(en.EventTypes.clientPlayerReady) )); // TODO: set an interval/timeout to resend until matchStart response (in case of network problem) matchmakingComplete(); break; case en.EventTypes.matchStart: diff --git a/src/server/class/Client.ts b/src/server/class/Client.ts index c1678848..fbdd8fd3 100644 --- a/src/server/class/Client.ts +++ b/src/server/class/Client.ts @@ -3,16 +3,14 @@ import { WebSocket } from "../wsServer.js"; import { Racket } from "../../shared_js/class/Rectangle.js"; import { GameSession } from "./GameSession.js"; import * as ev from "../../shared_js/class/Event.js" +import * as en from "../../shared_js/enums.js" class Client { socket: WebSocket; id: string; // Pas indispensable si "socket" a une copie de "id" isAlive: boolean = true; - gameSession: GameSession; - - inputBuffer: ev.EventInput = new ev.EventInput(); - lastInputId: number = 0; - + gameSession: GameSession = null; + matchOptions: en.MatchOptions = 0; constructor(socket: WebSocket, id: string) { this.socket = socket; this.id = id; @@ -20,6 +18,8 @@ class Client { } class ClientPlayer extends Client { + inputBuffer: ev.EventInput = new ev.EventInput(); + lastInputId: number = 0; racket: Racket; constructor(socket: WebSocket, id: string, racket: Racket) { super(socket, id); diff --git a/src/server/class/GameSession.ts b/src/server/class/GameSession.ts index dbec42bf..3b39c888 100644 --- a/src/server/class/GameSession.ts +++ b/src/server/class/GameSession.ts @@ -13,20 +13,20 @@ import { random } from "../utils.js"; */ class GameSession { id: string; // url ? - playersMap: Map; - unreadyPlayersMap: Map; - gameLoopInterval: NodeJS.Timer | number; - clientsUpdateInterval: NodeJS.Timer | number; + playersMap: Map = new Map(); + unreadyPlayersMap: Map = new Map(); + gameLoopInterval: NodeJS.Timer | number = 0; + clientsUpdateInterval: NodeJS.Timer | number = 0; components: GameComponentsServer; + matchOptions: en.MatchOptions; actual_time: number; last_time: number; delta_time: number; - constructor(id: string) { + constructor(id: string, matchOptions: en.MatchOptions) { this.id = id; - this.playersMap = new Map(); - this.unreadyPlayersMap = new Map(); + this.matchOptions = matchOptions; this.components = new GameComponentsServer(); } start() { diff --git a/src/server/wsServer.ts b/src/server/wsServer.ts index f9162906..c94c9297 100644 --- a/src/server/wsServer.ts +++ b/src/server/wsServer.ts @@ -62,10 +62,12 @@ function clientAnnounceListener(this: WebSocket, data: string) { // TODO: reconnection with msg.clientId ? // TODO: spectator/player distinction with msg.role ? - + + const player = clientsMap.get(this.id) as ClientPlayer; + player.matchOptions = msg.matchOptions; this.send(JSON.stringify( new ev.EventAssignId(this.id) )); this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchmakingInProgress) )); - matchmaking(this); + matchmaking(player); } else { console.log("Invalid ClientAnnounce"); @@ -79,43 +81,47 @@ function clientAnnounceListener(this: WebSocket, data: string) } -function matchmaking(socket: WebSocket) +function matchmaking(player: ClientPlayer) { - const player: ClientPlayer = clientsMap.get(socket.id) as ClientPlayer; - if (matchmakingPlayersMap.size < 1) - { - matchmakingPlayersMap.set(socket.id, player); + const minPlayersNumber = 2; + const maxPlayersNumber = 2; + const matchOptions = player.matchOptions; + matchmakingPlayersMap.set(player.id, player); + + const compatiblePlayers: ClientPlayer[] = []; + matchmakingPlayersMap.forEach((client) => { + if (compatiblePlayers.length === maxPlayersNumber) { + return; // how can we stop forEach entierly and not just this step ??? + } + if (client.matchOptions === matchOptions) { + compatiblePlayers.push(client); + // PLACE complete forEach stop here + } + }); + + if (compatiblePlayers.length < minPlayersNumber) { return; } - else - { - const id = uuidv4(); - const gameSession = new GameSession(id); - gameSessionsMap.set(id, gameSession); - // for player - gameSession.playersMap.set(player.id, player); - player.racket = gameSession.components.playerRight; - socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.right) )); + const id = uuidv4(); + const gameSession = new GameSession(id, matchOptions); + gameSessionsMap.set(id, gameSession); - // for opponent - const opponent: ClientPlayer = matchmakingPlayersMap.values().next().value; - gameSession.playersMap.set(opponent.id, opponent); - matchmakingPlayersMap.delete(opponent.id); - opponent.racket = gameSession.components.playerLeft; - opponent.socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.left) )); + compatiblePlayers.forEach((client) => { + matchmakingPlayersMap.delete(client.id); + client.gameSession = gameSession; + gameSession.playersMap.set(client.id, client); + gameSession.unreadyPlayersMap.set(client.id, client); + }); - // for both - gameSession.playersMap.forEach( (client) => { - gameSession.unreadyPlayersMap.set(client.id, client); - client.gameSession = gameSession; - }); - gameSession.playersMap.forEach( (client) => { - /* set listener last to be absolutly sure there no early game launch - (unlikely, but possible in theory) */ - client.socket.once("message", playerReadyConfirmationListener); - }); - } + // WIP: Not pretty, hardcoded two players. + // Could be done in gameSession maybe ? + compatiblePlayers[0].racket = gameSession.components.playerRight; + compatiblePlayers[1].racket = gameSession.components.playerLeft; + compatiblePlayers[0].socket.once("message", playerReadyConfirmationListener); + compatiblePlayers[1].socket.once("message", playerReadyConfirmationListener); + compatiblePlayers[0].socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.right) )); + compatiblePlayers[1].socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.left) )); } diff --git a/src/shared_js/class/Event.ts b/src/shared_js/class/Event.ts index 14b86ff3..1b6ae6cd 100644 --- a/src/shared_js/class/Event.ts +++ b/src/shared_js/class/Event.ts @@ -75,10 +75,12 @@ class ClientEvent { class ClientAnnounce extends ClientEvent { role: en.ClientRole; clientId: string; - constructor(role: en.ClientRole, clientId: string = "") { + matchOptions: en.MatchOptions; + constructor(role: en.ClientRole, matchOptions: en.MatchOptions, clientId: string = "") { super(en.EventTypes.clientAnnounce); this.role = role; this.clientId = clientId; + this.matchOptions = matchOptions; } } diff --git a/src/shared_js/enums.ts b/src/shared_js/enums.ts index cc8ec311..5dafa8e0 100644 --- a/src/shared_js/enums.ts +++ b/src/shared_js/enums.ts @@ -37,4 +37,10 @@ enum ClientRole { spectator } -export {EventTypes, InputEnum, PlayerSide, ClientRole} +enum MatchOptions { + // binary flags, can be mixed + noOption = 0b0, + multiBalls = 1 << 0 +} + +export {EventTypes, InputEnum, PlayerSide, ClientRole, MatchOptions}