Implémentation du jeu dans svelte HOPE

This commit is contained in:
batche
2022-12-12 12:37:47 +01:00
parent fb8368cc63
commit b04cd0aa48
121 changed files with 570 additions and 1793 deletions

View File

@@ -1,49 +0,0 @@
import * as c from "./constants.js";
import * as en from "../shared_js/enums.js"
import { gc, matchOptions, clientInfo } from "./global.js";
import { wallsMovements } from "../shared_js/wallsMovement.js";
let actual_time: number = Date.now();
let last_time: number;
let delta_time: number;
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 ) {
opponentInterpolation(delta_time);
}
// 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 opponentInterpolation(delta: number)
{
// interpolation
clientInfo.opponent.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]);
if ((clientInfo.opponent.dir.y > 0 && clientInfo.opponent.pos.y > clientInfo.opponentNextPos.y)
|| (clientInfo.opponent.dir.y < 0 && clientInfo.opponent.pos.y < clientInfo.opponentNextPos.y))
{
clientInfo.opponent.dir.y = 0;
clientInfo.opponent.pos.y = clientInfo.opponentNextPos.y;
}
}
export {gameLoop}

View File

@@ -1,3 +0,0 @@
export {pong, gc, matchOptions} from "./pong.js"
export {socket, clientInfo} from "./ws.js"

View File

@@ -4,7 +4,7 @@
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Svelte app</title>
<title>Potato Pong</title>
<link rel='icon' type='image/png' href='/favicon.png'>
<link rel='stylesheet' href='/global.css'>

View File

@@ -0,0 +1,9 @@
import App from './App.svelte';
const app = new App({
target: document.body,
props: {
// name: 'world'
}
});
export default app;
//# sourceMappingURL=main.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAC;AAE/B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC;IACnB,MAAM,EAAE,QAAQ,CAAC,IAAI;IACrB,KAAK,EAAE;IACN,gBAAgB;KAChB;CACD,CAAC,CAAC;AAEH,eAAe,GAAG,CAAC"}

View File

@@ -1,45 +1,104 @@
<script>
import "public/game/pong.js"
<script lang="ts">
import * as enumeration from './shared_js/enums'
import * as constants from './client/constants'
import { initPong, initGc, initMatchOptions, initStartFunction } from './client/global'
import { GameArea } from './client/class/GameArea';
import { GameComponentsClient } from './client/class/GameComponentsClient';
import { handleInput } from './client/handleInput';
import {gameLoop} from './client/gameLoop'
import { drawLoop } from './client/draw';
import {countdown} from './client/utils'
import { initWebSocket } from './client/ws';
import { initAudio } from './client/audio';
import { pong, gc} from './client/global'
let optionsAreNotSet = true
let sound = false;
let multi_balls = false;
let moving_walls = false;
let matchOption : enumeration.MatchOptions = enumeration.MatchOptions.noOption;
// En async au cas où pour la suite mais apriori inutile, les check se feront sûrement au onMount
const init = async() => {
if (multi_balls === true)
matchOption |= enumeration.MatchOptions.multiBalls
if (moving_walls === true )
matchOption |= enumeration.MatchOptions.movingWalls
initAudio(sound)
initMatchOptions(matchOption)
optionsAreNotSet = false
initPong(new GameArea())
initGc(new GameComponentsClient(matchOption, pong.ctx))
initStartFunction(start)
initWebSocket(matchOption)
}
function start() : void {
gc.text1.pos.assign(constants.w*0.5, constants.h*0.75);
countdown(constants.matchStartDelay/1000, (count: number) => {
gc.text1.clear();
gc.text1.text = `${count}`;
gc.text1.update();
}, resume);
}
function resume(): void {
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, constants.handleInputIntervalMS);
// pong.handleInputInterval = window.setInterval(sendLoop, c.sendLoopIntervalMS);
pong.gameLoopInterval = window.setInterval(gameLoop, constants.gameLoopIntervalMS);
pong.drawLoopInterval = window.setInterval(drawLoop, constants.drawLoopIntervalMS);
}
</script>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
<div id="preload_font">.</div>
<div id="div_game_options">
<fieldset>
<legend>game options</legend>
<div>
<input type="checkbox" id="multi_balls" name="multi_balls">
<label for="multi_balls">multiples balls</label>
</div>
<div>
<input type="checkbox" id="moving_walls" name="moving_walls">
<label for="moving_walls">moving walls</label>
</div>
<div>
<label>sound :</label>
<input type="radio" id="sound_on" name="sound_selector" checked>
<label for="sound_on">on</label>
<input type="radio" id="sound_off" name="sound_selector">
<label for="sound_off">off</label>
</div>
<div>
<button id="play_pong_button">PLAY</button>
</div>
</fieldset>
{#if optionsAreNotSet}
<form on:submit|preventDefault={init}>
<div id="div_game_options">
<fieldset>
<legend>game options</legend>
<div>
<input type="checkbox" id="multi_balls" name="multi_balls" bind:checked={multi_balls}>
<label for="multi_balls">Multiples balls</label>
</div>
<div>
<input type="checkbox" id="moving_walls" name="moving_walls" bind:checked={moving_walls}>
<label for="moving_walls">Moving walls</label>
</div>
<div>
<input type="checkbox" id="moving_walls" name="moving_walls" bind:checked={sound}>
<label for="moving_walls">Sound</label>
</div>
<div>
<button id="play_pong_button" >PLAY</button>
</div>
</fieldset>
</div>
</form>
<div id="div_game_instructions">
<h2>--- keys ---</h2>
<p>move up: 'w' or 'up arrow'</p>
<p>move down: 's' OR 'down arrow'</p>
<p>grid on/off: 'g'</p>
</div>
{/if}
<div id="canvas_container">
<!-- <p> =) </p> -->
</div>
<script src="public/game/pong.js" type="module" defer></script>
</body>
<style>

