suppression de dossier inutile
This commit is contained in:
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "Node",
|
|
||||||
"target": "ES2020",
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"strictFunctionTypes": true
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"**/node_modules/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
18
JEU2/make.sh
18
JEU2/make.sh
@@ -1,18 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
npx tsc
|
|
||||||
|
|
||||||
mkdir -p www
|
|
||||||
cp ./src/client/*.html ./www/
|
|
||||||
cp ./src/client/*.css ./www/
|
|
||||||
|
|
||||||
mkdir -p www/js
|
|
||||||
cp ./src/client/*.js ./www/js/
|
|
||||||
|
|
||||||
mkdir -p www/js/class
|
|
||||||
cp ./src/client/class/*.js ./www/js/class/
|
|
||||||
|
|
||||||
mkdir -p www/shared_js/
|
|
||||||
cp ./src/shared_js/*.js ./www/shared_js/
|
|
||||||
|
|
||||||
mkdir -p www/shared_js/class
|
|
||||||
cp ./src/shared_js/class/*.js ./www/shared_js/class/
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
Done:
|
|
||||||
- Connexion client/serveur via un Websocket
|
|
||||||
- implémentation basique (authoritative server)
|
|
||||||
- Matchmaking
|
|
||||||
- client prediction
|
|
||||||
- server reconciliation (buffer des inputs côté client + id sur les inputs)
|
|
||||||
- amélioration collision avec Hugo
|
|
||||||
- du son (rebonds de la balle, "Oof" de Roblox sur un point)
|
|
||||||
- init de GameComponents partagé entre serveur et client.
|
|
||||||
- draw on the canvas "WIN", "LOSE", "MATCHMAKING COMPLETE", ...
|
|
||||||
- interpolation (mis à jour progressif des mouvements de l'adversaire)
|
|
||||||
- traitement groupé des inputs clients toutes les x millisecondes
|
|
||||||
(BUG désynchronisation: revenu à un traitement immédiat en attendant)
|
|
||||||
- Détruire les GameSession une fois finies.
|
|
||||||
- mode multi-balles
|
|
||||||
- mode murs mouvant (la zone de jeu rétréci / agrandi en continu)
|
|
||||||
- Selection des modes de jeu via HTML
|
|
||||||
- Selection audio on/off via HTML
|
|
||||||
|
|
||||||
TODO:
|
|
||||||
- Match Abort si tout les joueurs ne sont pas pret assez vite (~15 secondes)
|
|
||||||
- mode spectateur
|
|
||||||
- certaines utilisations de Math.floor() superflu ? Vérifier les appels.
|
|
||||||
(éventuellement Math.round() ?)
|
|
||||||
- un autre mode de jeu alternatif ?
|
|
||||||
- changer les "localhost:8080" dans le code.
|
|
||||||
- sélection couleur des raquettes (your color/opponent color) dans le profil utilisateur.
|
|
||||||
Enregistrement dans la DB.
|
|
||||||
init des couleurs dans GameComponentsClient() basé sur les variables de l'utilsateur connecté.
|
|
||||||
-----------
|
|
||||||
idées modes de jeu :
|
|
||||||
- mode 2 raquettes (un joueur haut/gauche et bas/droite)
|
|
||||||
- skin patate ???
|
|
||||||
- (prediction de l'avancement de la balle basé sur la latence serveur ?)
|
|
||||||
- d'autres sons (foule qui applaudi/musique de victoire)
|
|
||||||
-----------
|
|
||||||
- BUG: 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.
|
|
||||||
Pour éviter ce bug il faudrait diviser le mouvement pour faire plusieurs tests de collision successifs.
|
|
||||||
- BUG mineur: sur un changement de fenêtre, les touches restent enfoncées et il faut les "décoincer"
|
|
||||||
en réappuyant. Ce n'est pas grave mais peut-on faire mieux ?
|
|
||||||
----------
|
|
||||||
OSEF, rebuts:
|
|
||||||
- reconnection
|
|
||||||
- amélioration du protocole, remplacement du JSON (compression. moins de bande passante).
|
|
||||||
121
JEU2/package-lock.json
generated
121
JEU2/package-lock.json
generated
@@ -1,121 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "ft_transcendence",
|
|
||||||
"lockfileVersion": 2,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"dependencies": {
|
|
||||||
"uuid": "^9.0.0",
|
|
||||||
"ws": "^8.10.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^18.11.5",
|
|
||||||
"@types/uuid": "^8.3.4",
|
|
||||||
"@types/ws": "^8.5.3",
|
|
||||||
"typescript": "^4.8.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
|
||||||
"version": "18.11.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.5.tgz",
|
|
||||||
"integrity": "sha512-3JRwhbjI+cHLAkUorhf8RnqUbFXajvzX4q6fMn5JwkgtuwfYtRQYI3u4V92vI6NJuTsbBQWWh3RZjFsuevyMGQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/@types/uuid": {
|
|
||||||
"version": "8.3.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
|
||||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/@types/ws": {
|
|
||||||
"version": "8.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
|
||||||
"integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/typescript": {
|
|
||||||
"version": "4.8.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
|
|
||||||
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"tsc": "bin/tsc",
|
|
||||||
"tsserver": "bin/tsserver"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/uuid": {
|
|
||||||
"version": "9.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
|
||||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
|
|
||||||
"bin": {
|
|
||||||
"uuid": "dist/bin/uuid"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ws": {
|
|
||||||
"version": "8.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz",
|
|
||||||
"integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": "^5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": {
|
|
||||||
"version": "18.11.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.5.tgz",
|
|
||||||
"integrity": "sha512-3JRwhbjI+cHLAkUorhf8RnqUbFXajvzX4q6fMn5JwkgtuwfYtRQYI3u4V92vI6NJuTsbBQWWh3RZjFsuevyMGQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"@types/uuid": {
|
|
||||||
"version": "8.3.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
|
||||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"@types/ws": {
|
|
||||||
"version": "8.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
|
||||||
"integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"typescript": {
|
|
||||||
"version": "4.8.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
|
|
||||||
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"uuid": {
|
|
||||||
"version": "9.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
|
||||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
|
|
||||||
},
|
|
||||||
"ws": {
|
|
||||||
"version": "8.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz",
|
|
||||||
"integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==",
|
|
||||||
"requires": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "module",
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^18.11.5",
|
|
||||||
"@types/uuid": "^8.3.4",
|
|
||||||
"@types/ws": "^8.5.3",
|
|
||||||
"typescript": "^4.8.4"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"uuid": "^9.0.0",
|
|
||||||
"ws": "^8.10.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
|
|
||||||
import { WebSocket } from "../wsServer.js";
|
|
||||||
import { Racket } from "../../shared_js/class/Rectangle.js";
|
|
||||||
import { GameSession } from "./GameSession.js";
|
|
||||||
import * as ev from "./Event.js"
|
|
||||||
import * as en from "../enums.js"
|
|
||||||
|
|
||||||
class Client {
|
|
||||||
socket: WebSocket;
|
|
||||||
id: string; // Pas indispensable si "socket" a une copie de "id"
|
|
||||||
isAlive: boolean = true;
|
|
||||||
gameSession: GameSession = null;
|
|
||||||
matchOptions: en.MatchOptions = 0;
|
|
||||||
constructor(socket: WebSocket, id: string) {
|
|
||||||
this.socket = socket;
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
this.racket = racket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ClientSpectator extends Client { // Wip, unused
|
|
||||||
constructor(socket: WebSocket, id: string) {
|
|
||||||
super(socket, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {Client, ClientPlayer, ClientSpectator}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
|
|
||||||
import * as en from "../enums.js"
|
|
||||||
|
|
||||||
/* From Server */
|
|
||||||
class ServerEvent {
|
|
||||||
type: en.EventTypes;
|
|
||||||
constructor(type: en.EventTypes = 0) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventAssignId extends ServerEvent {
|
|
||||||
id: string;
|
|
||||||
constructor(id: string) {
|
|
||||||
super(en.EventTypes.assignId);
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventMatchmakingComplete extends ServerEvent {
|
|
||||||
side: en.PlayerSide;
|
|
||||||
constructor(side: en.PlayerSide) {
|
|
||||||
super(en.EventTypes.matchmakingComplete);
|
|
||||||
this.side = side;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventGameUpdate extends ServerEvent {
|
|
||||||
playerLeft = {
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
playerRight = {
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
ballsArr: {
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
dirX: number,
|
|
||||||
dirY: number,
|
|
||||||
speed: number
|
|
||||||
}[] = [];
|
|
||||||
wallTop? = {
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
wallBottom? = {
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
lastInputId = 0;
|
|
||||||
constructor() { // TODO: constructor that take GameComponentsServer maybe ?
|
|
||||||
super(en.EventTypes.gameUpdate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventScoreUpdate extends ServerEvent {
|
|
||||||
scoreLeft: number;
|
|
||||||
scoreRight: number;
|
|
||||||
constructor(scoreLeft: number, scoreRight: number) {
|
|
||||||
super(en.EventTypes.scoreUpdate);
|
|
||||||
this.scoreLeft = scoreLeft;
|
|
||||||
this.scoreRight = scoreRight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventMatchEnd extends ServerEvent {
|
|
||||||
winner: en.PlayerSide;
|
|
||||||
constructor(winner: en.PlayerSide) {
|
|
||||||
super(en.EventTypes.matchEnd);
|
|
||||||
this.winner = winner;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* From Client */
|
|
||||||
class ClientEvent {
|
|
||||||
type: en.EventTypes; // readonly ?
|
|
||||||
constructor(type: en.EventTypes = 0) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ClientAnnounce extends ClientEvent {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventInput extends ClientEvent {
|
|
||||||
input: en.InputEnum;
|
|
||||||
id: number;
|
|
||||||
constructor(input: en.InputEnum = en.InputEnum.noInput, id: number = 0) {
|
|
||||||
super(en.EventTypes.clientInput);
|
|
||||||
this.input = input;
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
ServerEvent, EventAssignId, EventMatchmakingComplete,
|
|
||||||
EventGameUpdate, EventScoreUpdate, EventMatchEnd,
|
|
||||||
ClientEvent, ClientAnnounce, EventInput
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
|
|
||||||
import * as c from "../constants.js"
|
|
||||||
import * as en from "../enums.js"
|
|
||||||
import { VectorInteger } from "./Vector.js";
|
|
||||||
import { Rectangle, MovingRectangle, Racket, Ball } from "./Rectangle.js";
|
|
||||||
import { random } from "../utils.js";
|
|
||||||
|
|
||||||
class GameComponents {
|
|
||||||
wallTop: Rectangle | MovingRectangle;
|
|
||||||
wallBottom: Rectangle | MovingRectangle;
|
|
||||||
playerLeft: Racket;
|
|
||||||
playerRight: Racket;
|
|
||||||
ballsArr: Ball[] = [];
|
|
||||||
constructor(options: en.MatchOptions)
|
|
||||||
{
|
|
||||||
const pos = new VectorInteger;
|
|
||||||
|
|
||||||
// Rackets
|
|
||||||
pos.assign(0+c.pw, c.h_mid-c.ph/2);
|
|
||||||
this.playerLeft = new Racket(pos, c.pw, c.ph, c.racketSpeed);
|
|
||||||
pos.assign(c.w-c.pw-c.pw, c.h_mid-c.ph/2);
|
|
||||||
this.playerRight = new Racket(pos, c.pw, c.ph, c.racketSpeed);
|
|
||||||
|
|
||||||
// Balls
|
|
||||||
let ballsCount = 1;
|
|
||||||
if (options & en.MatchOptions.multiBalls) {
|
|
||||||
ballsCount = c.multiBallsCount;
|
|
||||||
}
|
|
||||||
pos.assign(-c.ballSize, -c.ballSize); // ball out =)
|
|
||||||
while (this.ballsArr.length < ballsCount) {
|
|
||||||
this.ballsArr.push(new Ball(pos, c.ballSize, c.ballSpeed, c.ballSpeedIncrease))
|
|
||||||
}
|
|
||||||
this.ballsArr.forEach((ball) => {
|
|
||||||
ball.dir.x = 1;
|
|
||||||
if (random() > 0.5) {
|
|
||||||
ball.dir.x *= -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ball.dir.y = random(0, 0.2);
|
|
||||||
if (random() > 0.5) {
|
|
||||||
ball.dir.y *= -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ball.dir = ball.dir.normalized();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Walls
|
|
||||||
if (options & en.MatchOptions.movingWalls) {
|
|
||||||
pos.assign(0, 0);
|
|
||||||
this.wallTop = new MovingRectangle(pos, c.w, c.wallSize, c.movingWallSpeed);
|
|
||||||
(<MovingRectangle>this.wallTop).dir.y = -1;
|
|
||||||
pos.assign(0, c.h-c.wallSize);
|
|
||||||
this.wallBottom = new MovingRectangle(pos, c.w, c.wallSize, c.movingWallSpeed);
|
|
||||||
(<MovingRectangle>this.wallBottom).dir.y = 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
pos.assign(0, 0);
|
|
||||||
this.wallTop = new Rectangle(pos, c.w, c.wallSize);
|
|
||||||
pos.assign(0, c.h-c.wallSize);
|
|
||||||
this.wallBottom = new Rectangle(pos, c.w, c.wallSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {GameComponents}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
|
|
||||||
import * as c from "../constants.js"
|
|
||||||
import * as en from "../enums.js"
|
|
||||||
import { GameComponents } from "../../shared_js/class/GameComponents.js";
|
|
||||||
|
|
||||||
class GameComponentsServer extends GameComponents {
|
|
||||||
scoreLeft: number = 0;
|
|
||||||
scoreRight: number = 0;
|
|
||||||
constructor(options: en.MatchOptions)
|
|
||||||
{
|
|
||||||
super(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {GameComponentsServer}
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
|
|
||||||
import * as en from "../enums.js"
|
|
||||||
import * as ev from "./Event.js"
|
|
||||||
import * as c from "../constants.js"
|
|
||||||
import { ClientPlayer } from "./Client";
|
|
||||||
import { GameComponentsServer } from "./GameComponentsServer.js";
|
|
||||||
import { clientInputListener } from "../wsServer.js";
|
|
||||||
import { random } from "../utils.js";
|
|
||||||
import { Ball } from "../../shared_js/class/Rectangle.js";
|
|
||||||
import { wallsMovements } from "../../shared_js/wallsMovement.js";
|
|
||||||
|
|
||||||
/*
|
|
||||||
Arg "s: GameSession" replace "this: GameSession" for use with setTimeout(),
|
|
||||||
because "this" is equal to "this: Timeout"
|
|
||||||
*/
|
|
||||||
class GameSession {
|
|
||||||
id: string; // url ?
|
|
||||||
playersMap: Map<string, ClientPlayer> = new Map();
|
|
||||||
unreadyPlayersMap: Map<string, ClientPlayer> = new Map();
|
|
||||||
gameLoopInterval: NodeJS.Timer | number = 0;
|
|
||||||
clientsUpdateInterval: NodeJS.Timer | number = 0;
|
|
||||||
components: GameComponentsServer;
|
|
||||||
matchOptions: en.MatchOptions;
|
|
||||||
matchEnded: boolean = false;
|
|
||||||
|
|
||||||
actual_time: number;
|
|
||||||
last_time: number;
|
|
||||||
delta_time: number;
|
|
||||||
|
|
||||||
constructor(id: string, matchOptions: en.MatchOptions) {
|
|
||||||
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: GameSession) {
|
|
||||||
s.playersMap.forEach( (client) => {
|
|
||||||
client.socket.on("message", clientInputListener);
|
|
||||||
});
|
|
||||||
|
|
||||||
s.actual_time = Date.now();
|
|
||||||
s.gameLoopInterval = setInterval(s._gameLoop, c.serverGameLoopIntervalMS, s);
|
|
||||||
s.clientsUpdateInterval = setInterval(s._clientsUpdate, c.clientsUpdateIntervalMS, s);
|
|
||||||
}
|
|
||||||
pause(s: GameSession) {
|
|
||||||
s.playersMap.forEach( (client) => {
|
|
||||||
client.socket.off("message", clientInputListener);
|
|
||||||
});
|
|
||||||
|
|
||||||
clearInterval(s.gameLoopInterval);
|
|
||||||
clearInterval(s.clientsUpdateInterval);
|
|
||||||
}
|
|
||||||
instantInputDebug(client: ClientPlayer) {
|
|
||||||
this._handleInput(c.fixedDeltaTime, client);
|
|
||||||
}
|
|
||||||
private _handleInput(delta: number, client: ClientPlayer) {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
private _gameLoop(s: GameSession) {
|
|
||||||
/* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private _ballMovement(delta: number, ball: 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private _clientsUpdate(s: GameSession) {
|
|
||||||
const gc = s.components;
|
|
||||||
const update = new ev.EventGameUpdate();
|
|
||||||
update.playerLeft.y = gc.playerLeft.pos.y;
|
|
||||||
update.playerRight.y = gc.playerRight.pos.y;
|
|
||||||
gc.ballsArr.forEach((ball) => {
|
|
||||||
update.ballsArr.push({
|
|
||||||
x: ball.pos.x,
|
|
||||||
y: ball.pos.y,
|
|
||||||
dirX: ball.dir.x,
|
|
||||||
dirY: ball.dir.y,
|
|
||||||
speed: ball.speed
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (s.matchOptions & en.MatchOptions.movingWalls) {
|
|
||||||
update.wallTop.y = gc.wallTop.pos.y;
|
|
||||||
update.wallBottom.y = gc.wallBottom.pos.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
s.playersMap.forEach( (client) => {
|
|
||||||
update.lastInputId = client.lastInputId;
|
|
||||||
client.socket.send(JSON.stringify(update));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
private _newRound(s: GameSession, ball: Ball) {
|
|
||||||
const gc = s.components;
|
|
||||||
// https://fr.wikipedia.org/wiki/Tennis_de_table#Nombre_de_manches
|
|
||||||
if (gc.scoreLeft >= 11 || gc.scoreRight >= 11)
|
|
||||||
// if (gc.scoreLeft >= 2 || gc.scoreRight >= 2) // WIP: for testing
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
private _matchEnd(s: GameSession) {
|
|
||||||
s.matchEnded = true;
|
|
||||||
const gc = s.components;
|
|
||||||
|
|
||||||
let eventEnd: ev.EventMatchEnd;
|
|
||||||
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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {GameSession}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
|
|
||||||
import { Vector, VectorInteger } from "./Vector.js";
|
|
||||||
import { Component, Moving } from "./interface.js";
|
|
||||||
import * as c from "../constants.js"
|
|
||||||
|
|
||||||
class Rectangle implements Component {
|
|
||||||
pos: VectorInteger;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
constructor(pos: VectorInteger, width: number, height: number) {
|
|
||||||
this.pos = new VectorInteger(pos.x, pos.y);
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
}
|
|
||||||
collision(collider: Rectangle): boolean {
|
|
||||||
const thisLeft = this.pos.x;
|
|
||||||
const thisRight = this.pos.x + this.width;
|
|
||||||
const thisTop = this.pos.y;
|
|
||||||
const thisBottom = this.pos.y + this.height;
|
|
||||||
const colliderLeft = collider.pos.x;
|
|
||||||
const colliderRight = collider.pos.x + collider.width;
|
|
||||||
const colliderTop = collider.pos.y;
|
|
||||||
const colliderBottom = collider.pos.y + collider.height;
|
|
||||||
if ((thisBottom < colliderTop)
|
|
||||||
|| (thisTop > colliderBottom)
|
|
||||||
|| (thisRight < colliderLeft)
|
|
||||||
|| (thisLeft > colliderRight)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MovingRectangle extends Rectangle implements Moving {
|
|
||||||
dir: Vector = new Vector(0,0);
|
|
||||||
speed: number;
|
|
||||||
readonly baseSpeed: number;
|
|
||||||
constructor(pos: VectorInteger, width: number, height: number, baseSpeed: number) {
|
|
||||||
super(pos, width, height);
|
|
||||||
this.baseSpeed = baseSpeed;
|
|
||||||
this.speed = baseSpeed;
|
|
||||||
}
|
|
||||||
move(delta: number) { // Math.floor WIP until VectorInteger debug
|
|
||||||
// console.log(`delta: ${delta}, speed: ${this.speed}, speed*delta: ${this.speed * delta}`);
|
|
||||||
// this.pos.x += Math.floor(this.dir.x * this.speed * delta);
|
|
||||||
// this.pos.y += Math.floor(this.dir.y * this.speed * delta);
|
|
||||||
this.pos.x += this.dir.x * this.speed * delta;
|
|
||||||
this.pos.y += this.dir.y * this.speed * delta;
|
|
||||||
}
|
|
||||||
moveAndCollide(delta: number, colliderArr: Rectangle[]) {
|
|
||||||
this._moveAndCollideAlgo(delta, colliderArr);
|
|
||||||
}
|
|
||||||
protected _moveAndCollideAlgo(delta: number, colliderArr: Rectangle[]) {
|
|
||||||
let oldPos = new VectorInteger(this.pos.x, this.pos.y);
|
|
||||||
this.move(delta);
|
|
||||||
if (colliderArr.some(this.collision, this)) {
|
|
||||||
this.pos = oldPos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Racket extends MovingRectangle {
|
|
||||||
constructor(pos: VectorInteger, width: number, height: number, baseSpeed: number) {
|
|
||||||
super(pos, width, height, baseSpeed);
|
|
||||||
}
|
|
||||||
moveAndCollide(delta: number, colliderArr: Rectangle[]) {
|
|
||||||
// let oldPos = new VectorInteger(this.pos.x, this.pos.y); // debug
|
|
||||||
this._moveAndCollideAlgo(delta, colliderArr);
|
|
||||||
// console.log(`y change: ${this.pos.y - oldPos.y}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Ball extends MovingRectangle {
|
|
||||||
readonly speedIncrease: number;
|
|
||||||
ballInPlay: boolean = false;
|
|
||||||
constructor(pos: VectorInteger, size: number, baseSpeed: number, speedIncrease: number) {
|
|
||||||
super(pos, size, size, baseSpeed);
|
|
||||||
this.speedIncrease = speedIncrease;
|
|
||||||
}
|
|
||||||
bounce(collider?: Rectangle) {
|
|
||||||
this._bounceAlgo(collider);
|
|
||||||
}
|
|
||||||
protected _bounceAlgo(collider?: Rectangle) {
|
|
||||||
/* Could be more generic, but testing only Racket is enough,
|
|
||||||
because in Pong collider can only be Racket or Wall. */
|
|
||||||
if (collider instanceof Racket) {
|
|
||||||
this._bounceRacket(collider);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._bounceWall();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
moveAndBounce(delta: number, colliderArr: Rectangle[]) {
|
|
||||||
this.move(delta);
|
|
||||||
let i = colliderArr.findIndex(this.collision, this);
|
|
||||||
if (i != -1)
|
|
||||||
{
|
|
||||||
this.bounce(colliderArr[i]);
|
|
||||||
this.move(delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
protected _bounceWall() { // Should be enough for Wall
|
|
||||||
this.dir.y = this.dir.y * -1;
|
|
||||||
}
|
|
||||||
protected _bounceRacket(racket: Racket) {
|
|
||||||
this._bounceRacketAlgo(racket);
|
|
||||||
}
|
|
||||||
protected _bounceRacketAlgo(racket: Racket) {
|
|
||||||
this.speed += this.speedIncrease;
|
|
||||||
|
|
||||||
let x = this.dir.x * -1;
|
|
||||||
|
|
||||||
const angleFactorDegree = 60;
|
|
||||||
const angleFactor = angleFactorDegree / 90;
|
|
||||||
const racketHalf = racket.height/2;
|
|
||||||
const ballMid = this.pos.y + this.height/2;
|
|
||||||
const racketMid = racket.pos.y + racketHalf;
|
|
||||||
|
|
||||||
let impact = ballMid - racketMid;
|
|
||||||
const horizontalMargin = racketHalf * 0.15;
|
|
||||||
if (impact < horizontalMargin && impact > -horizontalMargin) {
|
|
||||||
impact = 0;
|
|
||||||
}
|
|
||||||
else if (impact > 0) {
|
|
||||||
impact = impact - horizontalMargin;
|
|
||||||
}
|
|
||||||
else if (impact < 0) {
|
|
||||||
impact = impact + horizontalMargin;
|
|
||||||
}
|
|
||||||
|
|
||||||
let y = impact / (racketHalf - horizontalMargin) * angleFactor;
|
|
||||||
|
|
||||||
this.dir.assign(x, y);
|
|
||||||
// Normalize Vector (for consistency in speed independent of direction)
|
|
||||||
if (c.normalizedSpeed) {
|
|
||||||
this.dir = this.dir.normalized();
|
|
||||||
}
|
|
||||||
// console.log(`x: ${this.dir.x}, y: ${this.dir.y}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {Rectangle, MovingRectangle, Racket, Ball}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
|
|
||||||
class Vector {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
constructor(x: number = 0, y: number = 0) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
assign(x: number, y: number) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
normalized() : Vector {
|
|
||||||
const normalizationFactor = Math.abs(this.x) + Math.abs(this.y);
|
|
||||||
return new Vector(this.x/normalizationFactor, this.y/normalizationFactor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class VectorInteger extends Vector {
|
|
||||||
// PLACEHOLDER
|
|
||||||
// VectorInteger with set/get dont work (No draw on the screen). Why ?
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
class VectorInteger {
|
|
||||||
// private _x: number = 0;
|
|
||||||
// private _y: number = 0;
|
|
||||||
// constructor(x: number = 0, y: number = 0) {
|
|
||||||
// this._x = x;
|
|
||||||
// this._y = y;
|
|
||||||
// }
|
|
||||||
// get x(): number {
|
|
||||||
// return this._x;
|
|
||||||
// }
|
|
||||||
// set x(v: number) {
|
|
||||||
// // this._x = Math.floor(v);
|
|
||||||
// this._x = v;
|
|
||||||
// }
|
|
||||||
// get y(): number {
|
|
||||||
// return this._y;
|
|
||||||
// }
|
|
||||||
// set y(v: number) {
|
|
||||||
// // this._y = Math.floor(v);
|
|
||||||
// this._y = v;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
export {Vector, VectorInteger}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
|
|
||||||
import { Vector, VectorInteger } from "./Vector.js";
|
|
||||||
|
|
||||||
interface Component {
|
|
||||||
pos: VectorInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GraphicComponent extends Component {
|
|
||||||
ctx: CanvasRenderingContext2D;
|
|
||||||
color: string;
|
|
||||||
update: () => void;
|
|
||||||
clear: (pos?: VectorInteger) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Moving {
|
|
||||||
dir: Vector;
|
|
||||||
speed: number; // pixel per second
|
|
||||||
move(delta: number): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {Component, GraphicComponent, Moving}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
|
|
||||||
export * from "../shared_js/constants.js"
|
|
||||||
|
|
||||||
// 15ms == 1000/66.666
|
|
||||||
export const serverGameLoopIntervalMS = 15; // millisecond
|
|
||||||
export const fixedDeltaTime = serverGameLoopIntervalMS/1000; // second
|
|
||||||
|
|
||||||
// 33.333ms == 1000/30
|
|
||||||
export const clientsUpdateIntervalMS = 1000/30; // millisecond
|
|
||||||
|
|
||||||
export const CanvasWidth = 1500;
|
|
||||||
export const CanvasRatio = 1.66666;
|
|
||||||
/* ratio 5/3 (1.66) */
|
|
||||||
|
|
||||||
export const w = CanvasWidth;
|
|
||||||
export const h = CanvasWidth / CanvasRatio;
|
|
||||||
export const w_mid = Math.floor(w/2);
|
|
||||||
export const h_mid = Math.floor(h/2);
|
|
||||||
export const pw = Math.floor(w*0.017);
|
|
||||||
export const ph = pw*6;
|
|
||||||
export const ballSize = pw;
|
|
||||||
export const wallSize = Math.floor(w*0.01);
|
|
||||||
export const racketSpeed = Math.floor(w*0.66); // pixel per second
|
|
||||||
export const ballSpeed = Math.floor(w*0.66); // pixel per second
|
|
||||||
export const ballSpeedIncrease = Math.floor(ballSpeed*0.05); // pixel per second
|
|
||||||
|
|
||||||
export const normalizedSpeed = false; // for consistency in speed independent of direction
|
|
||||||
|
|
||||||
export const matchStartDelay = 3000; // millisecond
|
|
||||||
export const newRoundDelay = 1500; // millisecond
|
|
||||||
|
|
||||||
// Game Variantes
|
|
||||||
export const multiBallsCount = 3;
|
|
||||||
export const movingWallPosMax = Math.floor(w*0.12);
|
|
||||||
export const movingWallSpeed = Math.floor(w*0.08);
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
|
|
||||||
enum EventTypes {
|
|
||||||
// Class Implemented
|
|
||||||
gameUpdate = 1,
|
|
||||||
scoreUpdate,
|
|
||||||
matchEnd,
|
|
||||||
assignId,
|
|
||||||
matchmakingComplete,
|
|
||||||
|
|
||||||
// Generic
|
|
||||||
matchmakingInProgress,
|
|
||||||
matchStart,
|
|
||||||
matchNewRound, // unused
|
|
||||||
matchPause, // unused
|
|
||||||
matchResume, // unused
|
|
||||||
|
|
||||||
// Client
|
|
||||||
clientAnnounce,
|
|
||||||
clientPlayerReady,
|
|
||||||
clientInput,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
enum InputEnum {
|
|
||||||
noInput = 0,
|
|
||||||
up = 1,
|
|
||||||
down,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum PlayerSide {
|
|
||||||
left = 1,
|
|
||||||
right
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ClientRole {
|
|
||||||
player = 1,
|
|
||||||
spectator
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MatchOptions {
|
|
||||||
// binary flags, can be mixed
|
|
||||||
noOption = 0b0,
|
|
||||||
multiBalls = 1 << 0,
|
|
||||||
movingWalls = 1 << 1
|
|
||||||
}
|
|
||||||
|
|
||||||
export {EventTypes, InputEnum, PlayerSide, ClientRole, MatchOptions}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
|
|
||||||
import http from "http";
|
|
||||||
import url from "url";
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
import {wsServer} from "./wsServer.js"; wsServer; // no-op, just for loading
|
|
||||||
|
|
||||||
const hostname = "localhost";
|
|
||||||
const port = 8080;
|
|
||||||
const root = "../../www/";
|
|
||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
|
||||||
// let q = new URL(req.url, `http://${req.getHeaders().host}`)
|
|
||||||
let q = url.parse(req.url, true);
|
|
||||||
let filename = root + q.pathname;
|
|
||||||
fs.readFile(filename, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
res.writeHead(404, {"Content-Type": "text/html"});
|
|
||||||
return res.end("404 Not Found");
|
|
||||||
}
|
|
||||||
if (path.extname(filename) === ".html") {
|
|
||||||
res.writeHead(200, {"Content-Type": "text/html"});
|
|
||||||
}
|
|
||||||
else if (path.extname(filename) === ".js") {
|
|
||||||
res.writeHead(200, {"Content-Type": "application/javascript"});
|
|
||||||
}
|
|
||||||
else if (path.extname(filename) === ".mp3") {
|
|
||||||
res.writeHead(200, {"Content-Type": "audio/mpeg"});
|
|
||||||
}
|
|
||||||
else if (path.extname(filename) === ".ogg") {
|
|
||||||
res.writeHead(200, {"Content-Type": "audio/ogg"});
|
|
||||||
}
|
|
||||||
res.write(data);
|
|
||||||
return res.end();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen(port, hostname, () => {
|
|
||||||
console.log(`Pong running at http://${hostname}:${port}/pong.html`);
|
|
||||||
});
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { MovingRectangle } from "./class/Rectangle.js";
|
|
||||||
export * from "../shared_js/utils.js"
|
|
||||||
|
|
||||||
function shortId(id: string): string {
|
|
||||||
return id.substring(0, id.indexOf("-"));
|
|
||||||
}
|
|
||||||
|
|
||||||
export {shortId}
|
|
||||||
|
|
||||||
function random(min: number = 0, max: number = 1) {
|
|
||||||
return Math.random() * (max - min) + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sleep (ms: number) {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
function clamp(n: number, min: number, max: number) : number
|
|
||||||
{
|
|
||||||
if (n < min)
|
|
||||||
n = min;
|
|
||||||
else if (n > max)
|
|
||||||
n = max;
|
|
||||||
return (n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Typescript hack, unused
|
|
||||||
function assertMovingRectangle(value: unknown): asserts value is MovingRectangle {
|
|
||||||
// if (value !== MovingRectangle) throw new Error("Not a MovingRectangle");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {random, sleep, clamp, assertMovingRectangle}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
|
|
||||||
import * as c from "./constants.js";
|
|
||||||
import { MovingRectangle } from "../shared_js/class/Rectangle.js";
|
|
||||||
import { GameComponents } from "./class/GameComponents.js";
|
|
||||||
|
|
||||||
function wallsMovements(delta: number, gc: GameComponents)
|
|
||||||
{
|
|
||||||
const wallTop = <MovingRectangle>gc.wallTop;
|
|
||||||
const wallBottom = <MovingRectangle>gc.wallBottom;
|
|
||||||
if (wallTop.pos.y <= 0 || wallTop.pos.y >= c.movingWallPosMax) {
|
|
||||||
wallTop.dir.y *= -1;
|
|
||||||
}
|
|
||||||
if (wallBottom.pos.y >= c.h-c.wallSize || wallBottom.pos.y <= c.h-c.movingWallPosMax) {
|
|
||||||
wallBottom.dir.y *= -1;
|
|
||||||
}
|
|
||||||
wallTop.moveAndCollide(delta, [gc.playerLeft, gc.playerRight]);
|
|
||||||
wallBottom.moveAndCollide(delta, [gc.playerLeft, gc.playerRight]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export {wallsMovements}
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
|
|
||||||
import { WebSocketServer, WebSocket as BaseLibWebSocket } from "ws";
|
|
||||||
|
|
||||||
export class WebSocket extends BaseLibWebSocket {
|
|
||||||
id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
import { IncomingMessage } from "http";
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
import * as en from "./enums.js"
|
|
||||||
import * as ev from "./class/Event.js"
|
|
||||||
import { Client, ClientPlayer } from "./class/Client.js"
|
|
||||||
import { GameSession } from "./class/GameSession.js"
|
|
||||||
import { shortId } from "./utils.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>({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 gameSessionsMap: Map<string, GameSession> = new Map; // GameSession.id(url)/GameSession
|
|
||||||
|
|
||||||
wsServer.on("connection", connectionListener);
|
|
||||||
wsServer.on("error", errorListener);
|
|
||||||
wsServer.on("close", closeListener);
|
|
||||||
|
|
||||||
|
|
||||||
function connectionListener(socket: WebSocket, request: IncomingMessage)
|
|
||||||
{
|
|
||||||
const id = uuidv4();
|
|
||||||
const client = new Client(socket, id);
|
|
||||||
clientsMap.set(id, client);
|
|
||||||
socket.id = id;
|
|
||||||
|
|
||||||
socket.on("pong", function heartbeat() {
|
|
||||||
client.isAlive = true;
|
|
||||||
// console.log(`client ${shortId(client.id)} is alive`);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("message", function log(data: string) {
|
|
||||||
try {
|
|
||||||
const event: ev.ClientEvent = JSON.parse(data);
|
|
||||||
if (event.type === en.EventTypes.clientInput) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {}
|
|
||||||
console.log("data: " + data);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.once("message", clientAnnounceListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function clientAnnounceListener(this: WebSocket, data: string)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
const msg : ev.ClientAnnounce = JSON.parse(data);
|
|
||||||
if (msg.type === en.EventTypes.clientAnnounce)
|
|
||||||
{
|
|
||||||
// TODO: reconnection with msg.clientId ?
|
|
||||||
// TODO: spectator/player distinction with msg.role ?
|
|
||||||
// msg.role is probably not a good idea.
|
|
||||||
// something like a different route could be better
|
|
||||||
// "/pong" to play, "/ID_OF_A_GAMESESSION" to spectate
|
|
||||||
|
|
||||||
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(player);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log("Invalid ClientAnnounce");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log("Invalid JSON (clientAnnounceListener)");
|
|
||||||
}
|
|
||||||
this.once("message", clientAnnounceListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function matchmaking(player: ClientPlayer)
|
|
||||||
{
|
|
||||||
const minPlayersNumber = 2;
|
|
||||||
const maxPlayersNumber = 2;
|
|
||||||
const matchOptions = player.matchOptions;
|
|
||||||
matchmakingPlayersMap.set(player.id, player);
|
|
||||||
|
|
||||||
const compatiblePlayers: ClientPlayer[] = [];
|
|
||||||
for (const [id, client] of matchmakingPlayersMap)
|
|
||||||
{
|
|
||||||
if (client.matchOptions === matchOptions)
|
|
||||||
{
|
|
||||||
compatiblePlayers.push(client);
|
|
||||||
if (compatiblePlayers.length === maxPlayersNumber) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (compatiblePlayers.length < minPlayersNumber) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = uuidv4();
|
|
||||||
const gameSession = new GameSession(id, matchOptions);
|
|
||||||
gameSessionsMap.set(id, gameSession);
|
|
||||||
|
|
||||||
compatiblePlayers.forEach((client) => {
|
|
||||||
matchmakingPlayersMap.delete(client.id);
|
|
||||||
client.gameSession = gameSession;
|
|
||||||
gameSession.playersMap.set(client.id, client);
|
|
||||||
gameSession.unreadyPlayersMap.set(client.id, client);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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.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) ));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function playerReadyConfirmationListener(this: WebSocket, data: string)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
const msg : ev.ClientEvent = JSON.parse(data);
|
|
||||||
if (msg.type === en.EventTypes.clientPlayerReady)
|
|
||||||
{
|
|
||||||
const client = clientsMap.get(this.id);
|
|
||||||
const gameSession = client.gameSession;
|
|
||||||
gameSession.unreadyPlayersMap.delete(this.id);
|
|
||||||
if (gameSession.unreadyPlayersMap.size === 0) {
|
|
||||||
gameSession.playersMap.forEach( (client) => {
|
|
||||||
client.socket.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchStart) ));
|
|
||||||
});
|
|
||||||
gameSession.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log("Invalid playerReadyConfirmation");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log("Invalid JSON (playerReadyConfirmationListener)");
|
|
||||||
}
|
|
||||||
this.once("message", playerReadyConfirmationListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function clientInputListener(this: WebSocket, data: string)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
// const input: ev.ClientEvent = JSON.parse(data);
|
|
||||||
const input: ev.EventInput = JSON.parse(data);
|
|
||||||
if (input.type === en.EventTypes.clientInput)
|
|
||||||
{
|
|
||||||
const client = clientsMap.get(this.id) as ClientPlayer;
|
|
||||||
client.inputBuffer = input;
|
|
||||||
client.gameSession.instantInputDebug(client); // wip
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log("Invalid clientInput");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log("Invalid JSON (clientInputListener)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////
|
|
||||||
////////////
|
|
||||||
|
|
||||||
const pingInterval = setInterval( () => {
|
|
||||||
let deleteLog = "";
|
|
||||||
clientsMap.forEach( (client, key, map) => {
|
|
||||||
if (!client.isAlive) {
|
|
||||||
clientTerminate(client, key, map);
|
|
||||||
deleteLog += ` ${shortId(key)} |`;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
client.isAlive = false;
|
|
||||||
client.socket.ping();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (deleteLog) {
|
|
||||||
console.log(`Disconnected:${deleteLog}`);
|
|
||||||
}
|
|
||||||
console.log("gameSessionMap size: " + gameSessionsMap.size);
|
|
||||||
console.log("clientsMap size: " + clientsMap.size);
|
|
||||||
console.log("matchmakingPlayersMap size: " + matchmakingPlayersMap.size);
|
|
||||||
console.log("");
|
|
||||||
}, 4200);
|
|
||||||
|
|
||||||
|
|
||||||
function clientTerminate(client: Client, key: string, map: Map<string, Client>)
|
|
||||||
{
|
|
||||||
client.socket.terminate();
|
|
||||||
if (client.gameSession)
|
|
||||||
{
|
|
||||||
client.gameSession.playersMap.delete(key);
|
|
||||||
if (client.gameSession.playersMap.size === 0)
|
|
||||||
{
|
|
||||||
clearInterval(client.gameSession.clientsUpdateInterval);
|
|
||||||
clearInterval(client.gameSession.gameLoopInterval);
|
|
||||||
gameSessionsMap.delete(client.gameSession.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
map.delete(key);
|
|
||||||
if (matchmakingPlayersMap.has(key)) {
|
|
||||||
matchmakingPlayersMap.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function closeListener()
|
|
||||||
{
|
|
||||||
clearInterval(pingInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function errorListener(error: Error)
|
|
||||||
{
|
|
||||||
console.log("Error: " + JSON.stringify(error));
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
{
|
|
||||||
"include": ["src"],
|
|
||||||
// "exclude": ["node_modules"],
|
|
||||||
"compilerOptions": {
|
|
||||||
// "outDir": "./build",
|
|
||||||
// "types": ["node"], /* Specify type package names to be included without being referenced in a source file. */
|
|
||||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
|
||||||
|
|
||||||
/* Projects */
|
|
||||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
|
||||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
|
||||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
|
||||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
|
||||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
|
||||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
|
||||||
|
|
||||||
/* Language and Environment */
|
|
||||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
|
||||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
|
||||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
|
||||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
|
||||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
|
||||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.gc. 'React.createElement' or 'h'. */
|
|
||||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.gc. 'React.Fragment' or 'Fragment'. */
|
|
||||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
|
||||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
|
||||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
|
||||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
|
||||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
|
||||||
|
|
||||||
/* Modules */
|
|
||||||
// "module": "commonjs", /* Specify what module code is generated. */
|
|
||||||
"module": "ES6", /* Specify what module code is generated. */
|
|
||||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
|
||||||
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
|
||||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
|
||||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
|
||||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
|
||||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
|
||||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
|
||||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
|
||||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
|
||||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
|
||||||
|
|
||||||
/* JavaScript Support */
|
|
||||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
|
||||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
|
||||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
|
||||||
|
|
||||||
/* Emit */
|
|
||||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
|
||||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
|
||||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
|
||||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
|
||||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
|
||||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
|
||||||
// "removeComments": true, /* Disable emitting comments. */
|
|
||||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
|
||||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
|
||||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
|
||||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
|
||||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
|
||||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
|
||||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
|
||||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
|
||||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
|
||||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
|
||||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
|
||||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
|
||||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
|
||||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
|
||||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
|
||||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
|
||||||
|
|
||||||
/* Interop Constraints */
|
|
||||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
|
||||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
|
||||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
|
||||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
|
||||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
|
||||||
|
|
||||||
/* Type Checking */
|
|
||||||
"strict": true, /* Enable all strict type-checking options. */
|
|
||||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
|
||||||
"strictNullChecks": false, /* When type checking, take into account 'null' and 'undefined'. */
|
|
||||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
|
||||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
|
||||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
|
||||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
|
||||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
|
||||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
|
||||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
|
||||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
|
||||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
|
||||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
|
||||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
|
||||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
|
||||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
|
||||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
|
||||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
|
||||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
|
||||||
|
|
||||||
/* Completeness */
|
|
||||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
|
||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user