GameSpectator wip

+ forfeit button
+ refactoring GameSession
This commit is contained in:
LuckyLaszlo
2022-12-30 07:16:39 +01:00
parent efb9af8df9
commit 941b0ea7ea
9 changed files with 236 additions and 98 deletions

View File

@@ -48,7 +48,10 @@ export class GameSession {
timeout += c.newRoundDelay*0.5;
});
}
resume(s: GameSession) {
resume(s?: GameSession)
{
if (!s) { s = this; }
s.playersMap.forEach( (client) => {
client.socket.on("message", clientInputListener);
});
@@ -59,7 +62,10 @@ export class GameSession {
s.playersUpdateInterval = setInterval(s._playersUpdate, c.playersUpdateIntervalMS, s);
s.spectatorsUpdateInterval = setInterval(s._spectatorsUpdate, c.spectatorsUpdateIntervalMS, s);
}
pause(s: GameSession) {
pause(s?: GameSession)
{
if (!s) { s = this; }
s.playersMap.forEach( (client) => {
client.socket.off("message", clientInputListener);
});
@@ -68,6 +74,19 @@ export class GameSession {
clearInterval(s.playersUpdateInterval);
clearInterval(s.spectatorsUpdateInterval);
}
destroy(s?: GameSession)
{
if (!s) { s = this; }
s.pause();
s.spectatorsMap.forEach((client) => {
clientTerminate(client);
});
s.playersMap.forEach((client) => {
clientTerminate(client);
});
}
instantInputDebug(client: ClientPlayer) {
this._handleInput(c.fixedDeltaTime, client);
}
@@ -200,26 +219,35 @@ export class GameSession {
ball.speed = ball.baseSpeed;
ball.ballInPlay = true;
}
private _checkDisconnexions() {
private _checkDisconnexions()
{
if (this.playersMap.size !== 2)
{
this.matchEnded = true;
if (this.playersMap.size != 0)
{
console.log("Forfeit Ending");
const gc = this.components;
const luckyWinner: ClientPlayer = this.playersMap.values().next().value;
if (luckyWinner.racket === gc.playerLeft) {
this._matchEnd(en.PlayerSide.left, true);
}
else {
this._matchEnd(en.PlayerSide.right, true);
}
if (this.playersMap.size != 0) {
this._forfeit();
}
else {
// WIP: envoyer un truc à Nest ? Genre "match draw"
this.destroy();
}
return true;
}
return false;
}
private _forfeit()
{
this.matchEnded = true;
console.log("Forfeit Ending");
const gc = this.components;
const luckyWinner: ClientPlayer = this.playersMap.values().next().value;
if (luckyWinner.racket === gc.playerLeft) {
this._matchEnd(en.PlayerSide.left, true);
}
else {
this._matchEnd(en.PlayerSide.right, true);
}
}
private async _matchEnd(winner: en.PlayerSide, forfeit_flag: boolean = false)
{
this.matchEnded = true;
@@ -246,15 +274,7 @@ export class GameSession {
})
});
const gameSession = this;
setTimeout(function kickRemainingClients() {
gameSession.spectatorsMap.forEach((client) => {
clientTerminate(client);
});
gameSession.playersMap.forEach((client) => {
clientTerminate(client);
});
}, 15000);
setTimeout(this.destroy, 15000, this);
// logs
if (winner === en.PlayerSide.left) {

View File

@@ -40,6 +40,14 @@ function connectionListener(socket: WebSocket, request: IncomingMessage)
console.log(`client ${shortId(client.id)} is alive`);
});
socket.on("error", function errorPrint(this: WebSocket, err: Error) {
console.log(`error socket ${shortId(this.id)}:`);
console.log(`${err.name}: ${err.message}`);
if (err.stack) {
console.log(`err.stack: ${err.stack}`);
}
});
socket.on("message", function log(data: string) {
try {
const event: ev.ClientEvent = JSON.parse(data);
@@ -96,10 +104,13 @@ async function clientAnnounceListener(this: WebSocket, data: string)
const player = clientsMap.get(this.id) as ClientPlayer;
player.matchOptions = announce.matchOptions;
player.token = announce.token;
announce.isInvitedPerson ? player.username = announce.playerTwoUsername : player.username = announce.username;
player.username = announce.username;
this.send(JSON.stringify( new ev.EventAssignId(this.id) )); // unused
this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchmakingInProgress) ));
if (announce.privateMatch) {
if (announce.isInvitedPerson) {
player.username = announce.playerTwoUsername;
}
privateMatchmaking(player);
}
else {
@@ -365,9 +376,7 @@ export function clientTerminate(client: Client)
client.gameSession.playersMap.delete(client.id);
if (client.gameSession.playersMap.size === 0)
{
clearInterval(client.gameSession.playersUpdateInterval);
clearInterval(client.gameSession.spectatorsUpdateInterval);
clearInterval(client.gameSession.gameLoopInterval);
client.gameSession.destroy();
gameSessionsMap.delete(client.gameSession.id);
}
}

View File

@@ -1,35 +0,0 @@
<script lang="ts">
import Header from '../pieces/Header.svelte';
import MatchListElem from "../pieces/MatchListElem.svelte";
let arr = [
{
id: "match-01",
playerOneUsername: "toto",
playerTwoUsername: "bruno",
},
{
id: "match-02",
playerOneUsername: "bertand",
playerTwoUsername: "cassandre",
},
{
id: "match-03",
playerOneUsername: "madeleine",
playerTwoUsername: "jack",
},
];
</script>
<!-- -->
<Header/>
<menu>
{#each arr as match}
<MatchListElem match={match}/>
{/each}
</menu>
<!-- -->
<style>
</style>

View File

@@ -111,8 +111,7 @@
}
}
// Pour Cherif: renommer en un truc du genre "initGameForInvitedPlayer" ?
const initGameForPrivateParty = async(invitation : any) =>
const initGameForInvitedPlayer = async(invitation : any) =>
{
idOfIntevalCheckTerminationOfTheMatch = setInterval(matchTermitation, 1000);
optionsAreNotSet = false
@@ -206,12 +205,18 @@
if (res.status === 200)
{
showInvitation()
initGameForPrivateParty(invitation)
initGameForInvitedPlayer(invitation)
}
//Au final c'est utile !
initGameForPrivateParty(invitation)
initGameForInvitedPlayer(invitation) // Luke: normal de initGameForInvitedPlayer() sur un "res.status" different de 200 ?
}
function leaveMatch() {
hiddenGame = true;
pong.destroy();
};
</script>
<Header />
@@ -232,9 +237,16 @@
</fieldset>
</div>
{/if}
<div id="canvas_container" hidden={hiddenGame}>
<canvas id={gameAreaId}/>
</div>
{#if !hiddenGame}
<div id="div_game">
<button id="pong_button" on:click={leaveMatch}>forfeit</button>
</div>
{/if}
{#if showWaitPage === true}
<div id="div_game" in:fly="{{ y: 10, duration: 1000 }}">
@@ -282,7 +294,7 @@
</select>
{/if}
<div>
<button id="pong_button" >PLAY</button>
<button id="pong_button">PLAY</button>
</div>
</fieldset>
</div>
@@ -343,12 +355,12 @@
/* max-height: 80vh; */
/* overflow: hidden; */
}
#users_name {
text-align: center;
font-family: "Bit5x3";
color: rgb(245, 245, 245);
font-size: x-large;
canvas {
/* background-color: #ff0000; */
background-color: #333333;
max-width: 75vw;
/* max-height: 100vh; */
width: 80%;
}
#div_game {
@@ -358,15 +370,6 @@
color: rgb(245, 245, 245);
font-size: x-large;
}
#error_notification {
text-align: center;
display: block;
font-family: "Bit5x3";
color: rgb(143, 19, 19);
font-size: x-large;
}
#div_game fieldset {
max-width: 50vw;
width: auto;
@@ -382,12 +385,19 @@
font-size: x-large;
padding: 10px;
}
canvas {
/* background-color: #ff0000; */
background-color: #333333;
max-width: 75vw;
/* max-height: 100vh; */
width: 80%;
#users_name { /* UNUSED */
text-align: center;
font-family: "Bit5x3";
color: rgb(245, 245, 245);
font-size: x-large;
}
#error_notification { /* UNUSED */
text-align: center;
display: block;
font-family: "Bit5x3";
color: rgb(143, 19, 19);
font-size: x-large;
}
</style>

