diff --git a/jeu/jsconfig.json b/jeu/jsconfig.json new file mode 100644 index 00000000..347bf03f --- /dev/null +++ b/jeu/jsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Node", + "target": "ES2020", + "strictNullChecks": true, + "strictFunctionTypes": true + }, + "exclude": [ + "node_modules", + "**/node_modules/*" + ] +} \ No newline at end of file diff --git a/jeu/make.sh b/jeu/make.sh new file mode 100644 index 00000000..ebce024a --- /dev/null +++ b/jeu/make.sh @@ -0,0 +1,18 @@ +#!/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/ diff --git a/jeu/package-lock.json b/jeu/package-lock.json new file mode 100644 index 00000000..03e6f035 --- /dev/null +++ b/jeu/package-lock.json @@ -0,0 +1,121 @@ +{ + "name": "jeu", + "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.9.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.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "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.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "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": {} + } + } +} diff --git a/jeu/package.json b/jeu/package.json new file mode 100644 index 00000000..7fdb209b --- /dev/null +++ b/jeu/package.json @@ -0,0 +1,12 @@ +{ + "devDependencies": { + "@types/node": "^18.11.5", + "@types/uuid": "^8.3.4", + "@types/ws": "^8.5.3", + "typescript": "^4.9.4" + }, + "dependencies": { + "uuid": "^9.0.0", + "ws": "^8.10.0" + } +} diff --git a/jeu/src/client/audio.js b/jeu/src/client/audio.js new file mode 100644 index 00000000..df9ec3e0 --- /dev/null +++ b/jeu/src/client/audio.js @@ -0,0 +1,12 @@ +import * as c from "./constants.js"; +export const soundPongArr = []; +export const soundRoblox = new Audio("http://localhost:8080/sound/roblox-oof.ogg"); +export function initAudio(muteFlag) { + for (let i = 0; i <= 32; i++) { + soundPongArr.push(new Audio("http://localhost:8080/sound/pong/" + i + ".ogg")); + soundPongArr[i].volume = c.soundPongVolume; + soundPongArr[i].muted = muteFlag; + } + soundRoblox.volume = c.soundRobloxVolume; + soundRoblox.muted = muteFlag; +} diff --git a/jeu/src/client/audio.ts b/jeu/src/client/audio.ts new file mode 100644 index 00000000..74c73336 --- /dev/null +++ b/jeu/src/client/audio.ts @@ -0,0 +1,16 @@ + +import * as c from "./constants.js" + +export const soundPongArr: HTMLAudioElement[] = []; +export const soundRoblox = new Audio("http://localhost:8080/sound/roblox-oof.ogg"); + +export function initAudio(muteFlag: boolean) +{ + for (let i = 0; i <= 32; i++) { + soundPongArr.push(new Audio("http://localhost:8080/sound/pong/"+i+".ogg")); + soundPongArr[i].volume = c.soundPongVolume; + soundPongArr[i].muted = muteFlag; + } + soundRoblox.volume = c.soundRobloxVolume; + soundRoblox.muted = muteFlag; +} diff --git a/jeu/src/client/class/GameArea.js b/jeu/src/client/class/GameArea.js new file mode 100644 index 00000000..5748c9e7 --- /dev/null +++ b/jeu/src/client/class/GameArea.js @@ -0,0 +1,32 @@ +import * as c from ".././constants.js"; +export class GameArea { + constructor() { + this.keys = []; + this.handleInputInterval = 0; + this.gameLoopInterval = 0; + this.drawLoopInterval = 0; + this.canvas = document.createElement("canvas"); + this.ctx = this.canvas.getContext("2d"); + this.canvas.width = c.CanvasWidth; + this.canvas.height = c.CanvasWidth / c.CanvasRatio; + let container = document.getElementById("canvas_container"); + if (container) + container.insertBefore(this.canvas, container.childNodes[0]); + } + addKey(key) { + key = key.toLowerCase(); + var i = this.keys.indexOf(key); + if (i == -1) + this.keys.push(key); + } + deleteKey(key) { + key = key.toLowerCase(); + var i = this.keys.indexOf(key); + if (i != -1) { + this.keys.splice(i, 1); + } + } + clear() { + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + } +} diff --git a/jeu/src/client/class/GameArea.ts b/jeu/src/client/class/GameArea.ts new file mode 100644 index 00000000..5483baa4 --- /dev/null +++ b/jeu/src/client/class/GameArea.ts @@ -0,0 +1,36 @@ + +import * as c from ".././constants.js" + +export class GameArea { + keys: string[] = []; + handleInputInterval: number = 0; + gameLoopInterval: number = 0; + drawLoopInterval: number = 0; + canvas: HTMLCanvasElement; + ctx: CanvasRenderingContext2D; + constructor() { + this.canvas = document.createElement("canvas"); + this.ctx = this.canvas.getContext("2d") as CanvasRenderingContext2D; + this.canvas.width = c.CanvasWidth; + this.canvas.height = c.CanvasWidth / c.CanvasRatio; + let container = document.getElementById("canvas_container"); + if (container) + container.insertBefore(this.canvas, container.childNodes[0]); + } + addKey(key: string) { + key = key.toLowerCase(); + var i = this.keys.indexOf(key); + if (i == -1) + this.keys.push(key); + } + deleteKey(key: string) { + key = key.toLowerCase(); + var i = this.keys.indexOf(key); + if (i != -1) { + this.keys.splice(i, 1); + } + } + clear() { + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + } +} diff --git a/jeu/src/client/class/GameComponentsClient.js b/jeu/src/client/class/GameComponentsClient.js new file mode 100644 index 00000000..7207d1e0 --- /dev/null +++ b/jeu/src/client/class/GameComponentsClient.js @@ -0,0 +1,71 @@ +import * as c from "../constants.js"; +import * as en from "../../shared_js/enums.js"; +import { VectorInteger } from "../../shared_js/class/Vector.js"; +import { TextElem, TextNumericValue } from "./Text.js"; +import { RectangleClient, MovingRectangleClient, RacketClient, BallClient, Line } from "./RectangleClient.js"; +import { GameComponents } from "../../shared_js/class/GameComponents.js"; +class GameComponentsExtensionForClient extends GameComponents { + constructor(options, ctx) { + super(options); + // Rackets + const basePL = this.playerLeft; + const basePR = this.playerRight; + this.playerLeft = new RacketClient(basePL.pos, basePL.width, basePL.height, basePL.baseSpeed, ctx, "white"); + this.playerRight = new RacketClient(basePR.pos, basePR.width, basePR.height, basePR.baseSpeed, ctx, "white"); + // Balls + const newBallsArr = []; + this.ballsArr.forEach((ball) => { + newBallsArr.push(new BallClient(ball.pos, ball.width, ball.baseSpeed, ball.speedIncrease, ctx, "white")); + }); + this.ballsArr = newBallsArr; + // Walls + if (options & en.MatchOptions.movingWalls) { + const baseWT = this.wallTop; + const baseWB = this.wallBottom; + this.wallTop = new MovingRectangleClient(baseWT.pos, baseWT.width, baseWT.height, baseWT.baseSpeed, ctx, "grey"); + this.wallTop.dir.assign(baseWT.dir.x, baseWT.dir.y); + this.wallBottom = new MovingRectangleClient(baseWB.pos, baseWB.width, baseWB.height, baseWB.baseSpeed, ctx, "grey"); + this.wallBottom.dir.assign(baseWB.dir.x, baseWB.dir.y); + } + else { + const baseWT = this.wallTop; + const baseWB = this.wallBottom; + this.wallTop = new RectangleClient(baseWT.pos, baseWT.width, baseWT.height, ctx, "grey"); + this.wallBottom = new RectangleClient(baseWB.pos, baseWB.width, baseWB.height, ctx, "grey"); + } + } +} +export class GameComponentsClient extends GameComponentsExtensionForClient { + constructor(options, ctx) { + super(options, ctx); + let pos = new VectorInteger; + // Scores + pos.assign(c.w_mid - c.scoreSize * 1.6, c.scoreSize * 1.5); + this.scoreLeft = new TextNumericValue(pos, c.scoreSize, ctx, "white"); + pos.assign(c.w_mid + c.scoreSize * 1.1, c.scoreSize * 1.5); + this.scoreRight = new TextNumericValue(pos, c.scoreSize, ctx, "white"); + this.scoreLeft.value = 0; + this.scoreRight.value = 0; + // Text + pos.assign(0, c.h_mid); + this.text1 = new TextElem(pos, Math.floor(c.w / 8), ctx, "white"); + this.text2 = new TextElem(pos, Math.floor(c.w / 24), ctx, "white"); + this.text3 = new TextElem(pos, Math.floor(c.w / 24), ctx, "white"); + // Dotted Midline + pos.assign(c.w_mid - c.midLineSize / 2, 0 + c.wallSize); + this.midLine = new Line(pos, c.midLineSize, c.h - c.wallSize * 2, ctx, "white", 15); + // Grid + pos.assign(0, c.h_mid); + this.w_grid_mid = new RectangleClient(pos, c.w, c.gridSize, ctx, "darkgreen"); + pos.assign(0, c.h / 4); + this.w_grid_u1 = new RectangleClient(pos, c.w, c.gridSize, ctx, "darkgreen"); + pos.assign(0, c.h - c.h / 4); + this.w_grid_d1 = new RectangleClient(pos, c.w, c.gridSize, ctx, "darkgreen"); + pos.assign(c.w_mid, 0); + this.h_grid_mid = new RectangleClient(pos, c.gridSize, c.h, ctx, "darkgreen"); + pos.assign(c.w / 4, 0); + this.h_grid_u1 = new RectangleClient(pos, c.gridSize, c.h, ctx, "darkgreen"); + pos.assign(c.w - c.w / 4, 0); + this.h_grid_d1 = new RectangleClient(pos, c.gridSize, c.h, ctx, "darkgreen"); + } +} diff --git a/jeu/src/client/class/GameComponentsClient.ts b/jeu/src/client/class/GameComponentsClient.ts new file mode 100644 index 00000000..9fa0a2a3 --- /dev/null +++ b/jeu/src/client/class/GameComponentsClient.ts @@ -0,0 +1,115 @@ + +import * as c from "../constants.js" +import * as en from "../../shared_js/enums.js" +import { Vector, VectorInteger } from "../../shared_js/class/Vector.js"; +import { TextElem, TextNumericValue } from "./Text.js"; +import { RectangleClient, MovingRectangleClient, RacketClient, BallClient, Line } from "./RectangleClient.js"; +import { GameComponents } from "../../shared_js/class/GameComponents.js"; +import { MovingRectangle } from "../../shared_js/class/Rectangle.js"; + +class GameComponentsExtensionForClient extends GameComponents { + wallTop: RectangleClient | MovingRectangleClient; + wallBottom: RectangleClient | MovingRectangleClient; + playerLeft: RacketClient; + playerRight: RacketClient; + ballsArr: BallClient[]; + constructor(options: en.MatchOptions, ctx: CanvasRenderingContext2D) + { + super(options); + + // Rackets + const basePL = this.playerLeft; + const basePR = this.playerRight; + this.playerLeft = new RacketClient( + basePL.pos, basePL.width, basePL.height, basePL.baseSpeed, + ctx, "white"); + this.playerRight = new RacketClient( + basePR.pos, basePR.width, basePR.height, basePR.baseSpeed, + ctx, "white"); + + // Balls + const newBallsArr: BallClient[] = []; + this.ballsArr.forEach((ball) => { + newBallsArr.push(new BallClient(ball.pos, ball.width, ball.baseSpeed, ball.speedIncrease, + ctx, "white") + ); + }); + this.ballsArr = newBallsArr; + + // Walls + if (options & en.MatchOptions.movingWalls) + { + const baseWT = this.wallTop; + const baseWB = this.wallBottom; + + this.wallTop = new MovingRectangleClient(baseWT.pos, baseWT.width, baseWT.height, baseWT.baseSpeed, + ctx, "grey"); + (this.wallTop).dir.assign(baseWT.dir.x, baseWT.dir.y); + + this.wallBottom = new MovingRectangleClient(baseWB.pos, baseWB.width, baseWB.height, baseWB.baseSpeed, + ctx, "grey"); + (this.wallBottom).dir.assign(baseWB.dir.x, baseWB.dir.y); + } + else + { + const baseWT = this.wallTop; + const baseWB = this.wallBottom; + this.wallTop = new RectangleClient(baseWT.pos, baseWT.width, baseWT.height, + ctx, "grey"); + this.wallBottom = new RectangleClient(baseWB.pos, baseWB.width, baseWB.height, + ctx, "grey"); + } + } +} + +export class GameComponentsClient extends GameComponentsExtensionForClient { + midLine: Line; + scoreLeft: TextNumericValue; + scoreRight: TextNumericValue; + text1: TextElem; + text2: TextElem; + text3: TextElem; + + w_grid_mid: RectangleClient; + w_grid_u1: RectangleClient; + w_grid_d1: RectangleClient; + h_grid_mid: RectangleClient; + h_grid_u1: RectangleClient; + h_grid_d1: RectangleClient; + constructor(options: en.MatchOptions, ctx: CanvasRenderingContext2D) + { + super(options, ctx); + let pos = new VectorInteger; + // Scores + pos.assign(c.w_mid-c.scoreSize*1.6, c.scoreSize*1.5); + this.scoreLeft = new TextNumericValue(pos, c.scoreSize, ctx, "white"); + pos.assign(c.w_mid+c.scoreSize*1.1, c.scoreSize*1.5); + this.scoreRight = new TextNumericValue(pos, c.scoreSize, ctx, "white"); + this.scoreLeft.value = 0; + this.scoreRight.value = 0; + + // Text + pos.assign(0, c.h_mid); + this.text1 = new TextElem(pos, Math.floor(c.w/8), ctx, "white"); + this.text2 = new TextElem(pos, Math.floor(c.w/24), ctx, "white"); + this.text3 = new TextElem(pos, Math.floor(c.w/24), ctx, "white"); + + // Dotted Midline + pos.assign(c.w_mid-c.midLineSize/2, 0+c.wallSize); + this.midLine = new Line(pos, c.midLineSize, c.h-c.wallSize*2, ctx, "white", 15); + + // Grid + pos.assign(0, c.h_mid); + this.w_grid_mid = new RectangleClient(pos, c.w, c.gridSize, ctx, "darkgreen"); + pos.assign(0, c.h/4); + this.w_grid_u1 = new RectangleClient(pos, c.w, c.gridSize, ctx, "darkgreen"); + pos.assign(0, c.h-c.h/4); + this.w_grid_d1 = new RectangleClient(pos, c.w, c.gridSize, ctx, "darkgreen"); + pos.assign(c.w_mid, 0); + this.h_grid_mid = new RectangleClient(pos, c.gridSize, c.h, ctx, "darkgreen"); + pos.assign(c.w/4, 0); + this.h_grid_u1 = new RectangleClient(pos, c.gridSize, c.h, ctx, "darkgreen"); + pos.assign(c.w-c.w/4, 0); + this.h_grid_d1 = new RectangleClient(pos, c.gridSize, c.h, ctx, "darkgreen"); + } +} diff --git a/jeu/src/client/class/InputHistory.js b/jeu/src/client/class/InputHistory.js new file mode 100644 index 00000000..d7c31ed3 --- /dev/null +++ b/jeu/src/client/class/InputHistory.js @@ -0,0 +1,7 @@ +export class InputHistory { + constructor(inputState, deltaTime) { + this.input = inputState.input; + this.id = inputState.id; + this.deltaTime = deltaTime; + } +} diff --git a/jeu/src/client/class/InputHistory.ts b/jeu/src/client/class/InputHistory.ts new file mode 100644 index 00000000..952693af --- /dev/null +++ b/jeu/src/client/class/InputHistory.ts @@ -0,0 +1,14 @@ + +import * as en from "../../shared_js/enums.js" +import * as ev from "../../shared_js/class/Event.js" + +export class InputHistory { + input: en.InputEnum; + id: number; + deltaTime: number; + constructor(inputState: ev.EventInput, deltaTime: number) { + this.input = inputState.input; + this.id = inputState.id; + this.deltaTime = deltaTime; + } +} diff --git a/jeu/src/client/class/RectangleClient.js b/jeu/src/client/class/RectangleClient.js new file mode 100644 index 00000000..1fe3a10b --- /dev/null +++ b/jeu/src/client/class/RectangleClient.js @@ -0,0 +1,85 @@ +import { VectorInteger } from "../../shared_js/class/Vector.js"; +import { Rectangle, MovingRectangle, Racket, Ball } from "../../shared_js/class/Rectangle.js"; +import { soundPongArr } from "../audio.js"; +import { random } from "../utils.js"; +function updateRectangle() { + this.ctx.fillStyle = this.color; + this.ctx.fillRect(this.pos.x, this.pos.y, this.width, this.height); +} +function clearRectangle(pos) { + if (pos) + this.ctx.clearRect(pos.x, pos.y, this.width, this.height); + else + this.ctx.clearRect(this.pos.x, this.pos.y, this.width, this.height); +} +export class RectangleClient extends Rectangle { + constructor(pos, width, height, ctx, color) { + super(pos, width, height); + this.ctx = ctx; + this.color = color; + this.update = updateRectangle; + this.clear = clearRectangle; + } +} +export class MovingRectangleClient extends MovingRectangle { + constructor(pos, width, height, baseSpeed, ctx, color) { + super(pos, width, height, baseSpeed); + this.ctx = ctx; + this.color = color; + this.update = updateRectangle; + this.clear = clearRectangle; + } +} +export class RacketClient extends Racket { + constructor(pos, width, height, baseSpeed, ctx, color) { + super(pos, width, height, baseSpeed); + this.ctx = ctx; + this.color = color; + this.update = updateRectangle; + this.clear = clearRectangle; + } +} +export class BallClient extends Ball { + constructor(pos, size, baseSpeed, speedIncrease, ctx, color) { + super(pos, size, baseSpeed, speedIncrease); + this.ctx = ctx; + this.color = color; + this.update = updateRectangle; + this.clear = clearRectangle; + } + bounce(collider) { + this._bounceAlgo(collider); + soundPongArr[Math.floor(random(0, soundPongArr.length))].play(); + } +} +function updateLine() { + this.ctx.fillStyle = this.color; + let pos = new VectorInteger; + let i = 0; + while (i < this.segmentCount) { + /* Horizontal Line */ + // pos.y = this.pos.y; + // pos.x = this.pos.x + this.segmentWidth * i; + /* Vertical Line */ + pos.x = this.pos.x; + pos.y = this.pos.y + this.segmentHeight * i; + this.ctx.fillRect(pos.x, pos.y, this.segmentWidth, this.segmentHeight); + i += 2; + } +} +export class Line extends RectangleClient { + constructor(pos, width, height, ctx, color, gapeCount) { + super(pos, width, height, ctx, color); + this.gapeCount = 0; + this.update = updateLine; + if (gapeCount) + this.gapeCount = gapeCount; + this.segmentCount = this.gapeCount * 2 + 1; + /* Vertical Line */ + this.segmentWidth = this.width; + this.segmentHeight = this.height / this.segmentCount; + /* Horizontal Line */ + // this.segmentWidth = this.width / this.segmentCount; + // this.segmentHeight = this.height; + } +} diff --git a/jeu/src/client/class/RectangleClient.ts b/jeu/src/client/class/RectangleClient.ts new file mode 100644 index 00000000..1cc1c4f5 --- /dev/null +++ b/jeu/src/client/class/RectangleClient.ts @@ -0,0 +1,129 @@ + +import { Vector, VectorInteger } from "../../shared_js/class/Vector.js"; +import { Component, GraphicComponent, Moving } from "../../shared_js/class/interface.js"; +import { Rectangle, MovingRectangle, Racket, Ball } from "../../shared_js/class/Rectangle.js"; +import { soundPongArr } from "../audio.js" +import { random } from "../utils.js"; + +function updateRectangle(this: RectangleClient) { + this.ctx.fillStyle = this.color; + this.ctx.fillRect(this.pos.x, this.pos.y, this.width, this.height); +} + +function clearRectangle(this: RectangleClient, pos?: VectorInteger) { + if (pos) + this.ctx.clearRect(pos.x, pos.y, this.width, this.height); + else + this.ctx.clearRect(this.pos.x, this.pos.y, this.width, this.height); +} + +export class RectangleClient extends Rectangle implements GraphicComponent { + ctx: CanvasRenderingContext2D; + color: string; + update: () => void; + clear: (pos?: VectorInteger) => void; + constructor(pos: VectorInteger, width: number, height: number, + ctx: CanvasRenderingContext2D, color: string) + { + super(pos, width, height); + this.ctx = ctx; + this.color = color; + this.update = updateRectangle; + this.clear = clearRectangle; + } +} + +export class MovingRectangleClient extends MovingRectangle implements GraphicComponent { + ctx: CanvasRenderingContext2D; + color: string; + update: () => void; + clear: (pos?: VectorInteger) => void; + constructor(pos: VectorInteger, width: number, height: number, baseSpeed: number, + ctx: CanvasRenderingContext2D, color: string) + { + super(pos, width, height, baseSpeed); + this.ctx = ctx; + this.color = color; + this.update = updateRectangle; + this.clear = clearRectangle; + } +} + +export class RacketClient extends Racket implements GraphicComponent { + ctx: CanvasRenderingContext2D; + color: string; + update: () => void; + clear: (pos?: VectorInteger) => void; + constructor(pos: VectorInteger, width: number, height: number, baseSpeed: number, + ctx: CanvasRenderingContext2D, color: string) + { + super(pos, width, height, baseSpeed); + this.ctx = ctx; + this.color = color; + this.update = updateRectangle; + this.clear = clearRectangle; + } +} + +export class BallClient extends Ball implements GraphicComponent { + ctx: CanvasRenderingContext2D; + color: string; + update: () => void; + clear: (pos?: VectorInteger) => void; + constructor(pos: VectorInteger, size: number, baseSpeed: number, speedIncrease: number, + ctx: CanvasRenderingContext2D, color: string) + { + super(pos, size, baseSpeed, speedIncrease); + this.ctx = ctx; + this.color = color; + this.update = updateRectangle; + this.clear = clearRectangle; + } + bounce(collider?: Rectangle) { + this._bounceAlgo(collider); + soundPongArr[ Math.floor(random(0, soundPongArr.length)) ].play(); + } +} + +function updateLine(this: Line) { + this.ctx.fillStyle = this.color; + let pos: VectorInteger = new VectorInteger; + let i = 0; + while (i < this.segmentCount) + { + /* Horizontal Line */ + // pos.y = this.pos.y; + // pos.x = this.pos.x + this.segmentWidth * i; + + /* Vertical Line */ + pos.x = this.pos.x; + pos.y = this.pos.y + this.segmentHeight * i; + + this.ctx.fillRect(pos.x, pos.y, this.segmentWidth, this.segmentHeight); + i += 2; + } +} + +export class Line extends RectangleClient { + gapeCount: number = 0; + segmentCount: number; + segmentWidth: number; + segmentHeight: number; + constructor(pos: VectorInteger, width: number, height: number, + ctx: CanvasRenderingContext2D, color: string, gapeCount?: number) + { + super(pos, width, height, ctx, color); + this.update = updateLine; + if (gapeCount) + this.gapeCount = gapeCount; + this.segmentCount = this.gapeCount * 2 + 1; + + /* Vertical Line */ + this.segmentWidth = this.width; + this.segmentHeight = this.height / this.segmentCount; + + /* Horizontal Line */ + // this.segmentWidth = this.width / this.segmentCount; + // this.segmentHeight = this.height; + } +} diff --git a/jeu/src/client/class/Text.js b/jeu/src/client/class/Text.js new file mode 100644 index 00000000..94370c93 --- /dev/null +++ b/jeu/src/client/class/Text.js @@ -0,0 +1,43 @@ +import { VectorInteger } from "../../shared_js/class/Vector.js"; +// conflict with Text +export class TextElem { + constructor(pos, size, ctx, color, font = "Bit5x3") { + this.text = ""; + // this.pos = Object.assign({}, pos); // create bug, Uncaught TypeError: X is not a function + this.pos = new VectorInteger(pos.x, pos.y); + this.size = size; + this.ctx = ctx; + this.color = color; + this.font = font; + } + update() { + this.ctx.font = this.size + "px" + " " + this.font; + this.ctx.fillStyle = this.color; + this.ctx.fillText(this.text, this.pos.x, this.pos.y); + } + clear() { + // clear no very accurate for Text + // https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics + let textMetric = this.ctx.measureText(this.text); + // console.log("textMetric.width = "+textMetric.width); + // console.log("size = "+this.size); + // console.log("x = "+this.pos.x); + // console.log("y = "+this.pos.y); + this.ctx.clearRect(this.pos.x - 1, this.pos.y - this.size + 1, textMetric.width, this.size); + // +1 and -1 because float imprecision (and Math.floor() with VectorInteger dont work for the moment) + // (or maybe its textMetric imprecision ?) + } +} +export class TextNumericValue extends TextElem { + constructor(pos, size, ctx, color, font) { + super(pos, size, ctx, color, font); + this._value = 0; + } + get value() { + return this._value; + } + set value(v) { + this._value = v; + this.text = v.toString(); + } +} diff --git a/jeu/src/client/class/Text.ts b/jeu/src/client/class/Text.ts new file mode 100644 index 00000000..11f00ab9 --- /dev/null +++ b/jeu/src/client/class/Text.ts @@ -0,0 +1,56 @@ + +import { Vector, VectorInteger } from "../../shared_js/class/Vector.js"; +import { Component } from "../../shared_js/class/interface.js"; + +// conflict with Text +export class TextElem implements Component { + ctx: CanvasRenderingContext2D; + pos: VectorInteger; + color: string; + size: number; + font: string; + text: string = ""; + constructor(pos: VectorInteger, size: number, + ctx: CanvasRenderingContext2D, color: string, font: string = "Bit5x3") + { + // this.pos = Object.assign({}, pos); // create bug, Uncaught TypeError: X is not a function + this.pos = new VectorInteger(pos.x, pos.y); + this.size = size; + this.ctx = ctx; + this.color = color; + this.font = font; + } + update() { + this.ctx.font = this.size + "px" + " " + this.font; + this.ctx.fillStyle = this.color; + this.ctx.fillText(this.text, this.pos.x, this.pos.y); + } + clear() { + // clear no very accurate for Text + // https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics + let textMetric = this.ctx.measureText(this.text); + // console.log("textMetric.width = "+textMetric.width); + // console.log("size = "+this.size); + // console.log("x = "+this.pos.x); + // console.log("y = "+this.pos.y); + this.ctx.clearRect(this.pos.x - 1, this.pos.y-this.size + 1, textMetric.width, this.size); + // +1 and -1 because float imprecision (and Math.floor() with VectorInteger dont work for the moment) + // (or maybe its textMetric imprecision ?) + } +} + +export class TextNumericValue extends TextElem { + private _value: number = 0; + constructor(pos: VectorInteger, size: number, + ctx: CanvasRenderingContext2D, color: string, font?: string) + { + super(pos, size, ctx, color, font); + } + get value() { + return this._value; + } + set value(v: number) { + this._value = v; + this.text = v.toString(); + } +} diff --git a/jeu/src/client/constants.js b/jeu/src/client/constants.js new file mode 100644 index 00000000..33aecc6e --- /dev/null +++ b/jeu/src/client/constants.js @@ -0,0 +1,13 @@ +import { w } from "../shared_js/constants.js"; +export * from "../shared_js/constants.js"; +export const midLineSize = Math.floor(w / 150); +export const scoreSize = Math.floor(w / 16); +export const gridSize = Math.floor(w / 500); +// min interval on Firefox seems to be 15. Chrome can go lower. +export const handleInputIntervalMS = 15; // millisecond +export const sendLoopIntervalMS = 15; // millisecond // unused +export const gameLoopIntervalMS = 15; // millisecond +export const drawLoopIntervalMS = 15; // millisecond +export const fixedDeltaTime = gameLoopIntervalMS / 1000; // second +export const soundRobloxVolume = 0.3; // between 0 and 1 +export const soundPongVolume = 0.3; // between 0 and 1 diff --git a/jeu/src/client/constants.ts b/jeu/src/client/constants.ts new file mode 100644 index 00000000..97bae265 --- /dev/null +++ b/jeu/src/client/constants.ts @@ -0,0 +1,18 @@ + +import { w } from "../shared_js/constants.js" +export * from "../shared_js/constants.js" + +export const midLineSize = Math.floor(w/150); +export const scoreSize = Math.floor(w/16); +export const gridSize = Math.floor(w/500); + +// min interval on Firefox seems to be 15. Chrome can go lower. +export const handleInputIntervalMS = 15; // millisecond +export const sendLoopIntervalMS = 15; // millisecond // unused +export const gameLoopIntervalMS = 15; // millisecond +export const drawLoopIntervalMS = 15; // millisecond + +export const fixedDeltaTime = gameLoopIntervalMS/1000; // second + +export const soundRobloxVolume = 0.3; // between 0 and 1 +export const soundPongVolume = 0.3; // between 0 and 1 diff --git a/jeu/src/client/draw.js b/jeu/src/client/draw.js new file mode 100644 index 00000000..93270c04 --- /dev/null +++ b/jeu/src/client/draw.js @@ -0,0 +1,35 @@ +import { pong, gc } from "./global.js"; +import { gridDisplay } from "./handleInput.js"; +export function drawLoop() { + pong.clear(); + if (gridDisplay) { + drawGrid(); + } + drawStatic(); + gc.text1.update(); + gc.text2.update(); + gc.text3.update(); + drawDynamic(); +} +function drawDynamic() { + gc.scoreLeft.update(); + gc.scoreRight.update(); + gc.playerLeft.update(); + gc.playerRight.update(); + gc.ballsArr.forEach((ball) => { + ball.update(); + }); +} +function drawStatic() { + gc.midLine.update(); + gc.wallTop.update(); + gc.wallBottom.update(); +} +function drawGrid() { + gc.w_grid_mid.update(); + gc.w_grid_u1.update(); + gc.w_grid_d1.update(); + gc.h_grid_mid.update(); + gc.h_grid_u1.update(); + gc.h_grid_d1.update(); +} diff --git a/jeu/src/client/draw.ts b/jeu/src/client/draw.ts new file mode 100644 index 00000000..204c55f5 --- /dev/null +++ b/jeu/src/client/draw.ts @@ -0,0 +1,49 @@ + +import { pong, gc } from "./global.js" +import { gridDisplay } from "./handleInput.js"; + +export function drawLoop() +{ + pong.clear(); + + if (gridDisplay) { + drawGrid(); + } + + drawStatic(); + + gc.text1.update(); + gc.text2.update(); + gc.text3.update(); + + drawDynamic(); +} + +function drawDynamic() +{ + gc.scoreLeft.update(); + gc.scoreRight.update(); + gc.playerLeft.update(); + gc.playerRight.update(); + gc.ballsArr.forEach((ball) => { + ball.update(); + }); +} + +function drawStatic() +{ + gc.midLine.update(); + gc.wallTop.update(); + gc.wallBottom.update(); +} + +function drawGrid() +{ + gc.w_grid_mid.update(); + gc.w_grid_u1.update(); + gc.w_grid_d1.update(); + + gc.h_grid_mid.update(); + gc.h_grid_u1.update(); + gc.h_grid_d1.update(); +} diff --git a/jeu/src/client/gameLoop.js b/jeu/src/client/gameLoop.js new file mode 100644 index 00000000..3a3197fa --- /dev/null +++ b/jeu/src/client/gameLoop.js @@ -0,0 +1,52 @@ +import * as c from "./constants.js"; +import * as en from "../shared_js/enums.js"; +import { gc, matchOptions, clientInfo, clientInfoSpectator } from "./global.js"; +import { wallsMovements } from "../shared_js/wallsMovement.js"; +let actual_time = Date.now(); +let last_time; +let delta_time; +export function gameLoop() { + /* last_time = actual_time; + actual_time = Date.now(); + delta_time = (actual_time - last_time) / 1000; */ + delta_time = c.fixedDeltaTime; + // console.log(`delta_gameLoop: ${delta_time}`); + // interpolation + // 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) { + racketInterpolation(delta_time, clientInfo.opponent, clientInfo.opponentNextPos); + } + // 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); + } +} +export function gameLoopSpectator() { + delta_time = c.fixedDeltaTime; + // interpolation + if (gc.playerLeft.dir.y != 0) { + racketInterpolation(delta_time, gc.playerLeft, clientInfoSpectator.playerLeftNextPos); + } + 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, racket, nextPos) { + // 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; + } +} diff --git a/jeu/src/client/gameLoop.ts b/jeu/src/client/gameLoop.ts new file mode 100644 index 00000000..784841c3 --- /dev/null +++ b/jeu/src/client/gameLoop.ts @@ -0,0 +1,71 @@ + +import * as c from "./constants.js"; +import * as en from "../shared_js/enums.js" +import { gc, matchOptions, clientInfo, clientInfoSpectator} from "./global.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 last_time: number; +let delta_time: number; + +export function gameLoop() +{ + /* last_time = actual_time; + actual_time = Date.now(); + delta_time = (actual_time - last_time) / 1000; */ + + delta_time = c.fixedDeltaTime; + // console.log(`delta_gameLoop: ${delta_time}`); + + // interpolation + // 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 ) { + racketInterpolation(delta_time, clientInfo.opponent, clientInfo.opponentNextPos); + } + + // 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); + } +} + +export function gameLoopSpectator() +{ + delta_time = c.fixedDeltaTime; + + // interpolation + if (gc.playerLeft.dir.y != 0 ) { + racketInterpolation(delta_time, gc.playerLeft, clientInfoSpectator.playerLeftNextPos); + } + 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; + } +} diff --git a/jeu/src/client/global.js b/jeu/src/client/global.js new file mode 100644 index 00000000..e752c4f5 --- /dev/null +++ b/jeu/src/client/global.js @@ -0,0 +1,19 @@ +import * as en from "../shared_js/enums.js"; +// export {pong, gc, matchOptions} from "./pong.js" +export { socket, clientInfo, clientInfoSpectator } from "./ws.js"; +export let pong; +export let gc; +export let matchOptions = en.MatchOptions.noOption; +export function initPong(value) { + pong = value; +} +export function initGc(value) { + gc = value; +} +export function initMatchOptions(value) { + matchOptions = value; +} +export let startFunction; +export function initStartFunction(value) { + startFunction = value; +} diff --git a/jeu/src/client/global.ts b/jeu/src/client/global.ts new file mode 100644 index 00000000..c789fca6 --- /dev/null +++ b/jeu/src/client/global.ts @@ -0,0 +1,29 @@ + +import * as en from "../shared_js/enums.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; +} diff --git a/jeu/src/client/handleInput.js b/jeu/src/client/handleInput.js new file mode 100644 index 00000000..36014674 --- /dev/null +++ b/jeu/src/client/handleInput.js @@ -0,0 +1,83 @@ +import { pong, gc, socket, clientInfo } from "./global.js"; +import * as ev from "../shared_js/class/Event.js"; +import * as en from "../shared_js/enums.js"; +import { InputHistory } from "./class/InputHistory.js"; +import * as c from "./constants.js"; +export let gridDisplay = false; +let actual_time = Date.now(); +let last_time; +let delta_time; +const inputState = new ev.EventInput(); +const inputHistoryArr = []; +// test +/* export function sendLoop() +{ + socket.send(JSON.stringify(inputState)); +} */ +export function handleInput() { + /* last_time = actual_time; + actual_time = Date.now(); + delta_time = (actual_time - last_time) / 1000; */ + delta_time = c.fixedDeltaTime; + // console.log(`delta_time: ${delta_time}`); + inputState.id = Date.now(); + inputState.input = en.InputEnum.noInput; + const keys = pong.keys; + if (keys.length !== 0) { + if (keys.indexOf("g") != -1) { + gridDisplay = !gridDisplay; + pong.deleteKey("g"); + } + playerMovements(delta_time, keys); + } + socket.send(JSON.stringify(inputState)); + // setTimeout(testInputDelay, 100); + inputHistoryArr.push(new InputHistory(inputState, delta_time)); + // client prediction + if (inputState.input !== en.InputEnum.noInput) { + // TODO: peut-etre le mettre dans game loop ? + // Attention au delta time dans ce cas ! + playerMovePrediction(delta_time, inputState.input); + } +} +function playerMovements(delta, keys) { + if (keys.indexOf("w") !== -1 || keys.indexOf("ArrowUp".toLowerCase()) !== -1) { + if (keys.indexOf("s") === -1 && keys.indexOf("ArrowDown".toLowerCase()) === -1) { + inputState.input = en.InputEnum.up; + } + } + else if (keys.indexOf("s") !== -1 || keys.indexOf("ArrowDown".toLowerCase()) !== -1) { + inputState.input = en.InputEnum.down; + } +} +function testInputDelay() { + socket.send(JSON.stringify(inputState)); +} +function playerMovePrediction(delta, input) { + // client prediction + const racket = clientInfo.racket; + if (input === en.InputEnum.up) { + racket.dir.y = -1; + } + else if (input === en.InputEnum.down) { + racket.dir.y = 1; + } + racket.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]); +} +export function repeatInput(lastInputId) { + // server reconciliation + let i = inputHistoryArr.findIndex((value) => { + if (value.id === lastInputId) { + return true; + } + return false; + }); + // console.log(`inputHistory total: ${inputHistoryArr.length}` ); + inputHistoryArr.splice(0, i + 1); + // console.log(`inputHistory left: ${inputHistoryArr.length}` ); + inputHistoryArr.forEach((value) => { + if (value.input !== en.InputEnum.noInput) { + playerMovePrediction(value.deltaTime, value.input); + } + }); +} diff --git a/jeu/src/client/handleInput.ts b/jeu/src/client/handleInput.ts new file mode 100644 index 00000000..33b956f4 --- /dev/null +++ b/jeu/src/client/handleInput.ts @@ -0,0 +1,108 @@ + +import { pong, gc, socket, clientInfo } from "./global.js" +import * as ev from "../shared_js/class/Event.js" +import * as en from "../shared_js/enums.js" +import { InputHistory } from "./class/InputHistory.js" +import * as c from "./constants.js"; + +export let gridDisplay = false; + +let actual_time: number = Date.now(); +let last_time: number; +let delta_time: number; + +const inputState: ev.EventInput = new ev.EventInput(); +const inputHistoryArr: InputHistory[] = []; + +// test +/* export function sendLoop() +{ + socket.send(JSON.stringify(inputState)); +} */ + +export function handleInput() +{ + /* last_time = actual_time; + actual_time = Date.now(); + delta_time = (actual_time - last_time) / 1000; */ + + delta_time = c.fixedDeltaTime; + // console.log(`delta_time: ${delta_time}`); + + inputState.id = Date.now(); + inputState.input = en.InputEnum.noInput; + + const keys = pong.keys; + if (keys.length !== 0) + { + if (keys.indexOf("g") != -1) + { + gridDisplay = !gridDisplay; + pong.deleteKey("g"); + } + playerMovements(delta_time, keys); + } + + socket.send(JSON.stringify(inputState)); + // setTimeout(testInputDelay, 100); + inputHistoryArr.push(new InputHistory(inputState, delta_time)); + + // client prediction + if (inputState.input !== en.InputEnum.noInput) { + // TODO: peut-etre le mettre dans game loop ? + // Attention au delta time dans ce cas ! + playerMovePrediction(delta_time, inputState.input); + } +} + +function playerMovements(delta: number, keys: string[]) +{ + if (keys.indexOf("w") !== -1 || keys.indexOf("ArrowUp".toLowerCase()) !== -1) + { + if (keys.indexOf("s") === -1 && keys.indexOf("ArrowDown".toLowerCase()) === -1) { + inputState.input = en.InputEnum.up; + } + } + else if (keys.indexOf("s") !== -1 || keys.indexOf("ArrowDown".toLowerCase()) !== -1) { + inputState.input = en.InputEnum.down; + } +} + +function testInputDelay() { + socket.send(JSON.stringify(inputState)); +} + + +function playerMovePrediction(delta: number, input: en.InputEnum) +{ + // client prediction + const racket = clientInfo.racket; + if (input === en.InputEnum.up) { + racket.dir.y = -1; + } + else if (input === en.InputEnum.down) { + racket.dir.y = 1; + } + racket.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]); +} + +export function repeatInput(lastInputId: number) +{ + // server reconciliation + let i = inputHistoryArr.findIndex((value: InputHistory) => { + if (value.id === lastInputId) { + return true; + } + return false; + }); + + // console.log(`inputHistory total: ${inputHistoryArr.length}` ); + inputHistoryArr.splice(0, i+1); + // console.log(`inputHistory left: ${inputHistoryArr.length}` ); + + inputHistoryArr.forEach((value: InputHistory) => { + if (value.input !== en.InputEnum.noInput) { + playerMovePrediction(value.deltaTime, value.input); + } + }); +} diff --git a/jeu/src/client/message.js b/jeu/src/client/message.js new file mode 100644 index 00000000..2a73b9ee --- /dev/null +++ b/jeu/src/client/message.js @@ -0,0 +1,65 @@ +import * as c from "./constants.js"; +import { gc } from "./global.js"; +import * as en from "../shared_js/enums.js"; +/* + before game +*/ +export function matchmaking() { + const text = "searching..."; + console.log(text); + gc.text1.clear(); + gc.text1.pos.assign(c.w * 0.2, c.h * 0.5); + gc.text1.text = text; + gc.text1.update(); +} +export function matchmakingComplete() { + const text = "match found !"; + console.log(text); + gc.text1.clear(); + gc.text1.pos.assign(c.w * 0.15, c.h * 0.5); + gc.text1.text = text; + gc.text1.update(); +} +export function matchAbort() { + const text = "match abort"; + console.log(text); + gc.text1.clear(); + gc.text1.pos.assign(c.w * 0.15, c.h * 0.5); + gc.text1.text = text; + gc.text1.update(); + setTimeout(() => { + gc.text2.pos.assign(c.w * 0.44, c.h * 0.6); + gc.text2.text = "pardon =("; + const oriSize = gc.text2.size; + gc.text2.size = c.w * 0.025; + gc.text2.update(); + gc.text2.size = oriSize; + }, 2500); +} +/* + in game +*/ +export function win() { + gc.text1.pos.assign(c.w * 0.415, c.h * 0.5); + gc.text1.text = "WIN"; +} +export function lose() { + gc.text1.pos.assign(c.w * 0.383, c.h * 0.5); + gc.text1.text = "LOSE"; +} +export function forfeit(playerSide) { + if (playerSide === en.PlayerSide.left) { + gc.text2.pos.assign(c.w * 0.65, c.h * 0.42); + gc.text3.pos.assign(c.w * 0.65, c.h * 0.52); + } + else { + gc.text2.pos.assign(c.w * 0.09, c.h * 0.42); + gc.text3.pos.assign(c.w * 0.09, c.h * 0.52); + } + setTimeout(() => { + gc.text2.text = "par forfait"; + }, 1500); + setTimeout(() => { + gc.text3.text = "calme ta joie"; + }, 3500); +} diff --git a/jeu/src/client/message.ts b/jeu/src/client/message.ts new file mode 100644 index 00000000..5c1cdc15 --- /dev/null +++ b/jeu/src/client/message.ts @@ -0,0 +1,80 @@ + +import * as c from "./constants.js" +import { gc } from "./global.js" +import * as en from "../shared_js/enums.js" + +/* + before game +*/ +export function matchmaking() +{ + const text = "searching..."; + console.log(text); + gc.text1.clear(); + gc.text1.pos.assign(c.w*0.2, c.h*0.5); + gc.text1.text = text; + gc.text1.update(); +} + +export function matchmakingComplete() +{ + const text = "match found !"; + console.log(text); + gc.text1.clear(); + gc.text1.pos.assign(c.w*0.15, c.h*0.5); + gc.text1.text = text; + gc.text1.update(); +} + +export function matchAbort() +{ + const text = "match abort"; + console.log(text); + gc.text1.clear(); + gc.text1.pos.assign(c.w*0.15, c.h*0.5); + gc.text1.text = text; + gc.text1.update(); + + setTimeout(() => { + gc.text2.pos.assign(c.w*0.44, c.h*0.6); + gc.text2.text = "pardon =("; + const oriSize = gc.text2.size; + gc.text2.size = c.w*0.025; + gc.text2.update(); + gc.text2.size = oriSize; + }, 2500); +} + + +/* + in game +*/ +export function win() +{ + gc.text1.pos.assign(c.w*0.415, c.h*0.5); + gc.text1.text = "WIN"; +} + +export function lose() +{ + gc.text1.pos.assign(c.w*0.383, c.h*0.5); + gc.text1.text = "LOSE"; +} + +export function forfeit(playerSide: en.PlayerSide) +{ + if (playerSide === en.PlayerSide.left) { + gc.text2.pos.assign(c.w*0.65, c.h*0.42); + gc.text3.pos.assign(c.w*0.65, c.h*0.52); + } + else { + gc.text2.pos.assign(c.w*0.09, c.h*0.42); + gc.text3.pos.assign(c.w*0.09, c.h*0.52); + } + setTimeout(() => { + gc.text2.text = "par forfait"; + }, 1500); + setTimeout(() => { + gc.text3.text = "calme ta joie"; + }, 3500); +} diff --git a/jeu/src/client/pong.css b/jeu/src/client/pong.css new file mode 100644 index 00000000..39368218 --- /dev/null +++ b/jeu/src/client/pong.css @@ -0,0 +1,54 @@ + +@font-face { + font-family: "Bit5x3"; + src: url("http://localhost:8080/Bit5x3.woff2") format("woff2"), + url("http://localhost:8080/Bit5x3.woff") format("woff"); + font-weight: normal; + font-style: normal; + font-display: swap; +} +body { + margin: 0; + background-color: #222425; +} +#canvas_container { + margin-top: 20px; + text-align: center; + /* border: dashed rgb(245, 245, 245) 5px; */ + /* max-height: 80vh; */ + /* overflow: hidden; */ +} +#div_game_instructions { + text-align: center; + font-family: "Bit5x3"; + color: rgb(245, 245, 245); + font-size: large; +} +#div_game_options { + margin-top: 20px; + text-align: center; + font-family: "Bit5x3"; + color: rgb(245, 245, 245); + font-size: x-large; +} +#div_game_options fieldset { + max-width: 50vw; + width: auto; + margin: 0 auto; +} +#div_game_options fieldset div { + padding: 10px; +} +#play_pong_button { + font-family: "Bit5x3"; + color: rgb(245, 245, 245); + background-color: #333333; + font-size: x-large; + padding: 10px; +} +canvas { + background-color: #333333; + max-width: 75vw; + /* max-height: 100vh; */ + width: 80%; +} diff --git a/jeu/src/client/pong.html b/jeu/src/client/pong.html new file mode 100644 index 00000000..30f2e781 --- /dev/null +++ b/jeu/src/client/pong.html @@ -0,0 +1,47 @@ + + + + + + + + +
+
+ game options +
+ + +
+
+ + +
+
+ + + + + +
+
+ +
+
+
+ +
+

