From f1f94cb9bc3b983d6e5ac85e5ecd50db48dee3f8 Mon Sep 17 00:00:00 2001 From: batche Date: Wed, 14 Dec 2022 10:45:10 +0100 Subject: [PATCH] serveur de jeu - work in progress --- jeu/jsconfig.json | 13 + jeu/make.sh | 18 ++ jeu/package-lock.json | 121 +++++++ jeu/package.json | 12 + jeu/src/client/audio.js | 12 + jeu/src/client/audio.ts | 16 + jeu/src/client/class/GameArea.js | 32 ++ jeu/src/client/class/GameArea.ts | 36 +++ jeu/src/client/class/GameComponentsClient.js | 71 ++++ jeu/src/client/class/GameComponentsClient.ts | 115 +++++++ jeu/src/client/class/InputHistory.js | 7 + jeu/src/client/class/InputHistory.ts | 14 + jeu/src/client/class/RectangleClient.js | 85 +++++ jeu/src/client/class/RectangleClient.ts | 129 ++++++++ jeu/src/client/class/Text.js | 43 +++ jeu/src/client/class/Text.ts | 56 ++++ jeu/src/client/constants.js | 13 + jeu/src/client/constants.ts | 18 ++ jeu/src/client/draw.js | 35 ++ jeu/src/client/draw.ts | 49 +++ jeu/src/client/gameLoop.js | 52 +++ jeu/src/client/gameLoop.ts | 71 ++++ jeu/src/client/global.js | 19 ++ jeu/src/client/global.ts | 29 ++ jeu/src/client/handleInput.js | 83 +++++ jeu/src/client/handleInput.ts | 108 +++++++ jeu/src/client/message.js | 65 ++++ jeu/src/client/message.ts | 80 +++++ jeu/src/client/pong.css | 54 ++++ jeu/src/client/pong.html | 47 +++ jeu/src/client/pong.js | 63 ++++ jeu/src/client/pong.ts | 78 +++++ jeu/src/client/pongSpectator.html | 19 ++ jeu/src/client/pongSpectator.js | 33 ++ jeu/src/client/pongSpectator.ts | 46 +++ jeu/src/client/utils.js | 13 + jeu/src/client/utils.ts | 16 + jeu/src/client/ws.js | 230 +++++++++++++ jeu/src/client/ws.ts | 302 ++++++++++++++++++ jeu/src/server/class/Client.js | 23 ++ jeu/src/server/class/Client.ts | 34 ++ jeu/src/server/class/GameComponentsServer.js | 8 + jeu/src/server/class/GameComponentsServer.ts | 12 + .../src/server/class/GameSession.js | 53 +-- jeu/src/server/class/GameSession.ts | 241 ++++++++++++++ jeu/src/server/constants.js | 7 + jeu/src/server/constants.ts | 10 + jeu/src/server/server.js | 37 +++ jeu/src/server/server.ts | 41 +++ jeu/src/server/utils.js | 4 + jeu/src/server/utils.ts | 6 + .../game_back => jeu}/src/server/wsServer.js | 66 ++-- jeu/src/server/wsServer.ts | 266 +++++++++++++++ jeu/src/shared_js/class/Event.js | 84 +++++ jeu/src/shared_js/class/Event.ts | 117 +++++++ jeu/src/shared_js/class/GameComponents.js | 51 +++ jeu/src/shared_js/class/GameComponents.ts | 63 ++++ .../src/shared_js/class/Rectangle.js | 48 +-- jeu/src/shared_js/class/Rectangle.ts | 142 ++++++++ .../src/shared_js/class/Vector.js | 9 +- jeu/src/shared_js/class/Vector.ts | 47 +++ jeu/src/shared_js/class/interface.js | 1 + jeu/src/shared_js/class/interface.ts | 19 ++ jeu/src/shared_js/constants.js | 23 ++ jeu/src/shared_js/constants.ts | 30 ++ .../game_back => jeu}/src/shared_js/enums.js | 23 +- jeu/src/shared_js/enums.ts | 46 +++ jeu/src/shared_js/utils.js | 18 ++ jeu/src/shared_js/utils.ts | 25 ++ jeu/src/shared_js/wallsMovement.js | 13 + jeu/src/shared_js/wallsMovement.ts | 18 ++ jeu/tsconfig.json | 103 ++++++ jeu/www/Bit5x3.woff | Bin 0 -> 3124 bytes jeu/www/Bit5x3.woff2 | Bin 0 -> 2012 bytes jeu/www/favicon.ico | Bin 0 -> 15406 bytes jeu/www/sound/pong/0.ogg | Bin 0 -> 4228 bytes jeu/www/sound/pong/1.ogg | Bin 0 -> 4298 bytes jeu/www/sound/pong/10.ogg | Bin 0 -> 4226 bytes jeu/www/sound/pong/11.ogg | Bin 0 -> 4306 bytes jeu/www/sound/pong/12.ogg | Bin 0 -> 4398 bytes jeu/www/sound/pong/13.ogg | Bin 0 -> 4322 bytes jeu/www/sound/pong/14.ogg | Bin 0 -> 4439 bytes jeu/www/sound/pong/15.ogg | Bin 0 -> 4199 bytes jeu/www/sound/pong/16.ogg | Bin 0 -> 4428 bytes jeu/www/sound/pong/17.ogg | Bin 0 -> 4427 bytes jeu/www/sound/pong/18.ogg | Bin 0 -> 4324 bytes jeu/www/sound/pong/19.ogg | Bin 0 -> 4358 bytes jeu/www/sound/pong/2.ogg | Bin 0 -> 4378 bytes jeu/www/sound/pong/20.ogg | Bin 0 -> 4238 bytes jeu/www/sound/pong/21.ogg | Bin 0 -> 4320 bytes jeu/www/sound/pong/22.ogg | Bin 0 -> 4343 bytes jeu/www/sound/pong/23.ogg | Bin 0 -> 4240 bytes jeu/www/sound/pong/24.ogg | Bin 0 -> 4241 bytes jeu/www/sound/pong/25.ogg | Bin 0 -> 4348 bytes jeu/www/sound/pong/26.ogg | Bin 0 -> 4399 bytes jeu/www/sound/pong/27.ogg | Bin 0 -> 4227 bytes jeu/www/sound/pong/28.ogg | Bin 0 -> 4160 bytes jeu/www/sound/pong/29.ogg | Bin 0 -> 4205 bytes jeu/www/sound/pong/3.ogg | Bin 0 -> 4292 bytes jeu/www/sound/pong/30.ogg | Bin 0 -> 4305 bytes jeu/www/sound/pong/31.ogg | Bin 0 -> 4319 bytes jeu/www/sound/pong/32.ogg | Bin 0 -> 4397 bytes jeu/www/sound/pong/4.ogg | Bin 0 -> 4464 bytes jeu/www/sound/pong/5.ogg | Bin 0 -> 4220 bytes jeu/www/sound/pong/6.ogg | Bin 0 -> 4197 bytes jeu/www/sound/pong/7.ogg | Bin 0 -> 4442 bytes jeu/www/sound/pong/8.ogg | Bin 0 -> 4351 bytes jeu/www/sound/pong/9.ogg | Bin 0 -> 4291 bytes jeu/www/sound/roblox-oof.ogg | Bin 0 -> 6383 bytes srcs/docker-compose.yml | 6 +- srcs/requirements/game_server/Dockerfile | 8 +- .../game_back/src/server/class/Client.js | 52 --- .../src/server/class/GameComponentsServer.js | 12 - .../game_back/src/server/constants.js | 24 -- .../game_back/src/server/server.js | 42 --- .../game_back/src/server/server.ts | 29 +- .../game_server/game_back/src/server/utils.js | 22 -- .../game_back/src/shared_js/class/Event.js | 121 ------- .../src/shared_js/class/GameComponents.js | 78 ----- .../src/shared_js/class/interface.js | 2 - .../game_back/src/shared_js/constants.js | 26 -- .../game_back/src/shared_js/utils.js | 25 -- .../game_back/src/shared_js/wallsMovement.js | 40 --- .../game_server/game_back/tsconfig.json | 186 +++++------ srcs/requirements/nginx/conf/default.conf | 2 +- 125 files changed, 3947 insertions(+), 719 deletions(-) create mode 100644 jeu/jsconfig.json create mode 100644 jeu/make.sh create mode 100644 jeu/package-lock.json create mode 100644 jeu/package.json create mode 100644 jeu/src/client/audio.js create mode 100644 jeu/src/client/audio.ts create mode 100644 jeu/src/client/class/GameArea.js create mode 100644 jeu/src/client/class/GameArea.ts create mode 100644 jeu/src/client/class/GameComponentsClient.js create mode 100644 jeu/src/client/class/GameComponentsClient.ts create mode 100644 jeu/src/client/class/InputHistory.js create mode 100644 jeu/src/client/class/InputHistory.ts create mode 100644 jeu/src/client/class/RectangleClient.js create mode 100644 jeu/src/client/class/RectangleClient.ts create mode 100644 jeu/src/client/class/Text.js create mode 100644 jeu/src/client/class/Text.ts create mode 100644 jeu/src/client/constants.js create mode 100644 jeu/src/client/constants.ts create mode 100644 jeu/src/client/draw.js create mode 100644 jeu/src/client/draw.ts create mode 100644 jeu/src/client/gameLoop.js create mode 100644 jeu/src/client/gameLoop.ts create mode 100644 jeu/src/client/global.js create mode 100644 jeu/src/client/global.ts create mode 100644 jeu/src/client/handleInput.js create mode 100644 jeu/src/client/handleInput.ts create mode 100644 jeu/src/client/message.js create mode 100644 jeu/src/client/message.ts create mode 100644 jeu/src/client/pong.css create mode 100644 jeu/src/client/pong.html create mode 100644 jeu/src/client/pong.js create mode 100644 jeu/src/client/pong.ts create mode 100644 jeu/src/client/pongSpectator.html create mode 100644 jeu/src/client/pongSpectator.js create mode 100644 jeu/src/client/pongSpectator.ts create mode 100644 jeu/src/client/utils.js create mode 100644 jeu/src/client/utils.ts create mode 100644 jeu/src/client/ws.js create mode 100644 jeu/src/client/ws.ts create mode 100644 jeu/src/server/class/Client.js create mode 100644 jeu/src/server/class/Client.ts create mode 100644 jeu/src/server/class/GameComponentsServer.js create mode 100644 jeu/src/server/class/GameComponentsServer.ts rename {srcs/requirements/game_server/game_back => jeu}/src/server/class/GameSession.js (78%) create mode 100644 jeu/src/server/class/GameSession.ts create mode 100644 jeu/src/server/constants.js create mode 100644 jeu/src/server/constants.ts create mode 100644 jeu/src/server/server.js create mode 100644 jeu/src/server/server.ts create mode 100644 jeu/src/server/utils.js create mode 100644 jeu/src/server/utils.ts rename {srcs/requirements/game_server/game_back => jeu}/src/server/wsServer.js (76%) create mode 100644 jeu/src/server/wsServer.ts create mode 100644 jeu/src/shared_js/class/Event.js create mode 100644 jeu/src/shared_js/class/Event.ts create mode 100644 jeu/src/shared_js/class/GameComponents.js create mode 100644 jeu/src/shared_js/class/GameComponents.ts rename {srcs/requirements/game_server/game_back => jeu}/src/shared_js/class/Rectangle.js (69%) create mode 100644 jeu/src/shared_js/class/Rectangle.ts rename {srcs/requirements/game_server/game_back => jeu}/src/shared_js/class/Vector.js (77%) create mode 100644 jeu/src/shared_js/class/Vector.ts create mode 100644 jeu/src/shared_js/class/interface.js create mode 100644 jeu/src/shared_js/class/interface.ts create mode 100644 jeu/src/shared_js/constants.js create mode 100644 jeu/src/shared_js/constants.ts rename {srcs/requirements/game_server/game_back => jeu}/src/shared_js/enums.js (73%) create mode 100644 jeu/src/shared_js/enums.ts create mode 100644 jeu/src/shared_js/utils.js create mode 100644 jeu/src/shared_js/utils.ts create mode 100644 jeu/src/shared_js/wallsMovement.js create mode 100644 jeu/src/shared_js/wallsMovement.ts create mode 100644 jeu/tsconfig.json create mode 100644 jeu/www/Bit5x3.woff create mode 100644 jeu/www/Bit5x3.woff2 create mode 100644 jeu/www/favicon.ico create mode 100644 jeu/www/sound/pong/0.ogg create mode 100644 jeu/www/sound/pong/1.ogg create mode 100644 jeu/www/sound/pong/10.ogg create mode 100644 jeu/www/sound/pong/11.ogg create mode 100644 jeu/www/sound/pong/12.ogg create mode 100644 jeu/www/sound/pong/13.ogg create mode 100644 jeu/www/sound/pong/14.ogg create mode 100644 jeu/www/sound/pong/15.ogg create mode 100644 jeu/www/sound/pong/16.ogg create mode 100644 jeu/www/sound/pong/17.ogg create mode 100644 jeu/www/sound/pong/18.ogg create mode 100644 jeu/www/sound/pong/19.ogg create mode 100644 jeu/www/sound/pong/2.ogg create mode 100644 jeu/www/sound/pong/20.ogg create mode 100644 jeu/www/sound/pong/21.ogg create mode 100644 jeu/www/sound/pong/22.ogg create mode 100644 jeu/www/sound/pong/23.ogg create mode 100644 jeu/www/sound/pong/24.ogg create mode 100644 jeu/www/sound/pong/25.ogg create mode 100644 jeu/www/sound/pong/26.ogg create mode 100644 jeu/www/sound/pong/27.ogg create mode 100644 jeu/www/sound/pong/28.ogg create mode 100644 jeu/www/sound/pong/29.ogg create mode 100644 jeu/www/sound/pong/3.ogg create mode 100644 jeu/www/sound/pong/30.ogg create mode 100644 jeu/www/sound/pong/31.ogg create mode 100644 jeu/www/sound/pong/32.ogg create mode 100644 jeu/www/sound/pong/4.ogg create mode 100644 jeu/www/sound/pong/5.ogg create mode 100644 jeu/www/sound/pong/6.ogg create mode 100644 jeu/www/sound/pong/7.ogg create mode 100644 jeu/www/sound/pong/8.ogg create mode 100644 jeu/www/sound/pong/9.ogg create mode 100644 jeu/www/sound/roblox-oof.ogg delete mode 100644 srcs/requirements/game_server/game_back/src/server/class/Client.js delete mode 100644 srcs/requirements/game_server/game_back/src/server/class/GameComponentsServer.js delete mode 100644 srcs/requirements/game_server/game_back/src/server/constants.js delete mode 100644 srcs/requirements/game_server/game_back/src/server/server.js delete mode 100644 srcs/requirements/game_server/game_back/src/server/utils.js delete mode 100644 srcs/requirements/game_server/game_back/src/shared_js/class/Event.js delete mode 100644 srcs/requirements/game_server/game_back/src/shared_js/class/GameComponents.js delete mode 100644 srcs/requirements/game_server/game_back/src/shared_js/class/interface.js delete mode 100644 srcs/requirements/game_server/game_back/src/shared_js/constants.js delete mode 100644 srcs/requirements/game_server/game_back/src/shared_js/utils.js delete mode 100644 srcs/requirements/game_server/game_back/src/shared_js/wallsMovement.js 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 0000000000000000000000000000000000000000..72c8b2934a6ab203b58e8fe6f4d7134924143c89 GIT binary patch literal 3124 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 Lz@`}kCGr0P>KTfy literal 0 HcmV?d00001 diff --git a/jeu/www/Bit5x3.woff2 b/jeu/www/Bit5x3.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..27d538bf85567b90aa23deb22a234ee8c6c5b80d GIT binary patch literal 2012 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 QKdM;XQ#%1Cumb{r0LMl#2><{9 literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/0.ogg b/jeu/www/sound/pong/0.ogg new file mode 100644 index 0000000000000000000000000000000000000000..93d054091a3fd97fb2cb0a5870b7e2dafff60fa9 GIT binary patch literal 4228 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

oatvYAs+uRxKfjwm6x z<0&Q5C)brB5c$cGiWDQV;46f$K#{kcQabdjg!7&HK6M&`q(Wpps$^<^=WHMH%xIud z4fU2$^=!YoY2Iicss`fJuNJO5mpZRWR=m;5VN|_kUIVq7?NjY;M4I&I^w}#_x?bb_ zr8;#UX?msEJgV8PcO5lk`N0qjcNdPrJb@u-;6c`1ijc!%aXdnX+IcVp;guRo2^oP| zG#oP2DrC!rvF*GGD14sBF-Wwfk{E5)q?R`}rBOfaS2xcijj!6D&3C8kRnM0LfKLa& z%()6{u~uyk^5Csd5L@74c8qz8y0j{+kE$ai-10RemJU$Oh_VvMlgV6l1X&5w3{uf6 zh%y;7WibF2p77p!P)R=PiAD=1I|6{5$tdX0YQb0PczOl&Y6H^#G)S&HQq6}a#ep1U z1ESLF6$>2w6X+i!N2764R5u`qUZYmQLqSxVJJd6JNFe$?mE(LjqET&zF4cFp!0qq| zM%#CP4R80(+4=6`X1&Jg=WZ8lAO?&tTLpX;do&fMGbvV4$=4ZxU<$IGmk@Jsv?WAo z5tBizlbOTodK|u1Kc-oeC<*=mXAd2+om`8)bB!A}a2**xXv+ zJMt&j74Y^Z07BOy9G*aWNi)lHfkkZI0B%_>tEH^#esGD}y>NWILID zbmtm(Db0+AI}~N(gIVV3IdRzj;dAM_^2+dtCj_*z0$4B()2b5K+A11bE_7YL^I&}X zIV=%jvjQ&x@O(czFK^1h(aG7B;^FD#{kN~V?Cfl#ln)%UvyI9aEG;b8+u5ccms$YO`k-%u1HjLxZ4zrgIJ2nr!6$D0ps4rKi(;8_IQ~Rs z>w|SUq9q{mCrYQFsv>vKcOR?)O%DQl{7k#l+s^N6Q_OD4ecdzpIUFmA?DB zB<4QD>6b6gIDv#mo7aW@61&x0_M(2b;UU@KnD?3R_47ZXUzwLAyLZ*t+G7g73y+y3 zzBB!q3-Dxs``Xu+4E_0C(dV4qx0al=ZMre%Y)Nti>o{#U!c<BT$Q$W||NA!0UlmOT|>ok8%nRNY^_ zxAgP`l}H`V?>*4(L7jb+qyJ%#YVS{!Pv=~YpB6@p-T%Ztek-@>!hg6~&0JfbB0kRv zSaUHmNid^c6MXY8$=@FI7BjJPi>w;H}*?r3QkaMK(#83@=DxX#f1Srun2?CGt2)yPLtmZaYOHT(BA gb?}<-wngr1LuWz7^n#n)<6{@ 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 literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/13.ogg b/jeu/www/sound/pong/13.ogg new file mode 100644 index 0000000000000000000000000000000000000000..71cfead6f5639d86ca130a6528258fa4fd514dcd GIT binary patch literal 4322 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 literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/15.ogg b/jeu/www/sound/pong/15.ogg new file mode 100644 index 0000000000000000000000000000000000000000..011f139c110360529716d44a8b602630913b6182 GIT binary patch literal 4199 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 Mj)rs}y6tQJpSTMp82|tP literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/16.ogg b/jeu/www/sound/pong/16.ogg new file mode 100644 index 0000000000000000000000000000000000000000..7e852275d2a5b5c7f409ebb1d65bafc8b5475e23 GIT binary patch literal 4428 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 YWxk`;2jKX3tpO1Oz5u(x^SQ;~Z`7QX`~Uy| literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/18.ogg b/jeu/www/sound/pong/18.ogg new file mode 100644 index 0000000000000000000000000000000000000000..6ad253919ee38994ff8c7ea0f36f64d7cbf94700 GIT binary patch literal 4324 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 ZRGn)*LUvvBCI{1C{vGHOE(A+3{{v%VWTXH9 literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/19.ogg b/jeu/www/sound/pong/19.ogg new file mode 100644 index 0000000000000000000000000000000000000000..f6fc42d64d57c8f3653f02da71c51e2acef2472b GIT binary patch literal 4358 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 I1oq&60qYcbo&W#< literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/2.ogg b/jeu/www/sound/pong/2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..0f09bb3057cf801d9b9dd7c4c6f7feb4092f51a3 GIT binary patch literal 4378 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 I0B1J)AM6m6LjV8( literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/20.ogg b/jeu/www/sound/pong/20.ogg new file mode 100644 index 0000000000000000000000000000000000000000..11ac780e985c9e1410d152434c8d0392751684d0 GIT binary patch literal 4238 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 Xe6?FHo3(r+AdOF8TE{WQBBQ?m2bNen literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/22.ogg b/jeu/www/sound/pong/22.ogg new file mode 100644 index 0000000000000000000000000000000000000000..b2ca975852361bc914636fdd03ace40c491f719d GIT binary patch literal 4343 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 literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/24.ogg b/jeu/www/sound/pong/24.ogg new file mode 100644 index 0000000000000000000000000000000000000000..90093efc7e6aa6f17613621bd0b952f890a4eaf0 GIT binary patch literal 4241 zcmd5j0t0WThS}^diM!-ruVz^`0h9V%>H(E)?Rz< z-&t$zwf0#jDlIJrn1FXpMOz^Ho;0OlR%7yZ<|cD^C<_zRwy+}XS@=JTi9&b&rJ*}9 zaCU*z^XUi4Z-3i7O&4&;a5&Vq@X&J`3H?~pU)=+e36zpgJ9S_3$_sW-(p}E$_&ICFb+l|3 zoleRdp*SDq4fyD#ywcnq9_dg{%CjoiL0q8fe$oYGA}DWw(p<1>BM!<*I&}dV-fGCR z>6BA4+|EO4%{?8B>oyLl5egJB4#ISvIFj$fmA<)CSw;-}9&_@2_$T?t%6O z;Jk}>HRD5%9sdd!T*A57xOnpTkB;dUxl3C@jvue2-^Yt$2WZ6F1XE3{1%x><4n!8QRz5O!(H(jvGXksV;v!UgUp{0$XE57&8o>d>{- zVNqq(l*fC^3+g;&7P(W@8EnC7=(G<(5VU*%e*w$8NC{~$Z;EU|^_D$vVG~)Rq z$(IS$H|TALc;)nv_T!>tl53ubeyC*tMvI9({E-Tw`inp`0EHf*#767nGD@>Fu#Bib z!YlUB+4IUN`g%UyMK9%V3i}BMr_E9x-PgIAw<*lI0mcVd41e!8j8-hi_)CE>pvo`m z897=+jHoB4_nHz#0fs;AZ5>2)m4O9peU2(F^Y=L9U=2F8p0n7pECKKr(& z=r%K(O83;>nlQYzP(4oxp8j+7xelReNtPe`b$>Wt%;zzodF> zap7^}{Lt7qH>DBY;N$-FE&@ga%zE@eMFw7%x>Z0vG zW$1nWOK%71_=DH|8N>Y@H@iJ<^=6a4&#@oQGz@tddZW7qUvAs8^vCCX0c-CL)`d9% zeuV5*eE&dQrV%;gCc=@R$gW4_F2f4$e6L|hnT;S>5ZQn#nKj(|cnEo9G|;Gq z(p9T`JgjV+HyVhlfw&DTxhqd+&8yNS@3eB!DqZJQP^-s7ik+=Un*p6ZdacMYsNDak z%$i5qUaQt>RXYuy+PVB77=kfgTrJEK7=m+b$hu1ACNda_-jU7;H^-WROVrRh(1DD+z>rP(c@D`0?Zw%FHy`LL{*R{5_#(JqAI#Mq!QkD z5Q*rSW&l|Gz#u+sbq3WSjW(i(vq73WV2?>Q7! z(8C;>L{{*+z5`#Y+o`q$a%wQZIzxv95*yKXuJOhDe}~Y&x9~T2wEQEezq#Z81^;g# zT>bZ5^{?2s6jYSvfQo&;MrJ4sva;wf;()XO>!HJ?jM50iD(F~J>eh}`dr`GXq|YO@(rDS@RUxH`KYmTOQyn0pODb%rIGS3pX;>ARKO~n zH|zliSoh)H6@O;n4%r6?SKsLH?WbLh-~MYzo7gs?BC}X}8AT5tx~2%7nmfdzQ$Ch@ z0>?x1q_D!c3JWLRGGAk{y?7O@xM9YisZ@v!BftT!7GizT!APM4J=(jeLl!1k|9 zfxpnQ^%JS}Au}5^IbjJRv=`UYM+yVV1HQEk7>fSfk{Us|T9dpf(HnPW zpTH6kHXHB?0G|!Qk`kQ@$<5u9?Cs<0_m|H(p-^a)@`s~PX#8MXB(yZL0Q$j(3R2I5 zmCG*s6Cw+S)oG3*(gs*#>9VR$jxj zjqXvKsn2ZE>z>xeO+5;{s~d^4j@UZ&hxL!XSXB6DUT5CN-4+!e{7G;Ltk<;>CYuUw zKb3bLU47@xe$}(=bm>zbkmtw9{SVBBle(j9)4q{5zut9b-;pJ^u(mhXI;XBL`uO7) z8M{_H{deF4iz{`{FCTY)fm`pH^t(}D?*zQBCD{eCwTDxCw6Blp-`2&)wwBE+A}iv+NE0VY0%!XYQ-d&X{f5x&0$D zurm3mxY7-Lv)MM+|M&HMCQ~OOTb#b@FIh@I^7z!VpZ(r+RK{<#S}D+UHQkJIwQL#M zx&PL=$Ln^xT-o_2e~W#`nx&?(?6U;Z`RY?W(^I!z4DM~|xjVY@nd1xAxx2y>|5-&S zJm|D*{nFikP8|%tVY|J;{xrMCb9Y?wCGEA%z60iOW)JPYvG>2`&R+kq!|wS5Wx)q& z!Oc6ao@C6P+~xWlTV}cS;v1jlFK~FwH%>IG>#G$fSB$vnE`N7QuZfNP?laBFEf4RV z+OVzbJLfdU+sz{+5FhuYxTmaEnMYSwrA?PjM76(a_>3+-DC(|_`(@)GGjB=;h{JB| yhYGpWW4`k4887@|;k^&@ZI;aiJ~c?ROZH@*%mm=f;=CcNc#zJ(`N9|4?Ee7t1S;(S literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/25.ogg b/jeu/www/sound/pong/25.ogg new file mode 100644 index 0000000000000000000000000000000000000000..27dfe8eb65134e787b8b1a3893765dad077884a9 GIT binary patch literal 4348 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 A%K!iX literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/26.ogg b/jeu/www/sound/pong/26.ogg new file mode 100644 index 0000000000000000000000000000000000000000..80cb60faaf2968a2b91934455797ca0ed2b6f1a6 GIT binary patch literal 4399 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 literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/27.ogg b/jeu/www/sound/pong/27.ogg new file mode 100644 index 0000000000000000000000000000000000000000..13332de644c46885bb092b2b671e1a26b5bcb14d GIT binary patch literal 4227 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 literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/28.ogg b/jeu/www/sound/pong/28.ogg new file mode 100644 index 0000000000000000000000000000000000000000..29615795329d22b87942cafaeb723234c0f6b02c GIT binary patch literal 4160 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 PmeuZCcTAVmE?WK<#WD_& literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/29.ogg b/jeu/www/sound/pong/29.ogg new file mode 100644 index 0000000000000000000000000000000000000000..41f953432ca83a51dcd32e5fcb76f3fdde187a74 GIT binary patch literal 4205 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 K<#RQd)&B!XG)(^h literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/31.ogg b/jeu/www/sound/pong/31.ogg new file mode 100644 index 0000000000000000000000000000000000000000..4447b52aba0950509d1897bd1e580dbf21d0038a GIT binary patch literal 4319 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 bgD*ug-Cjo8|C+K4e0m2+=a4^mgsJ~;6*glX literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/32.ogg b/jeu/www/sound/pong/32.ogg new file mode 100644 index 0000000000000000000000000000000000000000..a58240a199bc12a8f2344f098a2a90198327ce87 GIT binary patch literal 4397 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 Bi248k literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/4.ogg b/jeu/www/sound/pong/4.ogg new file mode 100644 index 0000000000000000000000000000000000000000..688b1f81469413df22373bdb217f2aefe960b3e6 GIT binary patch literal 4464 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 T&bE+U1#C;)g^QWI4TgUMw~Vm9 literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/5.ogg b/jeu/www/sound/pong/5.ogg new file mode 100644 index 0000000000000000000000000000000000000000..d2163268bfeef79ceb362fd2dc3f7bdd13f1f3d7 GIT binary patch literal 4220 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 I`Q4(w0+*p2tN;K2 literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/7.ogg b/jeu/www/sound/pong/7.ogg new file mode 100644 index 0000000000000000000000000000000000000000..9c9c994f7d387f7166b403194366e816c4baf583 GIT binary patch literal 4442 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 O<6q*+E4H|UN&f&x8>s>S literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/8.ogg b/jeu/www/sound/pong/8.ogg new file mode 100644 index 0000000000000000000000000000000000000000..0f9acf9968e994117ed9136747447e7989a19bab GIT binary patch literal 4351 zcmd5BoHAmx0FYa1cH>C;G^P~3Me8n zh#>zM;<$0m!8=+)`Z5j_f23g0XNxjWa94_L=|lxCm_LFAdv? z12X~D``9OmKmEFS&6>fXfaO?DR+@W6_I6?hCuy4zok%Bod3bqvto5J}Nep(1Aeoc7 zowzYGhm*Y{lgG~Fld)%D(7)YGXCAPOwqY9(#6g7QX!52?#=X!(c8JJxQ)NT^E2qS+hVieKL?V^gwS450Z+MJdA`F=WwsFF}p;CU(1&?*)uA6D?oLiJ85VYCo( zL0RaWQ&C%JA>QHv26+j>U~mJUfk)EXi1}9g@0Kz#Jv#&N_NuwiRtUlu)ewqI@((nc zKwc0uyP8z;lvJ{UT9!<=HHv^E!b4DJmNosT^^G%5oA0@8)&xZ|bNhXm%t-;P#X6Ca zVuPUugw$7_vL|*~rVz}nSf&sWIBpZE3qR{;_drP`YT>FD-Cm?5FO{0+az)3_THddt zXS(QgQsg$(`8aabS1(11vQs_NfSeRrke5oHp{l;s1*W4I?<%!E?~6?YASY?j1#*1` z0ME8XPUX5^0o3v_9i8Xi52)KzAYvTQMfFJ577ST7sf){`EdtbfyZSE?8piv(+>CpG zy@3Ru17Fk=Lr&IyMc7!#xn4JCxc0JBnq~G}MR0BHQN}n?6n&LWJ~3mtIRrW8TQfo& z3qB5ojt2-y*U9zL{c~Lub+RI_Znv{Hy?U_0(0ul`-wTg#@f$3gS{7^$Z|#pwZn~hC zrq90@$7yOE2qm8ar%X2bPY903+%Bawa1(}H=qQ(Fcau+7n;6{*7P0K{zkvh=^Z(wj zA^yRlAUfzRVLg3e5jM>JayWi~Jv_-vT$D3BC3rj~7}XXevc&9v=rg7RON4lu`HUf_ z2+21+_0`9=(tb9R2p-rO>2T!>r{HX-8)a_vW0cKj{cmaf!@m!Tv<}xqZ;at@9B$b- zC<@o|!w30HRx8YE;|&h*62!d6Q@OwDf+a>1Lm=o`NJ(8tQC&#MsBf7QuEwzeg6yzp zA{Ev8mW+m!Inl*+^r|tIER`$Yz6U*RViXC$M6gDZJSEGYl4Vb+vK65+cAQMkRg#XY z3~J@S>+Z$?FNL7HM;&e)bpQcQ2L#UOU62bVP?qJjQV{eY;y>CL@jY-T+-v&l1Arh8 zr^^htV1z;!MQ?764sVU#Y{QIT{=4QKuAzhj$F2lf2X!VL`STdjaplUfNBA!Mu`171 zj;)_L&OTc9$kWUwugXhinf-`1iqBgLoc1Xc1T4QwyoN`vQ-c-97Etx4`67G0!>JO+ zcJh^qq+6u&`;5jTNC_jjsaBNawmL_|IHI@;qQ%4k{M_<^`m>=(2+H>iA=gZ|z-7rm6fCG=YYnAS@XhM#jevL)2H2E+$62mI#M+%2`i1xO)b zV3~i!8%m^z5@EeDiVsIpVC@_K{_n%0nCp6@nNdvMU^J5@_P;|}r-^3Xp)mWw6x&;J;;X#bJ#KGVAf==Mo{p$oShKEW)X7Wse*WI z`4}G_j$y~F-3C_UwFM&sg2YPy2qr*=?@-_v0h|y6YX`hFA`E#j8fN7HWH@F(aEF2+ z5Bb0`VhlM(EEwhkVK(a>GiFl2(w2`5@J80c+9@V$vH+}N$QJ}KVLkINxluUwIU7L5@&*apShfce-klhm%pKh&fbBAe^*7ZM zme(fgrn@!$yyVK|Hzw|SRd8m^w5?obsg4bv!?x(s!l zF?78Bp`+QY_Qm@yZckV9gEr5H9hq*IvK)HS4c(rGj>tB_-h^FqFTdpr;N7Y4wMhYg zG}w1vH#k}xcC-t-18N*yO@zKU=#Nf^WGO5ei6tf#$)BlZX=-I1N3P&hapEM~C2|g` zB1@ER90@0`Dpew>uau`rl<5+AW09QPBj+^t>JFr<7j zuC8Or6}ziA+$sQN6jnCuuHx>gN<*tiD9T0ACKPpx2dk223b}I;;K-XKs7sF=eW2o? zZ5b%aYe!$aSMdyLpa~C^uRe})F=Un7kotE(D-2npuapBdG9;*Ld{w;!L)IOV18}$F zavqo-q{8~x5`pivFg z>LJxckE(InXdtEr>fWQ`Ek2tutxl7C)XHT@wR&0&w3_Hv?r1<84cPR%_sT4T+T)@s zV;XIIuXY_$?=W}`P38uH5RCHX4S_rXAvnnftX>K)p2>{&3GZ)XgAfE)Y9b|k5MSd3rX&P;K-}c>fSC&EfYAys3vLMLZ zqsG$OUbO%!1h+zQVzHkWx9Kiat#k!0HFPzD?cfN8 z+IBnyw|mFLbl1@)gWCOvE>Fu~9Q4FuJ``Z;(^!(lpxDNyT@Qtbvml{|nPVQFX6Be* z$_RBlC0YQk>nGr~`Z>*xL`m5Q;hlj)HjwMEcdqfp`_DtjZ!P@G9g6=8>R;~of5HD3 z2$%o1tNs)F=0c@KSy1VopCdB_1X*!pDEX>15AUfXB;Tcz$VoS;R~e}vMpWdt&fXq@YYI^TNhev)0LnK zM|wQL0O~%%$KuZvdP4DqgiG%?`*qQKV?X~nq)qHjVIt4)jAE)DJal;hHZ^yQ#-@Bc z?KA1|3(c?s{vVtt3Cm^k=q24wB7$ZMy@Md%^}@nJor{~hhZn`i*U$f#uQ{PmXp{;7qflu4UYiX8d$5LmuVI4e zFB)QPa2B6hTiYyJv~=kbdk1^_rOSwA2UkHC@2`A43zKcRKa@Ia6ZE8G@401v9haS- z(|G@&)ncp537@V?N$8Jhe6-$MRw3;>Xuia%c6@3evQ=wq(eY5abyZ_u6mrAvD*0qv zkB?cM;M(BE^x)9Odj^jicDA3mu3y^v&FMeUaihv>$s2Hu_ufx@9q2`geBW>bNx;7d z+>=aCak1#TH?{j!W|G;0*C*s(K2QB&*Pi8%E2pKPaHRFI^W-eT6eX2QnxV zHC+XZdeFXiH9fV-+ZWI3wiLZaTIxS+H=B>g**RAC4hy)bpe%mp>*mv&Z0AW|Yw+!N zyaJZ`?x{$+qN&Xp+Vka)g1P&vA?XBy%Q-pl=~mo@yX(Er6#c$Qv3o_{sC<)UmxpKq zpFjOaTl1L}XXWFtZ!U&E_|yD{bQ(k%Mq183*9enCbT1dUJ}ggG92;Zr zoj#eIe)LvgvFYrHCld#0R-G#r(v`CAvnRGLUUen!rq9BCy43OGXZj(vVqF!dXa6vX zaONkFONZ*6$4@(_9twxboGfbhZF)KJwra)2N$)AvfhWu#L!US8YwVd?y+-8z{@&op zHS4pTGe@<($Uar`I^>49@Q2s8?>rz$F3mU04|+Rj*R0W->VI<0>HDNTYX5~Xd`+*F z={Y*>Zh;eC#oppK@Ak5(zgjCEoXj2$535HkgV$1}l}n(G3V7T9tg0~iyknkO2BF=J zZpn>1-Q$f!U6Ng6ZP8@ywm+P>Ymwy_@YDE|JHC4Li90#$k)sW6o#(VRyPn$=*xbr_ zT7Jj%Vw&D_Qsv~xT^kYdK2Clgez9_^YEl2uH?muwaKEhRH0!&yGpz4+K-Wow>5#)2 z*y1u`Sa)Tl_})Q$#A0!W*}m%^rbs)~CO%L!4n+EgJteI$&Pu8~|KnWYq9cRQ)>ROm Gi2ECRx@F@4 literal 0 HcmV?d00001 diff --git a/jeu/www/sound/pong/9.ogg b/jeu/www/sound/pong/9.ogg new file mode 100644 index 0000000000000000000000000000000000000000..15d82091bd70e6e6b7594b8358af7db583c28f53 GIT binary patch literal 4291 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�zdXr7cPdag)Z~el?Z)?@X z$<*y}KXHP90zJ-g5u$Ph47IDL2g5xPMUXXt35%(uU9n;={>7Lk z;Qlt9`SYPZTR)!O+f8~wwY?uS4(zS`srx+nZ*^v-WW&HB{z`R~Ms#PJ8VD@}|_ zq#tMYoEhDADYI6XCZEYStv+Tz@=OwIawYD^FG@!?M@Z849sj~LxzDN~?Ci*a{&5S- zzLtwaOU|LZVp;MSJia|j6WxI9JM?b7YybvjnXab>@1EDhq3n+fjBJy{XxvTCfiCHg zIM}?TvTV>J6jh^O7G!&7->gQ{h{A==x9_G5{apF@@pVpLZ*IMO?keg_IE78HB7kMv77+YRYsTglS6OBFRmpt5KY4ymI=WT)qv1;5*hSU8J;#6hE_KeW^%m|o zC$SGH)VQ9qY<}@nrV)K=@U#=LN$!_x#bLknu^mS@>%C2mseEf9vz=Yy5!%>^z z+SXskCkTP*AHxQ@i)vB@5qi6O8;cf|yA6&GtD$0TyC-dfldq4N+FqMg2a?p3HZ$rM9=H0p<#Jg|-oBf^XYx}cj%%+b9-n>UQLS3}HwpVa> zO-pPD(fO(S_{Sz@Z$nzzm{e7^`$h>CME^X%MFhF z&!!lS29-=f_%$TghH>hLMZxatUG4Nu+WXI0r-+G$9d#G&T=zP-P@J3U#@^qZQKy|x z=#yDT*G@fu^>PXRry(_qnEU>9g1xu;PtKgaKRJotaNEKs$Y%fD-R9RYdD5B6ujoo& zO+Ednv5P+$@IGnEe79SSs5wY3h2=4@)~q;A@+6+k^w}QM=E3)KulKL-dA$soiF)>7 zIbFwZjk7L?iuRW752YP@oV%CooJXA?tkH6~-BFw7@zT{OyVZ8z%CzOhrp=!`mi*?z zB+~eAc}tcK*fQyH;Lb?2HB3;#uVdY z+so2kS5LpOt9}&O^J*q$g5mtaar&1HKgI-dGwQlr1i5f<~ug!~;9E7}_sD(#DopL!xIE>2XU4xN)L z8k%aEDx!MTasv0LP0saJ*I+h9Z&I4|U#mYlEHYK!zJ&6HR94u3;Kt)uv9*GGRYs$O@(J#NPc_RuOLmARWM?|!HHHYSQ!7BUbZI^?lfLe@`hHBNmq_addi z#WeDz{}tSU^P~B8GjrR2x@A;oxua%BrhHb^Gi->q*&!hU1n~YDlwgXMs*;!m-^%Ee|H9>-TGX>WXz_`{~cN4p^J2-*9rVOI+?9Sm-hrPtD;wCJ!v# zv}G_?sqmU7GJ!U48fcFqRyca#OpS~zEUPopse|PBR{EKVfdKZ5=ZZ1G&aqu#?&c)W$Ckcn!pjJpJXESmnum-xJPa$@kap^)8nK z7rNHE$2Qcg{lwofcXqL4S6Rvw&| zghzUEF(}{|;bFua?EbjAq3C_V$GnJ4ftbiNz9l$MG%AcT>gp)hr5|{DQnLDJi|zS{ z2SJLlKI%=cd)|Ge8vUWbJal=Nas-S1MS%l$9cP3%bsc)FVl|Gn7uGOYFJzF>)t z>eXS&`NXdqzdp32NzCe;B*Vq*>yn>a$=htYrwNvON<%Ju-C-}gR9q`s UUsjEEkpJHXAU^UBQk$p013HTK@&Et; literal 0 HcmV?d00001 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;