View File

@@ -2,6 +2,7 @@
<script lang="ts">
import { onMount, onDestroy } from "svelte";
import Header from '../../pieces/Header.svelte';
import MatchListElem from "../../pieces/MatchListElem.svelte";
import { fade, fly } from 'svelte/transition';
import * as pongSpectator from "./client/pongSpectator";
@@ -15,6 +16,28 @@
//Game's stuff client side only
const gameAreaId = "game_area";
let sound = "off";
const dummyMatchList = [
{
gameSessionId: "id2445",
matchOptions: pongSpectator.MatchOptions.noOption,
playerOneUsername: "toto",
playerTwoUsername: "bruno",
},
{
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 = dummyMatchList;
//html boolean for pages
let hiddenGame = true;
@@ -24,21 +47,26 @@
.then( x => x.json() );
allUsers = await fetch('http://transcendance:8080/api/v2/user/all')
.then( x => x.json() );
// WIP: fetch for match list here
matchList = dummyMatchList;
})
onDestroy( async() => {
pongSpectator.destroy();
})
const initGameSpectator = async() => {
let gameSessionId = "ID_PLACEHOLDER"; // PLACEHOLDER
let matchOptions = 0; // PLACEHOLDER
let sound = "off"; // PLACEHOLDER
const initGameSpectator = async(gameSessionId: string, matchOptions: pongSpectator.MatchOptions) => {
pongSpectator.init(matchOptions, sound, gameAreaId, gameSessionId);
hiddenGame = false;
};
function leaveMatch() {
hiddenGame = true;
pongSpectator.destroy();
};
</script>
<!-- -->
<Header />
<!-- <div id="game_page"> Replacement for <body>.
@@ -49,7 +77,87 @@
<canvas id={gameAreaId}/>
</div>
{#if hiddenGame}
<div id="div_game">
<div id="game_options">
<fieldset>
<legend>options</legend>
<div>
<p>sound :</p>
<input type="radio" id="sound_on" name="sound_selector" bind:group={sound} value="on">
<label for="sound_on">on</label>
<input type="radio" id="sound_off" name="sound_selector" bind:group={sound} value="off">
<label for="sound_off">off</label>
</div>
</fieldset>
</div>
<menu id="match_list">
{#each matchList as match}
<MatchListElem match={match} on:click={(e) => initGameSpectator(match.gameSessionId, match.matchOptions)} />
{/each}
</menu>
</div>
{:else}
<div id="div_game">
<button id="pong_button" on:click={leaveMatch}>leave match</button>
</div>
{/if}
</div> <!-- div "game_page" -->
<!-- -->
<style>
@font-face {
font-family: "Bit5x3";
src:
url("/fonts/Bit5x3.woff2") format("woff2"),
local("Bit5x3"),
url("/fonts/Bit5x3.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
}
#game_page {
margin: 0;
background-color: #222425;
position: relative;
width: 100%;
height: 100%;
}
#canvas_container {
margin-top: 20px;
text-align: center;
}
canvas {
background-color: #333333;
max-width: 75vw;
width: 80%;
}
#div_game {
margin-top: 20px;
text-align: center;
font-family: "Bit5x3";
color: rgb(245, 245, 245);
font-size: x-large;
}
#div_game fieldset {
max-width: 50vw;
width: auto;
margin: 0 auto;
}
#div_game fieldset div {
padding: 10px;
}
#match_list {
font-family: 'Courier New', Courier, monospace;
font-size: large;
}
#pong_button {
font-family: "Bit5x3";
color: rgb(245, 245, 245);
background-color: #333333;
font-size: x-large;
padding: 10px;
}
</style>

