bugfix with countdown() on early pong.destroy call

+ svelte reactivity with forfeit button
+ little matchmaking changes
+ Wip audio rework
This commit is contained in:
LuckyLaszlo
2023-01-05 20:18:59 +01:00
parent b35082148c
commit 68ae8ac333
11 changed files with 117 additions and 114 deletions

View File

@@ -250,10 +250,10 @@ export class GameSession {
} }
} }
private async _matchEnd(winner: en.PlayerSide, forfeit_flag: boolean = false) private async _matchEnd(winner: en.PlayerSide, forfeitFlag: boolean = false)
{ {
this.matchEnded = true; this.matchEnded = true;
const eventEnd = new ev.EventMatchEnd(winner, forfeit_flag); const eventEnd = new ev.EventMatchEnd(winner, forfeitFlag);
this.playersMap.forEach( (client) => { this.playersMap.forEach( (client) => {
client.socket.send(JSON.stringify(eventEnd)); client.socket.send(JSON.stringify(eventEnd));
}); });
@@ -261,19 +261,18 @@ export class GameSession {
client.socket.send(JSON.stringify(eventEnd)); client.socket.send(JSON.stringify(eventEnd));
}); });
// TODO: mettre à jour la route pour gerer les forfaits (actuellement le plus haut score gagne par defaut)
const gc = this.components; const gc = this.components;
console.log("================================= MATCH ENDED"); console.log("================================= MATCH ENDED");
if (forfeit_flag) { if (forfeitFlag) {
if (winner === en.PlayerSide.left) if (winner === en.PlayerSide.left)
{ {
gc.scoreLeft = 3 gc.scoreLeft = 3;
gc.scoreRight = 0 gc.scoreRight = 0;
} }
else else
{ {
gc.scoreLeft = 0 gc.scoreLeft = 0;
gc.scoreRight = 3 gc.scoreRight = 3;
} }
} }
await fetch(c.addressBackEnd + "/game/gameserver/updategame", await fetch(c.addressBackEnd + "/game/gameserver/updategame",

View File

@@ -150,10 +150,10 @@ function publicMatchmaking(player: ClientPlayer)
{ {
const minPlayersNumber = 2; const minPlayersNumber = 2;
const maxPlayersNumber = 2; const maxPlayersNumber = 2;
matchmakingMap.set(player.id, player);
const matchOptions = player.matchOptions; const matchOptions = player.matchOptions;
const compatiblePlayers: ClientPlayer[] = []; const compatiblePlayers: ClientPlayer[] = [];
compatiblePlayers.push(player);
for (const [id, client] of matchmakingMap) for (const [id, client] of matchmakingMap)
{ {
if (client.matchOptions === matchOptions) if (client.matchOptions === matchOptions)
@@ -165,12 +165,27 @@ function publicMatchmaking(player: ClientPlayer)
} }
} }
// TODO: Replace with this code to disable the possibility to play against self
/* for (const [id, client] of matchmakingMap)
{
if (client.matchOptions === matchOptions && client.username !== player.username)
{
compatiblePlayers.push(client);
if (compatiblePlayers.length === maxPlayersNumber) {
break;
}
}
} */
if (compatiblePlayers.length >= minPlayersNumber) { if (compatiblePlayers.length >= minPlayersNumber) {
compatiblePlayers.forEach((client) => { compatiblePlayers.forEach((client) => {
matchmakingMap.delete(client.id); matchmakingMap.delete(client.id);
}); });
createGameSession(compatiblePlayers, matchOptions); createGameSession(compatiblePlayers, matchOptions);
} }
else {
matchmakingMap.set(player.id, player);
}
} }
@@ -178,11 +193,11 @@ function privateMatchmaking(player: ClientPlayer)
{ {
const minPlayersNumber = 2; const minPlayersNumber = 2;
const maxPlayersNumber = 2; const maxPlayersNumber = 2;
privateMatchmakingMap.set(player.id, player);
const matchOptions = player.matchOptions; const matchOptions = player.matchOptions;
const token = player.token; const token = player.token;
const compatiblePlayers: ClientPlayer[] = []; const compatiblePlayers: ClientPlayer[] = [];
compatiblePlayers.push(player);
for (const [id, client] of privateMatchmakingMap) for (const [id, client] of privateMatchmakingMap)
{ {
if (client.token === token) if (client.token === token)
@@ -202,6 +217,7 @@ function privateMatchmaking(player: ClientPlayer)
} }
else else
{ {
privateMatchmakingMap.set(player.id, player);
setTimeout(async function abortMatch() { setTimeout(async function abortMatch() {
if (!player.gameSession) if (!player.gameSession)
{ {
@@ -226,7 +242,6 @@ function privateMatchmaking(player: ClientPlayer)
function createGameSession(playersArr: ClientPlayer[], matchOptions: en.MatchOptions) function createGameSession(playersArr: ClientPlayer[], matchOptions: en.MatchOptions)
{ {
// const id = c.gameSessionIdPLACEHOLDER; // Force ID, TESTING SPECTATOR
const id = uuidv4(); const id = uuidv4();
const gameSession = new GameSession(id, matchOptions); const gameSession = new GameSession(id, matchOptions);
gameSessionsMap.set(id, gameSession); gameSessionsMap.set(id, gameSession);

View File

@@ -1,22 +0,0 @@
NODE_ENV=development
POSTGRES_HOST=postgresql
POSTGRES_PORT=5432
POSTGRES_USERNAME=postgres
POSTGRES_PASSWORD=9pKpKEgiamxwk5P7Ggsz
POSTGRES_DATABASE=transcendance_db
# OAUTH2 42 API
FORTYTWO_CLIENT_ID=u-s4t2ud-49dc7b539bcfe1acb48b928b2b281671c99fc5bfab1faca57a536ab7e0075500
FORTYTWO_CLIENT_SECRET=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
FORTYTWO_CALLBACK_URL=http://transcendance:8080/api/v2/auth/redirect
COOKIE_SECRET=248cdc831110eec8796d7c1edbf79835
# JWT
JWT_SECRET=442d774798979fcc14a4a2b6b7535902
# Misc
PORT=3000
#Redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=1a5e04138b91b3d683c708e4689454c2
#2fa
TWO_FACTOR_AUTHENTICATION_APP_NAME=Transcendance

View File

@@ -30,9 +30,10 @@
let isThereAnyInvitation = false; let isThereAnyInvitation = false;
let invitations = []; let invitations = [];
let waitingMessage = "Please wait..." let waitingMessage = "Please wait...";
let errorMessageWhenAttemptingToGetATicket = ""; let errorMessageWhenAttemptingToGetATicket = "";
let idOfIntevalCheckTerminationOfTheMatch; let watchGameStateInterval;
const watchGameStateIntervalRate = 142;
onMount( async() => { onMount( async() => {
user = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user`) user = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user`)
@@ -43,7 +44,7 @@
}) })
onDestroy( async() => { onDestroy( async() => {
clearInterval(idOfIntevalCheckTerminationOfTheMatch); clearInterval(watchGameStateInterval);
pong.destroy(); pong.destroy();
}) })
@@ -84,7 +85,7 @@
} }
else if (token) else if (token)
{ {
idOfIntevalCheckTerminationOfTheMatch = setInterval(matchTermitation, 1000); watchGameStateInterval = setInterval(watchGameState, watchGameStateIntervalRate);
// options.isInvitedPerson = false // ??? // options.isInvitedPerson = false // ???
pong.init(options, gameAreaId, token); pong.init(options, gameAreaId, token);
hiddenGame = false; hiddenGame = false;
@@ -101,7 +102,7 @@
console.log(invitation) console.log(invitation)
if (invitation.token) if (invitation.token)
{ {
idOfIntevalCheckTerminationOfTheMatch = setInterval(matchTermitation, 1000); watchGameStateInterval = setInterval(watchGameState, watchGameStateIntervalRate);
options.playerOneUsername = invitation.playerOneUsername; options.playerOneUsername = invitation.playerOneUsername;
options.playerTwoUsername = invitation.playerTwoUsername; options.playerTwoUsername = invitation.playerTwoUsername;
options.isSomeoneIsInvited = true; options.isSomeoneIsInvited = true;
@@ -112,23 +113,28 @@
} }
} }
const matchTermitation = () => { const watchGameState = () => {
console.log("Ping matchTermitation") console.log("watchGameState")
if (gameState.matchAbort || gameState.matchEnded) if (gameState) { // trigger Svelte reactivity
gameState.matchStarted = gameState.matchStarted;
gameState.matchEnded = gameState.matchEnded;
gameState.matchAborted = gameState.matchAborted;
}
if (gameState.matchAborted || gameState.matchEnded)
{ {
clearInterval(idOfIntevalCheckTerminationOfTheMatch); clearInterval(watchGameStateInterval);
console.log("matchTermitation was called") console.log("watchGameState, end")
showWaitPage = false showWaitPage = false
gameState.matchAbort ? gameState.matchAborted ?
errorMessageWhenAttemptingToGetATicket = "The match has been aborted" errorMessageWhenAttemptingToGetATicket = "The match has been aborted"
: errorMessageWhenAttemptingToGetATicket = "The match is finished !" : errorMessageWhenAttemptingToGetATicket = "The match is finished !"
gameState.matchAbort ? showError = true : showMatchEnded = true; gameState.matchAborted ? showError = true : showMatchEnded = true;
setTimeout(() => { setTimeout(() => {
resetPage(); resetPage();
errorMessageWhenAttemptingToGetATicket = ""; errorMessageWhenAttemptingToGetATicket = "";
isThereAnyInvitation = false; isThereAnyInvitation = false;
invitations = []; // ??? invitations = []; // ???
console.log("matchTermitation : setTimeout") console.log("watchGameState : setTimeout")
}, 5000); }, 5000);
} }
} }
@@ -183,6 +189,7 @@
} }
function leaveMatch() { function leaveMatch() {
clearInterval(watchGameStateInterval);
resetPage(); resetPage();
}; };
@@ -221,9 +228,15 @@
<canvas id={gameAreaId}/> <canvas id={gameAreaId}/>
</div> </div>
{#if !hiddenGame} {#if !hiddenGame}
<div id="div_game"> {#if !hiddenGame && gameState.matchStarted && !gameState.matchEnded}
<button id="pong_button" on:click={leaveMatch}>forfeit</button> <div id="div_game">
</div> <button id="pong_button" on:click={leaveMatch}>forfeit</button>
</div>
{:else if !hiddenGame && !gameState.matchStarted}
<div id="div_game">
<button id="pong_button" on:click={leaveMatch}>leave matchmaking</button>
</div>
{/if}
{/if} {/if}

View File

@@ -8,7 +8,6 @@
import * as pongSpectator from "./client/pongSpectator"; import * as pongSpectator from "./client/pongSpectator";
import { gameState } from "./client/ws"; import { gameState } from "./client/ws";
import { gameSessionIdPLACEHOLDER } from "./shared_js/constants";
//user's stuff //user's stuff
let user; let user;
@@ -17,32 +16,6 @@
//Game's stuff client side only //Game's stuff client side only
const gameAreaId = "game_area"; const gameAreaId = "game_area";
let sound = "off"; let sound = "off";
// const dummyMatchList = [
// {
// gameSessionId: gameSessionIdPLACEHOLDER,
// matchOptions: pongSpectator.MatchOptions.noOption,
// playerOneUsername: "toto",
// playerTwoUsername: "bruno",
// },
// {
// gameSessionId: gameSessionIdPLACEHOLDER,
// matchOptions: pongSpectator.MatchOptions.multiBalls,
// playerOneUsername: "pl1",
// playerTwoUsername: "pl2",
// },
// {
// gameSessionId: "id6543",
// matchOptions: pongSpectator.MatchOptions.movingWalls | pongSpectator.MatchOptions.multiBalls,
// playerOneUsername: "bertand",
// playerTwoUsername: "cassandre",
// },
// {
// gameSessionId: "id3452",
// matchOptions: pongSpectator.MatchOptions.multiBalls,
// playerOneUsername: "madeleine",
// playerTwoUsername: "jack",
// },
// ];
let matchList = []; let matchList = [];
//html boolean for pages //html boolean for pages
@@ -55,14 +28,12 @@
.then( x => x.json() ); .then( x => x.json() );
allUsers = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user/all`) allUsers = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user/all`)
.then( x => x.json() ); .then( x => x.json() );
// WIP: fetch for match list here
const responseForMatchList = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/game/match/all`) const responseForMatchList = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/game/match/all`)
const jsonForMatchList = await responseForMatchList.json(); const jsonForMatchList = await responseForMatchList.json();
matchList = jsonForMatchList; matchList = jsonForMatchList;
console.log("matchList"); if (matchList.length <= 0) {
if (matchList.length <= 0)
hiddenMatchList = true; hiddenMatchList = true;
console.log(matchList); }
}) })
onDestroy( async() => { onDestroy( async() => {
@@ -81,13 +52,11 @@
async function resetPage() { async function resetPage() {
hiddenGame = true; hiddenGame = true;
pongSpectator.destroy(); pongSpectator.destroy();
// WIP: fetch for match list here
matchList = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/game/match/all`) matchList = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/game/match/all`)
.then( x => x.json() ); .then( x => x.json() );
console.log("matchList"); if (matchList.length <= 0) {
if (matchList.length <= 0)
hiddenMatchList = true; hiddenMatchList = true;
console.log(matchList); }
}; };
</script> </script>

View File

@@ -2,11 +2,8 @@
import * as c from "./constants.js" import * as c from "./constants.js"
// export const soundPongArr: HTMLAudioElement[] = []; // export const soundPongArr: HTMLAudioElement[] = [];
export const soundPongArr: HTMLAudioElement[] = [ export const soundPongArr: HTMLAudioElement[] = [];
new Audio("http://" + process.env.WEBSITE_HOST + ":" + process.env.WEBSITE_PORT + "/sound/pong/"+1+".ogg"), export let soundRoblox: HTMLAudioElement;
new Audio("http://" + process.env.WEBSITE_HOST + ":" + process.env.WEBSITE_PORT + "/sound/pong/"+2+".ogg")
];
export const soundRoblox = new Audio("http://" + process.env.WEBSITE_HOST + ":" + process.env.WEBSITE_PORT + "/sound/roblox-oof.ogg");
export function initAudio(sound: string) export function initAudio(sound: string)
{ {
@@ -18,10 +15,15 @@ export function initAudio(sound: string)
muteFlag = true; muteFlag = true;
} }
soundPongArr.length = 0;
soundPongArr.push(new Audio("http://" + process.env.WEBSITE_HOST + ":" + process.env.WEBSITE_PORT + "/sound/pong/"+1+".ogg"));
soundPongArr.push(new Audio("http://" + process.env.WEBSITE_HOST + ":" + process.env.WEBSITE_PORT + "/sound/pong/"+2+".ogg"));
soundPongArr.forEach((value) => { soundPongArr.forEach((value) => {
value.volume = c.soundRobloxVolume; value.volume = c.soundRobloxVolume;
value.muted = muteFlag; value.muted = muteFlag;
}); });
soundRoblox = new Audio("http://" + process.env.WEBSITE_HOST + ":" + process.env.WEBSITE_PORT + "/sound/roblox-oof.ogg");
soundRoblox.volume = c.soundRobloxVolume; soundRoblox.volume = c.soundRobloxVolume;
soundRoblox.muted = muteFlag; soundRoblox.muted = muteFlag;
} }

View File

@@ -6,6 +6,7 @@ export class GameArea {
handleInputInterval: number = 0; handleInputInterval: number = 0;
gameLoopInterval: number = 0; gameLoopInterval: number = 0;
drawLoopInterval: number = 0; drawLoopInterval: number = 0;
timeoutArr: number[] = [];
canvas: HTMLCanvasElement; canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D; ctx: CanvasRenderingContext2D;
constructor(canvas_id: string) { constructor(canvas_id: string) {

View File

@@ -34,15 +34,28 @@ export function initBase(matchOptions: en.MatchOptions, sound: string, gameAreaI
export function destroyBase() export function destroyBase()
{ {
if (pong) if (socket && (socket.OPEN || socket.CONNECTING)) {
{
clearInterval(pong.handleInputInterval);
clearInterval(pong.gameLoopInterval);
clearInterval(pong.drawLoopInterval);
setPong(null);
}
if (socket && socket.OPEN) {
socket.close(); socket.close();
} }
if (pong)
{
pong.timeoutArr.forEach((value) => {
clearTimeout(value);
});
pong.timeoutArr = [];
clearInterval(pong.handleInputInterval);
pong.handleInputInterval = null;
clearInterval(pong.gameLoopInterval);
pong.gameLoopInterval = null;
clearInterval(pong.drawLoopInterval);
pong.drawLoopInterval = null;
setPong(null);
}
setGc(null);
setMatchOptions(null);
resetGameState(); resetGameState();
} }

View File

@@ -4,7 +4,7 @@ import { handleInput } from "./handleInput.js";
import { gameLoop } from "./gameLoop.js" import { gameLoop } from "./gameLoop.js"
import { drawLoop } from "./draw.js"; import { drawLoop } from "./draw.js";
import { countdown } from "./utils.js"; import { countdown } from "./utils.js";
import { initWebSocket } from "./ws.js"; import { gameState, initWebSocket } from "./ws.js";
import type { InitOptions } from "./class/InitOptions.js"; import type { InitOptions } from "./class/InitOptions.js";
export { InitOptions } from "./class/InitOptions.js"; export { InitOptions } from "./class/InitOptions.js";
import { initBase, destroyBase, computeMatchOptions } from "./init.js"; import { initBase, destroyBase, computeMatchOptions } from "./init.js";
@@ -47,11 +47,12 @@ export function destroy()
function start() function start()
{ {
gc.text1.pos.assign(c.w*0.5, c.h*0.75); gc.text1.pos.assign(c.w*0.5, c.h*0.75);
countdown(c.matchStartDelay/1000, (count: number) => { const countdownArr = countdown(c.matchStartDelay, 1000, (count: number) => {
gc.text1.clear(); gc.text1.clear();
gc.text1.text = `${count}`; gc.text1.text = `${count/1000}`;
gc.text1.update(); gc.text1.update();
}, start_after_countdown); }, start_after_countdown);
pong.timeoutArr = pong.timeoutArr.concat(countdownArr);
} }
function start_after_countdown() function start_after_countdown()
@@ -66,11 +67,12 @@ function start_after_countdown()
abortControllerKeyup = new AbortController(); abortControllerKeyup = new AbortController();
window.addEventListener( window.addEventListener(
'keyup', 'keyup',
(e) => { pong.deleteKey(e.key);}, (e) => { pong.deleteKey(e.key); },
{signal: abortControllerKeyup.signal} {signal: abortControllerKeyup.signal}
); );
resume(); resume();
gameState.matchStarted = true;
} }
function resume() function resume()

View File

@@ -1,16 +1,25 @@
export * from "../shared_js/utils.js" export * from "../shared_js/utils.js"
export function countdown(count: number, callback?: (count: number) => void, endCallback?: () => void) export function countdown(count: number, step: number, callback: (count: number) => void, endCallback?: () => void) : number[]
{ {
console.log("countdown ", count); const timeoutArr: number[] = [];
if (count > 0) {
if (callback) { if (endCallback) {
timeoutArr.push( window.setTimeout(endCallback, count) );
}
let reverseCount = 0;
while (count > 0)
{
timeoutArr.push( window.setTimeout((count: number) => {
console.log("countdown ", count);
callback(count); callback(count);
} }, reverseCount, count)
setTimeout(countdown, 1000, --count, callback, endCallback); );
} count -= step;
else if (endCallback) { reverseCount += step;
endCallback();
} }
return timeoutArr;
} }

View File

@@ -11,13 +11,15 @@ import { sleep } from "./utils.js";
import { Vector, VectorInteger } from "../shared_js/class/Vector.js"; import { Vector, VectorInteger } from "../shared_js/class/Vector.js";
export const gameState = { export const gameState = {
matchStarted: false,
matchEnded: false, matchEnded: false,
matchAbort: false matchAborted: false
} }
export function resetGameState() { export function resetGameState() {
gameState.matchStarted = false;
gameState.matchEnded = false; gameState.matchEnded = false;
gameState.matchAbort = false; gameState.matchAborted = false;
} }
class ClientInfo { class ClientInfo {
@@ -36,7 +38,7 @@ class ClientInfoSpectator {
} }
const wsUrl = "ws://" + process.env.WEBSITE_HOST + ":" + process.env.WEBSITE_PORT + "/pong"; const wsUrl = "ws://" + process.env.WEBSITE_HOST + ":" + process.env.WEBSITE_PORT + "/pong";
export let socket: WebSocket; /* TODO: A way to still use "const" not "let" ? */ export let socket: WebSocket;
export const clientInfo = new ClientInfo(); export const clientInfo = new ClientInfo();
export const clientInfoSpectator = new ClientInfoSpectator(); // WIP, could refactor this export const clientInfoSpectator = new ClientInfoSpectator(); // WIP, could refactor this
@@ -104,7 +106,7 @@ function preMatchListener(this: WebSocket, event: MessageEvent)
startFunction(); startFunction();
break; break;
case en.EventTypes.matchAbort: case en.EventTypes.matchAbort:
gameState.matchAbort = true; gameState.matchAborted = true;
socket.removeEventListener("message", preMatchListener); socket.removeEventListener("message", preMatchListener);
msg.matchAbort(); msg.matchAbort();
break; break;