seems like the merge worked

This commit is contained in:
Me
2023-01-05 12:30:54 +01:00
199 changed files with 30109 additions and 6796 deletions

View File

@@ -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) => {
@@ -12,22 +12,8 @@
replace('/unauthorized-access');
};
// this might be the part where we get rid of localstorage when the app is quit?
// onDestroy()
// maybe done with cookie
</script>
<!-- <h1>Testing</h1> -->
<Router routes={primaryRoutes} on:conditionsFailed={conditionsFailed}/>
<style>
/* doesn't work... */
/* body{
background: bisque;
} */
</style>

View File

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

View File

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

View File

@@ -7,4 +7,4 @@ const app = new App({
}
});
export default app;
export default app;

View File

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

View File

@@ -2,12 +2,11 @@
import Canvas from "../pieces/Canvas.svelte";
import { push } from "svelte-spa-router";
import { onMount } from 'svelte';
import { get } from "svelte/store";
let user;
onMount(async () => {
user = await fetch('http://transcendance:8080/api/v2/user')
user = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user`)
.then((resp) => resp.json())
// i mean i could do a failed to load user or some shit, maybe with a .catch or something? but atm why bother
@@ -31,14 +30,14 @@
});
const login = async() => {
window.location.href = 'http://transcendance:8080/api/v2/auth';
window.location.href = `http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/auth`;
console.log('you are now logged in');
}
// i could prolly put this in it's own compoent, i seem to use it in several places... or maybe just some JS? like no need for html
// we could .then( () => replace('/') ) need the func so TS compatible...
const logout = async() => {
await fetch('http://transcendance:8080/api/v2/auth/logout', {
await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/auth/logout`, {
method: 'POST',
});
user = undefined;

View File

@@ -1,22 +0,0 @@
<script lang="ts">
import { replace } from "svelte-spa-router";
export let user;
</script>
<div class="wrapper">
<h1>You made it to Test</h1>
<button on:click={ () => (replace('/'))}>Go Home</button>
<div>{user}</div>
</div>
<style>
div.wrapper{
display: flexbox;
align-items: center;
}
</style>

View File

@@ -2,24 +2,13 @@
import { onMount } from "svelte";
import { push } from "svelte-spa-router";
// onMount( async() => {
// await fetch("http://transcendance:8080/api/v2/auth/2fa/generate",
// {
// method: 'POST',
// })
// .then(response => {return response.blob()})
// .then(blob => {
// const url = URL.createObjectURL(blob);
// qrCodeImg = url;
// });
// });
let qrCodeImg;
let qrCode = "";
let wrongCode = "";
const fetchQrCodeImg = (async() => {
await fetch("http://transcendance:8080/api/v2/auth/2fa/generate",
await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/auth/2fa/generate`,
{
method: 'POST',
})
@@ -31,7 +20,7 @@
})()
const submitCode = async() => {
const response = await fetch("http://transcendance:8080/api/v2/auth/2fa/check",
const response = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/auth/2fa/check`,
{
method : 'POST',
headers : {

View File

@@ -1,99 +1,379 @@
<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";
import { gameState } 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://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user`)
.then( x => x.json() );
allUsers = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user/all`)
.then( x => x.json() );
options.playerOneUsername = user.username;
})
onDestroy( async() => {
clearInterval(idOfIntevalCheckTerminationOfTheMatch);
pong.destroy();
})
const initGame = async() =>
{
optionsAreNotSet = false;
showWaitPage = true;
const matchOptions = pong.computeMatchOptions(options);
const responseWhenGrantToken = fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/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");
errorMessageWhenAttemptingToGetATicket = responseInjson.message;
showError = true;
options.reset();
options.playerOneUsername = user.username;
setTimeout(() => {
optionsAreNotSet = true
showError = false;
// showWaitPage = false // ???
}, 5000);
}
else if (token)
{
idOfIntevalCheckTerminationOfTheMatch = setInterval(matchTermitation, 1000);
// options.isInvitedPerson = false // ???
pong.init(options, gameAreaId, token);
hiddenGame = false;
}
// TODO: Un "else" peut-être ? Si pas de token on fait un truc ?
// Si on ne rentre pas dans le else if, du coup il ne se passe rien.
}
const initGameForInvitedPlayer = async(invitation : any) =>
{
optionsAreNotSet = false
showWaitPage = true
console.log("invitation : ")
console.log(invitation)
if (invitation.token)
{
idOfIntevalCheckTerminationOfTheMatch = setInterval(matchTermitation, 1000);
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 (gameState.matchAbort || gameState.matchEnded)
{
clearInterval(idOfIntevalCheckTerminationOfTheMatch);
console.log("matchTermitation was called")
showWaitPage = false
gameState.matchAbort ?
errorMessageWhenAttemptingToGetATicket = "The match has been aborted"
: errorMessageWhenAttemptingToGetATicket = "The match is finished !"
gameState.matchAbort ? showError = true : showMatchEnded = true;
setTimeout(() => {
resetPage();
errorMessageWhenAttemptingToGetATicket = "";
isThereAnyInvitation = false;
invitations = []; // ???
console.log("matchTermitation : setTimeout")
}, 5000);
}
}
const showOptions = () => {
showGameOption = true
showInvitations = false
}
const showInvitation = async() => {
showGameOption = false;
showInvitations = true;
invitations = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/game/invitations`)
.then(x => x.json())
invitations.length !== 0 ? isThereAnyInvitation = true : isThereAnyInvitation = false
}
const rejectInvitation = async(invitation) => {
await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/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://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/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()
initGameForInvitedPlayer(invitation)
}
//Au final c'est utile !
initGameForInvitedPlayer(invitation) // Luke: normal de initGameForInvitedPlayer() sur un "res.status" different de 200 ?
}
function leaveMatch() {
resetPage();
};
function resetPage() {
hiddenGame = true;
optionsAreNotSet = true
showError = false;
showMatchEnded = false;
options.reset();
options.playerOneUsername = user.username;
pong.destroy();
};
</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>
</div>
{/if}
<div id="canvas_container">
<!-- <p> =) </p> -->
<div id="canvas_container" hidden={hiddenGame}>
<canvas id={gameAreaId}/>
</div>
{#if !hiddenGame}
<div id="div_game">
<button id="pong_button" on:click={leaveMatch}>forfeit</button>
</div>
{/if}
<script src="public/game/pong.js" type="module" defer></script>
</body>
{#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}
{#if optionsAreNotSet}
{#if showGameOption === true}
<div id="game_option">
<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" on:click={initGame}>PLAY</button>
</div>
</fieldset>
</div>
</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 {
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: #ff0000; */
background-color: #333333;
max-width: 75vw;
/* max-height: 100vh; */
width: 80%;
}
#div_game {
margin-top: 20px;
text-align: center;
font-family: "Bit5x3";
color: rgb(245, 245, 245);
font-size: x-large;
}
#div_game fieldset {
max-width: 50vw;
width: auto;
margin: 0 auto;
}
#div_game fieldset div {
padding: 10px;
}
#pong_button {
font-family: "Bit5x3";
color: rgb(245, 245, 245);
background-color: #333333;
font-size: x-large;
padding: 10px;
}
#users_name { /* UNUSED */
text-align: center;
font-family: "Bit5x3";
color: rgb(245, 245, 245);
font-size: x-large;
}
#error_notification { /* UNUSED */
text-align: center;
display: block;
font-family: "Bit5x3";
color: rgb(143, 19, 19);
font-size: x-large;
}
</style>

View File

