merge master
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
// routing
|
||||
// may not need {link} here
|
||||
import Router, { link, replace } from "svelte-spa-router";
|
||||
import { primaryRoutes } from "./routes/primaryRoutes.js";
|
||||
import { primaryRoutes } from "./routes/primaryRoutes.js";
|
||||
// import primaryRoutes from "./routes/primaryRoutes.svelte";
|
||||
|
||||
const conditionsFailed = (event) => {
|
||||
|
||||
9
srcs/requirements/svelte/api_front/src/main.js
Normal file
9
srcs/requirements/svelte/api_front/src/main.js
Normal 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
|
||||
1
srcs/requirements/svelte/api_front/src/main.js.map
Normal file
1
srcs/requirements/svelte/api_front/src/main.js.map
Normal 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"}
|
||||
@@ -1,106 +0,0 @@
|
||||
<script lang="ts">
|
||||
|
||||
// import { initDom } from "../game/client/pong.js";
|
||||
import {onMount} from 'svelte';
|
||||
|
||||
onMount(() => {
|
||||
// initDom();
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<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>
|
||||
</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">
|
||||
<!-- <p> =) </p> -->
|
||||
</div>
|
||||
|
||||
<!-- <script src="http://localhost:8080/js/pong.js" type="module" defer></script> -->
|
||||
</body>
|
||||
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Bit5x3";
|
||||
src: url("/fonts/Bit5x3.woff2") format("woff2"),
|
||||
url("/fonts/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%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,35 @@
|
||||
<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>
|
||||
@@ -1,88 +1,381 @@
|
||||
|
||||
<script>
|
||||
import "public/game/pong.js"
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import Header from '../../pieces/Header.svelte';
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
|
||||
import * as pong from "./client/pong";
|
||||
|
||||
// Pour Chérif: variables indiquant l'état du match
|
||||
import { matchEnded, matchAbort } from "./client/ws";
|
||||
|
||||
//user's stuff
|
||||
let user;
|
||||
let allUsers;
|
||||
|
||||
//Game's stuff
|
||||
let optionsAreNotSet = true;
|
||||
const options = new pong.InitOptions();
|
||||
|
||||
//Game's stuff client side only
|
||||
const gameAreaId = "game_area";
|
||||
|
||||
//html boolean for pages
|
||||
let showWaitPage = false;
|
||||
let showInvitations = false;
|
||||
let showGameOption = true;
|
||||
let showError = false;
|
||||
let hiddenGame = true;
|
||||
let showMatchEnded = false;
|
||||
|
||||
let isThereAnyInvitation = false;
|
||||
let invitations = [];
|
||||
|
||||
let waitingMessage = "Please wait..."
|
||||
let errorMessageWhenAttemptingToGetATicket = "";
|
||||
let idOfIntevalCheckTerminationOfTheMatch;
|
||||
|
||||
onMount( async() => {
|
||||
user = await fetch('http://transcendance:8080/api/v2/user')
|
||||
.then( x => x.json() );
|
||||
allUsers = await fetch('http://transcendance:8080/api/v2/user/all')
|
||||
.then( x => x.json() );
|
||||
options.playerOneUsername = user.username;
|
||||
})
|
||||
|
||||
onDestroy( async() => {
|
||||
options.playerOneUsername = user.username;
|
||||
showError = false;
|
||||
showMatchEnded = false;
|
||||
optionsAreNotSet = true
|
||||
options.playerTwoUsername = "";
|
||||
options.isSomeoneIsInvited = false;
|
||||
options.isInvitedPerson = false;
|
||||
options.moving_walls = false;
|
||||
options.multi_balls = false;
|
||||
errorMessageWhenAttemptingToGetATicket = "";
|
||||
hiddenGame = true;
|
||||
isThereAnyInvitation = false;
|
||||
invitations = [];
|
||||
pong.destroy();
|
||||
clearInterval(idOfIntevalCheckTerminationOfTheMatch);
|
||||
})
|
||||
|
||||
const initGame = async() =>
|
||||
{
|
||||
optionsAreNotSet = false;
|
||||
showWaitPage = true;
|
||||
idOfIntevalCheckTerminationOfTheMatch = setInterval(matchTermitation, 1000);
|
||||
const matchOptions = pong.computeMatchOptions(options);
|
||||
|
||||
const responseWhenGrantToken = fetch("http://transcendance:8080/api/v2/game/ticket", {
|
||||
method : "POST",
|
||||
headers : {'Content-Type': 'application/json'},
|
||||
body : JSON.stringify({
|
||||
playerOneUsername : options.playerOneUsername,
|
||||
playerTwoUsername : options.playerTwoUsername,
|
||||
gameOptions : matchOptions,
|
||||
isGameIsWithInvitation : options.isSomeoneIsInvited
|
||||
})
|
||||
})
|
||||
const responseFromServer = await responseWhenGrantToken;
|
||||
const responseInjson = await responseFromServer.json();
|
||||
const token : string = responseInjson.token;
|
||||
showWaitPage = false;
|
||||
console.log("status : " + responseFromServer.status)
|
||||
if (responseFromServer.status != 200)
|
||||
{
|
||||
console.log(responseInjson)
|
||||
console.log("On refuse le ticket");
|
||||
clearInterval(idOfIntevalCheckTerminationOfTheMatch);
|
||||
errorMessageWhenAttemptingToGetATicket = responseInjson.message;
|
||||
showError = true;
|
||||
options.playerTwoUsername = "";
|
||||
options.playerOneUsername = user.username;
|
||||
options.isSomeoneIsInvited = false;
|
||||
options.isInvitedPerson = false;
|
||||
options.moving_walls = false;
|
||||
options.multi_balls = false;
|
||||
setTimeout(() => {
|
||||
showError = false;
|
||||
showWaitPage = false
|
||||
optionsAreNotSet = true
|
||||
|
||||
}, 5000);
|
||||
}
|
||||
else if (token)
|
||||
{
|
||||
options.isInvitedPerson = false
|
||||
pong.init(options, gameAreaId, token);
|
||||
hiddenGame = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Pour Cherif: renommer en un truc du genre "initGameForInvitedPlayer" ?
|
||||
const initGameForPrivateParty = async(invitation : any) =>
|
||||
{
|
||||
idOfIntevalCheckTerminationOfTheMatch = setInterval(matchTermitation, 1000);
|
||||
optionsAreNotSet = false
|
||||
showWaitPage = true
|
||||
console.log("invitation : ")
|
||||
console.log(invitation)
|
||||
if (invitation.token)
|
||||
{
|
||||
options.playerOneUsername = invitation.playerOneUsername;
|
||||
options.playerTwoUsername = invitation.playerTwoUsername;
|
||||
options.isSomeoneIsInvited = true;
|
||||
options.isInvitedPerson = true
|
||||
pong.init(options, gameAreaId, invitation.token);
|
||||
showWaitPage = false
|
||||
hiddenGame = false;
|
||||
}
|
||||
}
|
||||
|
||||
const matchTermitation = () => {
|
||||
console.log("Ping matchTermitation")
|
||||
if (matchAbort || matchEnded)
|
||||
{
|
||||
clearInterval(idOfIntevalCheckTerminationOfTheMatch);
|
||||
console.log("matchTermitation was called")
|
||||
showWaitPage = false
|
||||
matchAbort ?
|
||||
errorMessageWhenAttemptingToGetATicket = "The match has been aborted"
|
||||
: errorMessageWhenAttemptingToGetATicket = "The match is finished !"
|
||||
matchAbort ? showError = true : showMatchEnded = true;
|
||||
setTimeout(() => {
|
||||
hiddenGame = true;
|
||||
showError = false;
|
||||
showMatchEnded = false;
|
||||
optionsAreNotSet = true
|
||||
options.playerTwoUsername = "";
|
||||
options.playerOneUsername = user.username;
|
||||
options.isSomeoneIsInvited = false;
|
||||
options.isInvitedPerson = false;
|
||||
options.moving_walls = false;
|
||||
options.multi_balls = false;
|
||||
errorMessageWhenAttemptingToGetATicket = "";
|
||||
options.playerTwoUsername = "";
|
||||
hiddenGame = true;
|
||||
isThereAnyInvitation = false;
|
||||
invitations = [];
|
||||
pong.destroy();
|
||||
console.log("matchTermitation : setTimeout")
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const showOptions = () => {
|
||||
showGameOption = true
|
||||
showInvitations = false
|
||||
}
|
||||
|
||||
const showInvitation = async() => {
|
||||
showGameOption = false;
|
||||
showInvitations = true;
|
||||
invitations = await fetch("http://transcendance:8080/api/v2/game/invitations")
|
||||
.then(x => x.json())
|
||||
invitations.length !== 0 ? isThereAnyInvitation = true : isThereAnyInvitation = false
|
||||
}
|
||||
|
||||
const rejectInvitation = async(invitation) => {
|
||||
await fetch("http://transcendance:8080/api/v2/game/decline",{
|
||||
method: "POST",
|
||||
headers: { 'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
token : invitation.token
|
||||
})
|
||||
})
|
||||
.then(x => x.json())
|
||||
.catch(error => console.log(error))
|
||||
showInvitation()
|
||||
}
|
||||
|
||||
const acceptInvitation = async(invitation : any) => {
|
||||
const res = await fetch("http://transcendance:8080/api/v2/game/accept",{
|
||||
method: "POST",
|
||||
headers: { 'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
token : invitation.token
|
||||
})
|
||||
})
|
||||
.then(x => x.json())
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
if (res.status === 200)
|
||||
{
|
||||
showInvitation()
|
||||
initGameForPrivateParty(invitation)
|
||||
}
|
||||
//Au final c'est utile !
|
||||
|
||||
initGameForPrivateParty(invitation)
|
||||
}
|
||||
</script>
|
||||
|
||||
<Header />
|
||||
<!-- <div id="game_page"> Replacement for <body>.
|
||||
Might become useless after CSS rework. -->
|
||||
<div id="game_page">
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="preload_font">.</div>
|
||||
|
||||
<div id="div_game_options">
|
||||
{#if showMatchEnded === true}
|
||||
<div id="div_game" in:fly="{{ y: 10, duration: 1000 }}">
|
||||
<p>{errorMessageWhenAttemptingToGetATicket}</p>
|
||||
</div>
|
||||
{/if}
|
||||
{#if showError === true}
|
||||
<div id="div_game" in:fly="{{ y: 10, duration: 1000 }}">
|
||||
<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>
|
||||
<legend>Error</legend>
|
||||
<p>{errorMessageWhenAttemptingToGetATicket}</p>
|
||||
</fieldset>
|
||||
</div>
|
||||
{/if}
|
||||
<div id="canvas_container" hidden={hiddenGame}>
|
||||
<canvas id={gameAreaId}/>
|
||||
</div>
|
||||
|
||||
<div id="canvas_container">
|
||||
<!-- <p> =) </p> -->
|
||||
</div>
|
||||
{#if showWaitPage === true}
|
||||
<div id="div_game" in:fly="{{ y: 10, duration: 1000 }}">
|
||||
<fieldset>
|
||||
<legend>Connecting to the game...</legend>
|
||||
<p>{waitingMessage}</p>
|
||||
</fieldset>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<script src="public/game/pong.js" type="module" defer></script>
|
||||
</body>
|
||||
|
||||
|
||||
{#if optionsAreNotSet}
|
||||
{#if showGameOption === true}
|
||||
<div id="game_option">
|
||||
<form on:submit|preventDefault={() => initGame()}>
|
||||
<div id="div_game">
|
||||
<button id="pong_button" on:click={showInvitation}>Show invitations</button>
|
||||
<fieldset>
|
||||
<legend>game options</legend>
|
||||
<div>
|
||||
<input type="checkbox" id="multi_balls" name="multi_balls" bind:checked={options.multi_balls}>
|
||||
<label for="multi_balls">Multiples balls</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="moving_walls" name="moving_walls" bind:checked={options.moving_walls}>
|
||||
<label for="moving_walls">Moving walls</label>
|
||||
</div>
|
||||
<div>
|
||||
<p>sound :</p>
|
||||
<input type="radio" id="sound_on" name="sound_selector" bind:group={options.sound} value="on">
|
||||
<label for="sound_on">on</label>
|
||||
<input type="radio" id="sound_off" name="sound_selector" bind:group={options.sound} value="off">
|
||||
<label for="sound_off">off</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="isSomeoneIsInvited" bind:checked={options.isSomeoneIsInvited}>
|
||||
<label for="moving_walls">Invite a friend</label>
|
||||
</div>
|
||||
{#if options.isSomeoneIsInvited === true}
|
||||
<select bind:value={options.playerTwoUsername}>
|
||||
{#each allUsers as user }
|
||||
<option value={user.username}>{user.username}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
<div>
|
||||
<button id="pong_button" >PLAY</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showInvitations}
|
||||
<div id="invitations_options" in:fly="{{ y: 10, duration: 1000 }}">
|
||||
<div id="div_game">
|
||||
<button id="pong_button" on:click={showOptions}>Play a Game</button>
|
||||
<fieldset>
|
||||
<legend>Current invitation(s)</legend>
|
||||
{#if isThereAnyInvitation}
|
||||
{#each invitations as invitation }
|
||||
<div>
|
||||
{invitation.playerOneUsername} has invited you to play a pong !
|
||||
<button id="pong_button" on:click={() => acceptInvitation(invitation)}>V</button>
|
||||
<button id="pong_button" on:click={() => rejectInvitation(invitation)}>X</button>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if isThereAnyInvitation === false}
|
||||
<p>Currently, no one asked to play with you.</p>
|
||||
<button id="pong_button" on:click={showInvitation}>Reload</button>
|
||||
{/if}
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/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");
|
||||
src:
|
||||
url("/fonts/Bit5x3.woff2") format("woff2"),
|
||||
local("Bit5x3"),
|
||||
url("/fonts/Bit5x3.woff") format("woff");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
#preload_font {
|
||||
font-family: "Bit5x3";
|
||||
opacity:0;
|
||||
height:0;
|
||||
width:0;
|
||||
display:inline-block;
|
||||
}
|
||||
body {
|
||||
#game_page {
|
||||
margin: 0;
|
||||
background-color: #222425;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#canvas_container {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
/* border: dashed rgb(245, 245, 245) 5px; */
|
||||
/* max-height: 80vh; */
|
||||
/* overflow: hidden; */
|
||||
}
|
||||
#div_game_options {
|
||||
|
||||
#users_name {
|
||||
text-align: center;
|
||||
font-family: "Bit5x3";
|
||||
color: rgb(245, 245, 245);
|
||||
font-size: x-large;
|
||||
}
|
||||
#div_game_options fieldset {
|
||||
|
||||
#div_game {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
font-family: "Bit5x3";
|
||||
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;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#div_game_options fieldset div {
|
||||
#div_game fieldset div {
|
||||
padding: 10px;
|
||||
}
|
||||
#play_pong_button {
|
||||
#pong_button {
|
||||
font-family: "Bit5x3";
|
||||
color: rgb(245, 245, 245);
|
||||
background-color: #333333;
|
||||
@@ -90,6 +383,7 @@ body {
|
||||
padding: 10px;
|
||||
}
|
||||
canvas {
|
||||
/* background-color: #ff0000; */
|
||||
background-color: #333333;
|
||||
max-width: 75vw;
|
||||
/* max-height: 100vh; */
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import Header from '../../pieces/Header.svelte';
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
|
||||
import * as pongSpectator from "./client/pongSpectator";
|
||||
|
||||
// Pour Chérif: variables indiquant l'état du match
|
||||
import { matchEnded, matchAbort } from "./client/ws";
|
||||
|
||||
//user's stuff
|
||||
let user;
|
||||
let allUsers;
|
||||
|
||||
//Game's stuff client side only
|
||||
const gameAreaId = "game_area";
|
||||
|
||||
//html boolean for pages
|
||||
let hiddenGame = true;
|
||||
|
||||
onMount( async() => {
|
||||
user = await fetch('http://transcendance:8080/api/v2/user')
|
||||
.then( x => x.json() );
|
||||
allUsers = await fetch('http://transcendance:8080/api/v2/user/all')
|
||||
.then( x => x.json() );
|
||||
})
|
||||
|
||||
onDestroy( async() => {
|
||||
pongSpectator.destroy();
|
||||
})
|
||||
|
||||
const initGameSpectator = async() => {
|
||||
let gameSessionId = "ID_PLACEHOLDER"; // PLACEHOLDER
|
||||
let matchOptions = 0; // PLACEHOLDER
|
||||
let sound = "off"; // PLACEHOLDER
|
||||
pongSpectator.init(matchOptions, sound, gameAreaId, gameSessionId);
|
||||
hiddenGame = false;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<Header />
|
||||
<!-- <div id="game_page"> Replacement for <body>.
|
||||
Might become useless after CSS rework. -->
|
||||
<div id="game_page">
|
||||
|
||||
<div id="canvas_container" hidden={hiddenGame}>
|
||||
<canvas id={gameAreaId}/>
|
||||
</div>
|
||||
|
||||
</div> <!-- div "game_page" -->
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,73 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import Header from "../../pieces/Header.svelte";
|
||||
|
||||
//user's stuff
|
||||
let currentUser;
|
||||
let allUsers = [];
|
||||
let idInterval;
|
||||
onMount( async() => {
|
||||
currentUser = await fetch('http://transcendance:8080/api/v2/user')
|
||||
.then( x => x.json() );
|
||||
allUsers = await fetch('http://transcendance:8080/api/v2/game/ranking')
|
||||
.then( x => x.json() );
|
||||
idInterval = setInterval(fetchScores, 10000);
|
||||
})
|
||||
|
||||
onDestroy( async() => {
|
||||
clearInterval(idInterval);
|
||||
})
|
||||
|
||||
function fetchScores() {
|
||||
fetch('http://transcendance:8080/api/v2/game/ranking')
|
||||
.then( x => x.json() )
|
||||
.then( x => allUsers = x );
|
||||
}
|
||||
</script>
|
||||
<Header />
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>Ranking</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Username</th>
|
||||
<th scope="col">Win</th>
|
||||
<th scope="col">Lose</th>
|
||||
<th scope="col">Draw</th>
|
||||
<th scope="col">Games Played</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each allUsers as user, i}
|
||||
<tr>
|
||||
<th scope="row">{i + 1}</th>
|
||||
{#if user.username === currentUser.username}
|
||||
<td><b>You ({user.username})</b></td>
|
||||
{:else}
|
||||
<td>{user.username}</td>
|
||||
{/if}
|
||||
<td>{user.stats.winGame}</td>
|
||||
<td>{user.stats.loseGame}</td>
|
||||
<td>{user.stats.drawGame}</td>
|
||||
<td>{user.stats.totalGame}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
import * as c from "./constants.js"
|
||||
|
||||
export const soundPongArr: HTMLAudioElement[] = [];
|
||||
export const soundRoblox = new Audio("http://transcendance:8080/sound/roblox-oof.ogg");
|
||||
|
||||
export function initAudio(sound: string)
|
||||
{
|
||||
let muteFlag = true;
|
||||
if (sound === "on") {
|
||||
muteFlag = false;
|
||||
}
|
||||
|
||||
for (let i = 0; i <= 32; i++) {
|
||||
soundPongArr.push(new Audio("http://transcendance:8080/sound/pong/"+i+".ogg"));
|
||||
soundPongArr[i].volume = c.soundPongVolume;
|
||||
soundPongArr[i].muted = muteFlag;
|
||||
}
|
||||
soundRoblox.volume = c.soundRobloxVolume;
|
||||
soundRoblox.muted = muteFlag;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
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(canvas_id: string) {
|
||||
const canvas = document.getElementById("game_area");
|
||||
if (canvas && canvas instanceof HTMLCanvasElement) {
|
||||
this.canvas = canvas;
|
||||
}
|
||||
else {
|
||||
console.log("GameArea init error, invalid canvas_id");
|
||||
return;
|
||||
}
|
||||
this.ctx = this.canvas.getContext("2d") as CanvasRenderingContext2D;
|
||||
this.canvas.width = c.CanvasWidth;
|
||||
this.canvas.height = c.CanvasWidth / c.CanvasRatio;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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 type { 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 = <MovingRectangle>this.wallTop;
|
||||
const baseWB = <MovingRectangle>this.wallBottom;
|
||||
|
||||
this.wallTop = new MovingRectangleClient(baseWT.pos, baseWT.width, baseWT.height, baseWT.baseSpeed,
|
||||
ctx, "grey");
|
||||
(<MovingRectangleClient>this.wallTop).dir.assign(baseWT.dir.x, baseWT.dir.y);
|
||||
|
||||
this.wallBottom = new MovingRectangleClient(baseWB.pos, baseWB.width, baseWB.height, baseWB.baseSpeed,
|
||||
ctx, "grey");
|
||||
(<MovingRectangleClient>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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
export class InitOptions {
|
||||
sound = "off";
|
||||
multi_balls = false;
|
||||
moving_walls = false;
|
||||
isSomeoneIsInvited = false;
|
||||
isInvitedPerson = false;
|
||||
playerOneUsername = "";
|
||||
playerTwoUsername = "";
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
import type * as en from "../../shared_js/enums.js"
|
||||
import type * 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
|
||||
import { Vector, VectorInteger } from "../../shared_js/class/Vector.js";
|
||||
import type { GraphicComponent } 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);
|
||||
let i = Math.floor(random(0, soundPongArr.length));
|
||||
soundPongArr[ i ].play();
|
||||
console.log(`sound_i=${i}`); // debug log
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
import { Vector, VectorInteger } from "../../shared_js/class/Vector.js";
|
||||
import type { 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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 type { RacketClient } from "./class/RectangleClient.js";
|
||||
import type { 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
|
||||
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";
|
||||
import { matchEnded } from "./ws.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);
|
||||
}
|
||||
|
||||
if (!matchEnded) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
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 { socket } from "./ws.js";
|
||||
import { initAudio } from "./audio.js";
|
||||
import type { InitOptions } from "./class/InitOptions.js";
|
||||
|
||||
import { pong } from "./global.js"
|
||||
import { initPong, initGc, initMatchOptions } from "./global.js"
|
||||
|
||||
export function computeMatchOptions(options: InitOptions)
|
||||
{
|
||||
let matchOptions = en.MatchOptions.noOption;
|
||||
|
||||
if (options.multi_balls === true) {
|
||||
matchOptions |= en.MatchOptions.multiBalls
|
||||
}
|
||||
if (options.moving_walls === true) {
|
||||
matchOptions |= en.MatchOptions.movingWalls
|
||||
}
|
||||
|
||||
return matchOptions;
|
||||
}
|
||||
|
||||
export function initBase(matchOptions: en.MatchOptions, sound: string, gameAreaId: string)
|
||||
{
|
||||
initMatchOptions(matchOptions);
|
||||
initAudio(sound);
|
||||
initPong(new GameArea(gameAreaId));
|
||||
initGc(new GameComponentsClient(matchOptions, pong.ctx));
|
||||
}
|
||||
|
||||
export function destroyBase()
|
||||
{
|
||||
if (pong)
|
||||
{
|
||||
clearInterval(pong.handleInputInterval);
|
||||
clearInterval(pong.gameLoopInterval);
|
||||
clearInterval(pong.drawLoopInterval);
|
||||
initPong(null);
|
||||
}
|
||||
if (socket && socket.OPEN) {
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
|
||||
import * as c from "./constants.js"
|
||||
import { gc, pong } from "./global.js"
|
||||
import * as en from "../shared_js/enums.js"
|
||||
|
||||
/*
|
||||
before game
|
||||
*/
|
||||
export function error(message: string)
|
||||
{
|
||||
console.log("msg.error()");
|
||||
pong.clear();
|
||||
const text = "error: " + message;
|
||||
console.log(text);
|
||||
gc.text2.clear();
|
||||
gc.text2.pos.assign(c.w*0.2, c.h*0.5);
|
||||
gc.text2.text = text;
|
||||
gc.text2.update();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
import * as c from "./constants.js"
|
||||
import { handleInput } from "./handleInput.js";
|
||||
import { gameLoop } from "./gameLoop.js"
|
||||
import { drawLoop } from "./draw.js";
|
||||
import { countdown } from "./utils.js";
|
||||
import { initWebSocket } from "./ws.js";
|
||||
import type { InitOptions } from "./class/InitOptions.js";
|
||||
export { InitOptions } from "./class/InitOptions.js";
|
||||
import { initBase, destroyBase, computeMatchOptions } from "./init.js";
|
||||
export { computeMatchOptions } from "./init.js";
|
||||
|
||||
/* TODO: A way to delay the init of variables, but still use "const" not "let" ? */
|
||||
import { pong, gc } from "./global.js"
|
||||
import { initStartFunction } from "./global.js"
|
||||
|
||||
|
||||
export function init(options: InitOptions, gameAreaId: string, token: string)
|
||||
{
|
||||
const matchOptions = computeMatchOptions(options);
|
||||
initBase(matchOptions, options.sound, gameAreaId);
|
||||
|
||||
initStartFunction(start);
|
||||
if (options.isSomeoneIsInvited) {
|
||||
initWebSocket(matchOptions, token, options.playerOneUsername, true, options.playerTwoUsername, options.isInvitedPerson);
|
||||
}
|
||||
else {
|
||||
initWebSocket(matchOptions, token, options.playerOneUsername);
|
||||
}
|
||||
}
|
||||
|
||||
export function destroy()
|
||||
{
|
||||
destroyBase();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
import * as c from "./constants.js"
|
||||
import type * as en from "../shared_js/enums.js"
|
||||
import { gameLoopSpectator } from "./gameLoop.js"
|
||||
import { drawLoop } from "./draw.js";
|
||||
import { initWebSocketSpectator } from "./ws.js";
|
||||
import { initBase, destroyBase, computeMatchOptions } from "./init.js";
|
||||
export { computeMatchOptions } from "./init.js";
|
||||
|
||||
/* TODO: A way to delay the init of variables, but still use "const" not "let" ? */
|
||||
import { pong, gc } from "./global.js"
|
||||
import { initStartFunction } from "./global.js"
|
||||
|
||||
|
||||
export function init(matchOptions: en.MatchOptions, sound: string, gameAreaId: string, gameSessionId: string)
|
||||
{
|
||||
initBase(matchOptions, sound, gameAreaId);
|
||||
|
||||
initStartFunction(start);
|
||||
initWebSocketSpectator(gameSessionId);
|
||||
}
|
||||
|
||||
export function destroy()
|
||||
{
|
||||
destroyBase();
|
||||
}
|
||||
|
||||
function start()
|
||||
{
|
||||
resume();
|
||||
}
|
||||
|
||||
function resume()
|
||||
{
|
||||
pong.gameLoopInterval = window.setInterval(gameLoopSpectator, c.gameLoopIntervalMS);
|
||||
pong.drawLoopInterval = window.setInterval(drawLoop, c.drawLoopIntervalMS);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
329
srcs/requirements/svelte/api_front/src/pages/game/client/ws.ts
Normal file
329
srcs/requirements/svelte/api_front/src/pages/game/client/ws.ts
Normal file
@@ -0,0 +1,329 @@
|
||||
|
||||
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 type { 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";
|
||||
|
||||
export let matchEnded = false;
|
||||
export let matchAbort = false;
|
||||
|
||||
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 wsUrl = "ws://transcendance:8080/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, token: string, username: string, privateMatch = false, playerTwoUsername?: string, isInvitedPerson? : boolean)
|
||||
{
|
||||
socket = new WebSocket(wsUrl, "json");
|
||||
console.log("Infos from ws.ts : options => " + options + " token => " + token + " username => " + username + " priavte match => " + privateMatch
|
||||
+ " player two => " + playerTwoUsername)
|
||||
socket.addEventListener("open", (event) => {
|
||||
if (privateMatch) {
|
||||
socket.send(JSON.stringify( new ev.ClientAnnouncePlayer(options, token, username, privateMatch, playerTwoUsername, isInvitedPerson) ));
|
||||
}
|
||||
else {
|
||||
socket.send(JSON.stringify( new ev.ClientAnnouncePlayer(options, token, username) ));
|
||||
}
|
||||
});
|
||||
// socket.addEventListener("message", logListener); // for testing purpose
|
||||
socket.addEventListener("message", errorListener);
|
||||
socket.addEventListener("message", preMatchListener);
|
||||
}
|
||||
|
||||
function logListener(this: WebSocket, event: MessageEvent) {
|
||||
console.log("%i: " + event.data, Date.now());
|
||||
}
|
||||
|
||||
function errorListener(this: WebSocket, event: MessageEvent) {
|
||||
console.log("errorListener");
|
||||
const data: ev.ServerEvent = JSON.parse(event.data);
|
||||
if (data.type === en.EventTypes.error) {
|
||||
console.log("actual Error");
|
||||
msg.error((data as ev.EventError).message);
|
||||
}
|
||||
}
|
||||
|
||||
function preMatchListener(this: WebSocket, event: MessageEvent)
|
||||
{
|
||||
const data: ev.ServerEvent = JSON.parse(event.data);
|
||||
switch (data.type) {
|
||||
case en.EventTypes.assignId:
|
||||
clientInfo.id = (<ev.EventAssignId>data).id;
|
||||
break;
|
||||
case en.EventTypes.matchmakingInProgress:
|
||||
msg.matchmaking();
|
||||
break;
|
||||
case en.EventTypes.matchmakingComplete:
|
||||
clientInfo.side = (<ev.EventMatchmakingComplete>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:
|
||||
matchAbort = true;
|
||||
socket.removeEventListener("message", preMatchListener);
|
||||
msg.matchAbort();
|
||||
setTimeout(() => {
|
||||
matchAbort = false;
|
||||
}, 1000);
|
||||
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)
|
||||
{
|
||||
matchEnded = true;
|
||||
socket.close();
|
||||
if (data.winner === clientInfo.side) {
|
||||
msg.win();
|
||||
if (data.forfeit) {
|
||||
msg.forfeit(clientInfo.side);
|
||||
}
|
||||
}
|
||||
else {
|
||||
msg.lose();
|
||||
}
|
||||
setTimeout(() => {
|
||||
matchEnded = false;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/* 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");
|
||||
matchEnded = true;
|
||||
socket.close();
|
||||
// WIP
|
||||
/* msg.win();
|
||||
if (data.forfeit) {
|
||||
msg.forfeit(clientInfo.side);
|
||||
} */
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export class EventMatchAbort extends ServerEvent {
|
||||
constructor() {
|
||||
super(en.EventTypes.matchAbort);
|
||||
}
|
||||
}
|
||||
|
||||
export class EventError extends ServerEvent {
|
||||
message: string;
|
||||
constructor(message: string) {
|
||||
super(en.EventTypes.error);
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 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; // unused
|
||||
matchOptions: en.MatchOptions;
|
||||
token: string;
|
||||
username: string;
|
||||
privateMatch: boolean;
|
||||
playerTwoUsername?: string;
|
||||
isInvitedPerson? : boolean;
|
||||
constructor(matchOptions: en.MatchOptions, token: string, username: string, privateMatch: boolean = false, playerTwoUsername?: string, isInvitedPerson? : boolean) {
|
||||
super(en.ClientRole.player);
|
||||
this.matchOptions = matchOptions;
|
||||
this.token = token;
|
||||
this.username = username;
|
||||
this.privateMatch = privateMatch;
|
||||
if (isInvitedPerson) {
|
||||
this.isInvitedPerson = isInvitedPerson;
|
||||
}
|
||||
if (playerTwoUsername) {
|
||||
this.playerTwoUsername = playerTwoUsername;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
(<MovingRectangle>this.wallTop).dir.y = -1;
|
||||
pos.assign(0, c.h-c.wallSize);
|
||||
this.wallBottom = new MovingRectangle(pos, c.w, c.wallSize, c.movingWallSpeed);
|
||||
(<MovingRectangle>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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
|
||||
import { Vector, VectorInteger } from "./Vector.js";
|
||||
import type { 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}`);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
// }
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
import type { 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;
|
||||
}
|
||||
@@ -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.60); // pixel per second
|
||||
export const ballSpeed = Math.floor(w*0.55); // 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()
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
export enum EventTypes {
|
||||
// Class Implemented
|
||||
gameUpdate = 1,
|
||||
scoreUpdate,
|
||||
matchEnd,
|
||||
assignId,
|
||||
matchmakingComplete,
|
||||
error,
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
import type { 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;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
import * as c from "./constants.js";
|
||||
import type { MovingRectangle } from "../shared_js/class/Rectangle.js";
|
||||
import type { GameComponents } from "./class/GameComponents.js";
|
||||
|
||||
export function wallsMovements(delta: number, gc: GameComponents)
|
||||
{
|
||||
const wallTop = <MovingRectangle>gc.wallTop;
|
||||
const wallBottom = <MovingRectangle>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]);
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
.then( (x) => x.json() );
|
||||
})
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<Chat color="bisque"/>
|
||||
@@ -117,7 +117,7 @@
|
||||
/* Glittery Star Stuff */
|
||||
|
||||
|
||||
:root {
|
||||
:root {
|
||||
--purple: rgb(123, 31, 162);
|
||||
--violet: rgb(103, 58, 183);
|
||||
--pink: rgb(244, 143, 177);
|
||||
@@ -128,7 +128,7 @@
|
||||
from {
|
||||
background-position: 0% center;
|
||||
}
|
||||
|
||||
|
||||
to {
|
||||
background-position: -200% center;
|
||||
}
|
||||
@@ -138,7 +138,7 @@
|
||||
from, to {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
|
||||
50% {
|
||||
transform: scale(1);
|
||||
}
|
||||
@@ -148,7 +148,7 @@
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
|
||||
to {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
@@ -193,7 +193,7 @@
|
||||
var(--purple)
|
||||
);
|
||||
background-size: 200%;
|
||||
|
||||
|
||||
/* Keep these for Safari and chrome */
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
<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('/ranking'))}>Ranking</button>
|
||||
{#if $location !== '/profile'}
|
||||
<button on:click={() => (push('/profile'))}>My Profile</button>
|
||||
{:else if $location === '/profile'}
|
||||
@@ -52,11 +55,11 @@
|
||||
src:url('/fonts/Bondi.ttf.woff') format('woff'),
|
||||
url('/fonts/Bondi.ttf.svg#Bondi') format('svg'),
|
||||
url('/fonts/Bondi.ttf.eot'),
|
||||
url('/fonts/Bondi.ttf.eot?#iefix') format('embedded-opentype');
|
||||
url('/fonts/Bondi.ttf.eot?#iefix') format('embedded-opentype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* There is a bunch of unncessary shit in here... why so many flex grids, why is everything the same class? just seemed easier but... */
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
export let match: {
|
||||
id: string,
|
||||
playerOneUsername: string,
|
||||
playerTwoUsername: string
|
||||
};
|
||||
|
||||
</script>
|
||||
<!-- -->
|
||||
|
||||
<li>
|
||||
{match.id} "{match.playerOneUsername}" VS "{match.playerTwoUsername}"
|
||||
</li>
|
||||
|
||||
|
||||
<!-- -->
|
||||
<style>
|
||||
</style>
|
||||
@@ -4,147 +4,19 @@ 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';
|
||||
import Ranking from '../pages/game/Ranking.svelte';
|
||||
import SpectatorMatchList from '../pages/SpectatorMatchList.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;
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }),
|
||||
'/test': wrap({
|
||||
component: TestPage,
|
||||
conditions: [
|
||||
async(detail) => {
|
||||
// THIS SHIT TOTALLY WORKS
|
||||
// Yea in the end this might be the best thing, like also from a Security point of view
|
||||
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;
|
||||
}
|
||||
],
|
||||
// props: {
|
||||
// user: user
|
||||
// }
|
||||
}),
|
||||
'/game': Game,
|
||||
'/matchlist': SpectatorMatchList,
|
||||
'/ranking' : Ranking,
|
||||
'/profile': wrap({
|
||||
component: ProfilePage,
|
||||
conditions: [
|
||||
@@ -179,82 +51,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
|
||||
// };
|
||||
|
||||
Reference in New Issue
Block a user