spectator mode WIP, working so far

This commit is contained in:
LuckyLaszlo
2022-12-08 14:43:07 +01:00
parent c88a6145eb
commit abf5c45aa7
13 changed files with 364 additions and 67 deletions

View File

@@ -1,5 +1,5 @@
TODO: TODO:
- mode spectateur - mode spectateur (WIP, semble fonctionner)
- certaines utilisations de Math.floor() superflu ? Vérifier les appels. - certaines utilisations de Math.floor() superflu ? Vérifier les appels.
(éventuellement Math.round() ?) (éventuellement Math.round() ?)
- un autre mode de jeu alternatif ? - un autre mode de jeu alternatif ?
@@ -29,6 +29,8 @@ Done:
----------- -----------
BUG: BUG:
- Uncaught (in promise) DOMException: The element has no supported sources.
Bug de son. Peut etre du au CORS. Il faut tester de referencer le son avec u chemin relatif.
- Si la balle va très vite, elle peut ignorer la collision avec une raquette ou mur. - Si la balle va très vite, elle peut ignorer la collision avec une raquette ou mur.
la collision est testée seulement après le mouvement. la collision est testée seulement après le mouvement.
Pour éviter ce bug il faudrait diviser le mouvement pour faire plusieurs tests de collision successifs. Pour éviter ce bug il faudrait diviser le mouvement pour faire plusieurs tests de collision successifs.

View File

@@ -1,8 +1,10 @@
import * as c from "./constants.js"; import * as c from "./constants.js";
import * as en from "../shared_js/enums.js" import * as en from "../shared_js/enums.js"
import { gc, matchOptions, clientInfo } from "./global.js"; import { gc, matchOptions, clientInfo, clientInfoSpectator} from "./global.js";
import { wallsMovements } from "../shared_js/wallsMovement.js"; import { wallsMovements } from "../shared_js/wallsMovement.js";
import { RacketClient } from "./class/RectangleClient.js";
import { VectorInteger } from "../shared_js/class/Vector.js";
let actual_time: number = Date.now(); let actual_time: number = Date.now();
let last_time: number; let last_time: number;
@@ -20,7 +22,7 @@ export function gameLoop()
// interpolation // interpolation
// console.log(`dir.y: ${clientInfo.opponent.dir.y}, pos.y: ${clientInfo.opponent.pos.y}, opponentNextPos.y: ${clientInfo.opponentNextPos.y}`); // console.log(`dir.y: ${clientInfo.opponent.dir.y}, pos.y: ${clientInfo.opponent.pos.y}, opponentNextPos.y: ${clientInfo.opponentNextPos.y}`);
if (clientInfo.opponent.dir.y != 0 ) { if (clientInfo.opponent.dir.y != 0 ) {
opponentInterpolation(delta_time); racketInterpolation(delta_time, clientInfo.opponent, clientInfo.opponentNextPos);
} }
// client prediction // client prediction
@@ -33,15 +35,37 @@ export function gameLoop()
} }
} }
function opponentInterpolation(delta: number) export function gameLoopSpectator()
{ {
// interpolation delta_time = c.fixedDeltaTime;
clientInfo.opponent.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]);
if ((clientInfo.opponent.dir.y > 0 && clientInfo.opponent.pos.y > clientInfo.opponentNextPos.y) // interpolation
|| (clientInfo.opponent.dir.y < 0 && clientInfo.opponent.pos.y < clientInfo.opponentNextPos.y)) if (gc.playerLeft.dir.y != 0 ) {
{ racketInterpolation(delta_time, gc.playerLeft, clientInfoSpectator.playerLeftNextPos);
clientInfo.opponent.dir.y = 0; }
clientInfo.opponent.pos.y = clientInfo.opponentNextPos.y; if (gc.playerRight.dir.y != 0 ) {
racketInterpolation(delta_time, gc.playerRight, clientInfoSpectator.playerRightNextPos);
}
// client prediction
gc.ballsArr.forEach((ball) => {
ball.moveAndBounce(delta_time, [gc.wallTop, gc.wallBottom, gc.playerLeft, gc.playerRight]);
});
if (matchOptions & en.MatchOptions.movingWalls) {
wallsMovements(delta_time, gc);
}
}
function racketInterpolation(delta: number, racket: RacketClient, nextPos: VectorInteger)
{
// interpolation
racket.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]);
if ((racket.dir.y > 0 && racket.pos.y > nextPos.y)
|| (racket.dir.y < 0 && racket.pos.y < nextPos.y))
{
racket.dir.y = 0;
racket.pos.y = nextPos.y;
} }
} }

