Wip fetch() from gameServer to nestServer

+ miscs (html, css, svelte warning, ...)
This commit is contained in:
LuckyLaszlo
2022-12-17 17:22:42 +01:00
parent c2242c481b
commit 42f2d76f30
22 changed files with 243 additions and 110 deletions

View File

@@ -17,6 +17,8 @@ export class Client {
}
export class ClientPlayer extends Client {
token: string;
username: string;
matchOptions: en.MatchOptions = 0;
inputBuffer: ev.EventInput = new ev.EventInput();
lastInputId: number = 0;

View File

@@ -24,15 +24,18 @@ export class GameSession {
spectatorsUpdateInterval: NodeJS.Timer | number = 0;
components: GameComponentsServer;
matchOptions: en.MatchOptions;
isPrivateMatch: boolean; // WIP: could be used to separate leaderboards for example.
matchEnded: boolean = false;
lastStateSnapshot: ev.EventGameUpdate;
actual_time: number;
last_time: number;
delta_time: number;
constructor(id: string, matchOptions: en.MatchOptions) {
constructor(id: string, matchOptions: en.MatchOptions, isPrivateMatch: boolean = false) {
this.id = id;
this.matchOptions = matchOptions;
this.isPrivateMatch = isPrivateMatch;
this.components = new GameComponentsServer(this.matchOptions);
}
start() {
@@ -51,6 +54,7 @@ export class GameSession {
});
s.actual_time = Date.now();
s.lastStateSnapshot = s._gameStateSnapshot();
s.gameLoopInterval = setInterval(s._gameLoop, c.serverGameLoopIntervalMS, s);
s.playersUpdateInterval = setInterval(s._playersUpdate, c.playersUpdateIntervalMS, s);
s.spectatorsUpdateInterval = setInterval(s._spectatorsUpdate, c.spectatorsUpdateIntervalMS, s);
@@ -139,16 +143,16 @@ export class GameSession {
});
}
private _playersUpdate(s: GameSession) {
const gameState: ev.EventGameUpdate = s._gameStateSnapshot();
s.lastStateSnapshot = s._gameStateSnapshot();
s.playersMap.forEach( (client) => {
gameState.lastInputId = client.lastInputId;
client.socket.send(JSON.stringify(gameState));
s.lastStateSnapshot.lastInputId = client.lastInputId;
client.socket.send(JSON.stringify(s.lastStateSnapshot));
});
}
private _spectatorsUpdate(s: GameSession) {
const gameState = s._gameStateSnapshot();
s.lastStateSnapshot.lastInputId = 0;
s.spectatorsMap.forEach( (client) => {
client.socket.send(JSON.stringify(gameState));
client.socket.send(JSON.stringify(s.lastStateSnapshot));
});
}
private _gameStateSnapshot() : ev.EventGameUpdate {
@@ -182,7 +186,12 @@ export class GameSession {
{
if (Math.abs(gc.scoreLeft - gc.scoreRight) >= 2)
{
s._matchEnd(s);
if (gc.scoreLeft > gc.scoreRight) {
s._matchEnd(en.PlayerSide.left);
}
else {
s._matchEnd(en.PlayerSide.right);
}
return;
}
}
@@ -197,45 +206,54 @@ export class GameSession {
this.matchEnded = true;
if (this.playersMap.size != 0)
{
console.log("Forfeit Ending");
const gc = this.components;
const luckyWinner: ClientPlayer = this.playersMap.values().next().value;
let eventEnd: ev.EventMatchEnd;
if (luckyWinner.racket === gc.playerLeft) {
eventEnd = new ev.EventMatchEnd(en.PlayerSide.left, true);
console.log("Player Left WIN (by forfeit)");
this._matchEnd(en.PlayerSide.left, true);
}
else {
eventEnd = new ev.EventMatchEnd(en.PlayerSide.right, true);
console.log("Player Right WIN (by forfeit)");
this._matchEnd(en.PlayerSide.right, true);
}
luckyWinner.socket.send(JSON.stringify(eventEnd));
this.spectatorsMap.forEach( (client) => {
client.socket.send(JSON.stringify(eventEnd));
});
}
return true;
}
return false;
}
private _matchEnd(s: GameSession) {
s.matchEnded = true;
const gc = s.components;
private async _matchEnd(winner: en.PlayerSide, forfeit_flag: boolean = false)
{
this.matchEnded = true;
let eventEnd: ev.EventMatchEnd;
if (gc.scoreLeft > gc.scoreRight) {
eventEnd = new ev.EventMatchEnd(en.PlayerSide.left);
eventEnd = new ev.EventMatchEnd(winner, forfeit_flag);
this.playersMap.forEach( (client) => {
client.socket.send(JSON.stringify(eventEnd));
});
this.spectatorsMap.forEach( (client) => {
client.socket.send(JSON.stringify(eventEnd));
});
/* // WIP nest , send match result
const gc = this.components;
await fetch(c.addressBackEnd + "/game/matchEnd",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
id: this.id,
scoreLeft: gc.scoreLeft,
scoreRight: gc.scoreRight,
isPrivateMatch: this.isPrivateMatch,
})
}); */
// logs
if (winner === en.PlayerSide.left) {
console.log("Player Left WIN");
}
else {
eventEnd = new ev.EventMatchEnd(en.PlayerSide.right);
console.log("Player Right WIN");
}
s.playersMap.forEach( (client) => {
client.socket.send(JSON.stringify(eventEnd));
});
s.spectatorsMap.forEach( (client) => {
client.socket.send(JSON.stringify(eventEnd));
});
}
}

View File

@@ -8,3 +8,5 @@ export const fixedDeltaTime = serverGameLoopIntervalMS/1000; // second
// 33.333ms == 1000/30
export const playersUpdateIntervalMS = 1000/30; // millisecond
export const spectatorsUpdateIntervalMS = 1000/30; // millisecond
export const addressBackEnd = "http://backend_dev:3000";

View File

@@ -10,16 +10,17 @@ import { v4 as uuidv4 } from 'uuid';
import * as en from "../shared_js/enums.js"
import * as ev from "../shared_js/class/Event.js"
import * as c from "./constants.js"
import { Client, ClientPlayer, ClientSpectator } from "./class/Client.js"
import { GameSession } from "./class/GameSession.js"
import { shortId } from "./utils.js";
import { gameSessionIdPLACEHOLDER } from "./constants.js";
// pas indispensable d'avoir un autre port si le WebSocket est relié à un serveur http préexistant ?
const wsPort = 8042;
export const wsServer = new WebSocketServer<WebSocket>({host: "0.0.0.0", port: wsPort, path: "/pong"});
const clientsMap: Map<string, Client> = new Map; // socket.id/Client
const matchmakingPlayersMap: Map<string, ClientPlayer> = new Map; // socket.id/ClientPlayer (duplicates with clientsMap)
const matchmakingPlayersMap: Map<string, ClientPlayer> = new Map; // socket.id/ClientPlayer (duplicates with clientsMap) // TODO: rename in matchmakingMap
const privateMatchmakingMap: Map<string, ClientPlayer> = new Map; // socket.id/ClientPlayer (duplicates with clientsMap)
const gameSessionsMap: Map<string, GameSession> = new Map; // GameSession.id(url)/GameSession
wsServer.on("connection", connectionListener);
@@ -54,7 +55,7 @@ function connectionListener(socket: WebSocket, request: IncomingMessage)
}
function clientAnnounceListener(this: WebSocket, data: string)
async function clientAnnounceListener(this: WebSocket, data: string)
{
try {
const msg : ev.ClientAnnounce = JSON.parse(data);
@@ -65,11 +66,44 @@ function clientAnnounceListener(this: WebSocket, data: string)
if (msg.role === en.ClientRole.player)
{
const announce: ev.ClientAnnouncePlayer = <ev.ClientAnnouncePlayer>msg;
/* // WIP nest, fetch token validation
const response = await fetch(c.addressBackEnd + "/game/validateToken",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
token: announce.token,
username: announce.username,
})
});
switch (response.status)
{
case 200:
case 204:
break;
case 403:
default:
// send message to client ? or just gtfo ?
clientTerminate(clientsMap.get(this.id));
return;
} */
const player = clientsMap.get(this.id) as ClientPlayer;
player.matchOptions = announce.matchOptions;
player.token = announce.token;
player.username = announce.username;
this.send(JSON.stringify( new ev.EventAssignId(this.id) ));
this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchmakingInProgress) ));
matchmaking(player);
if (announce.privateMatch) {
privateMatchmaking(player);
}
else {
publicMatchmaking(player);
}
}
else if (msg.role === en.ClientRole.spectator)
{
@@ -77,6 +111,7 @@ function clientAnnounceListener(this: WebSocket, data: string)
const gameSession = gameSessionsMap.get(announce.gameSessionId);
if (!gameSession) {
// WIP: send "invalid game session"
clientTerminate(clientsMap.get(this.id));
return;
}
const spectator = clientsMap.get(this.id) as ClientSpectator;
@@ -97,12 +132,12 @@ function clientAnnounceListener(this: WebSocket, data: string)
}
function matchmaking(player: ClientPlayer)
function publicMatchmaking(player: ClientPlayer)
{
const minPlayersNumber = 2;
const maxPlayersNumber = 2;
const matchOptions = player.matchOptions;
matchmakingPlayersMap.set(player.id, player);
const matchOptions = player.matchOptions;
const compatiblePlayers: ClientPlayer[] = [];
for (const [id, client] of matchmakingPlayersMap)
@@ -116,17 +151,52 @@ function matchmaking(player: ClientPlayer)
}
}
if (compatiblePlayers.length < minPlayersNumber) {
return;
if (compatiblePlayers.length >= minPlayersNumber) {
compatiblePlayers.forEach((client) => {
matchmakingPlayersMap.delete(client.id);
});
createGameSession(compatiblePlayers, matchOptions);
}
}
function privateMatchmaking(player: ClientPlayer)
{
const minPlayersNumber = 2;
const maxPlayersNumber = 2;
privateMatchmakingMap.set(player.id, player);
const matchOptions = player.matchOptions;
const token = player.token;
const compatiblePlayers: ClientPlayer[] = [];
for (const [id, client] of privateMatchmakingMap)
{
if (client.token === token)
{
compatiblePlayers.push(client);
if (compatiblePlayers.length === maxPlayersNumber) {
break;
}
}
}
if (compatiblePlayers.length >= minPlayersNumber) {
compatiblePlayers.forEach((client) => {
privateMatchmakingMap.delete(client.id);
});
createGameSession(compatiblePlayers, matchOptions);
}
}
async function createGameSession(playersArr: ClientPlayer[], matchOptions: en.MatchOptions)
{
// const id = gameSessionIdPLACEHOLDER; // Force ID, TESTING SPECTATOR
const id = uuidv4();
const gameSession = new GameSession(id, matchOptions);
gameSessionsMap.set(id, gameSession);
compatiblePlayers.forEach((client) => {
matchmakingPlayersMap.delete(client.id);
playersArr.forEach((client) => {
client.gameSession = gameSession;
gameSession.playersMap.set(client.id, client);
gameSession.unreadyPlayersMap.set(client.id, client);
@@ -134,15 +204,15 @@ function matchmaking(player: ClientPlayer)
// WIP: Not pretty, hardcoded two players.
// Could be done in gameSession maybe ?
compatiblePlayers[0].racket = gameSession.components.playerRight;
compatiblePlayers[1].racket = gameSession.components.playerLeft;
playersArr[0].racket = gameSession.components.playerRight;
playersArr[1].racket = gameSession.components.playerLeft;
compatiblePlayers.forEach((client) => {
playersArr.forEach((client) => {
client.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) ));
playersArr[0].socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.right) ));
playersArr[1].socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.left) ));
setTimeout(function abortMatch() {
if (gameSession.unreadyPlayersMap.size !== 0)
@@ -155,6 +225,18 @@ function matchmaking(player: ClientPlayer)
});
}
}, 5000);
/* // WIP nest , send gameSession.id
await fetch(c.addressBackEnd + "/game/newGameSession",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
id: id,
})
}); */
}
@@ -250,6 +332,9 @@ function clientTerminate(client: Client)
if (matchmakingPlayersMap.has(client.id)) {
matchmakingPlayersMap.delete(client.id);
}
else if (privateMatchmakingMap.has(client.id)) {
privateMatchmakingMap.delete(client.id);
}
}