@@ -0,0 +1,193 @@
<script lang="ts">
import { onMount, onDestroy } from "svelte";
import Header from '../../pieces/Header.svelte';
import MatchListElem from "../../pieces/MatchListElem.svelte";
import { fade, fly } from 'svelte/transition';
import * as pongSpectator from "./client/pongSpectator";
import { gameState } from "./client/ws";
import { gameSessionIdPLACEHOLDER } from "./shared_js/constants";
//user's stuff
let user;
let allUsers;
//Game's stuff client side only
const gameAreaId = "game_area";
let sound = "off";
// const dummyMatchList = [
// {
// gameSessionId: gameSessionIdPLACEHOLDER,
// matchOptions: pongSpectator.MatchOptions.noOption,
// playerOneUsername: "toto",
// playerTwoUsername: "bruno",
// },
// {
// gameSessionId: gameSessionIdPLACEHOLDER,
// matchOptions: pongSpectator.MatchOptions.multiBalls,
// playerOneUsername: "pl1",
// playerTwoUsername: "pl2",
// },
// {
// gameSessionId: "id6543",
// matchOptions: pongSpectator.MatchOptions.movingWalls | pongSpectator.MatchOptions.multiBalls,
// playerOneUsername: "bertand",
// playerTwoUsername: "cassandre",
// },
// {
// gameSessionId: "id3452",
// matchOptions: pongSpectator.MatchOptions.multiBalls,
// playerOneUsername: "madeleine",
// playerTwoUsername: "jack",
// },
// ];
let matchList = [];
//html boolean for pages
let hiddenGame = true;
let hiddenMatchList = false;
onMount( async() => {
user = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user`)
.then( x => x.json() );
allUsers = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user/all`)
.then( x => x.json() );
// WIP: fetch for match list here
const responseForMatchList = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/game/match/all`)
const jsonForMatchList = await responseForMatchList.json();
matchList = jsonForMatchList;
console.log("matchList");
if (matchList.length <= 0)
hiddenMatchList = true;
console.log(matchList);
})
onDestroy( async() => {
pongSpectator.destroy();
})
async function initGameSpectator(gameSessionId: string, matchOptions: pongSpectator.MatchOptions) {
pongSpectator.init(matchOptions, sound, gameAreaId, gameSessionId);
hiddenGame = false;
};
function leaveMatch() {
resetPage();
};
async function resetPage() {
hiddenGame = true;
pongSpectator.destroy();
// WIP: fetch for match list here
matchList = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/game/match/all`)
.then( x => x.json() );
console.log("matchList");
if (matchList.length <= 0)
hiddenMatchList = true;
console.log(matchList);
};
</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>
{#if hiddenGame}
<div id="div_game">
<div id="game_options">
<fieldset>
{#if hiddenMatchList}
<legend>no match available</legend>
{:else}
<legend>options</legend>
<div>
<p>sound :</p>
<input type="radio" id="sound_on" name="sound_selector" bind:group={sound} value="on">
<label for="sound_on">on</label>
<input type="radio" id="sound_off" name="sound_selector" bind:group={sound} value="off">
<label for="sound_off">off</label>
</div>
<menu id="match_list">
{#each matchList as match}
<MatchListElem match={match} on:click={(e) => initGameSpectator(match.gameServerIdOfTheMatch, match.gameOptions)} />
{/each}
</menu>
{/if}
</fieldset>
</div>
</div>
{:else}
<div id="div_game">
<button id="pong_button" on:click={leaveMatch}>leave match</button>
</div>
{/if}
</div> <!-- div "game_page" -->
<!-- -->
<style>
@font-face {
font-family: "Bit5x3";
src:
url("/fonts/Bit5x3.woff2") format("woff2"),
local("Bit5x3"),
url("/fonts/Bit5x3.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
}
#game_page {
margin: 0;
background-color: #222425;
position: relative;
width: 100%;
height: 100%;
}
#canvas_container {
margin-top: 20px;
text-align: center;
}
canvas {
background-color: #333333;
max-width: 75vw;
width: 80%;
}
#div_game {
margin-top: 20px;
text-align: center;
font-family: "Bit5x3";
color: rgb(245, 245, 245);
font-size: x-large;
}
#div_game fieldset {
max-width: 50vw;
width: auto;
margin: 0 auto;
}
#div_game fieldset div {
padding: 10px;
}
#match_list {
font-family: 'Courier New', Courier, monospace;
font-size: large;
}
#pong_button {
font-family: "Bit5x3";
color: rgb(245, 245, 245);
background-color: #333333;
font-size: x-large;
padding: 10px;
}
</style>

View File