View File

@@ -1,3 +1,29 @@
export {pong, gc, matchOptions} from "./pong.js" import * as en from "../shared_js/enums.js";
export {socket, clientInfo} from "./ws.js" import { GameArea } from "./class/GameArea.js";
import { GameComponentsClient } from "./class/GameComponentsClient.js";
// export {pong, gc, matchOptions} from "./pong.js"
export {socket, clientInfo, clientInfoSpectator} from "./ws.js"
export let pong: GameArea;
export let gc: GameComponentsClient;
export let matchOptions: en.MatchOptions = en.MatchOptions.noOption;
export function initPong(value: GameArea) {
pong = value;
}
export function initGc(value: GameComponentsClient) {
gc = value;
}
export function initMatchOptions(value: en.MatchOptions) {
matchOptions = value;
}
export let startFunction: () => void;
export function initStartFunction(value: () => void) {
startFunction = value;
}

View File

@@ -18,9 +18,9 @@ import { initAudio } from "./audio.js";
/* TODO: A way to delay the init of variables, but still use "const" not "let" ? */ /* TODO: A way to delay the init of variables, but still use "const" not "let" ? */
export let pong: GameArea; import { pong, gc } from "./global.js"
export let gc: GameComponentsClient; import { initPong, initGc, initMatchOptions, initStartFunction } from "./global.js"
export let matchOptions: en.MatchOptions = en.MatchOptions.noOption;
function init() function init()
{ {
@@ -34,22 +34,25 @@ function init()
} }
initAudio(soundMutedFlag); initAudio(soundMutedFlag);
let matchOptions: en.MatchOptions = en.MatchOptions.noOption;
if ( (<HTMLInputElement>document.getElementById("multi_balls")).checked ) { if ( (<HTMLInputElement>document.getElementById("multi_balls")).checked ) {
matchOptions |= en.MatchOptions.multiBalls; matchOptions |= en.MatchOptions.multiBalls;
} }
if ( (<HTMLInputElement>document.getElementById("moving_walls")).checked ) { if ( (<HTMLInputElement>document.getElementById("moving_walls")).checked ) {
matchOptions |= en.MatchOptions.movingWalls; matchOptions |= en.MatchOptions.movingWalls;
} }
initMatchOptions(matchOptions);
document.getElementById("div_game_options").remove(); document.getElementById("div_game_options").remove();
document.getElementById("div_game_instructions").remove(); document.getElementById("div_game_instructions").remove();
pong = new GameArea(); initPong(new GameArea());
gc = new GameComponentsClient(matchOptions, pong.ctx); initGc(new GameComponentsClient(matchOptions, pong.ctx));
initStartFunction(start);
initWebSocket(matchOptions); initWebSocket(matchOptions);
} }
export function start() function start()
{ {
gc.text1.pos.assign(c.w*0.5, c.h*0.75); gc.text1.pos.assign(c.w*0.5, c.h*0.75);
countdown(c.matchStartDelay/1000, (count: number) => { countdown(c.matchStartDelay/1000, (count: number) => {

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="pong.css">
</head>
<body>
<div id="canvas_container">
<!-- <p> =) </p> -->
</div>
<div>
<h2>Spectator View</h2>
</div>
<script src="http://localhost:8080/js/pongSpectator.js" type="module" defer></script>
</body>
</html>

View File

@@ -0,0 +1,46 @@
initSpectator();
function initSpectator() {
// Wip
init();
}
import * as c from "./constants.js"
import * as en from "../shared_js/enums.js"
import { GameArea } from "./class/GameArea.js";
import { GameComponentsClient } from "./class/GameComponentsClient.js";
import { gameLoopSpectator } from "./gameLoop.js"
import { drawLoop } from "./draw.js";
import { initWebSocketSpectator } from "./ws.js";
import { initAudio } from "./audio.js";
/* TODO: A way to delay the init of variables, but still use "const" not "let" ? */
import { pong, gc } from "./global.js"
import { initPong, initGc, initMatchOptions, initStartFunction } from "./global.js"
function init()
{
initAudio(false);
// WIP matchOptions
let matchOptions: en.MatchOptions = en.MatchOptions.noOption;
initMatchOptions(matchOptions);
initPong(new GameArea());
initGc(new GameComponentsClient(matchOptions, pong.ctx));
initStartFunction(start);
initWebSocketSpectator(c.gameSessionIdPLACEHOLDER);
}
function start()
{
resume();
}
function resume()
{
pong.gameLoopInterval = window.setInterval(gameLoopSpectator, c.gameLoopIntervalMS);
pong.drawLoopInterval = window.setInterval(drawLoop, c.drawLoopIntervalMS);
}

View File

@@ -1,10 +1,9 @@
import * as c from "./constants.js" import * as c from "./constants.js"
import { gc, matchOptions } from "./global.js" import { gc, matchOptions, startFunction } from "./global.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" import * as en from "../shared_js/enums.js"
import * as msg from "./message.js"; import * as msg from "./message.js";
import { start } from "./pong.js";
import { RacketClient } from "./class/RectangleClient.js"; import { RacketClient } from "./class/RectangleClient.js";
import { repeatInput } from "./handleInput.js"; import { repeatInput } from "./handleInput.js";
import { soundRoblox } from "./audio.js" import { soundRoblox } from "./audio.js"
@@ -19,16 +18,24 @@ class ClientInfo {
opponentNextPos: VectorInteger; opponentNextPos: VectorInteger;
} }
class ClientInfoSpectator {
// side: en.PlayerSide;
/* WIP: playerLeftNextPos and playerRightNextPos could be in clientInfo for simplicity */
playerLeftNextPos: VectorInteger;
playerRightNextPos: VectorInteger;
}
const wsPort = 8042; const wsPort = 8042;
const wsUrl = "ws://" + document.location.hostname + ":" + wsPort + "/pong"; const wsUrl = "ws://" + document.location.hostname + ":" + wsPort + "/pong";
export let socket: WebSocket; /* TODO: A way to still use "const" not "let" ? */ export let socket: WebSocket; /* TODO: A way to still use "const" not "let" ? */
export const clientInfo = new ClientInfo(); export const clientInfo = new ClientInfo();
export const clientInfoSpectator = new ClientInfoSpectator(); // WIP, could refactor this
export function initWebSocket(options: en.MatchOptions) export function initWebSocket(options: en.MatchOptions)
{ {
socket = new WebSocket(wsUrl, "json"); socket = new WebSocket(wsUrl, "json");
socket.addEventListener("open", (event) => { socket.addEventListener("open", (event) => {
socket.send(JSON.stringify( new ev.ClientAnnounce(en.ClientRole.player, options, clientInfo.id) )); socket.send(JSON.stringify( new ev.ClientAnnouncePlayer(options, clientInfo.id) ));
}); });
// socket.addEventListener("message", logListener); // for testing purpose // socket.addEventListener("message", logListener); // for testing purpose
socket.addEventListener("message", preMatchListener); socket.addEventListener("message", preMatchListener);
@@ -68,7 +75,7 @@ function preMatchListener(this: WebSocket, event: MessageEvent)
case en.EventTypes.matchStart: case en.EventTypes.matchStart:
socket.removeEventListener("message", preMatchListener); socket.removeEventListener("message", preMatchListener);
socket.addEventListener("message", inGameListener); socket.addEventListener("message", inGameListener);
start(); startFunction();
break; break;
case en.EventTypes.matchAbort: case en.EventTypes.matchAbort:
socket.removeEventListener("message", preMatchListener); socket.removeEventListener("message", preMatchListener);
@@ -77,7 +84,7 @@ function preMatchListener(this: WebSocket, event: MessageEvent)
} }
} }
function inGameListener(event: MessageEvent) function inGameListener(this: WebSocket, event: MessageEvent)
{ {
const data: ev.ServerEvent = JSON.parse(event.data); const data: ev.ServerEvent = JSON.parse(event.data);
switch (data.type) { switch (data.type) {
@@ -186,3 +193,110 @@ function matchEnd(data: ev.EventMatchEnd)
} }
// export let matchEnded = false; // unused // export let matchEnded = false; // unused
/* Spectator */
export function initWebSocketSpectator(gameSessionId: string)
{
socket = new WebSocket(wsUrl, "json");
socket.addEventListener("open", (event) => {
socket.send(JSON.stringify( new ev.ClientAnnounceSpectator(gameSessionId) ));
});
// socket.addEventListener("message", logListener); // for testing purpose
socket.addEventListener("message", preMatchListenerSpectator);
clientInfoSpectator.playerLeftNextPos = new VectorInteger(gc.playerLeft.pos.x, gc.playerLeft.pos.y);
clientInfoSpectator.playerRightNextPos = new VectorInteger(gc.playerRight.pos.x, gc.playerRight.pos.y);
}
export function preMatchListenerSpectator(this: WebSocket, event: MessageEvent)
{
const data: ev.ServerEvent = JSON.parse(event.data);
if (data.type === en.EventTypes.matchStart)
{
socket.removeEventListener("message", preMatchListenerSpectator);
socket.addEventListener("message", inGameListenerSpectator);
startFunction();
}
}
function inGameListenerSpectator(this: WebSocket, event: MessageEvent)
{
const data: ev.ServerEvent = JSON.parse(event.data);
switch (data.type) {
case en.EventTypes.gameUpdate:
gameUpdateSpectator(data as ev.EventGameUpdate);
break;
case en.EventTypes.scoreUpdate:
scoreUpdateSpectator(data as ev.EventScoreUpdate);
break;
case en.EventTypes.matchEnd:
matchEndSpectator(data as ev.EventMatchEnd);
break;
}
}
function gameUpdateSpectator(data: ev.EventGameUpdate)
{
console.log("gameUpdateSpectator");
if (matchOptions & en.MatchOptions.movingWalls) {
gc.wallTop.pos.y = data.wallTop.y;
gc.wallBottom.pos.y = data.wallBottom.y;
}
data.ballsArr.forEach((ball, i) => {
gc.ballsArr[i].pos.assign(ball.x, ball.y);
gc.ballsArr[i].dir.assign(ball.dirX, ball.dirY);
gc.ballsArr[i].speed = ball.speed;
});
// interpolation
for (const racket of [gc.playerLeft, gc.playerRight])
{
let nextPos: VectorInteger;
if (racket === gc.playerLeft) {
nextPos = clientInfoSpectator.playerLeftNextPos;
}
else {
nextPos = clientInfoSpectator.playerRightNextPos;
}
racket.pos.assign(nextPos.x, nextPos.y);
if (racket === gc.playerLeft) {
nextPos.assign(racket.pos.x, data.playerLeft.y);
}
else {
nextPos.assign(racket.pos.x, data.playerRight.y);
}
racket.dir = new Vector(
nextPos.x - racket.pos.x,
nextPos.y - racket.pos.y
);
if (Math.abs(racket.dir.x) + Math.abs(racket.dir.y) !== 0) {
racket.dir = racket.dir.normalized();
}
}
}
function scoreUpdateSpectator(data: ev.EventScoreUpdate)
{
console.log("scoreUpdateSpectator");
gc.scoreLeft.value = data.scoreLeft;
gc.scoreRight.value = data.scoreRight;
}
function matchEndSpectator(data: ev.EventMatchEnd)
{
console.log("matchEndSpectator");
// WIP
/* msg.win();
if (data.forfeit) {
msg.forfeit(clientInfo.side);
} */
}

View File

@@ -10,7 +10,6 @@ export class Client {
id: string; // same as "socket.id" id: string; // same as "socket.id"
isAlive: boolean = true; isAlive: boolean = true;
gameSession: GameSession = null; gameSession: GameSession = null;
matchOptions: en.MatchOptions = 0;
constructor(socket: WebSocket, id: string) { constructor(socket: WebSocket, id: string) {
this.socket = socket; this.socket = socket;
this.id = id; this.id = id;
@@ -18,6 +17,7 @@ export class Client {
} }
export class ClientPlayer extends Client { export class ClientPlayer extends Client {
matchOptions: en.MatchOptions = 0;
inputBuffer: ev.EventInput = new ev.EventInput(); inputBuffer: ev.EventInput = new ev.EventInput();
lastInputId: number = 0; lastInputId: number = 0;
racket: Racket; racket: Racket;
@@ -27,7 +27,7 @@ export class ClientPlayer extends Client {
} }
} }
export class ClientSpectator extends Client { // Wip, unused export class ClientSpectator extends Client {
constructor(socket: WebSocket, id: string) { constructor(socket: WebSocket, id: string) {
super(socket, id); super(socket, id);
} }

View File

@@ -2,7 +2,7 @@
import * as en from "../../shared_js/enums.js" import * as en from "../../shared_js/enums.js"
import * as ev from "../../shared_js/class/Event.js" import * as ev from "../../shared_js/class/Event.js"
import * as c from "../constants.js" import * as c from "../constants.js"
import { ClientPlayer } from "./Client"; import { ClientPlayer, ClientSpectator } from "./Client";
import { GameComponentsServer } from "./GameComponentsServer.js"; import { GameComponentsServer } from "./GameComponentsServer.js";
import { clientInputListener } from "../wsServer.js"; import { clientInputListener } from "../wsServer.js";
import { random } from "../utils.js"; import { random } from "../utils.js";
@@ -18,8 +18,10 @@ export class GameSession {
id: string; // url ? id: string; // url ?
playersMap: Map<string, ClientPlayer> = new Map(); playersMap: Map<string, ClientPlayer> = new Map();
unreadyPlayersMap: Map<string, ClientPlayer> = new Map(); unreadyPlayersMap: Map<string, ClientPlayer> = new Map();
spectatorsMap: Map<string, ClientSpectator> = new Map();
gameLoopInterval: NodeJS.Timer | number = 0; gameLoopInterval: NodeJS.Timer | number = 0;
clientsUpdateInterval: NodeJS.Timer | number = 0; playersUpdateInterval: NodeJS.Timer | number = 0;
spectatorsUpdateInterval: NodeJS.Timer | number = 0;
components: GameComponentsServer; components: GameComponentsServer;
matchOptions: en.MatchOptions; matchOptions: en.MatchOptions;
matchEnded: boolean = false; matchEnded: boolean = false;
@@ -50,7 +52,8 @@ export class GameSession {
s.actual_time = Date.now(); s.actual_time = Date.now();
s.gameLoopInterval = setInterval(s._gameLoop, c.serverGameLoopIntervalMS, s); s.gameLoopInterval = setInterval(s._gameLoop, c.serverGameLoopIntervalMS, s);
s.clientsUpdateInterval = setInterval(s._clientsUpdate, c.clientsUpdateIntervalMS, s); s.playersUpdateInterval = setInterval(s._playersUpdate, c.playersUpdateIntervalMS, s);
s.spectatorsUpdateInterval = setInterval(s._spectatorsUpdate, c.spectatorsUpdateIntervalMS, s);
} }
pause(s: GameSession) { pause(s: GameSession) {
s.playersMap.forEach( (client) => { s.playersMap.forEach( (client) => {
@@ -58,7 +61,8 @@ export class GameSession {
}); });
clearInterval(s.gameLoopInterval); clearInterval(s.gameLoopInterval);
clearInterval(s.clientsUpdateInterval); clearInterval(s.playersUpdateInterval);
clearInterval(s.spectatorsUpdateInterval);
} }
instantInputDebug(client: ClientPlayer) { instantInputDebug(client: ClientPlayer) {
this._handleInput(c.fixedDeltaTime, client); this._handleInput(c.fixedDeltaTime, client);
@@ -114,24 +118,46 @@ export class GameSession {
if (this.matchEnded) { if (this.matchEnded) {
return; return;
} }
this._scoreUpdate(ball);
if (ball.pos.x > c.w) { ++gc.scoreLeft; }
else if (ball.pos.x < 0 - ball.width) { ++gc.scoreRight; }
this.playersMap.forEach( (client) => {
client.socket.send(JSON.stringify(new ev.EventScoreUpdate(gc.scoreLeft, gc.scoreRight)));
});
setTimeout(this._newRound, c.newRoundDelay, this, ball); setTimeout(this._newRound, c.newRoundDelay, this, ball);
} }
} }
} }
private _clientsUpdate(s: GameSession) { private _scoreUpdate(ball: Ball) {
const gc = s.components; const gc = this.components;
const update = new ev.EventGameUpdate(); if (ball.pos.x > c.w) {
update.playerLeft.y = gc.playerLeft.pos.y; ++gc.scoreLeft;
update.playerRight.y = gc.playerRight.pos.y; }
else if (ball.pos.x < 0 - ball.width) {
++gc.scoreRight;
}
this.playersMap.forEach( (client) => {
client.socket.send(JSON.stringify(new ev.EventScoreUpdate(gc.scoreLeft, gc.scoreRight)));
});
this.spectatorsMap.forEach( (client) => {
client.socket.send(JSON.stringify(new ev.EventScoreUpdate(gc.scoreLeft, gc.scoreRight)));
});
}
private _playersUpdate(s: GameSession) {
const gameState: ev.EventGameUpdate = s._gameStateSnapshot();
s.playersMap.forEach( (client) => {
gameState.lastInputId = client.lastInputId;
client.socket.send(JSON.stringify(gameState));
});
}
private _spectatorsUpdate(s: GameSession) {
const gameState = s._gameStateSnapshot();
s.spectatorsMap.forEach( (client) => {
client.socket.send(JSON.stringify(gameState));
});
}
private _gameStateSnapshot() : ev.EventGameUpdate {
const gc = this.components;
const snapshot = new ev.EventGameUpdate();
snapshot.playerLeft.y = gc.playerLeft.pos.y;
snapshot.playerRight.y = gc.playerRight.pos.y;
gc.ballsArr.forEach((ball) => { gc.ballsArr.forEach((ball) => {
update.ballsArr.push({ snapshot.ballsArr.push({
x: ball.pos.x, x: ball.pos.x,
y: ball.pos.y, y: ball.pos.y,
dirX: ball.dir.x, dirX: ball.dir.x,
@@ -139,15 +165,11 @@ export class GameSession {
speed: ball.speed speed: ball.speed
}); });
}); });
if (s.matchOptions & en.MatchOptions.movingWalls) { if (this.matchOptions & en.MatchOptions.movingWalls) {
update.wallTop.y = gc.wallTop.pos.y; snapshot.wallTop.y = gc.wallTop.pos.y;
update.wallBottom.y = gc.wallBottom.pos.y; snapshot.wallBottom.y = gc.wallBottom.pos.y;
} }
return (snapshot);
s.playersMap.forEach( (client) => {
update.lastInputId = client.lastInputId;
client.socket.send(JSON.stringify(update));
});
} }
private _newRound(s: GameSession, ball: Ball) { private _newRound(s: GameSession, ball: Ball) {
if (s._checkDisconnexions()) { if (s._checkDisconnexions()) {
@@ -187,6 +209,9 @@ export class GameSession {
console.log("Player Right WIN (by forfeit)"); console.log("Player Right WIN (by forfeit)");
} }
luckyWinner.socket.send(JSON.stringify(eventEnd)); luckyWinner.socket.send(JSON.stringify(eventEnd));
this.spectatorsMap.forEach( (client) => {
client.socket.send(JSON.stringify(eventEnd));
});
} }
return true; return true;
} }
@@ -209,5 +234,8 @@ export class GameSession {
s.playersMap.forEach( (client) => { s.playersMap.forEach( (client) => {
client.socket.send(JSON.stringify(eventEnd)); client.socket.send(JSON.stringify(eventEnd));
}); });
s.spectatorsMap.forEach( (client) => {
client.socket.send(JSON.stringify(eventEnd));
});
} }
} }

View File

@@ -6,4 +6,5 @@ export const serverGameLoopIntervalMS = 15; // millisecond
export const fixedDeltaTime = serverGameLoopIntervalMS/1000; // second export const fixedDeltaTime = serverGameLoopIntervalMS/1000; // second
// 33.333ms == 1000/30 // 33.333ms == 1000/30
export const clientsUpdateIntervalMS = 1000/30; // millisecond export const playersUpdateIntervalMS = 1000/30; // millisecond
export const spectatorsUpdateIntervalMS = 1000/30; // millisecond

View File

@@ -10,9 +10,10 @@ import { v4 as uuidv4 } from 'uuid';
import * as en from "../shared_js/enums.js" import * as en from "../shared_js/enums.js"
import * as ev from "../shared_js/class/Event.js" import * as ev from "../shared_js/class/Event.js"
import { Client, ClientPlayer } from "./class/Client.js" import { Client, ClientPlayer, ClientSpectator } from "./class/Client.js"
import { GameSession } from "./class/GameSession.js" import { GameSession } from "./class/GameSession.js"
import { shortId } from "./utils.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 ? // pas indispensable d'avoir un autre port si le WebSocket est relié à un serveur http préexistant ?
const wsPort = 8042; const wsPort = 8042;
@@ -61,16 +62,29 @@ function clientAnnounceListener(this: WebSocket, data: string)
if (msg.type === en.EventTypes.clientAnnounce) if (msg.type === en.EventTypes.clientAnnounce)
{ {
// TODO: reconnection with msg.clientId ? // TODO: reconnection with msg.clientId ?
// TODO: spectator/player distinction with msg.role ? // "/pong" to play, "/pong?ID_OF_A_GAMESESSION" to spectate (or something like that)
// msg.role is probably not a good idea. if (msg.role === en.ClientRole.player)
// something like a different route could be better {
// "/pong" to play, "/ID_OF_A_GAMESESSION" to spectate const announce: ev.ClientAnnouncePlayer = <ev.ClientAnnouncePlayer>msg;
const player = clientsMap.get(this.id) as ClientPlayer;
const player = clientsMap.get(this.id) as ClientPlayer; player.matchOptions = announce.matchOptions;
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(player);
matchmaking(player); }
else if (msg.role === en.ClientRole.spectator)
{
const announce: ev.ClientAnnounceSpectator = <ev.ClientAnnounceSpectator>msg;
const gameSession = gameSessionsMap.get(announce.gameSessionId);
if (!gameSession) {
// WIP: send "invalid game session"
return;
}
const spectator = clientsMap.get(this.id) as ClientSpectator;
spectator.gameSession = gameSession;
gameSession.spectatorsMap.set(spectator.id, spectator);
this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchStart) ));
}
} }
else { else {
console.log("Invalid ClientAnnounce"); console.log("Invalid ClientAnnounce");
@@ -107,6 +121,7 @@ function matchmaking(player: ClientPlayer)
return; return;
} }
// const id = gameSessionIdPLACEHOLDER; // Force ID, TESTING SPECTATOR
const id = uuidv4(); const id = uuidv4();
const gameSession = new GameSession(id, matchOptions); const gameSession = new GameSession(id, matchOptions);
gameSessionsMap.set(id, gameSession); gameSessionsMap.set(id, gameSession);
@@ -226,7 +241,8 @@ function clientTerminate(client: Client)
client.gameSession.playersMap.delete(client.id); client.gameSession.playersMap.delete(client.id);
if (client.gameSession.playersMap.size === 0) if (client.gameSession.playersMap.size === 0)
{ {
clearInterval(client.gameSession.clientsUpdateInterval); clearInterval(client.gameSession.playersUpdateInterval);
clearInterval(client.gameSession.spectatorsUpdateInterval);
clearInterval(client.gameSession.gameLoopInterval); clearInterval(client.gameSession.gameLoopInterval);
gameSessionsMap.delete(client.gameSession.id); gameSessionsMap.delete(client.gameSession.id);
} }