--- keys ---

+

move up: 'w' or 'up arrow'

+

move down: 's' OR 'down arrow'

+

grid on/off: 'g'

+
+ +
+ +
+ + + + + diff --git a/jeu/src/client/pong.js b/jeu/src/client/pong.js new file mode 100644 index 00000000..b9754ddd --- /dev/null +++ b/jeu/src/client/pong.js @@ -0,0 +1,63 @@ +initDom(); +function initDom() { + document.getElementById("play_pong_button").addEventListener("click", 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 { handleInput } from "./handleInput.js"; +// import { sendLoop } from "./handleInput.js"; +import { gameLoop } from "./gameLoop.js"; +import { drawLoop } from "./draw.js"; +import { countdown } from "./utils.js"; +import { initWebSocket } 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() { + console.log("multi_balls:" + document.getElementById("multi_balls").checked); + console.log("moving_walls:" + document.getElementById("moving_walls").checked); + console.log("sound_on:" + document.getElementById("sound_on").checked); + let soundMutedFlag = false; + if (document.getElementById("sound_off").checked) { + soundMutedFlag = true; + } + initAudio(soundMutedFlag); + let matchOptions = en.MatchOptions.noOption; + if (document.getElementById("multi_balls").checked) { + matchOptions |= en.MatchOptions.multiBalls; + } + if (document.getElementById("moving_walls").checked) { + matchOptions |= en.MatchOptions.movingWalls; + } + initMatchOptions(matchOptions); + document.getElementById("div_game_options").remove(); + document.getElementById("div_game_instructions").remove(); + initPong(new GameArea()); + initGc(new GameComponentsClient(matchOptions, pong.ctx)); + initStartFunction(start); + initWebSocket(matchOptions); +} +function start() { + gc.text1.pos.assign(c.w * 0.5, c.h * 0.75); + countdown(c.matchStartDelay / 1000, (count) => { + gc.text1.clear(); + gc.text1.text = `${count}`; + gc.text1.update(); + }, resume); +} +function resume() { + gc.text1.text = ""; + window.addEventListener('keydown', function (e) { + pong.addKey(e.key); + }); + window.addEventListener('keyup', function (e) { + pong.deleteKey(e.key); + }); + pong.handleInputInterval = window.setInterval(handleInput, c.handleInputIntervalMS); + // pong.handleInputInterval = window.setInterval(sendLoop, c.sendLoopIntervalMS); + pong.gameLoopInterval = window.setInterval(gameLoop, c.gameLoopIntervalMS); + pong.drawLoopInterval = window.setInterval(drawLoop, c.drawLoopIntervalMS); +} diff --git a/jeu/src/client/pong.ts b/jeu/src/client/pong.ts new file mode 100644 index 00000000..07363807 --- /dev/null +++ b/jeu/src/client/pong.ts @@ -0,0 +1,78 @@ + +initDom(); +function initDom() { + document.getElementById("play_pong_button").addEventListener("click", 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 { handleInput } from "./handleInput.js"; +// import { sendLoop } from "./handleInput.js"; +import { gameLoop } from "./gameLoop.js" +import { drawLoop } from "./draw.js"; +import { countdown } from "./utils.js"; +import { initWebSocket } 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() +{ + console.log("multi_balls:"+(document.getElementById("multi_balls")).checked); + console.log("moving_walls:"+(document.getElementById("moving_walls")).checked); + console.log("sound_on:"+(document.getElementById("sound_on")).checked); + + let soundMutedFlag = false; + if ( (document.getElementById("sound_off")).checked ) { + soundMutedFlag = true; + } + initAudio(soundMutedFlag); + + let matchOptions: en.MatchOptions = en.MatchOptions.noOption; + if ( (document.getElementById("multi_balls")).checked ) { + matchOptions |= en.MatchOptions.multiBalls; + } + if ( (document.getElementById("moving_walls")).checked ) { + matchOptions |= en.MatchOptions.movingWalls; + } + initMatchOptions(matchOptions); + + document.getElementById("div_game_options").remove(); + document.getElementById("div_game_instructions").remove(); + + initPong(new GameArea()); + initGc(new GameComponentsClient(matchOptions, pong.ctx)); + initStartFunction(start); + initWebSocket(matchOptions); +} + +function start() +{ + gc.text1.pos.assign(c.w*0.5, c.h*0.75); + countdown(c.matchStartDelay/1000, (count: number) => { + gc.text1.clear(); + gc.text1.text = `${count}`; + gc.text1.update(); + }, resume); +} + +function resume() +{ + gc.text1.text = ""; + window.addEventListener('keydown', function (e) { + pong.addKey(e.key); + }); + window.addEventListener('keyup', function (e) { + pong.deleteKey(e.key); + }); + pong.handleInputInterval = window.setInterval(handleInput, c.handleInputIntervalMS); + // pong.handleInputInterval = window.setInterval(sendLoop, c.sendLoopIntervalMS); + pong.gameLoopInterval = window.setInterval(gameLoop, c.gameLoopIntervalMS); + pong.drawLoopInterval = window.setInterval(drawLoop, c.drawLoopIntervalMS); +} diff --git a/jeu/src/client/pongSpectator.html b/jeu/src/client/pongSpectator.html new file mode 100644 index 00000000..f0357cb6 --- /dev/null +++ b/jeu/src/client/pongSpectator.html @@ -0,0 +1,19 @@ + + + + + + + + +
+ +
+
+

Spectator View

+
+ + + + + diff --git a/jeu/src/client/pongSpectator.js b/jeu/src/client/pongSpectator.js new file mode 100644 index 00000000..b54a05c9 --- /dev/null +++ b/jeu/src/client/pongSpectator.js @@ -0,0 +1,33 @@ +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 } from "./global.js"; +import { initPong, initGc, initMatchOptions, initStartFunction } from "./global.js"; +function init() { + initAudio(false); + // WIP matchOptions + let 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); +} diff --git a/jeu/src/client/pongSpectator.ts b/jeu/src/client/pongSpectator.ts new file mode 100644 index 00000000..4efb9a19 --- /dev/null +++ b/jeu/src/client/pongSpectator.ts @@ -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); +} diff --git a/jeu/src/client/utils.js b/jeu/src/client/utils.js new file mode 100644 index 00000000..b4214463 --- /dev/null +++ b/jeu/src/client/utils.js @@ -0,0 +1,13 @@ +export * from "../shared_js/utils.js"; +export function countdown(count, callback, endCallback) { + console.log("countdown ", count); + if (count > 0) { + if (callback) { + callback(count); + } + setTimeout(countdown, 1000, --count, callback, endCallback); + } + else if (endCallback) { + endCallback(); + } +} diff --git a/jeu/src/client/utils.ts b/jeu/src/client/utils.ts new file mode 100644 index 00000000..ff45234d --- /dev/null +++ b/jeu/src/client/utils.ts @@ -0,0 +1,16 @@ + +export * from "../shared_js/utils.js" + +export function countdown(count: number, callback?: (count: number) => void, endCallback?: () => void) +{ + console.log("countdown ", count); + if (count > 0) { + if (callback) { + callback(count); + } + setTimeout(countdown, 1000, --count, callback, endCallback); + } + else if (endCallback) { + endCallback(); + } +} diff --git a/jeu/src/client/ws.js b/jeu/src/client/ws.js new file mode 100644 index 00000000..21990bae --- /dev/null +++ b/jeu/src/client/ws.js @@ -0,0 +1,230 @@ +import { gc, matchOptions, startFunction } from "./global.js"; +import * as ev from "../shared_js/class/Event.js"; +import * as en from "../shared_js/enums.js"; +import * as msg from "./message.js"; +import { repeatInput } from "./handleInput.js"; +import { soundRoblox } from "./audio.js"; +import { Vector, VectorInteger } from "../shared_js/class/Vector.js"; +class ClientInfo { + constructor() { + this.id = ""; + } +} +class ClientInfoSpectator { +} +const wsPort = 8042; +const wsUrl = "ws://" + document.location.hostname + ":" + wsPort + "/pong"; +export let socket; /* TODO: A way to still use "const" not "let" ? */ +export const clientInfo = new ClientInfo(); +export const clientInfoSpectator = new ClientInfoSpectator(); // WIP, could refactor this +export function initWebSocket(options) { + socket = new WebSocket(wsUrl, "json"); + socket.addEventListener("open", (event) => { + socket.send(JSON.stringify(new ev.ClientAnnouncePlayer(options, clientInfo.id))); + }); + // socket.addEventListener("message", logListener); // for testing purpose + socket.addEventListener("message", preMatchListener); +} +function logListener(event) { + console.log("%i: " + event.data, Date.now()); +} +function preMatchListener(event) { + const data = JSON.parse(event.data); + switch (data.type) { + case en.EventTypes.assignId: + clientInfo.id = data.id; + break; + case en.EventTypes.matchmakingInProgress: + msg.matchmaking(); + break; + case en.EventTypes.matchmakingComplete: + clientInfo.side = data.side; + if (clientInfo.side === en.PlayerSide.left) { + clientInfo.racket = gc.playerLeft; + clientInfo.opponent = gc.playerRight; + } + else if (clientInfo.side === en.PlayerSide.right) { + clientInfo.racket = gc.playerRight; + clientInfo.opponent = gc.playerLeft; + } + clientInfo.opponentNextPos = new VectorInteger(clientInfo.opponent.pos.x, clientInfo.opponent.pos.y); + clientInfo.racket.color = "darkgreen"; // for testing purpose + socket.send(JSON.stringify(new ev.ClientEvent(en.EventTypes.clientPlayerReady))); // TODO: set an interval/timeout to resend until matchStart response (in case of network problem) + msg.matchmakingComplete(); + break; + case en.EventTypes.matchStart: + socket.removeEventListener("message", preMatchListener); + socket.addEventListener("message", inGameListener); + startFunction(); + break; + case en.EventTypes.matchAbort: + socket.removeEventListener("message", preMatchListener); + msg.matchAbort(); + break; + } +} +function inGameListener(event) { + const data = JSON.parse(event.data); + switch (data.type) { + case en.EventTypes.gameUpdate: + // setTimeout(gameUpdate, 500, data as ev.EventGameUpdate); // artificial latency for testing purpose + gameUpdate(data); + break; + case en.EventTypes.scoreUpdate: + scoreUpdate(data); + break; + case en.EventTypes.matchEnd: + matchEnd(data); + break; + } +} +function gameUpdate(data) { + console.log("gameUpdate"); + 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; + }); + /* // Equivalent to + gc.ballsArr.forEach((ball, i) => { + ball.pos.assign(data.ballsArr[i].x, data.ballsArr[i].y); + ball.dir.assign(data.ballsArr[i].dirX, data.ballsArr[i].dirY); + ball.speed = data.ballsArr[i].speed; + }); */ + const predictionPos = new VectorInteger(clientInfo.racket.pos.x, clientInfo.racket.pos.y); // debug + if (clientInfo.side === en.PlayerSide.left) { + clientInfo.racket.pos.assign(clientInfo.racket.pos.x, data.playerLeft.y); + } + else if (clientInfo.side === en.PlayerSide.right) { + clientInfo.racket.pos.assign(clientInfo.racket.pos.x, data.playerRight.y); + } + // interpolation + clientInfo.opponent.pos.assign(clientInfo.opponentNextPos.x, clientInfo.opponentNextPos.y); + if (clientInfo.side === en.PlayerSide.left) { + clientInfo.opponentNextPos.assign(clientInfo.opponent.pos.x, data.playerRight.y); + } + else if (clientInfo.side === en.PlayerSide.right) { + clientInfo.opponentNextPos.assign(clientInfo.opponent.pos.x, data.playerLeft.y); + } + clientInfo.opponent.dir = new Vector(clientInfo.opponentNextPos.x - clientInfo.opponent.pos.x, clientInfo.opponentNextPos.y - clientInfo.opponent.pos.y); + if (Math.abs(clientInfo.opponent.dir.x) + Math.abs(clientInfo.opponent.dir.y) !== 0) { + clientInfo.opponent.dir = clientInfo.opponent.dir.normalized(); + } + // server reconciliation + repeatInput(data.lastInputId); + // debug + if (clientInfo.racket.pos.y > predictionPos.y + 1 + || clientInfo.racket.pos.y < predictionPos.y - 1) { + console.log(`Reconciliation error: + server y: ${data.playerLeft.y} + reconciliation y: ${clientInfo.racket.pos.y} + prediction y: ${predictionPos.y}`); + } +} +function scoreUpdate(data) { + // console.log("scoreUpdate"); + if (clientInfo.side === en.PlayerSide.left && data.scoreRight > gc.scoreRight.value) { + soundRoblox.play(); + } + else if (clientInfo.side === en.PlayerSide.right && data.scoreLeft > gc.scoreLeft.value) { + soundRoblox.play(); + } + gc.scoreLeft.value = data.scoreLeft; + gc.scoreRight.value = data.scoreRight; +} +function matchEnd(data) { + if (data.winner === clientInfo.side) { + msg.win(); + if (data.forfeit) { + msg.forfeit(clientInfo.side); + } + } + else { + msg.lose(); + } + // matchEnded = true; // unused +} +// export let matchEnded = false; // unused +/* Spectator */ +export function initWebSocketSpectator(gameSessionId) { + 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(event) { + const data = JSON.parse(event.data); + if (data.type === en.EventTypes.matchStart) { + socket.removeEventListener("message", preMatchListenerSpectator); + socket.addEventListener("message", inGameListenerSpectator); + startFunction(); + } +} +function inGameListenerSpectator(event) { + const data = JSON.parse(event.data); + switch (data.type) { + case en.EventTypes.gameUpdate: + gameUpdateSpectator(data); + break; + case en.EventTypes.scoreUpdate: + scoreUpdateSpectator(data); + break; + case en.EventTypes.matchEnd: + matchEndSpectator(data); + break; + } +} +function gameUpdateSpectator(data) { + 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; + 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) { + console.log("scoreUpdateSpectator"); + gc.scoreLeft.value = data.scoreLeft; + gc.scoreRight.value = data.scoreRight; +} +function matchEndSpectator(data) { + console.log("matchEndSpectator"); + // WIP + /* msg.win(); + if (data.forfeit) { + msg.forfeit(clientInfo.side); + } */ +} diff --git a/jeu/src/client/ws.ts b/jeu/src/client/ws.ts new file mode 100644 index 00000000..aa901cc0 --- /dev/null +++ b/jeu/src/client/ws.ts @@ -0,0 +1,302 @@ + +import * as c from "./constants.js" +import { gc, matchOptions, startFunction } from "./global.js" +import * as ev from "../shared_js/class/Event.js" +import * as en from "../shared_js/enums.js" +import * as msg from "./message.js"; +import { RacketClient } from "./class/RectangleClient.js"; +import { repeatInput } from "./handleInput.js"; +import { soundRoblox } from "./audio.js" +import { sleep } from "./utils.js"; +import { Vector, VectorInteger } from "../shared_js/class/Vector.js"; + +class ClientInfo { + id = ""; + side: en.PlayerSide; + racket: RacketClient; + opponent: RacketClient; + 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 wsUrl = "ws://" + document.location.hostname + ":" + wsPort + "/pong"; +export let socket: WebSocket; /* TODO: A way to still use "const" not "let" ? */ +export const clientInfo = new ClientInfo(); +export const clientInfoSpectator = new ClientInfoSpectator(); // WIP, could refactor this + +export function initWebSocket(options: en.MatchOptions) +{ + socket = new WebSocket(wsUrl, "json"); + socket.addEventListener("open", (event) => { + socket.send(JSON.stringify( new ev.ClientAnnouncePlayer(options, clientInfo.id) )); + }); + // socket.addEventListener("message", logListener); // for testing purpose + socket.addEventListener("message", preMatchListener); +} + +function logListener(this: WebSocket, event: MessageEvent) { + console.log("%i: " + event.data, Date.now()); +} + +function preMatchListener(this: WebSocket, event: MessageEvent) +{ + const data: ev.ServerEvent = JSON.parse(event.data); + switch (data.type) { + case en.EventTypes.assignId: + clientInfo.id = (data).id; + break; + case en.EventTypes.matchmakingInProgress: + msg.matchmaking(); + break; + case en.EventTypes.matchmakingComplete: + clientInfo.side = (data).side; + if (clientInfo.side === en.PlayerSide.left) + { + clientInfo.racket = gc.playerLeft; + clientInfo.opponent = gc.playerRight; + } + else if (clientInfo.side === en.PlayerSide.right) + { + clientInfo.racket = gc.playerRight; + clientInfo.opponent = gc.playerLeft; + } + clientInfo.opponentNextPos = new VectorInteger(clientInfo.opponent.pos.x, clientInfo.opponent.pos.y); + clientInfo.racket.color = "darkgreen"; // for testing purpose + socket.send(JSON.stringify( new ev.ClientEvent(en.EventTypes.clientPlayerReady) )); // TODO: set an interval/timeout to resend until matchStart response (in case of network problem) + msg.matchmakingComplete(); + break; + case en.EventTypes.matchStart: + socket.removeEventListener("message", preMatchListener); + socket.addEventListener("message", inGameListener); + startFunction(); + break; + case en.EventTypes.matchAbort: + socket.removeEventListener("message", preMatchListener); + msg.matchAbort(); + break; + } +} + +function inGameListener(this: WebSocket, event: MessageEvent) +{ + const data: ev.ServerEvent = JSON.parse(event.data); + switch (data.type) { + case en.EventTypes.gameUpdate: + // setTimeout(gameUpdate, 500, data as ev.EventGameUpdate); // artificial latency for testing purpose + gameUpdate(data as ev.EventGameUpdate); + break; + case en.EventTypes.scoreUpdate: + scoreUpdate(data as ev.EventScoreUpdate); + break; + case en.EventTypes.matchEnd: + matchEnd(data as ev.EventMatchEnd); + break; + } +} + +function gameUpdate(data: ev.EventGameUpdate) +{ + console.log("gameUpdate"); + + 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; + }); + /* // Equivalent to + gc.ballsArr.forEach((ball, i) => { + ball.pos.assign(data.ballsArr[i].x, data.ballsArr[i].y); + ball.dir.assign(data.ballsArr[i].dirX, data.ballsArr[i].dirY); + ball.speed = data.ballsArr[i].speed; + }); */ + + const predictionPos = new VectorInteger(clientInfo.racket.pos.x, clientInfo.racket.pos.y); // debug + + if (clientInfo.side === en.PlayerSide.left) { + clientInfo.racket.pos.assign(clientInfo.racket.pos.x, data.playerLeft.y); + } + else if (clientInfo.side === en.PlayerSide.right) { + clientInfo.racket.pos.assign(clientInfo.racket.pos.x, data.playerRight.y); + } + + // interpolation + clientInfo.opponent.pos.assign(clientInfo.opponentNextPos.x, clientInfo.opponentNextPos.y); + if (clientInfo.side === en.PlayerSide.left) { + clientInfo.opponentNextPos.assign(clientInfo.opponent.pos.x, data.playerRight.y); + } + else if (clientInfo.side === en.PlayerSide.right) { + clientInfo.opponentNextPos.assign(clientInfo.opponent.pos.x, data.playerLeft.y); + } + + clientInfo.opponent.dir = new Vector( + clientInfo.opponentNextPos.x - clientInfo.opponent.pos.x, + clientInfo.opponentNextPos.y - clientInfo.opponent.pos.y + ); + + if (Math.abs(clientInfo.opponent.dir.x) + Math.abs(clientInfo.opponent.dir.y) !== 0) { + clientInfo.opponent.dir = clientInfo.opponent.dir.normalized(); + } + + // server reconciliation + repeatInput(data.lastInputId); + + // debug + if (clientInfo.racket.pos.y > predictionPos.y + 1 + || clientInfo.racket.pos.y < predictionPos.y - 1) + { + console.log( + `Reconciliation error: + server y: ${data.playerLeft.y} + reconciliation y: ${clientInfo.racket.pos.y} + prediction y: ${predictionPos.y}` + ); + } +} + +function scoreUpdate(data: ev.EventScoreUpdate) +{ + // console.log("scoreUpdate"); + if (clientInfo.side === en.PlayerSide.left && data.scoreRight > gc.scoreRight.value) { + soundRoblox.play(); + } + else if (clientInfo.side === en.PlayerSide.right && data.scoreLeft > gc.scoreLeft.value) { + soundRoblox.play(); + } + gc.scoreLeft.value = data.scoreLeft; + gc.scoreRight.value = data.scoreRight; +} + +function matchEnd(data: ev.EventMatchEnd) +{ + if (data.winner === clientInfo.side) { + msg.win(); + if (data.forfeit) { + msg.forfeit(clientInfo.side); + } + } + else { + msg.lose(); + } + // matchEnded = true; // 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); + } */ +} diff --git a/jeu/src/server/class/Client.js b/jeu/src/server/class/Client.js new file mode 100644 index 00000000..0b2aee48 --- /dev/null +++ b/jeu/src/server/class/Client.js @@ -0,0 +1,23 @@ +import * as ev from "../../shared_js/class/Event.js"; +export class Client { + constructor(socket, id) { + this.isAlive = true; + this.gameSession = null; + this.socket = socket; + this.id = id; + } +} +export class ClientPlayer extends Client { + constructor(socket, id, racket) { + super(socket, id); + this.matchOptions = 0; + this.inputBuffer = new ev.EventInput(); + this.lastInputId = 0; + this.racket = racket; + } +} +export class ClientSpectator extends Client { + constructor(socket, id) { + super(socket, id); + } +} diff --git a/jeu/src/server/class/Client.ts b/jeu/src/server/class/Client.ts new file mode 100644 index 00000000..c6e4defa --- /dev/null +++ b/jeu/src/server/class/Client.ts @@ -0,0 +1,34 @@ + +import { WebSocket } from "../wsServer.js"; +import { Racket } from "../../shared_js/class/Rectangle.js"; +import { GameSession } from "./GameSession.js"; +import * as ev from "../../shared_js/class/Event.js" +import * as en from "../../shared_js/enums.js" + +export class Client { + socket: WebSocket; + id: string; // same as "socket.id" + isAlive: boolean = true; + gameSession: GameSession = null; + constructor(socket: WebSocket, id: string) { + this.socket = socket; + this.id = id; + } +} + +export class ClientPlayer extends Client { + matchOptions: en.MatchOptions = 0; + inputBuffer: ev.EventInput = new ev.EventInput(); + lastInputId: number = 0; + racket: Racket; + constructor(socket: WebSocket, id: string, racket: Racket) { + super(socket, id); + this.racket = racket; + } +} + +export class ClientSpectator extends Client { + constructor(socket: WebSocket, id: string) { + super(socket, id); + } +} diff --git a/jeu/src/server/class/GameComponentsServer.js b/jeu/src/server/class/GameComponentsServer.js new file mode 100644 index 00000000..e76cc052 --- /dev/null +++ b/jeu/src/server/class/GameComponentsServer.js @@ -0,0 +1,8 @@ +import { GameComponents } from "../../shared_js/class/GameComponents.js"; +export class GameComponentsServer extends GameComponents { + constructor(options) { + super(options); + this.scoreLeft = 0; + this.scoreRight = 0; + } +} diff --git a/jeu/src/server/class/GameComponentsServer.ts b/jeu/src/server/class/GameComponentsServer.ts new file mode 100644 index 00000000..691a3991 --- /dev/null +++ b/jeu/src/server/class/GameComponentsServer.ts @@ -0,0 +1,12 @@ + +import * as en from "../../shared_js/enums.js" +import { GameComponents } from "../../shared_js/class/GameComponents.js"; + +export class GameComponentsServer extends GameComponents { + scoreLeft: number = 0; + scoreRight: number = 0; + constructor(options: en.MatchOptions) + { + super(options); + } +} diff --git a/srcs/requirements/game_server/game_back/src/server/class/GameSession.js b/jeu/src/server/class/GameSession.js similarity index 78% rename from srcs/requirements/game_server/game_back/src/server/class/GameSession.js rename to jeu/src/server/class/GameSession.js index 00660040..091c7057 100644 --- a/srcs/requirements/game_server/game_back/src/server/class/GameSession.js +++ b/jeu/src/server/class/GameSession.js @@ -1,42 +1,16 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.GameSession = void 0; -const en = __importStar(require("../../shared_js/enums.js")); -const ev = __importStar(require("../../shared_js/class/Event.js")); -const c = __importStar(require("../constants.js")); -const GameComponentsServer_js_1 = require("./GameComponentsServer.js"); -const wsServer_js_1 = require("../wsServer.js"); -const utils_js_1 = require("../utils.js"); -const wallsMovement_js_1 = require("../../shared_js/wallsMovement.js"); +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" */ -class GameSession { +export class GameSession { constructor(id, matchOptions) { this.playersMap = new Map(); this.unreadyPlayersMap = new Map(); @@ -47,7 +21,7 @@ class GameSession { this.matchEnded = false; this.id = id; this.matchOptions = matchOptions; - this.components = new GameComponentsServer_js_1.GameComponentsServer(this.matchOptions); + this.components = new GameComponentsServer(this.matchOptions); } start() { const gc = this.components; @@ -60,7 +34,7 @@ class GameSession { } resume(s) { s.playersMap.forEach((client) => { - client.socket.on("message", wsServer_js_1.clientInputListener); + client.socket.on("message", clientInputListener); }); s.actual_time = Date.now(); s.gameLoopInterval = setInterval(s._gameLoop, c.serverGameLoopIntervalMS, s); @@ -69,7 +43,7 @@ class GameSession { } pause(s) { s.playersMap.forEach((client) => { - client.socket.off("message", wsServer_js_1.clientInputListener); + client.socket.off("message", clientInputListener); }); clearInterval(s.gameLoopInterval); clearInterval(s.playersUpdateInterval); @@ -108,7 +82,7 @@ class GameSession { s._ballMovement(s.delta_time, ball); }); if (s.matchOptions & en.MatchOptions.movingWalls) { - (0, wallsMovement_js_1.wallsMovements)(s.delta_time, gc); + wallsMovements(s.delta_time, gc); } } _ballMovement(delta, ball) { @@ -188,7 +162,7 @@ class GameSession { } } ball.pos.x = c.w_mid; - ball.pos.y = (0, utils_js_1.random)(c.h * 0.3, c.h * 0.7); + ball.pos.y = random(c.h * 0.3, c.h * 0.7); ball.speed = ball.baseSpeed; ball.ballInPlay = true; } @@ -236,4 +210,3 @@ class GameSession { }); } } -exports.GameSession = GameSession; diff --git a/jeu/src/server/class/GameSession.ts b/jeu/src/server/class/GameSession.ts new file mode 100644 index 00000000..55879135 --- /dev/null +++ b/jeu/src/server/class/GameSession.ts @@ -0,0 +1,241 @@ + +import * as en from "../../shared_js/enums.js" +import * as ev from "../../shared_js/class/Event.js" +import * as c from "../constants.js" +import { ClientPlayer, ClientSpectator } 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"; + +/* + 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 { + id: string; // url ? + playersMap: Map = new Map(); + unreadyPlayersMap: Map = new Map(); + spectatorsMap: Map = new Map(); + gameLoopInterval: NodeJS.Timer | number = 0; + playersUpdateInterval: NodeJS.Timer | number = 0; + spectatorsUpdateInterval: 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.playersUpdateInterval = setInterval(s._playersUpdate, c.playersUpdateIntervalMS, s); + s.spectatorsUpdateInterval = setInterval(s._spectatorsUpdate, c.spectatorsUpdateIntervalMS, s); + } + pause(s: GameSession) { + s.playersMap.forEach( (client) => { + client.socket.off("message", clientInputListener); + }); + + clearInterval(s.gameLoopInterval); + clearInterval(s.playersUpdateInterval); + clearInterval(s.spectatorsUpdateInterval); + } + 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; + } + this._scoreUpdate(ball); + setTimeout(this._newRound, c.newRoundDelay, this, ball); + } + } + } + private _scoreUpdate(ball: 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))); + }); + } + 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) => { + 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); + } + private _newRound(s: GameSession, ball: 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; + } + private _checkDisconnexions() { + if (this.playersMap.size !== 2) + { + this.matchEnded = true; + if (this.playersMap.size != 0) + { + 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)"); + } + 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; + } + 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)); + }); + s.spectatorsMap.forEach( (client) => { + client.socket.send(JSON.stringify(eventEnd)); + }); + } +} diff --git a/jeu/src/server/constants.js b/jeu/src/server/constants.js new file mode 100644 index 00000000..167853fb --- /dev/null +++ b/jeu/src/server/constants.js @@ -0,0 +1,7 @@ +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 playersUpdateIntervalMS = 1000 / 30; // millisecond +export const spectatorsUpdateIntervalMS = 1000 / 30; // millisecond diff --git a/jeu/src/server/constants.ts b/jeu/src/server/constants.ts new file mode 100644 index 00000000..b7efffd3 --- /dev/null +++ b/jeu/src/server/constants.ts @@ -0,0 +1,10 @@ + +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 playersUpdateIntervalMS = 1000/30; // millisecond +export const spectatorsUpdateIntervalMS = 1000/30; // millisecond diff --git a/jeu/src/server/server.js b/jeu/src/server/server.js new file mode 100644 index 00000000..3fda1562 --- /dev/null +++ b/jeu/src/server/server.js @@ -0,0 +1,37 @@ +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`); +}); diff --git a/jeu/src/server/server.ts b/jeu/src/server/server.ts new file mode 100644 index 00000000..20801d7f --- /dev/null +++ b/jeu/src/server/server.ts @@ -0,0 +1,41 @@ + +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`); +}); diff --git a/jeu/src/server/utils.js b/jeu/src/server/utils.js new file mode 100644 index 00000000..02071305 --- /dev/null +++ b/jeu/src/server/utils.js @@ -0,0 +1,4 @@ +export * from "../shared_js/utils.js"; +export function shortId(id) { + return id.substring(0, id.indexOf("-")); +} diff --git a/jeu/src/server/utils.ts b/jeu/src/server/utils.ts new file mode 100644 index 00000000..3cd0a4a5 --- /dev/null +++ b/jeu/src/server/utils.ts @@ -0,0 +1,6 @@ + +export * from "../shared_js/utils.js" + +export function shortId(id: string): string { + return id.substring(0, id.indexOf("-")); +} diff --git a/srcs/requirements/game_server/game_back/src/server/wsServer.js b/jeu/src/server/wsServer.js similarity index 76% rename from srcs/requirements/game_server/game_back/src/server/wsServer.js rename to jeu/src/server/wsServer.js index a34cd129..6273e540 100644 --- a/srcs/requirements/game_server/game_back/src/server/wsServer.js +++ b/jeu/src/server/wsServer.js @@ -1,56 +1,29 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.clientInputListener = exports.wsServer = exports.WebSocket = void 0; -const ws_1 = require("ws"); -class WebSocket extends ws_1.WebSocket { +import { WebSocketServer, WebSocket as BaseLibWebSocket } from "ws"; +export class WebSocket extends BaseLibWebSocket { } -exports.WebSocket = WebSocket; -const uuid_1 = require("uuid"); -const en = __importStar(require("../shared_js/enums.js")); -const ev = __importStar(require("../shared_js/class/Event.js")); -const Client_js_1 = require("./class/Client.js"); -const GameSession_js_1 = require("./class/GameSession.js"); -const utils_js_1 = require("./utils.js"); +import { v4 as uuidv4 } from 'uuid'; +import * as en from "../shared_js/enums.js"; +import * as ev from "../shared_js/class/Event.js"; +import { Client } 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; -exports.wsServer = new ws_1.WebSocketServer({ port: wsPort, path: "/pong" }); +export const wsServer = new WebSocketServer({ port: wsPort, path: "/pong" }); const clientsMap = new Map; // socket.id/Client const matchmakingPlayersMap = new Map; // socket.id/ClientPlayer (duplicates with clientsMap) const gameSessionsMap = new Map; // GameSession.id(url)/GameSession -exports.wsServer.on("connection", connectionListener); -exports.wsServer.on("error", errorListener); -exports.wsServer.on("close", closeListener); +wsServer.on("connection", connectionListener); +wsServer.on("error", errorListener); +wsServer.on("close", closeListener); function connectionListener(socket, request) { - const id = (0, uuid_1.v4)(); - const client = new Client_js_1.Client(socket, id); + 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 ${(0, utils_js_1.shortId)(client.id)} is alive`); + // console.log(`client ${shortId(client.id)} is alive`); }); socket.on("message", function log(data) { try { @@ -119,8 +92,8 @@ function matchmaking(player) { return; } // const id = gameSessionIdPLACEHOLDER; // Force ID, TESTING SPECTATOR - const id = (0, uuid_1.v4)(); - const gameSession = new GameSession_js_1.GameSession(id, matchOptions); + const id = uuidv4(); + const gameSession = new GameSession(id, matchOptions); gameSessionsMap.set(id, gameSession); compatiblePlayers.forEach((client) => { matchmakingPlayersMap.delete(client.id); @@ -172,7 +145,7 @@ function playerReadyConfirmationListener(data) { } this.once("message", playerReadyConfirmationListener); } -function clientInputListener(data) { +export function clientInputListener(data) { try { // const input: ev.ClientEvent = JSON.parse(data); const input = JSON.parse(data); @@ -189,7 +162,6 @@ function clientInputListener(data) { console.log("Invalid JSON (clientInputListener)"); } } -exports.clientInputListener = clientInputListener; //////////// //////////// const pingInterval = setInterval(() => { @@ -197,7 +169,7 @@ const pingInterval = setInterval(() => { clientsMap.forEach((client) => { if (!client.isAlive) { clientTerminate(client); - deleteLog += ` ${(0, utils_js_1.shortId)(client.id)} |`; + deleteLog += ` ${shortId(client.id)} |`; } else { client.isAlive = false; diff --git a/jeu/src/server/wsServer.ts b/jeu/src/server/wsServer.ts new file mode 100644 index 00000000..7a7e9e77 --- /dev/null +++ b/jeu/src/server/wsServer.ts @@ -0,0 +1,266 @@ + +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 "../shared_js/enums.js" +import * as ev from "../shared_js/class/Event.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({port: wsPort, path: "/pong"}); + +const clientsMap: Map = new Map; // socket.id/Client +const matchmakingPlayersMap: Map = new Map; // socket.id/ClientPlayer (duplicates with clientsMap) +const gameSessionsMap: Map = 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 ? + // "/pong" to play, "/pong?ID_OF_A_GAMESESSION" to spectate (or something like that) + if (msg.role === en.ClientRole.player) + { + const announce: ev.ClientAnnouncePlayer = msg; + const player = clientsMap.get(this.id) as ClientPlayer; + player.matchOptions = announce.matchOptions; + this.send(JSON.stringify( new ev.EventAssignId(this.id) )); + this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchmakingInProgress) )); + matchmaking(player); + } + else if (msg.role === en.ClientRole.spectator) + { + const announce: 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 { + 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 = 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); + 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) )); + + setTimeout(function abortMatch() { + if (gameSession.unreadyPlayersMap.size !== 0) + { + gameSessionsMap.delete(gameSession.id); + gameSession.playersMap.forEach((client) => { + client.socket.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchAbort) )); + client.gameSession = null; + clientTerminate(client); + }); + } + }, 5000); +} + + +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) => { + if (!client.isAlive) { + clientTerminate(client); + deleteLog += ` ${shortId(client.id)} |`; + } + 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) +{ + client.socket.terminate(); + if (client.gameSession) + { + client.gameSession.playersMap.delete(client.id); + if (client.gameSession.playersMap.size === 0) + { + clearInterval(client.gameSession.playersUpdateInterval); + clearInterval(client.gameSession.spectatorsUpdateInterval); + clearInterval(client.gameSession.gameLoopInterval); + gameSessionsMap.delete(client.gameSession.id); + } + } + clientsMap.delete(client.id); + if (matchmakingPlayersMap.has(client.id)) { + matchmakingPlayersMap.delete(client.id); + } +} + + +function closeListener() +{ + clearInterval(pingInterval); +} + + +function errorListener(error: Error) +{ + console.log("Error: " + JSON.stringify(error)); +} diff --git a/jeu/src/shared_js/class/Event.js b/jeu/src/shared_js/class/Event.js new file mode 100644 index 00000000..9cb476ee --- /dev/null +++ b/jeu/src/shared_js/class/Event.js @@ -0,0 +1,84 @@ +import * as en from "../enums.js"; +/* From Server */ +export class ServerEvent { + constructor(type = 0) { + this.type = type; + } +} +export class EventAssignId extends ServerEvent { + constructor(id) { + super(en.EventTypes.assignId); + this.id = id; + } +} +export class EventMatchmakingComplete extends ServerEvent { + constructor(side) { + super(en.EventTypes.matchmakingComplete); + this.side = side; + } +} +export class EventGameUpdate extends ServerEvent { + constructor() { + super(en.EventTypes.gameUpdate); + this.playerLeft = { + y: 0 + }; + this.playerRight = { + y: 0 + }; + this.ballsArr = []; + this.wallTop = { + y: 0 + }; + this.wallBottom = { + y: 0 + }; + this.lastInputId = 0; + } +} +export class EventScoreUpdate extends ServerEvent { + constructor(scoreLeft, scoreRight) { + super(en.EventTypes.scoreUpdate); + this.scoreLeft = scoreLeft; + this.scoreRight = scoreRight; + } +} +export class EventMatchEnd extends ServerEvent { + constructor(winner, forfeit = false) { + super(en.EventTypes.matchEnd); + this.winner = winner; + this.forfeit = forfeit; + } +} +/* From Client */ +export class ClientEvent { + constructor(type = 0) { + this.type = type; + } +} +export class ClientAnnounce extends ClientEvent { + constructor(role) { + super(en.EventTypes.clientAnnounce); + this.role = role; + } +} +export class ClientAnnouncePlayer extends ClientAnnounce { + constructor(matchOptions, clientId = "") { + super(en.ClientRole.player); + this.clientId = clientId; + this.matchOptions = matchOptions; + } +} +export class ClientAnnounceSpectator extends ClientAnnounce { + constructor(gameSessionId) { + super(en.ClientRole.spectator); + this.gameSessionId = gameSessionId; + } +} +export class EventInput extends ClientEvent { + constructor(input = en.InputEnum.noInput, id = 0) { + super(en.EventTypes.clientInput); + this.input = input; + this.id = id; + } +} diff --git a/jeu/src/shared_js/class/Event.ts b/jeu/src/shared_js/class/Event.ts new file mode 100644 index 00000000..992827e4 --- /dev/null +++ b/jeu/src/shared_js/class/Event.ts @@ -0,0 +1,117 @@ + +import * as en from "../enums.js" + +/* From Server */ +export class ServerEvent { + type: en.EventTypes; + constructor(type: en.EventTypes = 0) { + this.type = type; + } +} + +export class EventAssignId extends ServerEvent { + id: string; + constructor(id: string) { + super(en.EventTypes.assignId); + this.id = id; + } +} + +export class EventMatchmakingComplete extends ServerEvent { + side: en.PlayerSide; + constructor(side: en.PlayerSide) { + super(en.EventTypes.matchmakingComplete); + this.side = side; + } +} + +export 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); + } +} + +export class EventScoreUpdate extends ServerEvent { + scoreLeft: number; + scoreRight: number; + constructor(scoreLeft: number, scoreRight: number) { + super(en.EventTypes.scoreUpdate); + this.scoreLeft = scoreLeft; + this.scoreRight = scoreRight; + } +} + +export class EventMatchEnd extends ServerEvent { + winner: en.PlayerSide; + forfeit: boolean; + constructor(winner: en.PlayerSide, forfeit = false) { + super(en.EventTypes.matchEnd); + this.winner = winner; + this.forfeit = forfeit; + } +} + + +/* From Client */ +export class ClientEvent { + type: en.EventTypes; // readonly ? + constructor(type: en.EventTypes = 0) { + this.type = type; + } +} + +export class ClientAnnounce extends ClientEvent { + role: en.ClientRole; + constructor(role: en.ClientRole) { + super(en.EventTypes.clientAnnounce); + 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.matchOptions = matchOptions; + } +} + +export class ClientAnnounceSpectator extends ClientAnnounce { + gameSessionId: string; + constructor(gameSessionId: string) { + super(en.ClientRole.spectator); + this.gameSessionId = gameSessionId; + } +} + +export 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; + } +} diff --git a/jeu/src/shared_js/class/GameComponents.js b/jeu/src/shared_js/class/GameComponents.js new file mode 100644 index 00000000..d423c5c8 --- /dev/null +++ b/jeu/src/shared_js/class/GameComponents.js @@ -0,0 +1,51 @@ +import * as c from "../constants.js"; +import * as en from "../../shared_js/enums.js"; +import { VectorInteger } from "./Vector.js"; +import { Rectangle, MovingRectangle, Racket, Ball } from "./Rectangle.js"; +import { random } from "../utils.js"; +export class GameComponents { + constructor(options) { + this.ballsArr = []; + 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); + this.wallTop.dir.y = -1; + pos.assign(0, c.h - c.wallSize); + this.wallBottom = new MovingRectangle(pos, c.w, c.wallSize, c.movingWallSpeed); + 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); + } + } +} diff --git a/jeu/src/shared_js/class/GameComponents.ts b/jeu/src/shared_js/class/GameComponents.ts new file mode 100644 index 00000000..ec36f15f --- /dev/null +++ b/jeu/src/shared_js/class/GameComponents.ts @@ -0,0 +1,63 @@ + +import * as c from "../constants.js" +import * as en from "../../shared_js/enums.js" +import { VectorInteger } from "./Vector.js"; +import { Rectangle, MovingRectangle, Racket, Ball } from "./Rectangle.js"; +import { random } from "../utils.js"; + +export 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); + (this.wallTop).dir.y = -1; + pos.assign(0, c.h-c.wallSize); + this.wallBottom = new MovingRectangle(pos, c.w, c.wallSize, c.movingWallSpeed); + (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); + } + } +} diff --git a/srcs/requirements/game_server/game_back/src/shared_js/class/Rectangle.js b/jeu/src/shared_js/class/Rectangle.js similarity index 69% rename from srcs/requirements/game_server/game_back/src/shared_js/class/Rectangle.js rename to jeu/src/shared_js/class/Rectangle.js index 5f73582c..0df9fa88 100644 --- a/srcs/requirements/game_server/game_back/src/shared_js/class/Rectangle.js +++ b/jeu/src/shared_js/class/Rectangle.js @@ -1,34 +1,8 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Ball = exports.Racket = exports.MovingRectangle = exports.Rectangle = void 0; -const Vector_js_1 = require("./Vector.js"); -const c = __importStar(require("../constants.js")); -class Rectangle { +import { Vector, VectorInteger } from "./Vector.js"; +import * as c from "../constants.js"; +export class Rectangle { constructor(pos, width, height) { - this.pos = new Vector_js_1.VectorInteger(pos.x, pos.y); + this.pos = new VectorInteger(pos.x, pos.y); this.width = width; this.height = height; } @@ -52,11 +26,10 @@ class Rectangle { } } } -exports.Rectangle = Rectangle; -class MovingRectangle extends Rectangle { +export class MovingRectangle extends Rectangle { constructor(pos, width, height, baseSpeed) { super(pos, width, height); - this.dir = new Vector_js_1.Vector(0, 0); + this.dir = new Vector(0, 0); this.baseSpeed = baseSpeed; this.speed = baseSpeed; } @@ -71,15 +44,14 @@ class MovingRectangle extends Rectangle { this._moveAndCollideAlgo(delta, colliderArr); } _moveAndCollideAlgo(delta, colliderArr) { - let oldPos = new Vector_js_1.VectorInteger(this.pos.x, this.pos.y); + let oldPos = new VectorInteger(this.pos.x, this.pos.y); this.move(delta); if (colliderArr.some(this.collision, this)) { this.pos = oldPos; } } } -exports.MovingRectangle = MovingRectangle; -class Racket extends MovingRectangle { +export class Racket extends MovingRectangle { constructor(pos, width, height, baseSpeed) { super(pos, width, height, baseSpeed); } @@ -89,8 +61,7 @@ class Racket extends MovingRectangle { // console.log(`y change: ${this.pos.y - oldPos.y}`); } } -exports.Racket = Racket; -class Ball extends MovingRectangle { +export class Ball extends MovingRectangle { constructor(pos, size, baseSpeed, speedIncrease) { super(pos, size, size, baseSpeed); this.ballInPlay = false; @@ -151,4 +122,3 @@ class Ball extends MovingRectangle { // console.log(`x: ${this.dir.x}, y: ${this.dir.y}`); } } -exports.Ball = Ball; diff --git a/jeu/src/shared_js/class/Rectangle.ts b/jeu/src/shared_js/class/Rectangle.ts new file mode 100644 index 00000000..bbbb30e5 --- /dev/null +++ b/jeu/src/shared_js/class/Rectangle.ts @@ -0,0 +1,142 @@ + +import { Vector, VectorInteger } from "./Vector.js"; +import { Component, Moving } from "./interface.js"; +import * as c from "../constants.js" + +export 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; + } + } +} + +export 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; + } + } +} + +export 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}`); + } +} + +export 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; + } + 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); + } + } + 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(); + } + } + 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}`); + } +} diff --git a/srcs/requirements/game_server/game_back/src/shared_js/class/Vector.js b/jeu/src/shared_js/class/Vector.js similarity index 77% rename from srcs/requirements/game_server/game_back/src/shared_js/class/Vector.js rename to jeu/src/shared_js/class/Vector.js index 0b73c84c..e20e40d0 100644 --- a/srcs/requirements/game_server/game_back/src/shared_js/class/Vector.js +++ b/jeu/src/shared_js/class/Vector.js @@ -1,7 +1,4 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.VectorInteger = exports.Vector = void 0; -class Vector { +export class Vector { constructor(x = 0, y = 0) { this.x = x; this.y = y; @@ -15,10 +12,8 @@ class Vector { return new Vector(this.x / normalizationFactor, this.y / normalizationFactor); } } -exports.Vector = Vector; -class VectorInteger extends Vector { +export class VectorInteger extends Vector { } -exports.VectorInteger = VectorInteger; /* export class VectorInteger { // private _x: number = 0; diff --git a/jeu/src/shared_js/class/Vector.ts b/jeu/src/shared_js/class/Vector.ts new file mode 100644 index 00000000..fbe121e5 --- /dev/null +++ b/jeu/src/shared_js/class/Vector.ts @@ -0,0 +1,47 @@ + +export 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); + } +} + +export class VectorInteger extends Vector { + // PLACEHOLDER + // VectorInteger with set/get dont work (No draw on the screen). Why ? +} + +/* +export 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; + // } +} +*/ diff --git a/jeu/src/shared_js/class/interface.js b/jeu/src/shared_js/class/interface.js new file mode 100644 index 00000000..cb0ff5c3 --- /dev/null +++ b/jeu/src/shared_js/class/interface.js @@ -0,0 +1 @@ +export {}; diff --git a/jeu/src/shared_js/class/interface.ts b/jeu/src/shared_js/class/interface.ts new file mode 100644 index 00000000..544d54a8 --- /dev/null +++ b/jeu/src/shared_js/class/interface.ts @@ -0,0 +1,19 @@ + +import { Vector, VectorInteger } from "./Vector.js"; + +export interface Component { + pos: VectorInteger; +} + +export interface GraphicComponent extends Component { + ctx: CanvasRenderingContext2D; + color: string; + update: () => void; + clear: (pos?: VectorInteger) => void; +} + +export interface Moving { + dir: Vector; + speed: number; // pixel per second + move(delta: number): void; +} diff --git a/jeu/src/shared_js/constants.js b/jeu/src/shared_js/constants.js new file mode 100644 index 00000000..283abf6c --- /dev/null +++ b/jeu/src/shared_js/constants.js @@ -0,0 +1,23 @@ +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); +export const gameSessionIdPLACEHOLDER = "42"; // TESTING SPECTATOR PLACEHOLDER +// for testing, force gameSession.id in wsServer.ts->matchmaking() diff --git a/jeu/src/shared_js/constants.ts b/jeu/src/shared_js/constants.ts new file mode 100644 index 00000000..be86ba7b --- /dev/null +++ b/jeu/src/shared_js/constants.ts @@ -0,0 +1,30 @@ + +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); + + +export const gameSessionIdPLACEHOLDER = "42"; // TESTING SPECTATOR PLACEHOLDER +// for testing, force gameSession.id in wsServer.ts->matchmaking() \ No newline at end of file diff --git a/srcs/requirements/game_server/game_back/src/shared_js/enums.js b/jeu/src/shared_js/enums.js similarity index 73% rename from srcs/requirements/game_server/game_back/src/shared_js/enums.js rename to jeu/src/shared_js/enums.js index dbf699a4..ccc31a74 100644 --- a/srcs/requirements/game_server/game_back/src/shared_js/enums.js +++ b/jeu/src/shared_js/enums.js @@ -1,7 +1,4 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.MatchOptions = exports.ClientRole = exports.PlayerSide = exports.InputEnum = exports.EventTypes = void 0; -var EventTypes; +export var EventTypes; (function (EventTypes) { // Class Implemented EventTypes[EventTypes["gameUpdate"] = 1] = "gameUpdate"; @@ -20,27 +17,27 @@ var EventTypes; EventTypes[EventTypes["clientAnnounce"] = 12] = "clientAnnounce"; EventTypes[EventTypes["clientPlayerReady"] = 13] = "clientPlayerReady"; EventTypes[EventTypes["clientInput"] = 14] = "clientInput"; -})(EventTypes = exports.EventTypes || (exports.EventTypes = {})); -var InputEnum; +})(EventTypes || (EventTypes = {})); +export var InputEnum; (function (InputEnum) { InputEnum[InputEnum["noInput"] = 0] = "noInput"; InputEnum[InputEnum["up"] = 1] = "up"; InputEnum[InputEnum["down"] = 2] = "down"; -})(InputEnum = exports.InputEnum || (exports.InputEnum = {})); -var PlayerSide; +})(InputEnum || (InputEnum = {})); +export var PlayerSide; (function (PlayerSide) { PlayerSide[PlayerSide["left"] = 1] = "left"; PlayerSide[PlayerSide["right"] = 2] = "right"; -})(PlayerSide = exports.PlayerSide || (exports.PlayerSide = {})); -var ClientRole; +})(PlayerSide || (PlayerSide = {})); +export var ClientRole; (function (ClientRole) { ClientRole[ClientRole["player"] = 1] = "player"; ClientRole[ClientRole["spectator"] = 2] = "spectator"; -})(ClientRole = exports.ClientRole || (exports.ClientRole = {})); -var MatchOptions; +})(ClientRole || (ClientRole = {})); +export var MatchOptions; (function (MatchOptions) { // binary flags, can be mixed MatchOptions[MatchOptions["noOption"] = 0] = "noOption"; MatchOptions[MatchOptions["multiBalls"] = 1] = "multiBalls"; MatchOptions[MatchOptions["movingWalls"] = 2] = "movingWalls"; -})(MatchOptions = exports.MatchOptions || (exports.MatchOptions = {})); +})(MatchOptions || (MatchOptions = {})); diff --git a/jeu/src/shared_js/enums.ts b/jeu/src/shared_js/enums.ts new file mode 100644 index 00000000..108f3a66 --- /dev/null +++ b/jeu/src/shared_js/enums.ts @@ -0,0 +1,46 @@ + +export enum EventTypes { + // Class Implemented + gameUpdate = 1, + scoreUpdate, + matchEnd, + assignId, + matchmakingComplete, + + // Generic + matchmakingInProgress, + matchStart, + matchAbort, + matchNewRound, // unused + matchPause, // unused + matchResume, // unused + + // Client + clientAnnounce, + clientPlayerReady, + clientInput, + +} + +export enum InputEnum { + noInput = 0, + up = 1, + down, +} + +export enum PlayerSide { + left = 1, + right +} + +export enum ClientRole { + player = 1, + spectator +} + +export enum MatchOptions { + // binary flags, can be mixed + noOption = 0b0, + multiBalls = 1 << 0, + movingWalls = 1 << 1 +} diff --git a/jeu/src/shared_js/utils.js b/jeu/src/shared_js/utils.js new file mode 100644 index 00000000..2c395dcd --- /dev/null +++ b/jeu/src/shared_js/utils.js @@ -0,0 +1,18 @@ +export function random(min = 0, max = 1) { + return Math.random() * (max - min) + min; +} +export function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} +export function clamp(n, min, max) { + if (n < min) + n = min; + else if (n > max) + n = max; + return (n); +} +// Typescript hack, unused +export function assertMovingRectangle(value) { + // if (value !== MovingRectangle) throw new Error("Not a MovingRectangle"); + return; +} diff --git a/jeu/src/shared_js/utils.ts b/jeu/src/shared_js/utils.ts new file mode 100644 index 00000000..6cba06d2 --- /dev/null +++ b/jeu/src/shared_js/utils.ts @@ -0,0 +1,25 @@ + +import { MovingRectangle } from "./class/Rectangle.js"; + +export function random(min: number = 0, max: number = 1) { + return Math.random() * (max - min) + min; +} + +export function sleep (ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export 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 +export function assertMovingRectangle(value: unknown): asserts value is MovingRectangle { + // if (value !== MovingRectangle) throw new Error("Not a MovingRectangle"); + return; +} diff --git a/jeu/src/shared_js/wallsMovement.js b/jeu/src/shared_js/wallsMovement.js new file mode 100644 index 00000000..3cfc9b32 --- /dev/null +++ b/jeu/src/shared_js/wallsMovement.js @@ -0,0 +1,13 @@ +import * as c from "./constants.js"; +export function wallsMovements(delta, gc) { + const wallTop = gc.wallTop; + const wallBottom = 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]); +} diff --git a/jeu/src/shared_js/wallsMovement.ts b/jeu/src/shared_js/wallsMovement.ts new file mode 100644 index 00000000..0f6dbe58 --- /dev/null +++ b/jeu/src/shared_js/wallsMovement.ts @@ -0,0 +1,18 @@ + +import * as c from "./constants.js"; +import { MovingRectangle } from "../shared_js/class/Rectangle.js"; +import { GameComponents } from "./class/GameComponents.js"; + +export function wallsMovements(delta: number, gc: GameComponents) +{ + const wallTop = gc.wallTop; + const wallBottom = 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]); +} diff --git a/jeu/tsconfig.json b/jeu/tsconfig.json new file mode 100644 index 00000000..714e097f --- /dev/null +++ b/jeu/tsconfig.json @@ -0,0 +1,103 @@ +{ + "compilerOptions": { + /* 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.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. '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": "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'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "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 ''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. */ + } +} diff --git a/jeu/www/Bit5x3.woff b/jeu/www/Bit5x3.woff new file mode 100644 index 00000000..72c8b293 Binary files /dev/null and b/jeu/www/Bit5x3.woff differ diff --git a/jeu/www/Bit5x3.woff2 b/jeu/www/Bit5x3.woff2 new file mode 100644 index 00000000..27d538bf Binary files /dev/null and b/jeu/www/Bit5x3.woff2 differ diff --git a/jeu/www/favicon.ico b/jeu/www/favicon.ico new file mode 100644 index 00000000..b7e7e5ca Binary files /dev/null and b/jeu/www/favicon.ico differ diff --git a/jeu/www/sound/pong/0.ogg b/jeu/www/sound/pong/0.ogg new file mode 100644 index 00000000..93d05409 Binary files /dev/null and b/jeu/www/sound/pong/0.ogg differ diff --git a/jeu/www/sound/pong/1.ogg b/jeu/www/sound/pong/1.ogg new file mode 100644 index 00000000..3a268b34 Binary files /dev/null and b/jeu/www/sound/pong/1.ogg differ diff --git a/jeu/www/sound/pong/10.ogg b/jeu/www/sound/pong/10.ogg new file mode 100644 index 00000000..855ad78b Binary files /dev/null and b/jeu/www/sound/pong/10.ogg differ diff --git a/jeu/www/sound/pong/11.ogg b/jeu/www/sound/pong/11.ogg new file mode 100644 index 00000000..655917b5 Binary files /dev/null and b/jeu/www/sound/pong/11.ogg differ diff --git a/jeu/www/sound/pong/12.ogg b/jeu/www/sound/pong/12.ogg new file mode 100644 index 00000000..11336a76 Binary files /dev/null and b/jeu/www/sound/pong/12.ogg differ diff --git a/jeu/www/sound/pong/13.ogg b/jeu/www/sound/pong/13.ogg new file mode 100644 index 00000000..71cfead6 Binary files /dev/null and b/jeu/www/sound/pong/13.ogg differ diff --git a/jeu/www/sound/pong/14.ogg b/jeu/www/sound/pong/14.ogg new file mode 100644 index 00000000..066fff69 Binary files /dev/null and b/jeu/www/sound/pong/14.ogg differ diff --git a/jeu/www/sound/pong/15.ogg b/jeu/www/sound/pong/15.ogg new file mode 100644 index 00000000..011f139c Binary files /dev/null and b/jeu/www/sound/pong/15.ogg differ diff --git a/jeu/www/sound/pong/16.ogg b/jeu/www/sound/pong/16.ogg new file mode 100644 index 00000000..7e852275 Binary files /dev/null and b/jeu/www/sound/pong/16.ogg differ diff --git a/jeu/www/sound/pong/17.ogg b/jeu/www/sound/pong/17.ogg new file mode 100644 index 00000000..9860139d Binary files /dev/null and b/jeu/www/sound/pong/17.ogg differ diff --git a/jeu/www/sound/pong/18.ogg b/jeu/www/sound/pong/18.ogg new file mode 100644 index 00000000..6ad25391 Binary files /dev/null and b/jeu/www/sound/pong/18.ogg differ diff --git a/jeu/www/sound/pong/19.ogg b/jeu/www/sound/pong/19.ogg new file mode 100644 index 00000000..f6fc42d6 Binary files /dev/null and b/jeu/www/sound/pong/19.ogg differ diff --git a/jeu/www/sound/pong/2.ogg b/jeu/www/sound/pong/2.ogg new file mode 100644 index 00000000..0f09bb30 Binary files /dev/null and b/jeu/www/sound/pong/2.ogg differ diff --git a/jeu/www/sound/pong/20.ogg b/jeu/www/sound/pong/20.ogg new file mode 100644 index 00000000..11ac780e Binary files /dev/null and b/jeu/www/sound/pong/20.ogg differ diff --git a/jeu/www/sound/pong/21.ogg b/jeu/www/sound/pong/21.ogg new file mode 100644 index 00000000..7c724dd4 Binary files /dev/null and b/jeu/www/sound/pong/21.ogg differ diff --git a/jeu/www/sound/pong/22.ogg b/jeu/www/sound/pong/22.ogg new file mode 100644 index 00000000..b2ca9758 Binary files /dev/null and b/jeu/www/sound/pong/22.ogg differ diff --git a/jeu/www/sound/pong/23.ogg b/jeu/www/sound/pong/23.ogg new file mode 100644 index 00000000..f57724b9 Binary files /dev/null and b/jeu/www/sound/pong/23.ogg differ diff --git a/jeu/www/sound/pong/24.ogg b/jeu/www/sound/pong/24.ogg new file mode 100644 index 00000000..90093efc Binary files /dev/null and b/jeu/www/sound/pong/24.ogg differ diff --git a/jeu/www/sound/pong/25.ogg b/jeu/www/sound/pong/25.ogg new file mode 100644 index 00000000..27dfe8eb Binary files /dev/null and b/jeu/www/sound/pong/25.ogg differ diff --git a/jeu/www/sound/pong/26.ogg b/jeu/www/sound/pong/26.ogg new file mode 100644 index 00000000..80cb60fa Binary files /dev/null and b/jeu/www/sound/pong/26.ogg differ diff --git a/jeu/www/sound/pong/27.ogg b/jeu/www/sound/pong/27.ogg new file mode 100644 index 00000000..13332de6 Binary files /dev/null and b/jeu/www/sound/pong/27.ogg differ diff --git a/jeu/www/sound/pong/28.ogg b/jeu/www/sound/pong/28.ogg new file mode 100644 index 00000000..29615795 Binary files /dev/null and b/jeu/www/sound/pong/28.ogg differ diff --git a/jeu/www/sound/pong/29.ogg b/jeu/www/sound/pong/29.ogg new file mode 100644 index 00000000..41f95343 Binary files /dev/null and b/jeu/www/sound/pong/29.ogg differ diff --git a/jeu/www/sound/pong/3.ogg b/jeu/www/sound/pong/3.ogg new file mode 100644 index 00000000..12448222 Binary files /dev/null and b/jeu/www/sound/pong/3.ogg differ diff --git a/jeu/www/sound/pong/30.ogg b/jeu/www/sound/pong/30.ogg new file mode 100644 index 00000000..bd4e4ffd Binary files /dev/null and b/jeu/www/sound/pong/30.ogg differ diff --git a/jeu/www/sound/pong/31.ogg b/jeu/www/sound/pong/31.ogg new file mode 100644 index 00000000..4447b52a Binary files /dev/null and b/jeu/www/sound/pong/31.ogg differ diff --git a/jeu/www/sound/pong/32.ogg b/jeu/www/sound/pong/32.ogg new file mode 100644 index 00000000..a58240a1 Binary files /dev/null and b/jeu/www/sound/pong/32.ogg differ diff --git a/jeu/www/sound/pong/4.ogg b/jeu/www/sound/pong/4.ogg new file mode 100644 index 00000000..688b1f81 Binary files /dev/null and b/jeu/www/sound/pong/4.ogg differ diff --git a/jeu/www/sound/pong/5.ogg b/jeu/www/sound/pong/5.ogg new file mode 100644 index 00000000..d2163268 Binary files /dev/null and b/jeu/www/sound/pong/5.ogg differ diff --git a/jeu/www/sound/pong/6.ogg b/jeu/www/sound/pong/6.ogg new file mode 100644 index 00000000..34bdd117 Binary files /dev/null and b/jeu/www/sound/pong/6.ogg differ diff --git a/jeu/www/sound/pong/7.ogg b/jeu/www/sound/pong/7.ogg new file mode 100644 index 00000000..9c9c994f Binary files /dev/null and b/jeu/www/sound/pong/7.ogg differ diff --git a/jeu/www/sound/pong/8.ogg b/jeu/www/sound/pong/8.ogg new file mode 100644 index 00000000..0f9acf99 Binary files /dev/null and b/jeu/www/sound/pong/8.ogg differ diff --git a/jeu/www/sound/pong/9.ogg b/jeu/www/sound/pong/9.ogg new file mode 100644 index 00000000..15d82091 Binary files /dev/null and b/jeu/www/sound/pong/9.ogg differ diff --git a/jeu/www/sound/roblox-oof.ogg b/jeu/www/sound/roblox-oof.ogg new file mode 100644 index 00000000..689946ed Binary files /dev/null and b/jeu/www/sound/roblox-oof.ogg differ diff --git a/srcs/docker-compose.yml b/srcs/docker-compose.yml index 44810255..344e148a 100644 --- a/srcs/docker-compose.yml +++ b/srcs/docker-compose.yml @@ -20,11 +20,11 @@ services: build: context: ./requirements/game_server dockerfile: Dockerfile - volumes: - - ./requirements/game_server/game_back:/usr/app/src environment: NODE_ENV: "${NODE_ENV}" restart: unless-stopped + ports: + - "8080:8080" depends_on: - backend_dev @@ -80,8 +80,6 @@ services: redis: container_name: nestjs_redis image: redis:alpine - expose: - - "6379" restart: unless-stopped environment: REDIS_HOST: "${REDIS_HOST}" diff --git a/srcs/requirements/game_server/Dockerfile b/srcs/requirements/game_server/Dockerfile index ba94307a..7606e6d0 100644 --- a/srcs/requirements/game_server/Dockerfile +++ b/srcs/requirements/game_server/Dockerfile @@ -4,12 +4,12 @@ WORKDIR /usr/app COPY ./game_back ./ +RUN npm install typescript + RUN npx tsc WORKDIR /usr/app/src/server -RUN ls -la - -CMD [ "node", "wsServer.js"] - +EXPOSE 8080 +CMD [ "node", "server.js"] diff --git a/srcs/requirements/game_server/game_back/src/server/class/Client.js b/srcs/requirements/game_server/game_back/src/server/class/Client.js deleted file mode 100644 index 4225a2d1..00000000 --- a/srcs/requirements/game_server/game_back/src/server/class/Client.js +++ /dev/null @@ -1,52 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ClientSpectator = exports.ClientPlayer = exports.Client = void 0; -const ev = __importStar(require("../../shared_js/class/Event.js")); -class Client { - constructor(socket, id) { - this.isAlive = true; - this.gameSession = null; - this.socket = socket; - this.id = id; - } -} -exports.Client = Client; -class ClientPlayer extends Client { - constructor(socket, id, racket) { - super(socket, id); - this.matchOptions = 0; - this.inputBuffer = new ev.EventInput(); - this.lastInputId = 0; - this.racket = racket; - } -} -exports.ClientPlayer = ClientPlayer; -class ClientSpectator extends Client { - constructor(socket, id) { - super(socket, id); - } -} -exports.ClientSpectator = ClientSpectator; diff --git a/srcs/requirements/game_server/game_back/src/server/class/GameComponentsServer.js b/srcs/requirements/game_server/game_back/src/server/class/GameComponentsServer.js deleted file mode 100644 index 27e8019a..00000000 --- a/srcs/requirements/game_server/game_back/src/server/class/GameComponentsServer.js +++ /dev/null @@ -1,12 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.GameComponentsServer = void 0; -const GameComponents_js_1 = require("../../shared_js/class/GameComponents.js"); -class GameComponentsServer extends GameComponents_js_1.GameComponents { - constructor(options) { - super(options); - this.scoreLeft = 0; - this.scoreRight = 0; - } -} -exports.GameComponentsServer = GameComponentsServer; diff --git a/srcs/requirements/game_server/game_back/src/server/constants.js b/srcs/requirements/game_server/game_back/src/server/constants.js deleted file mode 100644 index b9ede1dc..00000000 --- a/srcs/requirements/game_server/game_back/src/server/constants.js +++ /dev/null @@ -1,24 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.spectatorsUpdateIntervalMS = exports.playersUpdateIntervalMS = exports.fixedDeltaTime = exports.serverGameLoopIntervalMS = void 0; -__exportStar(require("../shared_js/constants.js"), exports); -// 15ms == 1000/66.666 -exports.serverGameLoopIntervalMS = 15; // millisecond -exports.fixedDeltaTime = exports.serverGameLoopIntervalMS / 1000; // second -// 33.333ms == 1000/30 -exports.playersUpdateIntervalMS = 1000 / 30; // millisecond -exports.spectatorsUpdateIntervalMS = 1000 / 30; // millisecond diff --git a/srcs/requirements/game_server/game_back/src/server/server.js b/srcs/requirements/game_server/game_back/src/server/server.js deleted file mode 100644 index 2ac805e5..00000000 --- a/srcs/requirements/game_server/game_back/src/server/server.js +++ /dev/null @@ -1,42 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const http_1 = __importDefault(require("http")); -const url_1 = __importDefault(require("url")); -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const wsServer_js_1 = require("./wsServer.js"); -wsServer_js_1.wsServer; // no-op, just for loading -const hostname = "localhost"; -const port = 8080; -const root = "../../www/"; -const server = http_1.default.createServer((req, res) => { - // let q = new URL(req.url, `http://${req.getHeaders().host}`) - let q = url_1.default.parse(req.url, true); - let filename = root + q.pathname; - fs_1.default.readFile(filename, (err, data) => { - if (err) { - res.writeHead(404, { "Content-Type": "text/html" }); - return res.end("404 Not Found"); - } - if (path_1.default.extname(filename) === ".html") { - res.writeHead(200, { "Content-Type": "text/html" }); - } - else if (path_1.default.extname(filename) === ".js") { - res.writeHead(200, { "Content-Type": "application/javascript" }); - } - else if (path_1.default.extname(filename) === ".mp3") { - res.writeHead(200, { "Content-Type": "audio/mpeg" }); - } - else if (path_1.default.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`); -}); diff --git a/srcs/requirements/game_server/game_back/src/server/server.ts b/srcs/requirements/game_server/game_back/src/server/server.ts index 20801d7f..ad07430d 100644 --- a/srcs/requirements/game_server/game_back/src/server/server.ts +++ b/srcs/requirements/game_server/game_back/src/server/server.ts @@ -6,35 +6,10 @@ import path from "path"; import {wsServer} from "./wsServer.js"; wsServer; // no-op, just for loading -const hostname = "localhost"; +const hostname = "0.0.0.0"; 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(); - }); -}); +const server = http.createServer(); server.listen(port, hostname, () => { console.log(`Pong running at http://${hostname}:${port}/pong.html`); diff --git a/srcs/requirements/game_server/game_back/src/server/utils.js b/srcs/requirements/game_server/game_back/src/server/utils.js deleted file mode 100644 index abffeb16..00000000 --- a/srcs/requirements/game_server/game_back/src/server/utils.js +++ /dev/null @@ -1,22 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.shortId = void 0; -__exportStar(require("../shared_js/utils.js"), exports); -function shortId(id) { - return id.substring(0, id.indexOf("-")); -} -exports.shortId = shortId; diff --git a/srcs/requirements/game_server/game_back/src/shared_js/class/Event.js b/srcs/requirements/game_server/game_back/src/shared_js/class/Event.js deleted file mode 100644 index d9312f90..00000000 --- a/srcs/requirements/game_server/game_back/src/shared_js/class/Event.js +++ /dev/null @@ -1,121 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.EventInput = exports.ClientAnnounceSpectator = exports.ClientAnnouncePlayer = exports.ClientAnnounce = exports.ClientEvent = exports.EventMatchEnd = exports.EventScoreUpdate = exports.EventGameUpdate = exports.EventMatchmakingComplete = exports.EventAssignId = exports.ServerEvent = void 0; -const en = __importStar(require("../enums.js")); -/* From Server */ -class ServerEvent { - constructor(type = 0) { - this.type = type; - } -} -exports.ServerEvent = ServerEvent; -class EventAssignId extends ServerEvent { - constructor(id) { - super(en.EventTypes.assignId); - this.id = id; - } -} -exports.EventAssignId = EventAssignId; -class EventMatchmakingComplete extends ServerEvent { - constructor(side) { - super(en.EventTypes.matchmakingComplete); - this.side = side; - } -} -exports.EventMatchmakingComplete = EventMatchmakingComplete; -class EventGameUpdate extends ServerEvent { - constructor() { - super(en.EventTypes.gameUpdate); - this.playerLeft = { - y: 0 - }; - this.playerRight = { - y: 0 - }; - this.ballsArr = []; - this.wallTop = { - y: 0 - }; - this.wallBottom = { - y: 0 - }; - this.lastInputId = 0; - } -} -exports.EventGameUpdate = EventGameUpdate; -class EventScoreUpdate extends ServerEvent { - constructor(scoreLeft, scoreRight) { - super(en.EventTypes.scoreUpdate); - this.scoreLeft = scoreLeft; - this.scoreRight = scoreRight; - } -} -exports.EventScoreUpdate = EventScoreUpdate; -class EventMatchEnd extends ServerEvent { - constructor(winner, forfeit = false) { - super(en.EventTypes.matchEnd); - this.winner = winner; - this.forfeit = forfeit; - } -} -exports.EventMatchEnd = EventMatchEnd; -/* From Client */ -class ClientEvent { - constructor(type = 0) { - this.type = type; - } -} -exports.ClientEvent = ClientEvent; -class ClientAnnounce extends ClientEvent { - constructor(role) { - super(en.EventTypes.clientAnnounce); - this.role = role; - } -} -exports.ClientAnnounce = ClientAnnounce; -class ClientAnnouncePlayer extends ClientAnnounce { - constructor(matchOptions, clientId = "") { - super(en.ClientRole.player); - this.clientId = clientId; - this.matchOptions = matchOptions; - } -} -exports.ClientAnnouncePlayer = ClientAnnouncePlayer; -class ClientAnnounceSpectator extends ClientAnnounce { - constructor(gameSessionId) { - super(en.ClientRole.spectator); - this.gameSessionId = gameSessionId; - } -} -exports.ClientAnnounceSpectator = ClientAnnounceSpectator; -class EventInput extends ClientEvent { - constructor(input = en.InputEnum.noInput, id = 0) { - super(en.EventTypes.clientInput); - this.input = input; - this.id = id; - } -} -exports.EventInput = EventInput; diff --git a/srcs/requirements/game_server/game_back/src/shared_js/class/GameComponents.js b/srcs/requirements/game_server/game_back/src/shared_js/class/GameComponents.js deleted file mode 100644 index 24126820..00000000 --- a/srcs/requirements/game_server/game_back/src/shared_js/class/GameComponents.js +++ /dev/null @@ -1,78 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.GameComponents = void 0; -const c = __importStar(require("../constants.js")); -const en = __importStar(require("../../shared_js/enums.js")); -const Vector_js_1 = require("./Vector.js"); -const Rectangle_js_1 = require("./Rectangle.js"); -const utils_js_1 = require("../utils.js"); -class GameComponents { - constructor(options) { - this.ballsArr = []; - const pos = new Vector_js_1.VectorInteger; - // Rackets - pos.assign(0 + c.pw, c.h_mid - c.ph / 2); - this.playerLeft = new Rectangle_js_1.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 Rectangle_js_1.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 Rectangle_js_1.Ball(pos, c.ballSize, c.ballSpeed, c.ballSpeedIncrease)); - } - this.ballsArr.forEach((ball) => { - ball.dir.x = 1; - if ((0, utils_js_1.random)() > 0.5) { - ball.dir.x *= -1; - } - ball.dir.y = (0, utils_js_1.random)(0, 0.2); - if ((0, utils_js_1.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 Rectangle_js_1.MovingRectangle(pos, c.w, c.wallSize, c.movingWallSpeed); - this.wallTop.dir.y = -1; - pos.assign(0, c.h - c.wallSize); - this.wallBottom = new Rectangle_js_1.MovingRectangle(pos, c.w, c.wallSize, c.movingWallSpeed); - this.wallBottom.dir.y = 1; - } - else { - pos.assign(0, 0); - this.wallTop = new Rectangle_js_1.Rectangle(pos, c.w, c.wallSize); - pos.assign(0, c.h - c.wallSize); - this.wallBottom = new Rectangle_js_1.Rectangle(pos, c.w, c.wallSize); - } - } -} -exports.GameComponents = GameComponents; diff --git a/srcs/requirements/game_server/game_back/src/shared_js/class/interface.js b/srcs/requirements/game_server/game_back/src/shared_js/class/interface.js deleted file mode 100644 index c8ad2e54..00000000 --- a/srcs/requirements/game_server/game_back/src/shared_js/class/interface.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/srcs/requirements/game_server/game_back/src/shared_js/constants.js b/srcs/requirements/game_server/game_back/src/shared_js/constants.js deleted file mode 100644 index 90e59565..00000000 --- a/srcs/requirements/game_server/game_back/src/shared_js/constants.js +++ /dev/null @@ -1,26 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.gameSessionIdPLACEHOLDER = exports.movingWallSpeed = exports.movingWallPosMax = exports.multiBallsCount = exports.newRoundDelay = exports.matchStartDelay = exports.normalizedSpeed = exports.ballSpeedIncrease = exports.ballSpeed = exports.racketSpeed = exports.wallSize = exports.ballSize = exports.ph = exports.pw = exports.h_mid = exports.w_mid = exports.h = exports.w = exports.CanvasRatio = exports.CanvasWidth = void 0; -exports.CanvasWidth = 1500; -exports.CanvasRatio = 1.66666; -/* ratio 5/3 (1.66) */ -exports.w = exports.CanvasWidth; -exports.h = exports.CanvasWidth / exports.CanvasRatio; -exports.w_mid = Math.floor(exports.w / 2); -exports.h_mid = Math.floor(exports.h / 2); -exports.pw = Math.floor(exports.w * 0.017); -exports.ph = exports.pw * 6; -exports.ballSize = exports.pw; -exports.wallSize = Math.floor(exports.w * 0.01); -exports.racketSpeed = Math.floor(exports.w * 0.66); // pixel per second -exports.ballSpeed = Math.floor(exports.w * 0.66); // pixel per second -exports.ballSpeedIncrease = Math.floor(exports.ballSpeed * 0.05); // pixel per second -exports.normalizedSpeed = false; // for consistency in speed independent of direction -exports.matchStartDelay = 3000; // millisecond -exports.newRoundDelay = 1500; // millisecond -// Game Variantes -exports.multiBallsCount = 3; -exports.movingWallPosMax = Math.floor(exports.w * 0.12); -exports.movingWallSpeed = Math.floor(exports.w * 0.08); -exports.gameSessionIdPLACEHOLDER = "42"; // TESTING SPECTATOR PLACEHOLDER -// for testing, force gameSession.id in wsServer.ts->matchmaking() diff --git a/srcs/requirements/game_server/game_back/src/shared_js/utils.js b/srcs/requirements/game_server/game_back/src/shared_js/utils.js deleted file mode 100644 index 4e8714c3..00000000 --- a/srcs/requirements/game_server/game_back/src/shared_js/utils.js +++ /dev/null @@ -1,25 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.assertMovingRectangle = exports.clamp = exports.sleep = exports.random = void 0; -function random(min = 0, max = 1) { - return Math.random() * (max - min) + min; -} -exports.random = random; -function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} -exports.sleep = sleep; -function clamp(n, min, max) { - if (n < min) - n = min; - else if (n > max) - n = max; - return (n); -} -exports.clamp = clamp; -// Typescript hack, unused -function assertMovingRectangle(value) { - // if (value !== MovingRectangle) throw new Error("Not a MovingRectangle"); - return; -} -exports.assertMovingRectangle = assertMovingRectangle; diff --git a/srcs/requirements/game_server/game_back/src/shared_js/wallsMovement.js b/srcs/requirements/game_server/game_back/src/shared_js/wallsMovement.js deleted file mode 100644 index 12782b1a..00000000 --- a/srcs/requirements/game_server/game_back/src/shared_js/wallsMovement.js +++ /dev/null @@ -1,40 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.wallsMovements = void 0; -const c = __importStar(require("./constants.js")); -function wallsMovements(delta, gc) { - const wallTop = gc.wallTop; - const wallBottom = 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]); -} -exports.wallsMovements = wallsMovements; diff --git a/srcs/requirements/game_server/game_back/tsconfig.json b/srcs/requirements/game_server/game_back/tsconfig.json index 50b7af31..9344b2b4 100644 --- a/srcs/requirements/game_server/game_back/tsconfig.json +++ b/srcs/requirements/game_server/game_back/tsconfig.json @@ -1,103 +1,103 @@ { - "compilerOptions": { - "strictNullChecks": false, - /* Visit https://aka.ms/tsconfig to read more about this file */ + "compilerOptions": { + /* 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. */ + /* 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.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. '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. */ + /* 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.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. '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": "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'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "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 ''s from expanding the number of files TypeScript should add to a project. */ + /* Modules */ + "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'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "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 ''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'. */ + /* 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. */ + /* 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. */ + /* 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. */ - // "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. */ + /* 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. */ + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } } -} diff --git a/srcs/requirements/nginx/conf/default.conf b/srcs/requirements/nginx/conf/default.conf index ea872059..d64f1df6 100644 --- a/srcs/requirements/nginx/conf/default.conf +++ b/srcs/requirements/nginx/conf/default.conf @@ -22,7 +22,7 @@ server { proxy_pass http://frontend_dev:8080; } location /pong { - proxy_pass http://pong; + http://game_server:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade;