import * as en from "../../shared_js/enums.js"; import * as ev from "../../shared_js/class/Event.js"; import * as c from "../constants.js"; import { GameComponentsServer } from "./GameComponentsServer.js"; import { clientInputListener } from "../wsServer.js"; import { random } from "../utils.js"; import { wallsMovements } from "../../shared_js/wallsMovement.js"; /* multiples methods of GameSession have parameter "s: GameSession". its used with calls to setTimeout(), because "this" is not equal to the GameSession but to "this: Timeout" */ export class GameSession { constructor(id, matchOptions) { this.playersMap = new Map(); this.unreadyPlayersMap = new Map(); this.spectatorsMap = new Map(); this.gameLoopInterval = 0; this.playersUpdateInterval = 0; this.spectatorsUpdateInterval = 0; this.matchEnded = false; this.id = id; this.matchOptions = matchOptions; this.components = new GameComponentsServer(this.matchOptions); } start() { const gc = this.components; setTimeout(this.resume, c.matchStartDelay, this); let timeout = c.matchStartDelay + c.newRoundDelay; gc.ballsArr.forEach((ball) => { setTimeout(this._newRound, timeout, this, ball); timeout += c.newRoundDelay * 0.5; }); } resume(s) { s.playersMap.forEach((client) => { client.socket.on("message", clientInputListener); }); s.actual_time = Date.now(); s.gameLoopInterval = setInterval(s._gameLoop, c.serverGameLoopIntervalMS, s); s.playersUpdateInterval = setInterval(s._playersUpdate, c.playersUpdateIntervalMS, s); s.spectatorsUpdateInterval = setInterval(s._spectatorsUpdate, c.spectatorsUpdateIntervalMS, s); } pause(s) { s.playersMap.forEach((client) => { client.socket.off("message", clientInputListener); }); clearInterval(s.gameLoopInterval); clearInterval(s.playersUpdateInterval); clearInterval(s.spectatorsUpdateInterval); } instantInputDebug(client) { this._handleInput(c.fixedDeltaTime, client); } _handleInput(delta, client) { // if (client.inputBuffer === null) {return;} const gc = this.components; const input = client.inputBuffer.input; if (input === en.InputEnum.up) { client.racket.dir.y = -1; } else if (input === en.InputEnum.down) { client.racket.dir.y = 1; } if (input !== en.InputEnum.noInput) { client.racket.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]); } client.lastInputId = client.inputBuffer.id; // client.inputBuffer = null; } _gameLoop(s) { /* s.last_time = s.actual_time; s.actual_time = Date.now(); s.delta_time = (s.actual_time - s.last_time) / 1000; */ s.delta_time = c.fixedDeltaTime; // WIP, replaced by instantInputDebug() to prevent desynchro /* s.playersMap.forEach( (client) => { s._handleInput(s.delta_time, client); }); */ const gc = s.components; gc.ballsArr.forEach((ball) => { s._ballMovement(s.delta_time, ball); }); if (s.matchOptions & en.MatchOptions.movingWalls) { wallsMovements(s.delta_time, gc); } } _ballMovement(delta, ball) { const gc = this.components; if (ball.ballInPlay) { ball.moveAndBounce(delta, [gc.wallTop, gc.wallBottom, gc.playerLeft, gc.playerRight]); if (ball.pos.x > c.w || ball.pos.x < 0 - ball.width) { ball.ballInPlay = false; if (this.matchEnded) { return; } this._scoreUpdate(ball); setTimeout(this._newRound, c.newRoundDelay, this, ball); } } } _scoreUpdate(ball) { const gc = this.components; 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))); }); this.spectatorsMap.forEach((client) => { client.socket.send(JSON.stringify(new ev.EventScoreUpdate(gc.scoreLeft, gc.scoreRight))); }); } _playersUpdate(s) { const gameState = s._gameStateSnapshot(); s.playersMap.forEach((client) => { gameState.lastInputId = client.lastInputId; client.socket.send(JSON.stringify(gameState)); }); } _spectatorsUpdate(s) { const gameState = s._gameStateSnapshot(); s.spectatorsMap.forEach((client) => { client.socket.send(JSON.stringify(gameState)); }); } _gameStateSnapshot() { 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) => { snapshot.ballsArr.push({ x: ball.pos.x, y: ball.pos.y, dirX: ball.dir.x, dirY: ball.dir.y, speed: ball.speed }); }); if (this.matchOptions & en.MatchOptions.movingWalls) { snapshot.wallTop.y = gc.wallTop.pos.y; snapshot.wallBottom.y = gc.wallBottom.pos.y; } return (snapshot); } _newRound(s, ball) { if (s._checkDisconnexions()) { return; } // https://fr.wikipedia.org/wiki/Tennis_de_table#Nombre_de_manches const gc = s.components; const minScore = 11; // can be changed for testing if (gc.scoreLeft >= minScore || gc.scoreRight >= minScore) { if (Math.abs(gc.scoreLeft - gc.scoreRight) >= 2) { s._matchEnd(s); return; } } ball.pos.x = c.w_mid; ball.pos.y = random(c.h * 0.3, c.h * 0.7); ball.speed = ball.baseSpeed; ball.ballInPlay = true; } _checkDisconnexions() { if (this.playersMap.size !== 2) { this.matchEnded = true; if (this.playersMap.size != 0) { const gc = this.components; const luckyWinner = this.playersMap.values().next().value; let eventEnd; if (luckyWinner.racket === gc.playerLeft) { eventEnd = new ev.EventMatchEnd(en.PlayerSide.left, true); console.log("Player Left WIN (by forfeit)"); } else { eventEnd = new ev.EventMatchEnd(en.PlayerSide.right, true); console.log("Player Right WIN (by forfeit)"); } luckyWinner.socket.send(JSON.stringify(eventEnd)); this.spectatorsMap.forEach((client) => { client.socket.send(JSON.stringify(eventEnd)); }); } return true; } return false; } _matchEnd(s) { s.matchEnded = true; const gc = s.components; let eventEnd; if (gc.scoreLeft > gc.scoreRight) { eventEnd = new ev.EventMatchEnd(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)); }); } }