View File

@@ -2,12 +2,12 @@
import * as c from "./constants.js"
export const soundPongArr: HTMLAudioElement[] = [];
export const soundRoblox = new Audio("http://localhost:8080/sound/roblox-oof.ogg");
export const soundRoblox = new Audio("http://transcendance: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.push(new Audio("http://transcendance:8080/sound/pong/"+i+".ogg"));
soundPongArr[i].volume = c.soundPongVolume;
soundPongArr[i].muted = muteFlag;
}

View File

@@ -1,7 +1,7 @@
import * as c from ".././constants.js"
class GameArea {
export class GameArea {
keys: string[] = [];
handleInputInterval: number = 0;
gameLoopInterval: number = 0;
@@ -34,5 +34,3 @@ class GameArea {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
export {GameArea}

View File

@@ -62,12 +62,13 @@ class GameComponentsExtensionForClient extends GameComponents {
}
}
class GameComponentsClient extends GameComponentsExtensionForClient {
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;
@@ -90,6 +91,8 @@ class GameComponentsClient extends GameComponentsExtensionForClient {
// 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);
@@ -110,5 +113,3 @@ class GameComponentsClient extends GameComponentsExtensionForClient {
this.h_grid_d1 = new RectangleClient(pos, c.gridSize, c.h, ctx, "darkgreen");
}
}
export {GameComponentsClient}

View File

@@ -2,7 +2,7 @@
import * as en from "../../shared_js/enums.js"
import * as ev from "../../shared_js/class/Event.js"
class InputHistory {
export class InputHistory {
input: en.InputEnum;
id: number;
deltaTime: number;
@@ -12,5 +12,3 @@ class InputHistory {
this.deltaTime = deltaTime;
}
}
export {InputHistory}

View File

@@ -17,7 +17,7 @@ function clearRectangle(this: RectangleClient, pos?: VectorInteger) {
this.ctx.clearRect(this.pos.x, this.pos.y, this.width, this.height);
}
class RectangleClient extends Rectangle implements GraphicComponent {
export class RectangleClient extends Rectangle implements GraphicComponent {
ctx: CanvasRenderingContext2D;
color: string;
update: () => void;
@@ -31,19 +31,9 @@ class RectangleClient extends Rectangle implements GraphicComponent {
this.update = updateRectangle;
this.clear = clearRectangle;
}
// update() {
// this.ctx.fillStyle = this.color;
// this.ctx.fillRect(this.pos.x, this.pos.y, this.width, this.height);
// }
// clear(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);
// }
}
class MovingRectangleClient extends MovingRectangle implements GraphicComponent {
export class MovingRectangleClient extends MovingRectangle implements GraphicComponent {
ctx: CanvasRenderingContext2D;
color: string;
update: () => void;
@@ -59,7 +49,7 @@ class MovingRectangleClient extends MovingRectangle implements GraphicComponent
}
}
class RacketClient extends Racket implements GraphicComponent {
export class RacketClient extends Racket implements GraphicComponent {
ctx: CanvasRenderingContext2D;
color: string;
update: () => void;
@@ -75,7 +65,7 @@ class RacketClient extends Racket implements GraphicComponent {
}
}
class BallClient extends Ball implements GraphicComponent {
export class BallClient extends Ball implements GraphicComponent {
ctx: CanvasRenderingContext2D;
color: string;
update: () => void;
@@ -93,10 +83,6 @@ class BallClient extends Ball implements GraphicComponent {
this._bounceAlgo(collider);
soundPongArr[ Math.floor(random(0, soundPongArr.length)) ].play();
}
/* protected _bounceRacket(collider: Racket) {
this._bounceRacketAlgo(collider);
soundRoblox.play();
} */
}
function updateLine(this: Line) {
@@ -105,17 +91,20 @@ function updateLine(this: Line) {
let i = 0;
while (i < this.segmentCount)
{
// for Horizontal Line
/* 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;
}
}
class Line extends RectangleClient {
export class Line extends RectangleClient {
gapeCount: number = 0;
segmentCount: number;
segmentWidth: number;
@@ -129,13 +118,12 @@ class Line extends RectangleClient {
this.gapeCount = gapeCount;
this.segmentCount = this.gapeCount * 2 + 1;
/* Vertical Line */
this.segmentWidth = this.width;
this.segmentHeight = this.height / this.segmentCount;
// for Horizontal Line
/* Horizontal Line */
// this.segmentWidth = this.width / this.segmentCount;
// this.segmentHeight = this.height;
}
}
export {RectangleClient, MovingRectangleClient, RacketClient, BallClient, Line}

View File

@@ -3,7 +3,7 @@ import { Vector, VectorInteger } from "../../shared_js/class/Vector.js";
import { Component } from "../../shared_js/class/interface.js";
// conflict with Text
class TextElem implements Component {
export class TextElem implements Component {
ctx: CanvasRenderingContext2D;
pos: VectorInteger;
color: string;
@@ -39,7 +39,7 @@ class TextElem implements Component {
}
}
class TextNumericValue extends TextElem {
export class TextNumericValue extends TextElem {
private _value: number = 0;
constructor(pos: VectorInteger, size: number,
ctx: CanvasRenderingContext2D, color: string, font?: string)
@@ -54,5 +54,3 @@ class TextNumericValue extends TextElem {
this.text = v.toString();
}
}
export {TextElem, TextNumericValue}

View File

@@ -1,10 +1,8 @@
import { pong, gc } from "./global.js"
import * as c from "./constants.js"
import * as en from "../shared_js/enums.js"
import { gridDisplay } from "./handleInput.js";
function drawLoop()
export function drawLoop()
{
pong.clear();
@@ -15,6 +13,8 @@ function drawLoop()
drawStatic();
gc.text1.update();
gc.text2.update();
gc.text3.update();
drawDynamic();
}
@@ -47,5 +47,3 @@ function drawGrid()
gc.h_grid_u1.update();
gc.h_grid_d1.update();
}
export {drawLoop}

View File

@@ -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;
}
}

View File

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

View File

@@ -20,7 +20,7 @@ const inputHistoryArr: InputHistory[] = [];
socket.send(JSON.stringify(inputState));
} */
function handleInput()
export function handleInput()
{
/* last_time = actual_time;
actual_time = Date.now();
@@ -86,7 +86,7 @@ function playerMovePrediction(delta: number, input: en.InputEnum)
racket.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]);
}
function repeatInput(lastInputId: number)
export function repeatInput(lastInputId: number)
{
// server reconciliation
let i = inputHistoryArr.findIndex((value: InputHistory) => {
@@ -106,5 +106,3 @@ function repeatInput(lastInputId: number)
}
});
}
export {handleInput, repeatInput}

View File

@@ -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);
}

View File

@@ -17,15 +17,10 @@ import { initWebSocket } from "./ws.js";
import { initAudio } from "./audio.js";
/* Keys
Racket: W/S OR Up/Down
Grid On-Off: G
*/
/* TODO: A way to delay the init of variables, but still use "const" not "let" ? */
export let pong: GameArea;
export let gc: GameComponentsClient;
export let matchOptions: en.MatchOptions = en.MatchOptions.noOption;
import { pong, gc } from "./global.js"
import { initPong, initGc, initMatchOptions, initStartFunction } from "./global.js"
function init()
{
@@ -39,48 +34,35 @@ function init()
}
initAudio(soundMutedFlag);
let matchOptions: en.MatchOptions = en.MatchOptions.noOption;
if ( (<HTMLInputElement>document.getElementById("multi_balls")).checked ) {
matchOptions |= en.MatchOptions.multiBalls;
}
if ( (<HTMLInputElement>document.getElementById("moving_walls")).checked ) {
matchOptions |= en.MatchOptions.movingWalls;
}
initMatchOptions(matchOptions);
document.getElementById("div_game_options").hidden = true;
document.getElementById("div_game_options").remove();
document.getElementById("div_game_instructions").remove();
pong = new GameArea();
gc = new GameComponentsClient(matchOptions, pong.ctx);
initPong(new GameArea());
initGc(new GameComponentsClient(matchOptions, pong.ctx));
initStartFunction(start);
initWebSocket(matchOptions);
}
function matchmaking()
function start()
{
console.log("Searching an opponent...");
gc.text1.clear();
gc.text1.pos.assign(c.w/5, c.h_mid);
gc.text1.text = "Searching...";
gc.text1.update();
}
function matchmakingComplete()
{
console.log("Match Found !");
gc.text1.clear();
gc.text1.pos.assign(c.w/8, c.h_mid);
gc.text1.text = "Match Found !";
gc.text1.update();
}
function startGame() {
gc.text1.pos.assign(c.w_mid, c.h_mid+c.h/4);
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();
}, resumeGame);
}, resume);
}
function resumeGame()
function resume()
{
gc.text1.text = "";
window.addEventListener('keydown', function (e) {
@@ -94,6 +76,3 @@ function resumeGame()
pong.gameLoopInterval = window.setInterval(gameLoop, c.gameLoopIntervalMS);
pong.drawLoopInterval = window.setInterval(drawLoop, c.drawLoopIntervalMS);
}
export {matchmaking, matchmakingComplete, startGame}

View File

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

View File

@@ -1,7 +1,7 @@
export * from "../shared_js/utils.js"
function countdown(count: number, callback?: (count: number) => void, endCallback?: () => void)
export function countdown(count: number, callback?: (count: number) => void, endCallback?: () => void)
{
console.log("countdown ", count);
if (count > 0) {
@@ -14,5 +14,3 @@ function countdown(count: number, callback?: (count: number) => void, endCallbac
endCallback();
}
}
export {countdown}

View File

@@ -1,10 +1,10 @@
import * as c from "./constants.js"
import { gc, matchOptions } from "./global.js"
import { gc, matchOptions, startFunction } from "./global.js"
import * as ev from "../shared_js/class/Event.js"
import * as en from "../shared_js/enums.js"
import { matchmaking, matchmakingComplete, startGame } from "./pong.js";
import { RacketClient } from "./class/RectangleClient.js";
import * as msg from "./message.js";
import type { RacketClient } from "./class/RectangleClient.js";
import { repeatInput } from "./handleInput.js";
import { soundRoblox } from "./audio.js"
import { sleep } from "./utils.js";
@@ -18,16 +18,24 @@ class ClientInfo {
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";
const wsUrl = "ws://transcendance:" + 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.ClientAnnounce(en.ClientRole.player, options, clientInfo.id) ));
socket.send(JSON.stringify( new ev.ClientAnnouncePlayer(options, clientInfo.id) ));
});
// socket.addEventListener("message", logListener); // for testing purpose
socket.addEventListener("message", preMatchListener);
@@ -45,7 +53,7 @@ function preMatchListener(this: WebSocket, event: MessageEvent)
clientInfo.id = (<ev.EventAssignId>data).id;
break;
case en.EventTypes.matchmakingInProgress:
matchmaking();
msg.matchmaking();
break;
case en.EventTypes.matchmakingComplete:
clientInfo.side = (<ev.EventMatchmakingComplete>data).side;
@@ -62,17 +70,21 @@ function preMatchListener(this: WebSocket, event: MessageEvent)
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)
matchmakingComplete();
msg.matchmakingComplete();
break;
case en.EventTypes.matchStart:
socket.removeEventListener("message", preMatchListener);
socket.addEventListener("message", inGameListener);
startGame();
startFunction();
break;
case en.EventTypes.matchAbort:
socket.removeEventListener("message", preMatchListener);
msg.matchAbort();
break;
}
}
function inGameListener(event: MessageEvent)
function inGameListener(this: WebSocket, event: MessageEvent)
{
const data: ev.ServerEvent = JSON.parse(event.data);
switch (data.type) {
@@ -169,14 +181,122 @@ function scoreUpdate(data: ev.EventScoreUpdate)
function matchEnd(data: ev.EventMatchEnd)
{
if (data.winner === clientInfo.side) {
gc.text1.pos.assign(c.w*0.415, c.h_mid);
gc.text1.text = "WIN";
msg.win();
if (data.forfeit) {
msg.forfeit(clientInfo.side);
}
}
else {
gc.text1.pos.assign(c.w*0.383, c.h_mid);
gc.text1.text = "LOSE";
msg.lose();
}
// matchEnded = true;
// matchEnded = true; // unused
}
// export let matchEnded = false;
// export let matchEnded = false; // unused
/* Spectator */
export function initWebSocketSpectator(gameSessionId: string)
{
socket = new WebSocket(wsUrl, "json");
socket.addEventListener("open", (event) => {
socket.send(JSON.stringify( new ev.ClientAnnounceSpectator(gameSessionId) ));
});
// socket.addEventListener("message", logListener); // for testing purpose
socket.addEventListener("message", preMatchListenerSpectator);
clientInfoSpectator.playerLeftNextPos = new VectorInteger(gc.playerLeft.pos.x, gc.playerLeft.pos.y);
clientInfoSpectator.playerRightNextPos = new VectorInteger(gc.playerRight.pos.x, gc.playerRight.pos.y);
}
export function preMatchListenerSpectator(this: WebSocket, event: MessageEvent)
{
const data: ev.ServerEvent = JSON.parse(event.data);
if (data.type === en.EventTypes.matchStart)
{
socket.removeEventListener("message", preMatchListenerSpectator);
socket.addEventListener("message", inGameListenerSpectator);
startFunction();
}
}
function inGameListenerSpectator(this: WebSocket, event: MessageEvent)
{
const data: ev.ServerEvent = JSON.parse(event.data);
switch (data.type) {
case en.EventTypes.gameUpdate:
gameUpdateSpectator(data as ev.EventGameUpdate);
break;
case en.EventTypes.scoreUpdate:
scoreUpdateSpectator(data as ev.EventScoreUpdate);
break;
case en.EventTypes.matchEnd:
matchEndSpectator(data as ev.EventMatchEnd);
break;
}
}
function gameUpdateSpectator(data: ev.EventGameUpdate)
{
console.log("gameUpdateSpectator");
if (matchOptions & en.MatchOptions.movingWalls) {
gc.wallTop.pos.y = data.wallTop.y;
gc.wallBottom.pos.y = data.wallBottom.y;
}
data.ballsArr.forEach((ball, i) => {
gc.ballsArr[i].pos.assign(ball.x, ball.y);
gc.ballsArr[i].dir.assign(ball.dirX, ball.dirY);
gc.ballsArr[i].speed = ball.speed;
});
// interpolation
for (const racket of [gc.playerLeft, gc.playerRight])
{
let nextPos: VectorInteger;
if (racket === gc.playerLeft) {
nextPos = clientInfoSpectator.playerLeftNextPos;
}
else {
nextPos = clientInfoSpectator.playerRightNextPos;
}
racket.pos.assign(nextPos.x, nextPos.y);
if (racket === gc.playerLeft) {
nextPos.assign(racket.pos.x, data.playerLeft.y);
}
else {
nextPos.assign(racket.pos.x, data.playerRight.y);
}
racket.dir = new Vector(
nextPos.x - racket.pos.x,
nextPos.y - racket.pos.y
);
if (Math.abs(racket.dir.x) + Math.abs(racket.dir.y) !== 0) {
racket.dir = racket.dir.normalized();
}
}
}
function scoreUpdateSpectator(data: ev.EventScoreUpdate)
{
console.log("scoreUpdateSpectator");
gc.scoreLeft.value = data.scoreLeft;
gc.scoreRight.value = data.scoreRight;
}
function matchEndSpectator(data: ev.EventMatchEnd)
{
console.log("matchEndSpectator");
// WIP
/* msg.win();
if (data.forfeit) {
msg.forfeit(clientInfo.side);
} */
}

View File

@@ -2,14 +2,14 @@
import * as en from "../enums.js"
/* From Server */
class ServerEvent {
export class ServerEvent {
type: en.EventTypes;
constructor(type: en.EventTypes = 0) {
this.type = type;
}
}
class EventAssignId extends ServerEvent {
export class EventAssignId extends ServerEvent {
id: string;
constructor(id: string) {
super(en.EventTypes.assignId);
@@ -17,7 +17,7 @@ class EventAssignId extends ServerEvent {
}
}
class EventMatchmakingComplete extends ServerEvent {
export class EventMatchmakingComplete extends ServerEvent {
side: en.PlayerSide;
constructor(side: en.PlayerSide) {
super(en.EventTypes.matchmakingComplete);
@@ -25,7 +25,7 @@ class EventMatchmakingComplete extends ServerEvent {
}
}
class EventGameUpdate extends ServerEvent {
export class EventGameUpdate extends ServerEvent {
playerLeft = {
y: 0
};
@@ -51,7 +51,7 @@ class EventGameUpdate extends ServerEvent {
}
}
class EventScoreUpdate extends ServerEvent {
export class EventScoreUpdate extends ServerEvent {
scoreLeft: number;
scoreRight: number;
constructor(scoreLeft: number, scoreRight: number) {
@@ -61,36 +61,52 @@ class EventScoreUpdate extends ServerEvent {
}
}
class EventMatchEnd extends ServerEvent {
export class EventMatchEnd extends ServerEvent {
winner: en.PlayerSide;
constructor(winner: en.PlayerSide) {
forfeit: boolean;
constructor(winner: en.PlayerSide, forfeit = false) {
super(en.EventTypes.matchEnd);
this.winner = winner;
this.forfeit = forfeit;
}
}
/* From Client */
class ClientEvent {
export class ClientEvent {
type: en.EventTypes; // readonly ?
constructor(type: en.EventTypes = 0) {
this.type = type;
}
}
class ClientAnnounce extends ClientEvent {
export class ClientAnnounce extends ClientEvent {
role: en.ClientRole;
clientId: string;
matchOptions: en.MatchOptions;
constructor(role: en.ClientRole, matchOptions: en.MatchOptions, clientId: string = "") {
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;
}
}
class EventInput extends ClientEvent {
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) {
@@ -99,9 +115,3 @@ class EventInput extends ClientEvent {
this.id = id;
}
}
export {
ServerEvent, EventAssignId, EventMatchmakingComplete,
EventGameUpdate, EventScoreUpdate, EventMatchEnd,
ClientEvent, ClientAnnounce, EventInput
}

View File

@@ -5,7 +5,7 @@ import { VectorInteger } from "./Vector.js";
import { Rectangle, MovingRectangle, Racket, Ball } from "./Rectangle.js";
import { random } from "../utils.js";
class GameComponents {
export class GameComponents {
wallTop: Rectangle | MovingRectangle;
wallBottom: Rectangle | MovingRectangle;
playerLeft: Racket;
@@ -61,5 +61,3 @@ class GameComponents {
}
}
}
export {GameComponents}

View File

@@ -3,7 +3,7 @@ import { Vector, VectorInteger } from "./Vector.js";
import { Component, Moving } from "./interface.js";
import * as c from "../constants.js"
class Rectangle implements Component {
export class Rectangle implements Component {
pos: VectorInteger;
width: number;
height: number;
@@ -33,7 +33,7 @@ class Rectangle implements Component {
}
}
class MovingRectangle extends Rectangle implements Moving {
export class MovingRectangle extends Rectangle implements Moving {
dir: Vector = new Vector(0,0);
speed: number;
readonly baseSpeed: number;
@@ -61,7 +61,7 @@ class MovingRectangle extends Rectangle implements Moving {
}
}
class Racket extends MovingRectangle {
export class Racket extends MovingRectangle {
constructor(pos: VectorInteger, width: number, height: number, baseSpeed: number) {
super(pos, width, height, baseSpeed);
}
@@ -72,13 +72,22 @@ class Racket extends MovingRectangle {
}
}
class Ball extends MovingRectangle {
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);
}
@@ -92,15 +101,6 @@ class Ball extends MovingRectangle {
this._bounceWall();
}
}
moveAndBounce(delta: number, colliderArr: Rectangle[]) {
this.move(delta);
let i = colliderArr.findIndex(this.collision, this);
if (i != -1)
{
this.bounce(colliderArr[i]);
this.move(delta);
}
}
protected _bounceWall() { // Should be enough for Wall
this.dir.y = this.dir.y * -1;
}
@@ -140,5 +140,3 @@ class Ball extends MovingRectangle {
// console.log(`x: ${this.dir.x}, y: ${this.dir.y}`);
}
}
export {Rectangle, MovingRectangle, Racket, Ball}

View File

@@ -1,5 +1,5 @@
class Vector {
export class Vector {
x: number;
y: number;
constructor(x: number = 0, y: number = 0) {
@@ -16,13 +16,13 @@ class Vector {
}
}
class VectorInteger extends Vector {
export class VectorInteger extends Vector {
// PLACEHOLDER
// VectorInteger with set/get dont work (No draw on the screen). Why ?
}
/*
class VectorInteger {
export class VectorInteger {
// private _x: number = 0;
// private _y: number = 0;
// constructor(x: number = 0, y: number = 0) {
@@ -45,5 +45,3 @@ class VectorInteger {
// }
}
*/
export {Vector, VectorInteger}

View File

@@ -1,21 +1,19 @@
import { Vector, VectorInteger } from "./Vector.js";
interface Component {
export interface Component {
pos: VectorInteger;
}
interface GraphicComponent extends Component {
export interface GraphicComponent extends Component {
ctx: CanvasRenderingContext2D;
color: string;
update: () => void;
clear: (pos?: VectorInteger) => void;
}
interface Moving {
export interface Moving {
dir: Vector;
speed: number; // pixel per second
move(delta: number): void;
}
export {Component, GraphicComponent, Moving}

View File

@@ -24,3 +24,7 @@ export const newRoundDelay = 1500; // millisecond
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()

View File

@@ -1,5 +1,5 @@
enum EventTypes {
export enum EventTypes {
// Class Implemented
gameUpdate = 1,
scoreUpdate,
@@ -10,6 +10,7 @@ enum EventTypes {
// Generic
matchmakingInProgress,
matchStart,
matchAbort,
matchNewRound, // unused
matchPause, // unused
matchResume, // unused
@@ -21,27 +22,25 @@ enum EventTypes {
}
enum InputEnum {
export enum InputEnum {
noInput = 0,
up = 1,
down,
}
enum PlayerSide {
export enum PlayerSide {
left = 1,
right
}
enum ClientRole {
export enum ClientRole {
player = 1,
spectator
}
enum MatchOptions {
export enum MatchOptions {
// binary flags, can be mixed
noOption = 0b0,
multiBalls = 1 << 0,
movingWalls = 1 << 1
}
export {EventTypes, InputEnum, PlayerSide, ClientRole, MatchOptions}

View File

@@ -1,15 +1,15 @@
import { MovingRectangle } from "./class/Rectangle.js";
function random(min: number = 0, max: number = 1) {
export function random(min: number = 0, max: number = 1) {
return Math.random() * (max - min) + min;
}
function sleep (ms: number) {
export function sleep (ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function clamp(n: number, min: number, max: number) : number
export function clamp(n: number, min: number, max: number) : number
{
if (n < min)
n = min;
@@ -19,9 +19,7 @@ function clamp(n: number, min: number, max: number) : number
}
// Typescript hack, unused
function assertMovingRectangle(value: unknown): asserts value is MovingRectangle {
export function assertMovingRectangle(value: unknown): asserts value is MovingRectangle {
// if (value !== MovingRectangle) throw new Error("Not a MovingRectangle");
return;
}
export {random, sleep, clamp, assertMovingRectangle}

View File

@@ -3,7 +3,7 @@ import * as c from "./constants.js";
import { MovingRectangle } from "../shared_js/class/Rectangle.js";
import { GameComponents } from "./class/GameComponents.js";
function wallsMovements(delta: number, gc: GameComponents)
export function wallsMovements(delta: number, gc: GameComponents)
{
const wallTop = <MovingRectangle>gc.wallTop;
const wallBottom = <MovingRectangle>gc.wallBottom;
@@ -16,5 +16,3 @@ function wallsMovements(delta: number, gc: GameComponents)
wallTop.moveAndCollide(delta, [gc.playerLeft, gc.playerRight]);
wallBottom.moveAndCollide(delta, [gc.playerLeft, gc.playerRight]);
}
export {wallsMovements}

View File

@@ -4,125 +4,15 @@ import SplashPage from "../pages/SplashPage.svelte";
import TwoFactorAuthentication from '../pages/TwoFactorAuthentication.svelte';
import UnauthorizedAccessPage from '../pages/UnauthorizedAccessPage.svelte';
import { wrap } from 'svelte-spa-router/wrap'
import { get } from 'svelte/store';
import TestPage from '../pages/TmpTestPage.svelte';
import Game from '../pages/game/Game.svelte'
// "/article/:title": Article, // this is how you would do parameters!
// "/": LoginPage,
// TMP not using this cuz need to work out how to authentical both 42 and 2FA from the backend
// export const primaryRoutes = {
// '/': SplashPage,
// // '/2fa': TwoFactorAuthentication,
// '/2fa': wrap({
// component: TwoFactorAuthentication,
// conditions: [
// (detail) => {
// // let loggedIn;
// // loginStatus.subscribe(value => {
// // loggedIn = value;
// // });
// const { fortyTwo, tfa } = get(loginStatus);
// console.log('condition in /2fa');
// // return (loginStatus.fortyTwo && loginStatus.tfa);
// // console.log($loginStatus.fortyTwo)
// console.log(fortyTwo);
// console.log(tfa);
// return true;
// }
// ]
// }),
// '/profile': wrap({
// component: ProfilePage,
// conditions: [
// (detail) => {
// const { fortyTwo, tfa } = get(loginStatus);
// // console.log(fortyTwo);
// // console.log(tfa);
// // return true;
// return (fortyTwo && tfa);
// }
// ]
// }),
// '/profile/*': wrap({
// component: ProfilePage,
// conditions: [
// (detail) => {
// const { fortyTwo, tfa } = get(loginStatus);
// // console.log(fortyTwo);
// // console.log(tfa);
// // return true;
// return (fortyTwo && tfa);
// }
// ]
// }),
// '/profile': wrap({
// // Use a dynamically-loaded component for this
// asyncComponent: () => import('./ProfilePage.svelte'),
// // Adding one pre-condition that's an async function
// conditions: [
// async (detail) => {
// // Make a network request, which are async operations
// const response = await fetch('http://transcendance:8080/api/v2/user')
// const data = await response.json()
// // Return true to continue loading the component, or false otherwise
// if (data.isAdmin) {
// return true
// }
// else {
// return false
// }
// }
// ]
// }),
// '/unauthorized-access': UnauthorizedAccessPage,
// '*': NotFound
// };
export const primaryRoutes = {
'/': SplashPage,
'/2fa': TwoFactorAuthentication,
// '/test': wrap({
// component: TestPage,
// conditions: [
// (detail) => {
// const user = get(userStore); // seems like get(store) is not an option
// // // const user = userStore;
// // // console.log(fortyTwo);
// // // console.log(tfa);
// // console.log('in /test what is in user')
// // console.log(user)
// // you moron $userStore is a Svelte Abreviation, this is .JS, duh
// // let user = $userStore;
// // let user;
// // const unsub = userStore.subscribe(value => {
// // console.log(value);
// // user = value;
// // });
// console.log('in /test what is in userStore directly')
// console.log(user)
// // return true;
// // obvi this doesn't work cuz skips to true after no user...
// // you gotta make the condition the true and the everything else false
// // if (user && user.statusCode && user.statusCode === 403)
// // if (user && user.username) {
// if (user !== null) {
// unsub();
// return true;
// } else {
// unsub();
// return false;
// }
// }
// ]
// }),
'/game': Game,
'/test': wrap({
component: TestPage,
conditions: [
@@ -141,9 +31,7 @@ export const primaryRoutes = {
return false;
}
],
// props: {
// user: user
// }
}),
'/profile': wrap({
component: ProfilePage,
@@ -179,82 +67,7 @@ export const primaryRoutes = {
}
]
}),
// '/game': wrap({
// component: ProfilePage,
// conditions: [
// async(detail) => {
// const user = await fetch('http://transcendance:8080/api/v2/user')
// .then((resp) => resp.json())
// console.log('in /test what is in user')
// console.log(user)
// if (user && user.username)
// return true;
// else
// return false;
// }
// ]
// }),
// '/spectate': wrap({
// component: ProfilePage,
// conditions: [
// async(detail) => {
// const user = await fetch('http://transcendance:8080/api/v2/user')
// .then((resp) => resp.json())
// console.log('in /test what is in user')
// console.log(user)
// if (user && user.username)
// return true;
// else
// return false;
// }
// ]
// }),
// '/chat': wrap({
// component: ProfilePage,
// conditions: [
// async(detail) => {
// const user = await fetch('http://transcendance:8080/api/v2/user')
// .then((resp) => resp.json())
// console.log('in /test what is in user')
// console.log(user)
// if (user && user.username)
// return true;
// else
// return false;
// }
// ]
// }),
'/unauthorized-access': UnauthorizedAccessPage,
'*': NotFound
};
// export const primaryRoutes = {
// "/": SplashPage,
// "/profile": ProfilePage,
// "/game": GamePage,
// "/chat": ChatPage,
// "*": NotFound
// };
// i might need to add /profile/* and such to make the nested routers work
// ok maybe these need to be in their own files?
// export const gameRoutes = {
// "/": GamePage,
// "*": NotFound
// };
// export const chatRoutes = {
// "/": ChatPage,
// "*": NotFound
// };

Some files were not shown because too many files have changed in this diff Show More