matchmaking rework for multiples game modes

This commit is contained in:
LuckyLaszlo
2022-12-01 05:50:31 +01:00
parent e3fd130729
commit 6e572f2fb5
7 changed files with 67 additions and 49 deletions

View File

@@ -17,3 +17,7 @@ export const fixedDeltaTime = gameLoopIntervalMS/1000; // second
export const soundMutedFlag = true; export const soundMutedFlag = true;
export const soundRobloxVolume = 0.3; // between 0 and 1 export const soundRobloxVolume = 0.3; // between 0 and 1
export const soundPongVolume = 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;

View File

@@ -25,7 +25,7 @@ class ClientInfo {
export const clientInfo = new ClientInfo(); export const clientInfo = new ClientInfo();
socket.addEventListener("open", (event) => { 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 // 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.opponentNextPos = new VectorInteger(clientInfo.opponent.pos.x, clientInfo.opponent.pos.y);
clientInfo.racket.color = "darkgreen"; // for testing purpose 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(); matchmakingComplete();
break; break;
case en.EventTypes.matchStart: case en.EventTypes.matchStart:

View File

@@ -3,16 +3,14 @@ import { WebSocket } from "../wsServer.js";
import { Racket } from "../../shared_js/class/Rectangle.js"; import { Racket } from "../../shared_js/class/Rectangle.js";
import { GameSession } from "./GameSession.js"; import { GameSession } from "./GameSession.js";
import * as ev from "../../shared_js/class/Event.js" import * as ev from "../../shared_js/class/Event.js"
import * as en from "../../shared_js/enums.js"
class Client { class Client {
socket: WebSocket; socket: WebSocket;
id: string; // Pas indispensable si "socket" a une copie de "id" id: string; // Pas indispensable si "socket" a une copie de "id"
isAlive: boolean = true; isAlive: boolean = true;
gameSession: GameSession; gameSession: GameSession = null;
matchOptions: en.MatchOptions = 0;
inputBuffer: ev.EventInput = new ev.EventInput();
lastInputId: number = 0;
constructor(socket: WebSocket, id: string) { constructor(socket: WebSocket, id: string) {
this.socket = socket; this.socket = socket;
this.id = id; this.id = id;
@@ -20,6 +18,8 @@ class Client {
} }
class ClientPlayer extends Client { class ClientPlayer extends Client {
inputBuffer: ev.EventInput = new ev.EventInput();
lastInputId: number = 0;
racket: Racket; racket: Racket;
constructor(socket: WebSocket, id: string, racket: Racket) { constructor(socket: WebSocket, id: string, racket: Racket) {
super(socket, id); super(socket, id);

View File

@@ -13,20 +13,20 @@ import { random } from "../utils.js";
*/ */
class GameSession { class GameSession {
id: string; // url ? id: string; // url ?
playersMap: Map<string, ClientPlayer>; playersMap: Map<string, ClientPlayer> = new Map();
unreadyPlayersMap: Map<string, ClientPlayer>; unreadyPlayersMap: Map<string, ClientPlayer> = new Map();
gameLoopInterval: NodeJS.Timer | number; gameLoopInterval: NodeJS.Timer | number = 0;
clientsUpdateInterval: NodeJS.Timer | number; clientsUpdateInterval: NodeJS.Timer | number = 0;
components: GameComponentsServer; components: GameComponentsServer;
matchOptions: en.MatchOptions;
actual_time: number; actual_time: number;
last_time: number; last_time: number;
delta_time: number; delta_time: number;
constructor(id: string) { constructor(id: string, matchOptions: en.MatchOptions) {
this.id = id; this.id = id;
this.playersMap = new Map(); this.matchOptions = matchOptions;
this.unreadyPlayersMap = new Map();
this.components = new GameComponentsServer(); this.components = new GameComponentsServer();
} }
start() { start() {

View File

@@ -63,9 +63,11 @@ function clientAnnounceListener(this: WebSocket, data: string)
// TODO: reconnection with msg.clientId ? // TODO: reconnection with msg.clientId ?
// TODO: spectator/player distinction with msg.role ? // 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.EventAssignId(this.id) ));
this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchmakingInProgress) )); this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchmakingInProgress) ));
matchmaking(this); matchmaking(player);
} }
else { else {
console.log("Invalid ClientAnnounce"); 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; const minPlayersNumber = 2;
if (matchmakingPlayersMap.size < 1) const maxPlayersNumber = 2;
{ const matchOptions = player.matchOptions;
matchmakingPlayersMap.set(socket.id, player); 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; return;
} }
else
{
const id = uuidv4(); const id = uuidv4();
const gameSession = new GameSession(id); const gameSession = new GameSession(id, matchOptions);
gameSessionsMap.set(id, gameSession); gameSessionsMap.set(id, gameSession);
// for player compatiblePlayers.forEach((client) => {
gameSession.playersMap.set(player.id, player); matchmakingPlayersMap.delete(client.id);
player.racket = gameSession.components.playerRight;
socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.right) ));
// 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) ));
// for both
gameSession.playersMap.forEach( (client) => {
gameSession.unreadyPlayersMap.set(client.id, client);
client.gameSession = gameSession; client.gameSession = gameSession;
gameSession.playersMap.set(client.id, client);
gameSession.unreadyPlayersMap.set(client.id, client);
}); });
gameSession.playersMap.forEach( (client) => {
/* set listener last to be absolutly sure there no early game launch // WIP: Not pretty, hardcoded two players.
(unlikely, but possible in theory) */ // Could be done in gameSession maybe ?
client.socket.once("message", playerReadyConfirmationListener); 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) ));
} }

View File

@@ -75,10 +75,12 @@ class ClientEvent {
class ClientAnnounce extends ClientEvent { class ClientAnnounce extends ClientEvent {
role: en.ClientRole; role: en.ClientRole;
clientId: string; clientId: string;
constructor(role: en.ClientRole, clientId: string = "") { matchOptions: en.MatchOptions;
constructor(role: en.ClientRole, matchOptions: en.MatchOptions, clientId: string = "") {
super(en.EventTypes.clientAnnounce); super(en.EventTypes.clientAnnounce);
this.role = role; this.role = role;
this.clientId = clientId; this.clientId = clientId;
this.matchOptions = matchOptions;
} }
} }

View File

@@ -37,4 +37,10 @@ enum ClientRole {
spectator spectator
} }
export {EventTypes, InputEnum, PlayerSide, ClientRole} enum MatchOptions {
// binary flags, can be mixed
noOption = 0b0,
multiBalls = 1 << 0
}
export {EventTypes, InputEnum, PlayerSide, ClientRole, MatchOptions}