View File

@@ -89,12 +89,17 @@ export class ClientAnnounce extends ClientEvent {
}
export class ClientAnnouncePlayer extends ClientAnnounce {
clientId: string;
clientId: string; // unused
matchOptions: en.MatchOptions;
constructor(matchOptions: en.MatchOptions, clientId: string = "") {
token: string;
username: string;
privateMatch: boolean;
constructor(matchOptions: en.MatchOptions, token: string, username: string, privateMatch: boolean = false) {
super(en.ClientRole.player);
this.clientId = clientId;
this.matchOptions = matchOptions;
this.token = token;
this.username = username;
this.privateMatch = privateMatch;
}
}

View File

@@ -1,6 +1,6 @@
import { Vector, VectorInteger } from "./Vector.js";
import { Component, Moving } from "./interface.js";
import type { Component, Moving } from "./interface.js";
import * as c from "../constants.js"
export class Rectangle implements Component {

View File

@@ -1,5 +1,5 @@
import { Vector, VectorInteger } from "./Vector.js";
import type { Vector, VectorInteger } from "./Vector.js";
export interface Component {
pos: VectorInteger;

View File

@@ -1,5 +1,5 @@
import { MovingRectangle } from "./class/Rectangle.js";
import type { MovingRectangle } from "./class/Rectangle.js";
export function random(min: number = 0, max: number = 1) {
return Math.random() * (max - min) + min;

View File

@@ -1,7 +1,7 @@
import * as c from "./constants.js";
import { MovingRectangle } from "../shared_js/class/Rectangle.js";
import { GameComponents } from "./class/GameComponents.js";
import type { MovingRectangle } from "../shared_js/class/Rectangle.js";
import type { GameComponents } from "./class/GameComponents.js";
export function wallsMovements(delta: number, gc: GameComponents)
{