View File

@@ -82,16 +82,30 @@ export class ClientEvent {
export class ClientAnnounce extends ClientEvent { export class ClientAnnounce extends ClientEvent {
role: en.ClientRole; role: en.ClientRole;
clientId: string; constructor(role: en.ClientRole) {
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;
}
}
export class ClientAnnouncePlayer extends ClientAnnounce {
clientId: string;
matchOptions: en.MatchOptions;
constructor(matchOptions: en.MatchOptions, clientId: string = "") {
super(en.ClientRole.player);
this.clientId = clientId; this.clientId = clientId;
this.matchOptions = matchOptions; this.matchOptions = matchOptions;
} }
} }
export class ClientAnnounceSpectator extends ClientAnnounce {
gameSessionId: string;
constructor(gameSessionId: string) {
super(en.ClientRole.spectator);
this.gameSessionId = gameSessionId;
}
}
export class EventInput extends ClientEvent { export class EventInput extends ClientEvent {
input: en.InputEnum; input: en.InputEnum;
id: number; id: number;

View File

@@ -24,3 +24,7 @@ export const newRoundDelay = 1500; // millisecond
export const multiBallsCount = 3; export const multiBallsCount = 3;
export const movingWallPosMax = Math.floor(w*0.12); export const movingWallPosMax = Math.floor(w*0.12);
export const movingWallSpeed = Math.floor(w*0.08); export const movingWallSpeed = Math.floor(w*0.08);
export const gameSessionIdPLACEHOLDER = "42"; // TESTING SPECTATOR PLACEHOLDER
// for testing, force gameSession.id in wsServer.ts->matchmaking()