@@ -0,0 +1,74 @@
<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://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user`)
.then( x => x.json() );
allUsers = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/game/ranking`)
.then( x => x.json() );
idInterval = setInterval(fetchScores, 10000);
})
onDestroy( async() => {
clearInterval(idInterval);
})
function fetchScores() {
fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/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>

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,19 @@
export class InitOptions {
sound = "off";
multi_balls = false;
moving_walls = false;
isSomeoneIsInvited = false;
isInvitedPerson = false;
playerOneUsername = "";
playerTwoUsername = "";
reset() {
this.sound = "off";
this.multi_balls = false;
this.moving_walls = false;
this.isSomeoneIsInvited = false;
this.isInvitedPerson = false;
this.playerOneUsername = "";
this.playerTwoUsername = "";
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,72 @@
import * as c from "./constants.js";
import * as en from "../shared_js/enums.js"
import { gc, matchOptions } from "./global.js";
import { clientInfo, clientInfoSpectator} from "./ws.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;
}
}

View File

@@ -0,0 +1,26 @@
import * as en from "../shared_js/enums.js";
import type { GameArea } from "./class/GameArea.js";
import type { GameComponentsClient } from "./class/GameComponentsClient.js";
export let pong: GameArea;
export let gc: GameComponentsClient;
export let matchOptions: en.MatchOptions = en.MatchOptions.noOption;
export function setPong(value: GameArea) {
pong = value;
}
export function setGc(value: GameComponentsClient) {
gc = value;
}
export function setMatchOptions(value: en.MatchOptions) {
matchOptions = value;
}
export let startFunction: () => void;
export function setStartFunction(value: () => void) {
startFunction = value;
}

View File

@@ -0,0 +1,111 @@
import { pong, gc } from "./global.js"
import { socket, clientInfo, gameState } from "./ws.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";
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 (!gameState.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);
}
});
}

View File

@@ -0,0 +1,48 @@
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, resetGameState } from "./ws.js";
import { initAudio } from "./audio.js";
import type { InitOptions } from "./class/InitOptions.js";
import { pong } from "./global.js"
import { setPong, setGc, setMatchOptions } 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)
{
initAudio(sound);
setMatchOptions(matchOptions);
setPong(new GameArea(gameAreaId));
setGc(new GameComponentsClient(matchOptions, pong.ctx));
}
export function destroyBase()
{
if (pong)
{
clearInterval(pong.handleInputInterval);
clearInterval(pong.gameLoopInterval);
clearInterval(pong.drawLoopInterval);
setPong(null);
}
if (socket && socket.OPEN) {
socket.close();
}
resetGameState();
}

View File

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

View File

@@ -0,0 +1,90 @@
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 { setStartFunction } from "./global.js"
let abortControllerKeydown: AbortController;
let abortControllerKeyup: AbortController;
export function init(options: InitOptions, gameAreaId: string, token: string)
{
const matchOptions = computeMatchOptions(options);
initBase(matchOptions, options.sound, gameAreaId);
setStartFunction(start);
if (options.isSomeoneIsInvited) {
initWebSocket(matchOptions, token, options.playerOneUsername, true, options.playerTwoUsername, options.isInvitedPerson);
}
else {
initWebSocket(matchOptions, token, options.playerOneUsername);
}
}
export function destroy()
{
destroyBase();
if (abortControllerKeydown) {
abortControllerKeydown.abort();
abortControllerKeydown = null;
}
if (abortControllerKeyup) {
abortControllerKeyup.abort();
abortControllerKeyup = null;
}
}
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();
}, start_after_countdown);
}
function start_after_countdown()
{
abortControllerKeydown = new AbortController();
window.addEventListener(
'keydown',
(e) => { pong.addKey(e.key); },
{signal: abortControllerKeydown.signal}
);
abortControllerKeyup = new AbortController();
window.addEventListener(
'keyup',
(e) => { pong.deleteKey(e.key);},
{signal: abortControllerKeyup.signal}
);
resume();
}
function resume()
{
gc.text1.text = "";
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);
}
function pause() // unused
{
clearInterval(pong.handleInputInterval);
clearInterval(pong.gameLoopInterval);
clearInterval(pong.drawLoopInterval);
}

View File

@@ -0,0 +1,44 @@
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";
export { MatchOptions } from "../shared_js/enums.js"
/* TODO: A way to delay the init of variables, but still use "const" not "let" ? */
import { pong, gc } from "./global.js"
import { setStartFunction } from "./global.js"
export function init(matchOptions: en.MatchOptions, sound: string, gameAreaId: string, gameSessionId: string)
{
initBase(matchOptions, sound, gameAreaId);
setStartFunction(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);
}
function pause() // unused
{
clearInterval(pong.gameLoopInterval);
clearInterval(pong.drawLoopInterval);
}

View File

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

View File

@@ -0,0 +1,331 @@
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 const gameState = {
matchEnded: false,
matchAbort: false
}
export function resetGameState() {
gameState.matchEnded = false;
gameState.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://" + process.env.WEBSITE_HOST + ":" + process.env.WEBSITE_PORT + "/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) {
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:
gameState.matchAbort = true;
socket.removeEventListener("message", preMatchListener);
msg.matchAbort();
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)
{
gameState.matchEnded = true;
socket.close();
if (data.winner === clientInfo.side) {
msg.win();
if (data.forfeit) {
msg.forfeit(clientInfo.side);
}
}
else {
msg.lose();
}
}
/* 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", errorListener);
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);
socket.send(JSON.stringify( new ev.ClientEvent(en.EventTypes.clientSpectatorReady) ));
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");
gameState.matchEnded = true;
socket.close();
// WIP
/* msg.win();
if (data.forfeit) {
msg.forfeit(clientInfo.side);
} */
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 = "match-id-test-42"; // TESTING SPECTATOR PLACEHOLDER
// for testing, force gameSession.id in wsServer.ts->createGameSession()

View File

@@ -0,0 +1,48 @@
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,
clientSpectatorReady,
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
}

View File

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

View File

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

View File

@@ -1,21 +1,24 @@
<script lang="ts">
import { onMount } from 'svelte';
import GenerateUserDisplay from '../../pieces/GenerateUserDisplay.svelte';
import GenerateUserDisplay from '../../pieces/GenerateUserDisplay.svelte';
import Chat from '../../pieces/chat/Chat.svelte';
let user;
onMount( async() => {
// console.log('mounting profile display')
user = await fetch('http://transcendance:8080/api/v2/user')
user = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user`)
.then( (x) => x.json() );
})
</script>
<Chat color="bisque"/>
<!-- is this if excessive? -->
<div class="outer">
<!-- OHHHH i could use #await instead of if and have an nice loading page! -->
@@ -28,40 +31,6 @@
{/if}
</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<div>testing when there's tons of stuff</div>
<style>
div.outer{
@@ -115,7 +84,7 @@
/* Glittery Star Stuff */
:root {
:root {
--purple: rgb(123, 31, 162);
--violet: rgb(103, 58, 183);
--pink: rgb(244, 143, 177);
@@ -126,7 +95,7 @@
from {
background-position: 0% center;
}
to {
background-position: -200% center;
}
@@ -136,7 +105,7 @@
from, to {
transform: scale(0);
}
50% {
transform: scale(1);
}
@@ -146,7 +115,7 @@
from {
transform: rotate(0deg);
}
to {
transform: rotate(180deg);
}
@@ -191,7 +160,7 @@
var(--purple)
);
background-size: 200%;
/* Keep these for Safari and chrome */
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
@@ -203,4 +172,4 @@
white-space: nowrap;
}
</style>
</style>

View File

@@ -30,7 +30,7 @@
// yea no idea what
// i mean do i fetch user? i will for now
user = await fetch('http://transcendance:8080/api/v2/user')
user = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user`)
.then( (x) => x.json() );
fetchAll();
@@ -53,7 +53,7 @@
/***** Fetch basic things *****/
const fetchAllUsers = async() => {
allUsers = await fetch('http://transcendance:8080/api/v2/user/all')
allUsers = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user/all`)
.then( x => x.json() );
console.log('got all users ')
console.log({...allUsers})
@@ -62,28 +62,28 @@
// it's more like fetch friendships
// then i need to extract the users
const fetchMyFriendships = async() => {
myFriendships = await fetch('http://transcendance:8080/api/v2/network/myfriends')
myFriendships = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/network/myfriends`)
.then( x => x.json() );
console.log('got my friends ')
console.log({...myFriendships})
};
const fetchRequestsMade = async() => {
requestsMade = await fetch('http://transcendance:8080/api/v2/network/pending')
requestsMade = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/network/pending`)
.then( x => x.json() );
console.log('got requests made ')
console.log({...requestsMade})
};
const fetchRequestsReceived = async() => {
requestsRecieved = await fetch('http://transcendance:8080/api/v2/network/received')
requestsRecieved = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/network/received`)
.then( x => x.json() );
console.log('got requests received ')
console.log({...requestsRecieved})
};
const fetchBlockedUsers = async() => {
blockedUsers = await fetch('http://transcendance:8080/api/v2/network/blocked')
blockedUsers = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/network/blocked`)
.then( x => x.json() );
console.log('got blocked users, is it empty?')
console.log({...blockedUsers})
@@ -95,7 +95,7 @@
const fetchFriendshipFull = async(aUsername) => {
console.log('fetch friendship from a username')
console.log(aUsername)
friendshipStatusFull = await fetch(`http://transcendance:8080/api/v2/network/myfriends?username=${aUsername}`)
friendshipStatusFull = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/network/myfriends?username=${aUsername}`)
.then( x => x.json());
console.log({...friendshipStatusFull})
};
@@ -104,7 +104,7 @@
const sendFriendRequest = async(aUsername) => {
console.log('sending a friend request')
console.log(aUsername)
const resp = await fetch("http://transcendance:8080/api/v2/network/relations", {
const resp = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/network/relations`, {
method : "POST",
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify({
@@ -134,7 +134,7 @@
const acceptFriendRequest = async(relationshipId) => {
console.log('accept friend request')
const resp = await fetch(`http://transcendance:8080/api/v2/network/relations/${relationshipId}/accept`, {
const resp = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/network/relations/${relationshipId}/accept`, {
method: "PATCH"})
.then( x => x.json());
// maybe not the most robust things, not super reusable cuz it depends on outside vars but works for now...
@@ -148,7 +148,7 @@
const declineFriendRequest = async(relationshipId) => {
console.log('decline friend request')
const resp = await fetch(`http://transcendance:8080/api/v2/network/relations/${relationshipId}/decline`, {
const resp = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/network/relations/${relationshipId}/decline`, {
method: "PATCH"})
.then( x => x.json());
// maybe not the most robust things, not super reusable cuz it depends on outside vars but works for now...
@@ -159,7 +159,7 @@
const unfriend = async(relationshipId) => {
console.log('Unfriend')
const resp = await fetch(`http://transcendance:8080/api/v2/network/relations/${relationshipId}`, {
const resp = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/network/relations/${relationshipId}`, {
method: "DELETE"})
.then( x => x.json());
@@ -177,15 +177,15 @@
console.log('Block a non friend user, their username')
console.log(aUsername)
const blockResp = await fetch("http://transcendance:8080/api/v2/network/relations", {
method : "POST",
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify({
"receiverUsername": aUsername,
"status": "B"
})
sentFriendRequest = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/network/relations`, {
method : "POST",
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify({
"receiverUsername": aUsername,
"status": "B"
})
.then( x => x.json())
})
.then( x => x.json())
await fetchBlockedUsers();
await fetchAllUsers();
usernameBeingViewed = undefined;
@@ -198,7 +198,7 @@
const blockAFriend = async(relationshipId) => {
console.log('blocking a friend, the relationshipID')
console.log(relationshipId)
const resp = await fetch(`http://transcendance:8080/api/v2/network/relations/${relationshipId}/block`, {method: "PATCH"})
const resp = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/network/relations/${relationshipId}/block`, {method: "PATCH"})
.then( x => x.json() );
console.log('blocked a user response')
console.log({...resp})

View File

@@ -4,55 +4,11 @@
import Router from "svelte-spa-router";
import { profileRoutes, prefix } from "../../routes/profileRoutes.js";
// let dispatch = createEventDispatcher();
// what if i did the fetch in ProfilePage rather than in each ProfileDisplay and ProfileSettings
// i mean it would update each time no matter what right? cuz onMount? and then i keep the results in
// a Store and it's all good right?
</script>
<!-- remove ={clickedHome} if you want to forward the event to App.svelte-->
<Header />
<!-- The Wave -->
<!-- <div class="spacer layer1"></div> -->
<div>
<Router routes={profileRoutes} {prefix} />
</div>
<!-- <Footer /> -->
<style>
/* this doesn't work, fucks up all my sub routes */
/* div {
max-width: 960px;
margin: 40px auto;
} */
/* from Haikei */
/* for any Haikei image */
/* .spacer{
aspect-ratio: 900/300;
width: 100%;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
} */
/* the specific image we use, you need both classes */
/* .layer1{
background-image: url('/img/wave-haikei.svg');
} */
</style>

View File

@@ -3,6 +3,7 @@
import Card from '../../pieces/Card.svelte';
import {onMount} from 'svelte';
import { push } from 'svelte-spa-router';
import Button from '../../pieces/Button.svelte';
let user;
@@ -16,7 +17,7 @@
let success = {username: '', avatar: '' };
onMount( async() => {
user = await fetch('http://transcendance:8080/api/v2/user')
user = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user`)
.then( (x) => x.json() );
// do a .catch?
@@ -33,7 +34,7 @@
// console.log('this is what is in the avatar before fetch')
// console.log(avatar)
await fetch("http://transcendance:8080/api/v2/user/avatar", {method: "GET"})
await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user/avatar`, {method: "GET"})
.then(response => {return response.blob()})
.then(data => {
const url = URL.createObjectURL(data);
@@ -63,7 +64,7 @@
else {
errors.username = '';
}
await fetch('http://transcendance:8080/api/v2/user',{
await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
@@ -100,15 +101,21 @@
// tmp
console.log(data);
await fetch("http://transcendance:8080/api/v2/user/avatar",
const responseWhenChangeAvatar = fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user/avatar`,
{
method : 'POST',
body : data,
})
.then(() => uploadAvatarSuccess = true ) // for some reason it needs to be a function, i think a TS thing, not a promis otherwise
.then(() => success.avatar = 'Your changes have been saved')
.catch(() => errors.avatar = 'Sorry failed to upload your new Avatar' );
await fetch("http://transcendance:8080/api/v2/user/avatar", {method: "GET"})
const responseFromServer = await responseWhenChangeAvatar;
if (responseFromServer.ok === true) {
uploadAvatarSuccess = true;
success.avatar = 'Your avatar has been updated';
}
else {
errors.avatar = responseFromServer.statusText;
}
await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user/avatar`, {method: "GET"})
.then(response => {return response.blob()})
.then(data => {
const url = URL.createObjectURL(data);

View File

@@ -45,4 +45,4 @@
background: white;
border: 2px solid #45c496;
}
</style>
</style>

View File

@@ -8,15 +8,10 @@
let user;
onMount( async() => {
// console.log('Display aUser username: '+ aUsername)
// http://transcendance:8080/api/v2/user?username=NomDuUserATrouver
// user = await fetch(`http://transcendance:8080/api/v2/user?username=${aUsername}`)
user = await fetch(`http://transcendance:8080/api/v2/user?username=${aUsername}`)
console.log('Display aUser username: '+ aUsername)
//`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user?username=NomDuUserATrouve`
user = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user?username=${aUsername}`)
.then( (x) => x.json() );
// console.log('Display a user: ')
// console.log({...user})
})
// sadly created an infinite loop
@@ -32,7 +27,7 @@
// console.log('Display Update aUser username: '+ aUsername)
// http://transcendance:8080/api/v2/user?username=NomDuUserATrouver
// user = await fetch(`http://transcendance:8080/api/v2/user?username=${aUsername}`)
user = await fetch(`http://transcendance:8080/api/v2/user?username=${aUsername}`)
user = await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user?username=${aUsername}`)
.then( (x) => x.json() );
};
@@ -43,7 +38,6 @@
</script>
<!-- OHHHH i could use #await instead of if and have an nice loading page! -->
{#if user !== undefined}
<GenerateUserDisplay user={user} primary={true}/>
<!-- <GenerateUserDisplay user={user} primary={true}/> -->
@@ -55,4 +49,4 @@
<style>
</style>
</style>

View File

@@ -1,5 +1,5 @@
<footer>
<div class="copyright">I am official I have a Copyright in 2022</div>
<div class="copyright">I am official I have a Copyright in 2023</div>
</footer>
<style>
@@ -15,4 +15,4 @@
padding: 20px;
border-top: 1px solid #ddd;
}
</style>
</style>

View File

@@ -2,6 +2,7 @@
import { onMount } from 'svelte';
export let user;
export let primary;
let rank = '';
@@ -11,21 +12,15 @@
onMount( async() => {
// using this for now cuz for some reason there is yet to be a way to fet another person's avatar
if (primary) {
await fetch("http://transcendance:8080/api/v2/user/avatar", {method: "GET"})
await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/user/avatar`, {method: "GET"})
.then(response => {return response.blob()})
.then(data => {
const url = URL.createObjectURL(data);
avatar = url;
});
}
// tmp
// console.log('mounted Profile Display')
// console.log(user);
})
if (user.loseGame > user.winGame) {
rank = 'Bitch Ass Loser!'
} else if (user.loseGame === user.winGame) {
@@ -39,12 +34,10 @@
let index = 0, interval = 1000;
const rand = (min, max) =>
const rand = (min, max) =>
Math.floor(Math.random() * (max - min + 1)) + min;
// it's unhappy that "star" isn't typeset, no idea what to do about it...
const animate = (star) => {
// the if seems to have fixed the type issue
if (star) {
star.style.setProperty("--star-left", `${rand(-10, 100)}%`);
star.style.setProperty("--star-top", `${rand(-40, 80)}%`);
@@ -55,13 +48,12 @@
}
}
// This is the part i invented, it was kinda a fucking nightmare...
let stars = [];
for (let i = 0; i < 3; i++) {
setTimeout(() => {
animate(stars[i]);
setInterval(() => animate(stars[i]), 1000);
}, index++ * (interval / 3))
}
@@ -76,7 +68,7 @@
<!-- <img class="icon" src="{user.image_url}" alt="default user icon"> -->
<img class="avatar" src="{avatar}" alt="default user icon">
<div class="username">{user.username}</div>
<div class="rank">Rank:
<div class="rank">Rank:
<span class="glitter">
<span bind:this={stars[0]} class="glitter-star">
<svg viewBox="0 0 512 512">
@@ -162,7 +154,7 @@
/* Glittery Star Stuff */
:root {
:root {
--purple: rgb(123, 31, 162);
--violet: rgb(103, 58, 183);
--pink: rgb(244, 143, 177);
@@ -173,7 +165,7 @@
from {
background-position: 0% center;
}
to {
background-position: -200% center;
}
@@ -183,7 +175,7 @@
from, to {
transform: scale(0);
}
50% {
transform: scale(1);
}
@@ -193,7 +185,7 @@
from {
transform: rotate(0deg);
}
to {
transform: rotate(180deg);
}
@@ -238,7 +230,7 @@
var(--purple)
);
background-size: 200%;
/* Keep these for Safari and chrome */
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
@@ -250,4 +242,4 @@
white-space: nowrap;
}
</style>
</style>

View File

@@ -2,17 +2,16 @@
import { push } from "svelte-spa-router";
import { location } from 'svelte-spa-router';
import active from 'svelte-spa-router/active'
// or i could leave them all and not display if they're active?
let handleClickLogout = async () => {
await fetch('http://transcendance:8080/api/v2/auth/logout', {
await fetch(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}/api/v2/auth/logout`, {
method: 'POST',
})
// .then(resp => resp.json)
// .then((resp) => console.log(resp))
.then( () => push('/') ) // i think for TS reasons it has to be a func not direclty push('/') or whatever
.then( () => push('/') )
console.log('clicked logout header')
};
@@ -24,18 +23,15 @@
<img src="/img/potato_logo.png" alt="Potato Pong Logo" on:click={() => (push('/'))}>
<h1>Potato Pong</h1>
<nav>
<button on:click={() => (push('/game'))}>Play</button>
<button on:click={() => (push('/spectator'))}>Spectate</button>
<button on:click={() => (push('/ranking'))}>Ranking</button>
{#if $location !== '/profile'}
<button on:click={() => (push('/profile'))}>My Profile</button>
{:else if $location === '/profile'}
<button on:click={() => (push('/profile/settings'))}>Settings</button>
{/if}
<!-- <button on:click={() => (push('/stream'))}>Stream</button> -->
<!-- <button on:click={() => (push('/chat'))}>Chat</button> -->
<!-- tmp -->
<button on:click={() => (push('/profile/friends'))}>Friends</button>
<button on:click={() => (push('/test'))}>test</button>
<button on:click={handleClickLogout}>Log Out</button>
</nav>
</header>
@@ -52,11 +48,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... */
@@ -116,4 +112,4 @@
/* .none{
} */
</style>
</style>

View File

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

View File

@@ -0,0 +1,58 @@
<script lang="ts">
import Layouts from './Chat_layouts.svelte';
export let color = "transparent";
/* web sockets with socket.io
*/
import { onMount } from 'svelte';
import io from 'socket.io-client';
const socket = io(`http://${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}`, {
path: '/chat'
});
onMount(async => {
socket.on('connect', function(){
console.log("socket.io connected");
});
socket.on('disconnect', function(){
console.log("socket.io disconnected");
});
socket.on('connect_error', function(){
console.log("socket.io connect_error");
});
socket.on('connect_timeout', function(){
console.log("socket.io connect_timeout");
});
socket.on('error', function(){
console.log("socket.io error");
});
socket.on('reconnect', function(){
console.log("socket.io reconnect");
});
socket.on('reconnect_attempt', function(){
console.log("socket.io reconnect_attempt");
});
socket.on('reconnecting', function(){
console.log("socket.io reconnecting");
});
socket.on('reconnect_error', function(){
console.log("socket.io reconnect_error");
});
socket.on('reconnect_failed', function(){
console.log("socket.io reconnect_failed");
});
socket.on('ping', function(){
console.log("socket.io ping");
});
socket.on('pong', function(){
console.log("socket.io pong");
});
});
</script>
<Layouts color={color} />
<style></style>

View File

@@ -0,0 +1,133 @@
<script lang="ts">
export let color;
export let layout;
</script>
<div class="{layout} chat_box" style="background-color: {color};">
<slot></slot>
</div>
<style>
/* chat_box and default style
*/
.chat_box {
display: flex;
position: fixed;
bottom: 20px;
right: 20px;
padding: 0px;
width: auto;
height: auto;
border: 1px solid black;
z-index: 1;
}
/* * * * * * * * * * * * * * * * * * * * *
GLOBAL STYLES
*/
/* Hide scrollbar
*/
.chat_box :global(*) {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.chat_box :global(*::-webkit-scrollbar) {
display: none; /* Chrome, Safari and Opera */
}
/* for grid_box and all childrens
*/
.chat_box :global(.grid_box) {
display: grid;
margin: 5px;
gap: 5px;
width: 300px;
height: 400px;
}
.chat_box :global(.grid_box *) {
display: flex;
flex-direction: column;
position: relative;
box-sizing: border-box;
}
/* all p
*/
.chat_box :global(.grid_box p) {
padding: 10px;
font-size: 15px;
}
/* all panel
*/
.chat_box :global(.panel) {
overflow-y: scroll;
}
.chat_box :global(.panel > *) {
margin-top: 10px;
margin-bottom: 10px;
}
/* * * * * * * * * * * * * * * * * * * * *
GLOBAL UTILITIES
*/
/* __show_if_only_child
*/
.chat_box :global(.__show_if_only_child) {
display: none;
}
.chat_box :global(.__show_if_only_child:only-child) {
display: flex;
color: rgb(100, 100, 100);
}
/* __center
*/
.chat_box :global(.__center) {
margin-left: auto;
margin-right: auto;
}
/* __border_top
*/
.chat_box :global(.__border_top) {
border-top: 1px solid black;
}
/* __check_change_next
*/
.chat_box :global(.__check_change_next:checked ~ .__to_show) {
display: flex;
}
.chat_box :global(.__check_change_next:checked ~ .__to_block),
.chat_box :global(.__check_change_next:checked ~ .__to_block *) {
pointer-events: none;
color: rgb(100, 100, 100);
}
.chat_box :global(.__to_show) {
display: none;
}
</style>

View File

@@ -0,0 +1,188 @@
<!--
<Button
bind:layout
new_layout=""
on_click={}
my_class=""
my_title=""
>
value
</Button>
-->
<script lang="ts">
export let my_class = "";
export let my_title = "";
export let layout = "";
export let new_layout = "";
export let on_click = "";
function update_layout() {
layout = new_layout;
}
</script>
<button on:click={update_layout} on:click={on_click} title={my_title} class={my_class}>
<p><slot></slot></p>
</button>
<style>
/*
- default config
- for btn list
- for transparent btn
- for deactivated btn
- for icon
- for 3 dots btn
- for close btn
- for back btn
*/
/* default config
*/
button {
padding: 0px;
margin: auto;
width: 100%;
cursor: pointer;
outline: none;
border: none;
background-color: rgb(220, 220, 220);
}
button p {
width: 100%;
margin: auto;
text-align: center;
}
button:hover {
background-color: rgb(200, 200, 200);
}
button:active {
background-color: rgb(190, 190, 190);
}
/* for btn list
*/
.list:not(:hover) {
background-color: rgb(240, 240, 240);
}
.list p {
text-align: left;
}
/* for transparent btn
*/
.transparent:not(:hover) {
background-color: transparent;
}
/* for deactivated btn
*/
.deactivate {
background-color: transparent;
pointer-events: none;
}
/* for icon
*/
.icon p {
display: none;
}
.icon:not(:hover) {
background-color: transparent;
}
.icon {
width: 30px;
height: 100%;
padding: 0px;
}
/* for 3 dots btn
*/
.dots::after {
content: '\2807';
font-size: 20px;
position: absolute;
top: 50%;
left: 0px;
width: 100%;
height: auto;
text-align: center;
transform: translateY(-50%);
cursor: pointer;
}
/* for close btn
*/
.close::before {
content: "";
position: absolute;
top: calc(50% - 1px);
left: 5px;
width: 20px;
height: 2px;
background-color: black;
}
/* for back btn
*/
.back::before {
content: "";
position: absolute;
top: calc(50% - 6px - 1px);
left: 6px;
width: 14px;
height: 14px;
border-left: 1px solid black;
border-bottom: 1px solid black;
transform: rotate(45deg);
}
/* for blocked user
https://www.fileformat.info/info/unicode/category/So/list.htm
U+1F512 LOCK 🔒
U+1F513 OPEN LOCK 🔓
*/
.blocked {
padding-left: 30px;
}
.blocked::before {
content: "";
position: absolute;
top: calc(50% - 2px);
left: 10px;
cursor: pointer;
width: 13px;
height: 10px;
border-radius: 2px;
background-color: rgb(110, 110, 110);
}
.blocked::after {
content: "";
position: absolute;
top: calc(50% - 9px);
left: 12px;
cursor: pointer;
width: 9px;
height: 13px;
border-radius: 5px;
box-sizing: border-box;
border: 3px solid rgb(110, 110, 110);
}
</style>

View File

@@ -0,0 +1,81 @@
<script lang="ts">
import ChatBox from './Chat_box_css.svelte';
import CloseLayout from './Layout_close.svelte';
import HomeLayout from './Layout_home.svelte';
import RoomLayout from './Layout_room.svelte';
import NewLayout from './Layout_new.svelte';
import SettingsLayout from './Layout_settings.svelte';
import RoomsetLayout from './Layout_room_set.svelte';
import ProtectedLayout from './Layout_protected.svelte';
import CreateLayout from './Layout_create.svelte';
import MuteLayout from './Layout_mute.svelte';
import UserLayout from './Layout_user.svelte';
import Button from './Chat_button.svelte';
/* global variables
*/
export let color;
let room = "";
let admin = false;
let layout = "close";
let layouts = ["home", "home"];
/* hold previous version of layout, to go back
*/
function set_layouts(layout)
{
if (layout === "close")
return;
if (layout === layouts[0])
return;
if (layout === layouts[1])
layouts = [layout, "home"];
else
layouts = [layout, layouts[0]];
}
$: set_layouts(layout);
</script>
<ChatBox layout={layout} color={color}>
{#if layout === "home"}
<HomeLayout bind:layout />
{:else if layout === "close"}
<CloseLayout bind:layout />
{:else if layout === "room"}
<RoomLayout bind:layout back={layouts[1]} />
{:else if layout === "new"}
<NewLayout bind:layout back={layouts[1]} />
{:else if layout === "settings"}
<SettingsLayout bind:layout back={layouts[1]} />
{:else if layout === "room_set"}
<RoomsetLayout bind:layout back={layouts[1]} />
{:else if layout === "protected"}
<ProtectedLayout bind:layout back={layouts[1]} />
{:else if layout === "create"}
<CreateLayout bind:layout back={layouts[1]} />
{:else if layout === "mute"}
<MuteLayout bind:layout back={layouts[1]} />
{:else if layout === "user"}
<UserLayout bind:layout back={layouts[1]} />
{/if}
</ChatBox>
<style></style>

View File

@@ -0,0 +1,67 @@
<script>
export let name;
</script>
<div class="chat_msg {name}">
<p class="name">{name}</p>
<p class="msg"><slot></slot></p>
</div>
<style>
.chat_msg {
/*
white-space: pre-wrap;
*/
margin: 5px auto;
padding: 5px;
border-radius: 5px;
}
/* all msg
*/
.chat_msg {
margin-left: 0px;
background-color: rgb(210, 210, 210);
max-width: 80%;
}
.chat_msg p {
padding: 0px;
}
.chat_msg p.name {
margin: 0px;
font-size: 12px;
color: rgb(100, 100, 100);
}
.chat_msg p.msg {
margin: 5px 0px;
}
.chat_msg p.msg :global(*) {
display: inline;
}
/* msg perso
*/
.chat_msg.me {
margin-right: 0px;
margin-left: auto;
background-color: rgb(210, 110, 10);
}
.chat_msg.me p.name {
display: none;
}
/* msg server
*/
.chat_msg.SERVER {
margin-left: auto;
background-color: transparent;
}
.chat_msg.SERVER p.name {
display: none;
}
.chat_msg.SERVER p.msg {
margin: 0px auto;
font-size: 12px;
color: rgb(100, 100, 100);
}
</style>

View File

@@ -0,0 +1,137 @@
<script>
import Button from './Chat_button.svelte';
export let layout;
</script>
<div class="grid_box">
<Button bind:layout new_layout="home" my_class="chat">
chat
</Button>
</div>
<style>
/* layout "close"
*/
.grid_box :global(.chat) {grid-area: chat;}
.grid_box {
gap: 0px;
grid:
' chat ' auto
/ auto ;
}
:global(.chat_box.close) .grid_box {
margin: 0px;
width: auto;
height: auto;
}
/* * * * * * * * * * * * * * * * * * * * *
GLOBAL STYLES
*/
/* Hide scrollbar
*/
.chat_box :global(*) {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.chat_box :global(*::-webkit-scrollbar) {
display: none; /* Chrome, Safari and Opera */
}
/* for grid_box and all childrens
*/
.chat_box :global(.grid_box) {
display: grid;
margin: 0px;
gap: 5px;
width: 100%;
height: 100%;
}
.chat_box :global(.grid_box *) {
display: flex;
flex-direction: column;
position: relative;
box-sizing: border-box;
}
/* all p
*/
.chat_box :global(.grid_box p) {
padding: 10px;
font-size: 15px;
}
/* all panel
*/
.chat_box :global(.panel) {
overflow-y: scroll;
}
.chat_box :global(.panel > *) {
margin-top: 10px;
margin-bottom: 10px;
}
/* * * * * * * * * * * * * * * * * * * * *
GLOBAL UTILITIES
*/
/* __show_if_only_child
*/
.chat_box :global(.__show_if_only_child) {
display: none;
}
.chat_box :global(.__show_if_only_child:only-child) {
display: flex;
color: rgb(100, 100, 100);
}
/* __center
*/
.chat_box :global(.__center) {
margin-left: auto;
margin-right: auto;
}
/* __border_top
*/
.chat_box :global(.__border_top) {
border-top: 1px solid black;
}
/* __check_change_next
*/
.chat_box :global(.__check_change_next:checked ~ .__to_show) {
display: flex;
}
.chat_box :global(.__check_change_next:checked ~ .__to_block),
.chat_box :global(.__check_change_next:checked ~ .__to_block *) {
pointer-events: none;
color: rgb(100, 100, 100);
}
.chat_box :global(.__to_show) {
display: none;
}
</style>

View File

@@ -0,0 +1,113 @@
<script>
import Button from './Chat_button.svelte';
export let layout = "";
export let back = "";
</script>
<div class="grid_box">
<!-- back -->
<Button bind:layout new_layout={back} my_class="back icon" my_title="go back {back}">
back
</Button>
<!-- create -->
<Button my_class="create deactivate">
create
</Button>
<!-- close -->
<Button bind:layout new_layout="close" my_class="close icon">
close
</Button>
<!-- panel_create -->
<div class="panel panel_create">
<form>
<!-- name: -->
<label for="chat_name"><p>new room name :</p></label>
<input id="chat_name" required>
<!-- [ ] pubic -->
<input id="chat_public" type="radio" name="chat_create_type" checked>
<label for="chat_public" class="_radio"><p>public</p></label>
<!-- [ ] private -->
<input id="chat_private" type="radio" name="chat_create_type">
<label for="chat_private" class="_radio"><p>private</p></label>
<!-- [ ] protected -->
<input id="chat_protected" class="__check_change_next" type="radio" name="chat_create_type">
<label for="chat_protected" class="_radio"><p>protected</p></label>
<!-- [x] protected -->
<div class="__to_show">
<label for="chat_pswd"><p>choose a password :</p></label>
<input id="chat_pswd" type="password" placeholder="minimum 8 characters" minlength="8">
<p>confirm password :</p>
<input type="password">
</div>
<input type="submit" value="&#x2BA1">
</form>
</div>
</div>
<style>
/* grid layout "create"
*/
.grid_box :global(.back ) {grid-area: back;}
.grid_box :global(.create ) {grid-area: create;}
.grid_box :global(.close ) {grid-area: close;}
.grid_box :global(.panel_create) {grid-area: panel_create;}
.grid_box {
grid:
' back create close ' auto
' panel_create panel_create panel_create ' 1fr
/ auto 1fr auto ;
}
/* radio elements style check
*/
form input[type=radio] {
display: none;
}
form label._radio {
margin: 0px 20px 0px auto;
padding-right: 10px;
cursor: pointer;
}
form label._radio p {
margin-top: 0px;
margin-bottom: 0px;
}
form label._radio::after {
content: "";
position: absolute;
top: calc(50% - 6px);
right: 0px;
width: 12px;
height: 12px;
border-radius: 6px;
border: 2px solid rgb(150, 150, 150);
box-sizing: border-box;
cursor: pointer;
}
form input[type=radio]:checked
+ label._radio::after {
background-color: rgb(200, 200, 200);
}
/* submit
*/
form input[type=submit] {
margin-top: 20px;
}
</style>

View File

@@ -0,0 +1,73 @@
<script>
import Button from './Chat_button.svelte';
export let layout;
</script>
<div class="grid_box">
<!-- settings -->
<Button bind:layout new_layout="settings" my_class="settings dots icon">
settings
</Button>
<!-- new -->
<Button bind:layout new_layout="new" my_class="new transparent">
new
</Button>
<!-- close -->
<Button bind:layout new_layout="close" my_class="close icon">
close
</Button>
<!-- panel home -->
<div class="panel panel_home __border_top">
<p class="title">list of your rooms :</p>
<div class="room_list">
<div class="__show_if_only_child">
<p class="__center">/ you have no chat room yet /</p>
</div>
<!-- placeholders
<Button bind:layout new_layout="room" my_class="list">
a room
</Button>
<Button bind:layout new_layout="room" my_class="list">
another room
</Button>
<Button bind:layout new_layout="room" my_class="list">
placeholder
</Button>
------------- -->
<!-- END placeholders -->
</div>
</div>
</div>
<style>
/* grid layout "home"
*/
.grid_box :global(.settings ) {grid-area: settings;}
.grid_box :global(.close ) {grid-area: close;}
.grid_box :global(.new ) {grid-area: new;}
.grid_box :global(.panel_home) {grid-area: panel_home;}
.grid_box {
grid:
' settings new close ' auto
' panel_home panel_home panel_home ' 1fr
/ auto 1fr auto ;
}
/* panel home
*/
.panel_home p.title {
margin: 10px auto 0px auto;
}
</style>

View File

@@ -0,0 +1,261 @@
<script>
import Button from './Chat_button.svelte';
export let layout = "";
export let back = "";
</script>
<div class="grid_box">
<!-- back -->
<Button bind:layout new_layout={back} my_class="back icon" my_title="go back {back}">
back
</Button>
<!-- user -->
<Button my_class="user deactivate">
&lt;user&gt;
</Button>
<!-- close -->
<Button bind:layout new_layout="close" my_class="close icon">
close
</Button>
<!-- panel_mute -->
<!-- MUTE -->
<div class="panel panel_mute __border_top">
<p class="__center">mute this user for a time :</p>
<form>
<!-- forever -->
<input id="chat_mute_forever" class="__check_change_next" type="checkbox">
<label for="chat_mute_forever" class="_checkbox"><p>forever</p></label>
<div class="__to_block">
<!-- minutes -->
<label for="chat_mute_minutes" class="_select">
<p>minutes :</p>
<select id="chat_mute_minutes">
<option value="01">00</option>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
<option value="04">04</option>
<option value="05">05</option>
<option value="06">06</option>
<option value="07">07</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14">14</option>
<option value="15">15</option>
<option value="16">16</option>
<option value="17">17</option>
<option value="20">20</option>
<option value="21">21</option>
<option value="22">22</option>
<option value="23">23</option>
<option value="24">24</option>
<option value="25">25</option>
<option value="26">26</option>
<option value="27">27</option>
<option value="30">30</option>
<option value="31">31</option>
<option value="32">32</option>
<option value="33">33</option>
<option value="34">34</option>
<option value="35">35</option>
<option value="36">36</option>
<option value="37">37</option>
<option value="40">40</option>
<option value="41">41</option>
<option value="42">42</option>
<option value="43">43</option>
<option value="44">44</option>
<option value="45">45</option>
<option value="46">46</option>
<option value="47">47</option>
<option value="50">50</option>
<option value="51">51</option>
<option value="52">52</option>
<option value="53">53</option>
<option value="54">54</option>
<option value="55">55</option>
<option value="56">56</option>
<option value="57">57</option>
<option value="60">60</option>
</select>
</label>
<!-- hours -->
<label for="chat_mute_hours" class="_select">
<p>hours :</p>
<select id="chat_mute_hours">
<option value="01">00</option>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
<option value="04">04</option>
<option value="05">05</option>
<option value="06">06</option>
<option value="07">07</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14">14</option>
<option value="15">15</option>
<option value="16">16</option>
<option value="17">17</option>
<option value="20">20</option>
<option value="21">21</option>
<option value="22">22</option>
<option value="23">23</option>
<option value="24">24</option>
<option value="25">25</option>
<option value="26">26</option>
<option value="27">27</option>
<option value="30">30</option>
<option value="31">31</option>
<option value="32">32</option>
<option value="33">33</option>
<option value="34">34</option>
<option value="35">35</option>
<option value="36">36</option>
<option value="37">37</option>
<option value="40">40</option>
<option value="41">41</option>
<option value="42">42</option>
<option value="43">43</option>
<option value="44">44</option>
<option value="45">45</option>
<option value="46">46</option>
<option value="47">47</option>
<option value="50">50</option>
<option value="51">51</option>
<option value="52">52</option>
<option value="53">53</option>
<option value="54">54</option>
<option value="55">55</option>
<option value="56">56</option>
<option value="57">57</option>
<option value="60">60</option>
</select>
</label>
<!-- days -->
<label for="chat_mute_days" class="_select">
<p>days :</p>
<select id="chat_mute_days">
<option value="00">00</option>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
<option value="04">04</option>
<option value="05">05</option>
<option value="06">06</option>
<option value="07">07</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14">14</option>
<option value="15">15</option>
<option value="16">16</option>
<option value="17">17</option>
<option value="20">20</option>
<option value="21">21</option>
<option value="22">22</option>
<option value="23">23</option>
<option value="24">24</option>
<option value="25">25</option>
<option value="26">26</option>
<option value="27">27</option>
<option value="30">30</option>
<option value="31">31</option>
</select>
</label>
</div>
<input type="submit" value="&#x2BA1">
</form>
</div>
</div>
<style>
/* grid layout "mute"
*/
.grid_box :global(.back ) {grid-area: back;}
.grid_box :global(.back ) {grid-area: back;}
.grid_box :global(.user ) {grid-area: user;}
.grid_box :global(.close ) {grid-area: close;}
.grid_box :global(.panel_mute) {grid-area: panel_mute;}
.grid_box {
grid:
' back user close ' auto
' panel_mute panel_mute panel_mute ' 1fr
/ auto 1fr auto ;
}
/* checkbox
*/
form input[type=checkbox] {
display: none;
}
form label._checkbox {
margin: 0px auto 0px 10px;
padding-left: 10px;
cursor: pointer;
}
form label._checkbox::after {
content: "";
position: absolute;
top: calc(50% - 6px);
left: 0px;
width: 12px;
height: 12px;
border: 2px solid rgb(150, 150, 150);
box-sizing: border-box;
cursor: pointer;
}
form input[type=checkbox]:checked
+ label._checkbox::after {
background-color: rgb(200, 200, 200);
}
/* select
*/
form label._select {
flex-direction: row;
}
form label._select p {
margin: 0px;
}
form select {
margin: auto auto auto 10px;
background-color: rgb(220, 220, 220);
border: none;
padding: 5px;
cursor: pointer;
}
form select:hover {
background-color: rgb(200, 200, 200);
}
/* submit
*/
form input[type=submit] {
margin-top: 20px;
}
</style>

View File

@@ -0,0 +1,89 @@
<script>
import Button from './Chat_button.svelte';
export let layout = "";
export let back = "";
</script>
<div class="grid_box">
<!-- back -->
<Button bind:layout new_layout={back} my_class="back icon" my_title="go back {back}">
back
</Button>
<!-- new -->
<Button my_class="new deactivate">
new
</Button>
<!-- close -->
<Button bind:layout new_layout="close" my_class="close icon">
close
</Button>
<!-- panel_new -->
<div class="panel panel_new __border_top">
<Button bind:layout new_layout="create" my_class="create">
create
</Button>
<p>join room :</p>
<div class="public_rooms">
<div class="__show_if_only_child">
<p class="__center">/ there are no public rooms yet /</p>
</div>
<!-- placeholders
<Button bind:layout new_layout="room" my_class="list">
placeholder
</Button>
<Button bind:layout new_layout="room" my_class="list">
join room
</Button>
<Button bind:layout new_layout="room" my_class="list">
one room
</Button>
<Button bind:layout new_layout="room" my_class="list">
another room
</Button>
<Button bind:layout new_layout="room" my_class="list">
one room
</Button>
<Button bind:layout new_layout="room" my_class="list">
another room
</Button>
<Button bind:layout new_layout="room" my_class="list">
one room
</Button>
<Button bind:layout new_layout="room" my_class="list">
another room
</Button>
<Button bind:layout new_layout="room" my_class="list">
one more room
</Button>
------------- -->
<!-- END placeholders -->
</div>
</div>
</div>
<style>
/* grid layout "new"
*/
.grid_box :global(.back ) {grid-area: back;}
.grid_box :global(.new ) {grid-area: new;}
.grid_box :global(.close ) {grid-area: close;}
.grid_box :global(.panel_new) {grid-area: panel_new;}
.grid_box {
grid:
' back new close ' auto
' panel_new panel_new panel_new ' 1fr
/ auto 1fr auto ;
}
</style>

View File

@@ -0,0 +1,65 @@
<script>
import Button from './Chat_button.svelte';
export let layout = "";
export let back = "";
</script>
<div class="grid_box">
<!-- back -->
<Button bind:layout new_layout={back} my_class="back icon" my_title="go back {back}">
back
</Button>
<!-- room_name -->
<Button my_class="room_name deactivate">
&lt;room_name&gt;
</Button>
<!-- close -->
<Button bind:layout new_layout="close" my_class="close icon">
close
</Button>
<!-- panel_protected -->
<div class="panel panel_protected __border_top">
<p class="title __center">this room is protected</p>
<form>
<label for="chat_pswd"><p>password :</p></label>
<input id="chat_pswd" type="password" required>
<input type="submit" value="&#x2BA1">
</form>
</div>
</div>
<style>
/* grid layout "protected"
*/
.grid_box :global(.back ) {grid-area: back;}
.grid_box :global(.room_name ) {grid-area: room_name;}
.grid_box :global(.close ) {grid-area: close;}
.grid_box :global(.panel_protected) {grid-area: panel_protected;}
.grid_box {
grid:
' back room_name close ' auto
' panel_protected panel_protected panel_protected ' 1fr
/ auto 1fr auto ;
}
/* submit
*/
form input[type=submit] {
margin-top: 20px;
}
</style>

View File

@@ -0,0 +1,149 @@
<script>
import Button from './Chat_button.svelte';
import Msg from './Chat_msg.svelte';
import io from 'socket.io-client';
export let layout = "";
export let back = "";
let msg = "";
let text_area;
let msgs = [];
function add_msg(from, the_msg)
{
msgs = [...msgs, { content: the_msg, name: from }];
}
function send_msg()
{
msg = msg.trim();
if (msg.length > 0) {
//socket.emit('sendmsg', msg);
add_msg("me", msg);
}
msg = "";
text_area.focus();
}
function send_msg_if(evt)
{
if (evt.shiftKey && evt.key === "Enter")
{
evt.preventDefault();
send_msg();
}
}
</script>
<div class="grid_box">
<!-- back -->
<Button bind:layout new_layout={back} my_class="back icon" my_title="go back {back}">
back
</Button>
<!-- room_name -->
<Button bind:layout new_layout="room_set" my_class="room_name transparent">
&lt;room_name&gt;
</Button>
<!-- close -->
<Button bind:layout new_layout="close" my_class="close icon">
close
</Button>
<!-- msg -->
<div class="panel panel_msg">
<div class="msg_thread">
{#each msgs as msg}
<Msg name={msg.name}>{@html msg.content}</Msg>
{/each}
</div>
</div>
<!-- write -->
<div class="panel_write">
<div
class="text_area"
bind:innerHTML={msg}
bind:this={text_area}
on:keypress={send_msg_if}
contenteditable="true"
></div>
</div>
<!-- send -->
<Button my_class="send" on_click={send_msg}>
send
</Button>
</div>
<style>
/* grid layout "room"
*/
.grid_box :global(.back ) {grid-area: back;}
.grid_box :global(.room_name ) {grid-area: room_name;}
.grid_box :global(.close ) {grid-area: close;}
.grid_box :global(.panel_msg ) {grid-area: panel_msg;}
.grid_box :global(.send ) {grid-area: send;}
.grid_box :global(.panel_write) {grid-area: panel_write;}
.grid_box {
grid:
' back room_name room_name close ' auto
' panel_msg panel_msg panel_msg panel_msg ' 1fr
' panel_write panel_write send send ' auto
/ auto 1fr auto auto ;
}
/* write area
*/
.grid_box .panel_write {
border: none;
overflow: visible;
}
.grid_box .text_area {
display: block;
position: absolute;
bottom: 0px;
left: 0px;
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
background-color: white;
border: 1px solid black;
}
.grid_box .text_area:focus {
height: auto;
min-height: 100%;
max-height: 300px;
}
.grid_box .panel_write .text_area :global(*) {
display: block ruby;
}
/* msg area
*/
.grid_box .panel_msg {
flex-direction: column-reverse;
border: 1px solid black;
}
.grid_box .msg_thread {
width: 100%;
padding: 0px 5px;
}
</style>

View File

@@ -0,0 +1,76 @@
<script>
import Button from './Chat_button.svelte';
export let layout = "";
export let back = "";
</script>
<div class="grid_box">
<!-- back -->
<Button bind:layout new_layout={back} my_class="back icon" my_title="go back {back}">
back
</Button>
<!-- room_name -->
<Button my_class="room_name deactivate">
&lt;room_name&gt;
</Button>
<!-- close -->
<Button bind:layout new_layout="close" my_class="close icon">
close
</Button>
<!-- panel_room_set -->
<div class="panel panel_room_set __border_top">
<Button bind:layout new_layout="create" my_class="create">
leave
</Button>
<p>room users :</p>
<div class="room_users">
<div class="__show_if_only_child">
<p class="__center">/ there are no public rooms yet /</p>
</div>
<!-- placeholders
------------- -->
<Button bind:layout new_layout="user" my_class="list">
user 1
</Button>
<Button bind:layout new_layout="user" my_class="list blocked">
user 2
</Button>
<Button bind:layout new_layout="user" my_class="list">
user 3
</Button>
<Button bind:layout new_layout="user" my_class="list">
user 4
</Button>
<!-- END placeholders -->
</div>
</div>
</div>
<style>
/* grid layout "room_set"
*/
.grid_box :global(.back ) {grid-area: back;}
.grid_box :global(.room_name ) {grid-area: room_name;}
.grid_box :global(.close ) {grid-area: close;}
.grid_box :global(.panel_room_set) {grid-area: panel_room_set;}
.grid_box {
grid:
' back room_name close ' auto
' panel_room_set panel_room_set panel_room_set ' 1fr
/ auto 1fr auto ;
}
</style>

View File

@@ -0,0 +1,72 @@
<script>
import Button from './Chat_button.svelte';
export let layout = "";
export let back = "";
</script>
<div class="grid_box">
<!-- back -->
<Button bind:layout new_layout={back} my_class="back icon" my_title="go back {back}">
back
</Button>
<!-- settings -->
<Button my_class="room_name deactivate">
settings
</Button>
<!-- close -->
<Button bind:layout new_layout="close" my_class="close icon">
close
</Button>
<!-- panel_settings -->
<div class="panel panel_settings __border_top">
<p>blocked users :</p>
<div class="blocked_users">
<div class="__show_if_only_child">
<p class="__center">/ you have blocked no one /</p>
</div>
<!-- placeholders
<Button bind:layout new_layout="user" my_class="list blocked">
user 1
</Button>
<Button bind:layout new_layout="user" my_class="list blocked">
user 2
</Button>
<Button bind:layout new_layout="user" my_class="list blocked">
user 3
</Button>
<Button bind:layout new_layout="user" my_class="list blocked">
user 4
</Button>
------------- -->
<!-- END placeholders -->
</div>
</div>
</div>
<style>
/* grid layout "settings"
*/
.grid_box :global(.back ) {grid-area: back;}
.grid_box :global(.settings ) {grid-area: settings;}
.grid_box :global(.close ) {grid-area: close;}
.grid_box :global(.panel_settings) {grid-area: panel_settings;}
.grid_box {
grid:
' back settings close ' auto
' panel_settings panel_settings panel_settings ' 1fr
/ auto 1fr auto ;
}
</style>

View File

@@ -0,0 +1,91 @@
<script>
import Button from './Chat_button.svelte';
export let layout = "";
export let back = "";
let mute = "mute";
let block = "block";
</script>
<div class="grid_box">
<!-- back -->
<Button bind:layout new_layout={back} my_class="back icon" my_title="go back {back}">
back
</Button>
<!-- user -->
<Button my_class="user deactivate">
&lt;user&gt;
</Button>
<!-- close -->
<Button bind:layout new_layout="close" my_class="close icon">
close
</Button>
<!-- room_name -->
{#if back === "room_set"}
<Button my_class="room_name deactivate __border_top">
&lt;room_name&gt;
</Button>
{/if}
<!-- panel_user -->
<div class="panel panel_user __border_top">
<p class="__center">user options :</p>
<Button>
view profile
</Button>
<Button>
game invitation
</Button>
<Button>
{block}
</Button>
{#if back === "room_set"}
<Button>
make admin
</Button>
<Button>
{mute}
</Button>
{/if}
</div>
</div>
<style>
/* grid layout "user"
*/
.grid_box :global(.back ) {grid-area: back;}
.grid_box :global(.user ) {grid-area: user;}
.grid_box :global(.close ) {grid-area: close;}
.grid_box :global(.room_name ) {grid-area: room_name;}
.grid_box :global(.panel_user) {grid-area: panel_user;}
.grid_box {
grid:
' back user close ' auto
' room_name room_name room_name ' auto
' panel_user panel_user panel_user ' 1fr
/ auto 1fr auto ;
}
/* for line height
*/
.panel_user {
margin-top: -5px;
}
</style>

View File

@@ -4,152 +4,23 @@ 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 GameSpectator from '../pages/game/GameSpectator.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,
'/spectator': GameSpectator,
'/ranking' : Ranking,
'/profile': wrap({
component: ProfilePage,
conditions: [
async(detail) => {
const user = await fetch('http://transcendance:8080/api/v2/user')
const user = await fetch('http://' + process.env.WEBSITE_HOST + ":" + process.env.WEBSITE_PORT + '/api/v2/user')
.then((resp) => resp.json())
console.log('in /profile what is in user')
@@ -166,7 +37,7 @@ export const primaryRoutes = {
component: ProfilePage,
conditions: [
async(detail) => {
const user = await fetch('http://transcendance:8080/api/v2/user')
const user = await fetch('http://' + process.env.WEBSITE_HOST + ":" + process.env.WEBSITE_PORT + '/api/v2/user')
.then((resp) => resp.json())
console.log('in /profile/* what is in user')
@@ -179,82 +50,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
// };