View File

@@ -6,6 +6,7 @@ import { drawLoop } from "./draw.js";
import { initWebSocketSpectator } from "./ws.js";
import { initBase, destroyBase, computeMatchOptions } from "./init.js";
export { computeMatchOptions } from "./init.js";
export { MatchOptions } from "../shared_js/enums.js"
/* TODO: A way to delay the init of variables, but still use "const" not "let" ? */
import { pong, gc } from "./global.js"

View File

@@ -24,8 +24,8 @@
<img src="/img/potato_logo.png" alt="Potato Pong Logo" on:click={() => (push('/'))}>
<h1>Potato Pong</h1>
<nav>
<button on:click={() => (push('/game'))}>Game</button>
<button on:click={() => (push('/matchlist'))}>Match List</button>
<button on:click={() => (push('/game'))}>Play</button>
<button on:click={() => (push('/spectator'))}>Spectate</button>
<button on:click={() => (push('/ranking'))}>Ranking</button>
{#if $location !== '/profile'}
<button on:click={() => (push('/profile'))}>My Profile</button>

View File

@@ -1,15 +1,40 @@
<script lang="ts">
import { onMount, onDestroy } from "svelte";
import { MatchOptions } from "../pages/game/client/pongSpectator";
export let match: {
id: string,
gameSessionId: string,
matchOptions: MatchOptions,
playerOneUsername: string,
playerTwoUsername: string
};
let matchOptionsString = "";
onMount( async() => {
if (match.matchOptions === MatchOptions.noOption) {
matchOptionsString = "standard";
}
else {
if (match.matchOptions & MatchOptions.multiBalls) {
matchOptionsString += "multi balls";
}
if (match.matchOptions & MatchOptions.movingWalls) {
if (matchOptionsString) { matchOptionsString += ", "; }
matchOptionsString += "moving walls";
}
}
})
</script>
<!-- -->
<li>
{match.id} "{match.playerOneUsername}" VS "{match.playerTwoUsername}"
<button on:click>
"{match.playerOneUsername}" VS "{match.playerTwoUsername}"
<br/> [{matchOptionsString}]
</button>
</li>

View File

@@ -7,7 +7,7 @@ import { wrap } from 'svelte-spa-router/wrap'
import TestPage from '../pages/TmpTestPage.svelte';
import Game from '../pages/game/Game.svelte';
import Ranking from '../pages/game/Ranking.svelte';
import SpectatorMatchList from '../pages/SpectatorMatchList.svelte';
import GameSpectator from '../pages/game/GameSpectator.svelte';
@@ -15,7 +15,7 @@ export const primaryRoutes = {
'/': SplashPage,
'/2fa': TwoFactorAuthentication,
'/game': Game,
'/matchlist': SpectatorMatchList,
'/spectator': GameSpectator,
'/ranking' : Ranking,
'/profile': wrap({
component: ProfilePage,