Match abort if all players not ready in time (5s)

+ HTML Keys instructions
This commit is contained in:
LuckyLaszlo
2022-12-07 19:33:56 +01:00
parent 6985d2218f
commit d2cdc564ac
7 changed files with 139 additions and 98 deletions

View File

@@ -1,45 +1,48 @@
Done:
- Connexion client/serveur via un Websocket
- implémentation basique (authoritative server)
- Matchmaking
- client prediction
- server reconciliation (buffer des inputs côté client + id sur les inputs)
- amélioration collision avec Hugo
- du son (rebonds de la balle, "Oof" de Roblox sur un point)
- init de GameComponents partagé entre serveur et client.
- draw on the canvas "WIN", "LOSE", "MATCHMAKING COMPLETE", ...
- interpolation (mis à jour progressif des mouvements de l'adversaire)
- traitement groupé des inputs clients toutes les x millisecondes
(BUG désynchronisation: revenu à un traitement immédiat en attendant)
- Détruire les GameSession une fois finies.
- mode multi-balles
- mode murs mouvant (la zone de jeu rétréci / agrandi en continu)
- Selection des modes de jeu via HTML
- Selection audio on/off via HTML
TODO:
- Match Abort si tout les joueurs ne sont pas pret assez vite (~15 secondes)
- mode spectateur
- certaines utilisations de Math.floor() superflu ? Vérifier les appels.
(éventuellement Math.round() ?)
- un autre mode de jeu alternatif ?
- changer les "localhost:8080" dans le code.
- sélection couleur des raquettes (your color/opponent color) dans le profil utilisateur.
Enregistrement dans la DB.
init des couleurs dans GameComponentsClient() basé sur les variables de l'utilsateur connecté.
- lors d'un newRound() verifier si tout les joueurs sont encore en ligne et stopper le match sinon
(victoire si encore un joueur en ligne, annulation du match si aucun joueur en ligne)
- mode spectateur
- certaines utilisations de Math.floor() superflu ? Vérifier les appels.
(éventuellement Math.round() ?)
- un autre mode de jeu alternatif ?
- changer les "localhost:8080" dans le code.
Done:
- Connexion client/serveur via un Websocket
- implémentation basique (authoritative server)
- Matchmaking
- client prediction
- server reconciliation (buffer des inputs côté client + id sur les inputs)
- amélioration collision avec Hugo
- du son (rebonds de la balle, "Oof" de Roblox sur un point)
- init de GameComponents partagé entre serveur et client.
- draw on the canvas "WIN", "LOSE", "MATCHMAKING COMPLETE", ...
- interpolation (mis à jour progressif des mouvements de l'adversaire)
- traitement groupé des inputs clients toutes les x millisecondes
(BUG désynchronisation: revenu à un traitement immédiat en attendant)
- Détruire les GameSession une fois finies.
- mode multi-balles
- mode murs mouvant (la zone de jeu rétréci / agrandi en continu)
- Selection des modes de jeu via HTML
- Selection audio on/off via HTML
- Match Abort si tout les joueurs n'ont pas répondus assez vite (5 secondes)
-----------
idées modes de jeu :
- mode 2 raquettes (un joueur haut/gauche et bas/droite)
- skin patate ???
- (prediction de l'avancement de la balle basé sur la latence serveur ?)
- d'autres sons (foule qui applaudi/musique de victoire)
-----------
- BUG: Si la balle va très vite, elle peut ignorer la collision avec une raquette ou mur.
la collision est testée seulement après le mouvement.
Pour éviter ce bug il faudrait diviser le mouvement pour faire plusieurs tests de collision successifs.
- BUG mineur: sur un changement de fenêtre, les touches restent enfoncées et il faut les "décoincer"
en réappuyant. Ce n'est pas grave mais peut-on faire mieux ?
BUG:
- Si la balle va très vite, elle peut ignorer la collision avec une raquette ou mur.
la collision est testée seulement après le mouvement.
Pour éviter ce bug il faudrait diviser le mouvement pour faire plusieurs tests de collision successifs.
- Sur un changement de fenêtre, les touches restent enfoncées et il faut les "décoincer"
en réappuyant. Ce n'est pas grave mais peut-on faire mieux ?
----------
OSEF, rebuts:
- reconnection
- amélioration du protocole, remplacement du JSON (compression. moins de bande passante).
- idées modes de jeu :
- mode 2 raquettes (un joueur haut/gauche et bas/droite)
- skin patate ???
- (prediction de l'avancement de la balle basé sur la latence serveur ?)
- d'autres sons (foule qui applaudi/musique de victoire)
- reconnection
- amélioration du protocole, remplacement du JSON (compression. moins de bande passante).
- sélection couleur des raquettes (your color/opponent color) dans le profil utilisateur.
Enregistrement dans la DB.
init des couleurs dans GameComponentsClient() basé sur les variables de l'utilsateur connecté.

View File

@@ -7,13 +7,6 @@
font-style: normal;
font-display: swap;
}
#preload_font {
font-family: "Bit5x3";
opacity:0;
height:0;
width:0;
display:inline-block;
}
body {
margin: 0;
background-color: #222425;
@@ -24,7 +17,14 @@ body {
/* 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);

View File

@@ -6,30 +6,35 @@
</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>
<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>
</div>
<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>
<div id="canvas_container">

View File

@@ -17,11 +17,6 @@ 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;
@@ -46,7 +41,8 @@ function init()
matchOptions |= en.MatchOptions.movingWalls;
}
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);
@@ -55,32 +51,52 @@ function init()
function matchmaking()
{
console.log("Searching an opponent...");
const text = "searching...";
console.log(text);
gc.text1.clear();
gc.text1.pos.assign(c.w/5, c.h_mid);
gc.text1.text = "Searching...";
gc.text1.pos.assign(c.w*0.2, c.h*0.5);
gc.text1.text = text;
gc.text1.update();
}
function matchmakingComplete()
{
console.log("Match Found !");
const text = "match found !";
console.log(text);
gc.text1.clear();
gc.text1.pos.assign(c.w/8, c.h_mid);
gc.text1.text = "Match Found !";
gc.text1.pos.assign(c.w*0.15, c.h*0.5);
gc.text1.text = text;
gc.text1.update();
}
function startGame() {
gc.text1.pos.assign(c.w_mid, c.h_mid+c.h/4);
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();
gc.text1.pos.assign(c.w*0.44, c.h*0.6);
gc.text1.text = "sorry =(";
const oriSize = gc.text1.size;
gc.text1.size = gc.text1.size*0.2;
gc.text1.update();
gc.text1.size = oriSize;
}
function matchStart()
{
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);
}, matchResume);
}
function resumeGame()
function matchResume()
{
gc.text1.text = "";
window.addEventListener('keydown', function (e) {
@@ -96,4 +112,4 @@ function resumeGame()
}
export {matchmaking, matchmakingComplete, startGame}
export {matchmaking, matchmakingComplete, matchAbort, matchStart}

View File

@@ -3,7 +3,7 @@ import * as c from "./constants.js"
import { gc, matchOptions } 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 { matchmaking, matchmakingComplete, matchAbort, matchStart } from "./pong.js";
import { RacketClient } from "./class/RectangleClient.js";
import { repeatInput } from "./handleInput.js";
import { soundRoblox } from "./audio.js"
@@ -67,7 +67,11 @@ function preMatchListener(this: WebSocket, event: MessageEvent)
case en.EventTypes.matchStart:
socket.removeEventListener("message", preMatchListener);
socket.addEventListener("message", inGameListener);
startGame();
matchStart();
break;
case en.EventTypes.matchAbort:
socket.removeEventListener("message", preMatchListener);
matchAbort();
break;
}
}

View File

@@ -129,6 +129,18 @@ function matchmaking(player: ClientPlayer)
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);
}
@@ -185,10 +197,10 @@ export function clientInputListener(this: WebSocket, data: string)
const pingInterval = setInterval( () => {
let deleteLog = "";
clientsMap.forEach( (client, key, map) => {
clientsMap.forEach( (client) => {
if (!client.isAlive) {
clientTerminate(client, key, map);
deleteLog += ` ${shortId(key)} |`;
clientTerminate(client);
deleteLog += ` ${shortId(client.id)} |`;
}
else {
client.isAlive = false;
@@ -206,12 +218,12 @@ const pingInterval = setInterval( () => {
}, 4200);
function clientTerminate(client: Client, key: string, map: Map<string, Client>)
function clientTerminate(client: Client)
{
client.socket.terminate();
if (client.gameSession)
{
client.gameSession.playersMap.delete(key);
client.gameSession.playersMap.delete(client.id);
if (client.gameSession.playersMap.size === 0)
{
clearInterval(client.gameSession.clientsUpdateInterval);
@@ -219,9 +231,9 @@ function clientTerminate(client: Client, key: string, map: Map<string, Client>)
gameSessionsMap.delete(client.gameSession.id);
}
}
map.delete(key);
if (matchmakingPlayersMap.has(key)) {
matchmakingPlayersMap.delete(key);
clientsMap.delete(client.id);
if (matchmakingPlayersMap.has(client.id)) {
matchmakingPlayersMap.delete(client.id);
}
}

View File

@@ -10,6 +10,7 @@ enum EventTypes {
// Generic
matchmakingInProgress,
matchStart,
matchAbort,
matchNewRound, // unused
matchPause, // unused
matchResume, // unused