seems like the merge worked
This commit is contained in:
15
srcs/requirements/game_server/Dockerfile
Normal file
15
srcs/requirements/game_server/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM node:alpine AS build
|
||||
|
||||
WORKDIR /usr/app
|
||||
|
||||
COPY ./game_back ./
|
||||
|
||||
RUN npm install typescript
|
||||
|
||||
RUN npx tsc
|
||||
|
||||
WORKDIR /usr/app/src/server
|
||||
|
||||
EXPOSE 8042
|
||||
|
||||
CMD [ "node", "wsServer.js"]
|
||||
13
srcs/requirements/game_server/game_back/jsconfig.json
Normal file
13
srcs/requirements/game_server/game_back/jsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ES2020",
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules/*"
|
||||
]
|
||||
}
|
||||
121
srcs/requirements/game_server/game_back/package-lock.json
generated
Normal file
121
srcs/requirements/game_server/game_back/package-lock.json
generated
Normal file
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"name": "game_back",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^8.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.5",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/ws": "^8.5.3",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.5.tgz",
|
||||
"integrity": "sha512-3JRwhbjI+cHLAkUorhf8RnqUbFXajvzX4q6fMn5JwkgtuwfYtRQYI3u4V92vI6NJuTsbBQWWh3RZjFsuevyMGQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||
"integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.9.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
|
||||
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz",
|
||||
"integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "18.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.5.tgz",
|
||||
"integrity": "sha512-3JRwhbjI+cHLAkUorhf8RnqUbFXajvzX4q6fMn5JwkgtuwfYtRQYI3u4V92vI6NJuTsbBQWWh3RZjFsuevyMGQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/ws": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||
"integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.9.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
|
||||
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
|
||||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
|
||||
},
|
||||
"ws": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz",
|
||||
"integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==",
|
||||
"requires": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
srcs/requirements/game_server/game_back/package.json
Normal file
13
srcs/requirements/game_server/game_back/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.5",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/ws": "^8.5.3",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^8.10.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
import { WebSocket } from "../wsServer.js";
|
||||
import { Racket } from "../../shared_js/class/Rectangle.js";
|
||||
import { GameSession } from "./GameSession.js";
|
||||
import * as ev from "../../shared_js/class/Event.js"
|
||||
import * as en from "../../shared_js/enums.js"
|
||||
|
||||
export class Client {
|
||||
socket: WebSocket;
|
||||
id: string; // same as "socket.id"
|
||||
isAlive: boolean = true;
|
||||
gameSession: GameSession = null;
|
||||
constructor(socket: WebSocket, id: string) {
|
||||
this.socket = socket;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
export class ClientPlayer extends Client {
|
||||
token: string;
|
||||
username: string;
|
||||
matchOptions: en.MatchOptions = 0;
|
||||
inputBuffer: ev.EventInput = new ev.EventInput();
|
||||
lastInputId: number = 0;
|
||||
racket: Racket;
|
||||
constructor(socket: WebSocket, id: string, racket: Racket) {
|
||||
super(socket, id);
|
||||
this.racket = racket;
|
||||
}
|
||||
}
|
||||
|
||||
export class ClientSpectator extends Client {
|
||||
constructor(socket: WebSocket, id: string) {
|
||||
super(socket, id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
import * as en from "../../shared_js/enums.js"
|
||||
import { GameComponents } from "../../shared_js/class/GameComponents.js";
|
||||
|
||||
export class GameComponentsServer extends GameComponents {
|
||||
scoreLeft: number = 0;
|
||||
scoreRight: number = 0;
|
||||
constructor(options: en.MatchOptions)
|
||||
{
|
||||
super(options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
|
||||
import * as en from "../../shared_js/enums.js"
|
||||
import * as ev from "../../shared_js/class/Event.js"
|
||||
import * as c from "../constants.js"
|
||||
import { ClientPlayer, ClientSpectator } from "./Client";
|
||||
import { GameComponentsServer } from "./GameComponentsServer.js";
|
||||
import { clientInputListener, clientTerminate } from "../wsServer.js";
|
||||
import { random } from "../utils.js";
|
||||
import { Ball } from "../../shared_js/class/Rectangle.js";
|
||||
import { wallsMovements } from "../../shared_js/wallsMovement.js";
|
||||
|
||||
/*
|
||||
multiples methods of GameSession have parameter "s: GameSession".
|
||||
its used with calls to setTimeout(),
|
||||
because "this" is not equal to the GameSession but to "this: Timeout"
|
||||
*/
|
||||
export class GameSession {
|
||||
id: string; // url ?
|
||||
playersMap: Map<string, ClientPlayer> = new Map();
|
||||
unreadyPlayersMap: Map<string, ClientPlayer> = new Map();
|
||||
spectatorsMap: Map<string, ClientSpectator> = new Map();
|
||||
gameLoopInterval: NodeJS.Timer | number = 0;
|
||||
playersUpdateInterval: NodeJS.Timer | number = 0;
|
||||
spectatorsUpdateInterval: NodeJS.Timer | number = 0;
|
||||
components: GameComponentsServer;
|
||||
matchOptions: en.MatchOptions;
|
||||
isPrivateMatch: boolean; // WIP: could be used to separate leaderboards for example.
|
||||
matchEnded: boolean = false;
|
||||
lastStateSnapshot: ev.EventGameUpdate;
|
||||
|
||||
actual_time: number;
|
||||
last_time: number;
|
||||
delta_time: number;
|
||||
|
||||
constructor(id: string, matchOptions: en.MatchOptions, isPrivateMatch: boolean = false) {
|
||||
this.id = id;
|
||||
this.matchOptions = matchOptions;
|
||||
this.isPrivateMatch = isPrivateMatch;
|
||||
this.components = new GameComponentsServer(this.matchOptions);
|
||||
}
|
||||
start() {
|
||||
const gc = this.components;
|
||||
setTimeout(this.resume, c.matchStartDelay, this);
|
||||
|
||||
let timeout = c.matchStartDelay + c.newRoundDelay;
|
||||
gc.ballsArr.forEach((ball) => {
|
||||
setTimeout(this._newRound, timeout, this, ball);
|
||||
timeout += c.newRoundDelay*0.5;
|
||||
});
|
||||
}
|
||||
resume(s?: GameSession)
|
||||
{
|
||||
if (!s) { s = this; }
|
||||
|
||||
s.playersMap.forEach( (client) => {
|
||||
client.socket.on("message", clientInputListener);
|
||||
});
|
||||
|
||||
s.actual_time = Date.now();
|
||||
s.lastStateSnapshot = s._gameStateSnapshot();
|
||||
s.gameLoopInterval = setInterval(s._gameLoop, c.serverGameLoopIntervalMS, s);
|
||||
s.playersUpdateInterval = setInterval(s._playersUpdate, c.playersUpdateIntervalMS, s);
|
||||
s.spectatorsUpdateInterval = setInterval(s._spectatorsUpdate, c.spectatorsUpdateIntervalMS, s);
|
||||
}
|
||||
pause(s?: GameSession)
|
||||
{
|
||||
if (!s) { s = this; }
|
||||
|
||||
s.playersMap.forEach( (client) => {
|
||||
client.socket.off("message", clientInputListener);
|
||||
});
|
||||
|
||||
clearInterval(s.gameLoopInterval);
|
||||
clearInterval(s.playersUpdateInterval);
|
||||
clearInterval(s.spectatorsUpdateInterval);
|
||||
}
|
||||
destroy(s?: GameSession)
|
||||
{
|
||||
if (!s) { s = this; }
|
||||
|
||||
s.pause();
|
||||
|
||||
s.spectatorsMap.forEach((client) => {
|
||||
clientTerminate(client);
|
||||
});
|
||||
s.playersMap.forEach((client) => {
|
||||
clientTerminate(client);
|
||||
});
|
||||
}
|
||||
instantInputDebug(client: ClientPlayer) {
|
||||
this._handleInput(c.fixedDeltaTime, client);
|
||||
}
|
||||
private _handleInput(delta: number, client: ClientPlayer) {
|
||||
// if (client.inputBuffer === null) {return;}
|
||||
const gc = this.components;
|
||||
const input = client.inputBuffer.input;
|
||||
|
||||
if (input === en.InputEnum.up) {
|
||||
client.racket.dir.y = -1;
|
||||
}
|
||||
else if (input === en.InputEnum.down) {
|
||||
client.racket.dir.y = 1;
|
||||
}
|
||||
|
||||
if (input !== en.InputEnum.noInput) {
|
||||
client.racket.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]);
|
||||
}
|
||||
|
||||
client.lastInputId = client.inputBuffer.id;
|
||||
// client.inputBuffer = null;
|
||||
}
|
||||
private _gameLoop(s: GameSession) {
|
||||
/* s.last_time = s.actual_time;
|
||||
s.actual_time = Date.now();
|
||||
s.delta_time = (s.actual_time - s.last_time) / 1000; */
|
||||
s.delta_time = c.fixedDeltaTime;
|
||||
|
||||
// WIP, replaced by instantInputDebug() to prevent desynchro
|
||||
/* s.playersMap.forEach( (client) => {
|
||||
s._handleInput(s.delta_time, client);
|
||||
}); */
|
||||
|
||||
const gc = s.components;
|
||||
gc.ballsArr.forEach((ball) => {
|
||||
s._ballMovement(s.delta_time, ball);
|
||||
});
|
||||
|
||||
if (s.matchOptions & en.MatchOptions.movingWalls) {
|
||||
wallsMovements(s.delta_time, gc);
|
||||
}
|
||||
}
|
||||
private _ballMovement(delta: number, ball: Ball) {
|
||||
const gc = this.components;
|
||||
if (ball.ballInPlay)
|
||||
{
|
||||
ball.moveAndBounce(delta, [gc.wallTop, gc.wallBottom, gc.playerLeft, gc.playerRight]);
|
||||
if (ball.pos.x > c.w
|
||||
|| ball.pos.x < 0 - ball.width)
|
||||
{
|
||||
ball.ballInPlay = false;
|
||||
if (this.matchEnded) {
|
||||
return;
|
||||
}
|
||||
this._scoreUpdate(ball);
|
||||
setTimeout(this._newRound, c.newRoundDelay, this, ball);
|
||||
}
|
||||
}
|
||||
}
|
||||
private _scoreUpdate(ball: Ball) {
|
||||
const gc = this.components;
|
||||
if (ball.pos.x > c.w) {
|
||||
++gc.scoreLeft;
|
||||
}
|
||||
else if (ball.pos.x < 0 - ball.width) {
|
||||
++gc.scoreRight;
|
||||
}
|
||||
const scoreUpdate = new ev.EventScoreUpdate(gc.scoreLeft, gc.scoreRight);
|
||||
this.playersMap.forEach( (client) => {
|
||||
client.socket.send(JSON.stringify(scoreUpdate));
|
||||
});
|
||||
this.spectatorsMap.forEach( (client) => {
|
||||
client.socket.send(JSON.stringify(scoreUpdate));
|
||||
});
|
||||
}
|
||||
private _playersUpdate(s: GameSession) {
|
||||
s.lastStateSnapshot = s._gameStateSnapshot();
|
||||
s.playersMap.forEach( (client) => {
|
||||
s.lastStateSnapshot.lastInputId = client.lastInputId;
|
||||
client.socket.send(JSON.stringify(s.lastStateSnapshot));
|
||||
});
|
||||
s.lastStateSnapshot.lastInputId = 0;
|
||||
}
|
||||
private _spectatorsUpdate(s: GameSession) {
|
||||
s.spectatorsMap.forEach( (client) => {
|
||||
client.socket.send(JSON.stringify(s.lastStateSnapshot));
|
||||
});
|
||||
}
|
||||
private _gameStateSnapshot() : ev.EventGameUpdate {
|
||||
const gc = this.components;
|
||||
const snapshot = new ev.EventGameUpdate();
|
||||
snapshot.playerLeft.y = gc.playerLeft.pos.y;
|
||||
snapshot.playerRight.y = gc.playerRight.pos.y;
|
||||
gc.ballsArr.forEach((ball) => {
|
||||
snapshot.ballsArr.push({
|
||||
x: ball.pos.x,
|
||||
y: ball.pos.y,
|
||||
dirX: ball.dir.x,
|
||||
dirY: ball.dir.y,
|
||||
speed: ball.speed
|
||||
});
|
||||
});
|
||||
if (this.matchOptions & en.MatchOptions.movingWalls) {
|
||||
snapshot.wallTop.y = gc.wallTop.pos.y;
|
||||
snapshot.wallBottom.y = gc.wallBottom.pos.y;
|
||||
}
|
||||
return (snapshot);
|
||||
}
|
||||
private _newRound(s: GameSession, ball: Ball) {
|
||||
if (s._checkDisconnexions()) {
|
||||
return;
|
||||
}
|
||||
// https://fr.wikipedia.org/wiki/Tennis_de_table#Nombre_de_manches
|
||||
const gc = s.components;
|
||||
const minScore = 11;// can be changed for testing
|
||||
if (gc.scoreLeft >= minScore || gc.scoreRight >= minScore)
|
||||
{
|
||||
if (Math.abs(gc.scoreLeft - gc.scoreRight) >= 2)
|
||||
{
|
||||
if (gc.scoreLeft > gc.scoreRight) {
|
||||
s._matchEnd(en.PlayerSide.left);
|
||||
}
|
||||
else {
|
||||
s._matchEnd(en.PlayerSide.right);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
ball.pos.x = c.w_mid;
|
||||
ball.pos.y = random(c.h*0.3, c.h*0.7);
|
||||
ball.speed = ball.baseSpeed;
|
||||
ball.ballInPlay = true;
|
||||
}
|
||||
private _checkDisconnexions()
|
||||
{
|
||||
if (this.playersMap.size !== 2)
|
||||
{
|
||||
this.matchEnded = true;
|
||||
if (this.playersMap.size != 0) {
|
||||
this._forfeit();
|
||||
}
|
||||
else {
|
||||
// WIP: envoyer un truc à Nest ? Genre "match draw"
|
||||
this.destroy();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private _forfeit()
|
||||
{
|
||||
this.matchEnded = true;
|
||||
console.log("Forfeit Ending");
|
||||
const gc = this.components;
|
||||
const luckyWinner: ClientPlayer = this.playersMap.values().next().value;
|
||||
if (luckyWinner.racket === gc.playerLeft) {
|
||||
this._matchEnd(en.PlayerSide.left, true);
|
||||
}
|
||||
else {
|
||||
this._matchEnd(en.PlayerSide.right, true);
|
||||
}
|
||||
}
|
||||
|
||||
private async _matchEnd(winner: en.PlayerSide, forfeit_flag: boolean = false)
|
||||
{
|
||||
this.matchEnded = true;
|
||||
const eventEnd = new ev.EventMatchEnd(winner, forfeit_flag);
|
||||
this.playersMap.forEach( (client) => {
|
||||
client.socket.send(JSON.stringify(eventEnd));
|
||||
});
|
||||
this.spectatorsMap.forEach( (client) => {
|
||||
client.socket.send(JSON.stringify(eventEnd));
|
||||
});
|
||||
|
||||
// TODO: mettre à jour la route pour gerer les forfaits (actuellement le plus haut score gagne par defaut)
|
||||
const gc = this.components;
|
||||
console.log("================================= MATCH ENDED");
|
||||
if (forfeit_flag) {
|
||||
if (winner === en.PlayerSide.left)
|
||||
{
|
||||
gc.scoreLeft = 3
|
||||
gc.scoreRight = 0
|
||||
}
|
||||
else
|
||||
{
|
||||
gc.scoreLeft = 0
|
||||
gc.scoreRight = 3
|
||||
}
|
||||
}
|
||||
await fetch(c.addressBackEnd + "/game/gameserver/updategame",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
gameServerIdOfTheMatch: this.id,
|
||||
playerOneUsernameResult: gc.scoreLeft,
|
||||
playerTwoUsernameResult: gc.scoreRight,
|
||||
})
|
||||
});
|
||||
|
||||
setTimeout(this.destroy, 15000, this);
|
||||
|
||||
// logs
|
||||
if (winner === en.PlayerSide.left) {
|
||||
console.log("Player Left WIN");
|
||||
}
|
||||
else {
|
||||
console.log("Player Right WIN");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
export * from "../shared_js/constants.js"
|
||||
|
||||
// 15ms == 1000/66.666
|
||||
export const serverGameLoopIntervalMS = 15; // millisecond
|
||||
export const fixedDeltaTime = serverGameLoopIntervalMS/1000; // second
|
||||
|
||||
// 33.333ms == 1000/30
|
||||
export const playersUpdateIntervalMS = 1000/30; // millisecond
|
||||
export const spectatorsUpdateIntervalMS = 1000/30; // millisecond
|
||||
|
||||
export const addressBackEnd = "http://backend_dev:3000/api/v2";
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
export * from "../shared_js/utils.js"
|
||||
|
||||
export function shortId(id: string): string {
|
||||
return id.substring(0, id.indexOf("-"));
|
||||
}
|
||||
426
srcs/requirements/game_server/game_back/src/server/wsServer.ts
Normal file
426
srcs/requirements/game_server/game_back/src/server/wsServer.ts
Normal file
@@ -0,0 +1,426 @@
|
||||
|
||||
import { WebSocketServer, WebSocket as BaseLibWebSocket } from "ws";
|
||||
|
||||
export class WebSocket extends BaseLibWebSocket {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
import { IncomingMessage } from "http";
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import * as en from "../shared_js/enums.js"
|
||||
import * as ev from "../shared_js/class/Event.js"
|
||||
import * as c from "./constants.js"
|
||||
import { Client, ClientPlayer, ClientSpectator } from "./class/Client.js"
|
||||
import { GameSession } from "./class/GameSession.js"
|
||||
import { shortId } from "./utils.js";
|
||||
|
||||
const wsPort = 8042;
|
||||
export const wsServer = new WebSocketServer<WebSocket>({host: "0.0.0.0", port: wsPort, path: "/pong"});
|
||||
const clientsMap: Map<string, Client> = new Map; // socket.id/Client
|
||||
const matchmakingMap: Map<string, ClientPlayer> = new Map; // socket.id/ClientPlayer (duplicates with clientsMap)
|
||||
const privateMatchmakingMap: Map<string, ClientPlayer> = new Map; // socket.id/ClientPlayer (duplicates with clientsMap)
|
||||
const gameSessionsMap: Map<string, GameSession> = new Map; // GameSession.id(url)/GameSession
|
||||
|
||||
wsServer.on("connection", serverConnectionListener);
|
||||
wsServer.on("error", serverErrorListener);
|
||||
wsServer.on("close", serverCloseListener);
|
||||
|
||||
|
||||
function serverConnectionListener(socket: WebSocket, request: IncomingMessage)
|
||||
{
|
||||
const id = uuidv4();
|
||||
const client = new Client(socket, id);
|
||||
clientsMap.set(id, client);
|
||||
socket.id = id;
|
||||
|
||||
socket.on("pong", function heartbeat() {
|
||||
client.isAlive = true;
|
||||
console.log(`client ${shortId(client.id)} is alive`);
|
||||
});
|
||||
|
||||
socket.on("close", function removeClient() {
|
||||
clientTerminate(client);
|
||||
});
|
||||
|
||||
socket.on("error", function errorLog(this: WebSocket, err: Error) {
|
||||
console.log(`error socket ${shortId(this.id)}:`);
|
||||
console.log(`${err.name}: ${err.message}`);
|
||||
if (err.stack) {
|
||||
console.log(`err.stack: ${err.stack}`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("message", function messageLog(data: string) {
|
||||
try {
|
||||
const event: ev.ClientEvent = JSON.parse(data);
|
||||
if (event.type === en.EventTypes.clientInput) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
console.log("data: " + data);
|
||||
});
|
||||
|
||||
socket.once("message", clientAnnounceListener);
|
||||
}
|
||||
|
||||
|
||||
async function clientAnnounceListener(this: WebSocket, data: string)
|
||||
{
|
||||
try {
|
||||
const msg : ev.ClientAnnounce = JSON.parse(data);
|
||||
if (msg.type === en.EventTypes.clientAnnounce)
|
||||
{
|
||||
// BONUS: reconnection with msg.clientId ?
|
||||
if (msg.role === en.ClientRole.player)
|
||||
{
|
||||
const announce: ev.ClientAnnouncePlayer = <ev.ClientAnnouncePlayer>msg;
|
||||
|
||||
const body = {
|
||||
playerOneUsername: announce.username,
|
||||
playerTwoUsername: "",
|
||||
gameOptions: announce.matchOptions,
|
||||
isGameIsWithInvitation: announce.privateMatch,
|
||||
token: announce.token,
|
||||
};
|
||||
if (announce.privateMatch) {
|
||||
body.playerTwoUsername = announce.playerTwoUsername;
|
||||
}
|
||||
const response = await fetch(c.addressBackEnd + "/game/gameserver/validate",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (!response.ok)
|
||||
{
|
||||
this.send(JSON.stringify( new ev.EventError((await response.json()).message) ));
|
||||
clientTerminate(clientsMap.get(this.id));
|
||||
return;
|
||||
}
|
||||
|
||||
const player = clientsMap.get(this.id) as ClientPlayer;
|
||||
player.matchOptions = announce.matchOptions;
|
||||
player.token = announce.token;
|
||||
player.username = announce.username;
|
||||
this.send(JSON.stringify( new ev.EventAssignId(this.id) )); // unused
|
||||
this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchmakingInProgress) ));
|
||||
if (announce.privateMatch) {
|
||||
if (announce.isInvitedPerson) {
|
||||
player.username = announce.playerTwoUsername;
|
||||
}
|
||||
privateMatchmaking(player);
|
||||
}
|
||||
else {
|
||||
publicMatchmaking(player);
|
||||
}
|
||||
}
|
||||
else if (msg.role === en.ClientRole.spectator)
|
||||
{
|
||||
const announce: ev.ClientAnnounceSpectator = <ev.ClientAnnounceSpectator>msg;
|
||||
const gameSession = gameSessionsMap.get(announce.gameSessionId);
|
||||
if (!gameSession) {
|
||||
this.send(JSON.stringify( new ev.EventError("invalid gameSessionId") ));
|
||||
clientTerminate(clientsMap.get(this.id));
|
||||
return;
|
||||
}
|
||||
const spectator = clientsMap.get(this.id) as ClientSpectator;
|
||||
spectator.gameSession = gameSession;
|
||||
gameSession.spectatorsMap.set(spectator.id, spectator);
|
||||
spectator.socket.once("message", spectatorReadyConfirmationListener);
|
||||
this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchStart) ));
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("Invalid ClientAnnounce");
|
||||
}
|
||||
return;
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Invalid JSON (clientAnnounceListener)");
|
||||
}
|
||||
this.once("message", clientAnnounceListener);
|
||||
}
|
||||
|
||||
|
||||
function publicMatchmaking(player: ClientPlayer)
|
||||
{
|
||||
const minPlayersNumber = 2;
|
||||
const maxPlayersNumber = 2;
|
||||
matchmakingMap.set(player.id, player);
|
||||
const matchOptions = player.matchOptions;
|
||||
|
||||
const compatiblePlayers: ClientPlayer[] = [];
|
||||
for (const [id, client] of matchmakingMap)
|
||||
{
|
||||
if (client.matchOptions === matchOptions)
|
||||
{
|
||||
compatiblePlayers.push(client);
|
||||
if (compatiblePlayers.length === maxPlayersNumber) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (compatiblePlayers.length >= minPlayersNumber) {
|
||||
compatiblePlayers.forEach((client) => {
|
||||
matchmakingMap.delete(client.id);
|
||||
});
|
||||
createGameSession(compatiblePlayers, matchOptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function privateMatchmaking(player: ClientPlayer)
|
||||
{
|
||||
const minPlayersNumber = 2;
|
||||
const maxPlayersNumber = 2;
|
||||
privateMatchmakingMap.set(player.id, player);
|
||||
const matchOptions = player.matchOptions;
|
||||
|
||||
const token = player.token;
|
||||
const compatiblePlayers: ClientPlayer[] = [];
|
||||
for (const [id, client] of privateMatchmakingMap)
|
||||
{
|
||||
if (client.token === token)
|
||||
{
|
||||
compatiblePlayers.push(client);
|
||||
if (compatiblePlayers.length === maxPlayersNumber) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (compatiblePlayers.length >= minPlayersNumber) {
|
||||
compatiblePlayers.forEach((client) => {
|
||||
privateMatchmakingMap.delete(client.id);
|
||||
});
|
||||
createGameSession(compatiblePlayers, matchOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
setTimeout(async function abortMatch() {
|
||||
if (!player.gameSession)
|
||||
{
|
||||
if (player.socket.OPEN) {
|
||||
player.socket.send(JSON.stringify( new ev.EventMatchAbort() ));
|
||||
}
|
||||
const response = await fetch(c.addressBackEnd + "/game/gameserver/destroysession",{
|
||||
method: "POST",
|
||||
headers : {"Content-Type": "application/json"},
|
||||
body : JSON.stringify({
|
||||
token : player.token
|
||||
})
|
||||
})
|
||||
.then(x => x.json())
|
||||
.catch(error => console.log("ERROR : " + error));
|
||||
clientTerminate(player);
|
||||
}
|
||||
}, 60000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createGameSession(playersArr: ClientPlayer[], matchOptions: en.MatchOptions)
|
||||
{
|
||||
// const id = c.gameSessionIdPLACEHOLDER; // Force ID, TESTING SPECTATOR
|
||||
const id = uuidv4();
|
||||
const gameSession = new GameSession(id, matchOptions);
|
||||
gameSessionsMap.set(id, gameSession);
|
||||
|
||||
playersArr.forEach((client) => {
|
||||
client.gameSession = gameSession;
|
||||
gameSession.playersMap.set(client.id, client);
|
||||
gameSession.unreadyPlayersMap.set(client.id, client);
|
||||
client.socket.once("message", playerReadyConfirmationListener);
|
||||
});
|
||||
|
||||
// REFACTORING: Not pretty, hardcoded two players.
|
||||
// Could be done in gameSession maybe ?
|
||||
const gameSessionPlayersIterator = gameSession.playersMap.values();
|
||||
let player: ClientPlayer;
|
||||
player = (<ClientPlayer>gameSessionPlayersIterator.next().value);
|
||||
player.racket = gameSession.components.playerLeft;
|
||||
player.socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.left) ));
|
||||
|
||||
player = (<ClientPlayer>gameSessionPlayersIterator.next().value);
|
||||
player.racket = gameSession.components.playerRight;
|
||||
player.socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.right) ));
|
||||
// REFACTORING
|
||||
|
||||
setTimeout(function abortMatch() {
|
||||
if (gameSession.unreadyPlayersMap.size !== 0)
|
||||
{
|
||||
gameSessionsMap.delete(gameSession.id);
|
||||
gameSession.playersMap.forEach((client) => {
|
||||
client.socket.send(JSON.stringify( new ev.EventMatchAbort() ));
|
||||
client.gameSession = null;
|
||||
clientTerminate(client);
|
||||
});
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
|
||||
async function playerReadyConfirmationListener(this: WebSocket, data: string)
|
||||
{
|
||||
try {
|
||||
const msg : ev.ClientEvent = JSON.parse(data);
|
||||
if (msg.type === en.EventTypes.clientPlayerReady)
|
||||
{
|
||||
const client = clientsMap.get(this.id);
|
||||
const gameSession = client.gameSession;
|
||||
gameSession.unreadyPlayersMap.delete(this.id);
|
||||
if (gameSession.unreadyPlayersMap.size === 0)
|
||||
{
|
||||
const gameSessionPlayersIterator = gameSession.playersMap.values();
|
||||
const body = {
|
||||
gameServerIdOfTheMatch : gameSession.id,
|
||||
gameOptions: gameSession.matchOptions,
|
||||
playerOneUsername: (<ClientPlayer>gameSessionPlayersIterator.next().value).username,
|
||||
playerTwoUsername: (<ClientPlayer>gameSessionPlayersIterator.next().value).username,
|
||||
playerOneUsernameResult : 0,
|
||||
playerTwoUsernameResult : 0
|
||||
};
|
||||
const response = await fetch(c.addressBackEnd + "/game/gameserver/creategame",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (!response.ok)
|
||||
{
|
||||
gameSessionsMap.delete(gameSession.id);
|
||||
gameSession.playersMap.forEach((client) => {
|
||||
client.socket.send(JSON.stringify( new ev.EventMatchAbort() ));
|
||||
client.gameSession = null;
|
||||
clientTerminate(client);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
gameSession.playersMap.forEach( (client) => {
|
||||
client.socket.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchStart) ));
|
||||
});
|
||||
gameSession.start();
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("Invalid playerReadyConfirmation");
|
||||
}
|
||||
return;
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Invalid JSON (playerReadyConfirmationListener)");
|
||||
}
|
||||
this.once("message", playerReadyConfirmationListener);
|
||||
}
|
||||
|
||||
|
||||
export function clientInputListener(this: WebSocket, data: string)
|
||||
{
|
||||
try {
|
||||
// const input: ev.ClientEvent = JSON.parse(data);
|
||||
const input: ev.EventInput = JSON.parse(data);
|
||||
if (input.type === en.EventTypes.clientInput)
|
||||
{
|
||||
const client = clientsMap.get(this.id) as ClientPlayer;
|
||||
client.inputBuffer = input;
|
||||
client.gameSession.instantInputDebug(client); // wip
|
||||
}
|
||||
else {
|
||||
console.log("Invalid clientInput");
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Invalid JSON (clientInputListener)");
|
||||
}
|
||||
}
|
||||
|
||||
function spectatorReadyConfirmationListener(this: WebSocket, data: string)
|
||||
{
|
||||
try {
|
||||
const msg : ev.ClientEvent = JSON.parse(data);
|
||||
if (msg.type === en.EventTypes.clientSpectatorReady)
|
||||
{
|
||||
const client = clientsMap.get(this.id);
|
||||
const gameSession = client.gameSession;
|
||||
const scoreUpdate = new ev.EventScoreUpdate(gameSession.components.scoreLeft, gameSession.components.scoreRight);
|
||||
this.send(JSON.stringify(scoreUpdate));
|
||||
}
|
||||
else {
|
||||
console.log("Invalid spectatorReadyConfirmation");
|
||||
}
|
||||
return;
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Invalid JSON (spectatorReadyConfirmationListener)");
|
||||
}
|
||||
this.once("message", spectatorReadyConfirmationListener);
|
||||
}
|
||||
|
||||
////////////
|
||||
////////////
|
||||
|
||||
const pingInterval = setInterval( () => {
|
||||
let deleteLog = "";
|
||||
clientsMap.forEach( (client) => {
|
||||
if (!client.isAlive) {
|
||||
clientTerminate(client);
|
||||
deleteLog += ` ${shortId(client.id)} |`;
|
||||
}
|
||||
else {
|
||||
client.isAlive = false;
|
||||
client.socket.ping();
|
||||
}
|
||||
});
|
||||
|
||||
if (deleteLog) {
|
||||
console.log(`Disconnected:${deleteLog}`);
|
||||
}
|
||||
console.log("gameSessionMap size: " + gameSessionsMap.size);
|
||||
console.log("clientsMap size: " + clientsMap.size);
|
||||
console.log("matchmakingMap size: " + matchmakingMap.size);
|
||||
console.log("privateMatchmakingMap size: " + privateMatchmakingMap.size);
|
||||
console.log("");
|
||||
}, 4200);
|
||||
|
||||
|
||||
export function clientTerminate(client: Client)
|
||||
{
|
||||
client.socket.terminate();
|
||||
if (client.gameSession)
|
||||
{
|
||||
client.gameSession.playersMap.delete(client.id);
|
||||
client.gameSession.spectatorsMap.delete(client.id);
|
||||
if (client.gameSession.playersMap.size === 0)
|
||||
{
|
||||
client.gameSession.destroy();
|
||||
gameSessionsMap.delete(client.gameSession.id);
|
||||
}
|
||||
}
|
||||
clientsMap.delete(client.id);
|
||||
if (matchmakingMap.has(client.id)) {
|
||||
matchmakingMap.delete(client.id);
|
||||
}
|
||||
else if (privateMatchmakingMap.has(client.id)) {
|
||||
privateMatchmakingMap.delete(client.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function serverCloseListener()
|
||||
{
|
||||
clearInterval(pingInterval);
|
||||
}
|
||||
|
||||
|
||||
function serverErrorListener(error: Error)
|
||||
{
|
||||
console.log("Error: " + JSON.stringify(error));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { VectorInteger } from "./Vector.js";
|
||||
import { Rectangle, MovingRectangle, Racket, Ball } from "./Rectangle.js";
|
||||
import { random } from "../utils.js";
|
||||
|
||||
class GameComponents {
|
||||
export class GameComponents {
|
||||
wallTop: Rectangle | MovingRectangle;
|
||||
wallBottom: Rectangle | MovingRectangle;
|
||||
playerLeft: Racket;
|
||||
@@ -61,5 +61,3 @@ class GameComponents {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {GameComponents}
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
import { Vector, VectorInteger } from "./Vector.js";
|
||||
import { Component, Moving } from "./interface.js";
|
||||
import type { Component, Moving } from "./interface.js";
|
||||
import * as c from "../constants.js"
|
||||
|
||||
class Rectangle implements Component {
|
||||
export class Rectangle implements Component {
|
||||
pos: VectorInteger;
|
||||
width: number;
|
||||
height: number;
|
||||
@@ -33,7 +33,7 @@ class Rectangle implements Component {
|
||||
}
|
||||
}
|
||||
|
||||
class MovingRectangle extends Rectangle implements Moving {
|
||||
export class MovingRectangle extends Rectangle implements Moving {
|
||||
dir: Vector = new Vector(0,0);
|
||||
speed: number;
|
||||
readonly baseSpeed: number;
|
||||
@@ -61,7 +61,7 @@ class MovingRectangle extends Rectangle implements Moving {
|
||||
}
|
||||
}
|
||||
|
||||
class Racket extends MovingRectangle {
|
||||
export class Racket extends MovingRectangle {
|
||||
constructor(pos: VectorInteger, width: number, height: number, baseSpeed: number) {
|
||||
super(pos, width, height, baseSpeed);
|
||||
}
|
||||
@@ -72,13 +72,22 @@ class Racket extends MovingRectangle {
|
||||
}
|
||||
}
|
||||
|
||||
class Ball extends MovingRectangle {
|
||||
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);
|
||||
}
|
||||
@@ -92,15 +101,6 @@ class Ball extends MovingRectangle {
|
||||
this._bounceWall();
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
protected _bounceWall() { // Should be enough for Wall
|
||||
this.dir.y = this.dir.y * -1;
|
||||
}
|
||||
@@ -140,5 +140,3 @@ class Ball extends MovingRectangle {
|
||||
// console.log(`x: ${this.dir.x}, y: ${this.dir.y}`);
|
||||
}
|
||||
}
|
||||
|
||||
export {Rectangle, MovingRectangle, Racket, Ball}
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
class Vector {
|
||||
export class Vector {
|
||||
x: number;
|
||||
y: number;
|
||||
constructor(x: number = 0, y: number = 0) {
|
||||
@@ -16,13 +16,13 @@ class Vector {
|
||||
}
|
||||
}
|
||||
|
||||
class VectorInteger extends Vector {
|
||||
export class VectorInteger extends Vector {
|
||||
// PLACEHOLDER
|
||||
// VectorInteger with set/get dont work (No draw on the screen). Why ?
|
||||
}
|
||||
|
||||
/*
|
||||
class VectorInteger {
|
||||
export class VectorInteger {
|
||||
// private _x: number = 0;
|
||||
// private _y: number = 0;
|
||||
// constructor(x: number = 0, y: number = 0) {
|
||||
@@ -45,5 +45,3 @@ class VectorInteger {
|
||||
// }
|
||||
}
|
||||
*/
|
||||
|
||||
export {Vector, VectorInteger}
|
||||
@@ -1,21 +1,19 @@
|
||||
|
||||
import { Vector, VectorInteger } from "./Vector.js";
|
||||
import type { Vector, VectorInteger } from "./Vector.js";
|
||||
|
||||
interface Component {
|
||||
export interface Component {
|
||||
pos: VectorInteger;
|
||||
}
|
||||
|
||||
interface GraphicComponent extends Component {
|
||||
export interface GraphicComponent extends Component {
|
||||
ctx: CanvasRenderingContext2D;
|
||||
color: string;
|
||||
update: () => void;
|
||||
clear: (pos?: VectorInteger) => void;
|
||||
}
|
||||
|
||||
interface Moving {
|
||||
export interface Moving {
|
||||
dir: Vector;
|
||||
speed: number; // pixel per second
|
||||
move(delta: number): void;
|
||||
}
|
||||
|
||||
export {Component, GraphicComponent, Moving}
|
||||
@@ -11,8 +11,8 @@ 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.66); // pixel per second
|
||||
export const ballSpeed = Math.floor(w*0.66); // pixel per second
|
||||
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
|
||||
@@ -24,3 +24,7 @@ export const newRoundDelay = 1500; // millisecond
|
||||
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()
|
||||
@@ -1,15 +1,17 @@
|
||||
|
||||
enum EventTypes {
|
||||
export enum EventTypes {
|
||||
// Class Implemented
|
||||
gameUpdate = 1,
|
||||
scoreUpdate,
|
||||
matchEnd,
|
||||
assignId,
|
||||
matchmakingComplete,
|
||||
error,
|
||||
|
||||
// Generic
|
||||
matchmakingInProgress,
|
||||
matchStart,
|
||||
matchAbort,
|
||||
matchNewRound, // unused
|
||||
matchPause, // unused
|
||||
matchResume, // unused
|
||||
@@ -17,31 +19,30 @@ enum EventTypes {
|
||||
// Client
|
||||
clientAnnounce,
|
||||
clientPlayerReady,
|
||||
clientSpectatorReady,
|
||||
clientInput,
|
||||
|
||||
}
|
||||
|
||||
enum InputEnum {
|
||||
export enum InputEnum {
|
||||
noInput = 0,
|
||||
up = 1,
|
||||
down,
|
||||
}
|
||||
|
||||
enum PlayerSide {
|
||||
export enum PlayerSide {
|
||||
left = 1,
|
||||
right
|
||||
}
|
||||
|
||||
enum ClientRole {
|
||||
export enum ClientRole {
|
||||
player = 1,
|
||||
spectator
|
||||
}
|
||||
|
||||
enum MatchOptions {
|
||||
export enum MatchOptions {
|
||||
// binary flags, can be mixed
|
||||
noOption = 0b0,
|
||||
multiBalls = 1 << 0,
|
||||
movingWalls = 1 << 1
|
||||
}
|
||||
|
||||
export {EventTypes, InputEnum, PlayerSide, ClientRole, MatchOptions}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
import * as c from "./constants.js";
|
||||
import { MovingRectangle } from "../shared_js/class/Rectangle.js";
|
||||
import { GameComponents } from "./class/GameComponents.js";
|
||||
import type { MovingRectangle } from "../shared_js/class/Rectangle.js";
|
||||
import type { GameComponents } from "./class/GameComponents.js";
|
||||
|
||||
function wallsMovements(delta: number, gc: GameComponents)
|
||||
export function wallsMovements(delta: number, gc: GameComponents)
|
||||
{
|
||||
const wallTop = <MovingRectangle>gc.wallTop;
|
||||
const wallBottom = <MovingRectangle>gc.wallBottom;
|
||||
@@ -16,5 +16,3 @@ function wallsMovements(delta: number, gc: GameComponents)
|
||||
wallTop.moveAndCollide(delta, [gc.playerLeft, gc.playerRight]);
|
||||
wallBottom.moveAndCollide(delta, [gc.playerLeft, gc.playerRight]);
|
||||
}
|
||||
|
||||
export {wallsMovements}
|
||||
103
srcs/requirements/game_server/game_back/tsconfig.json
Normal file
103
srcs/requirements/game_server/game_back/tsconfig.json
Normal file
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "ES6", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
"strictNullChecks": false, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,8 @@ FROM node:alpine AS development
|
||||
WORKDIR /usr/app
|
||||
|
||||
COPY ./api_back ./
|
||||
COPY ./api_back/.env ./.env
|
||||
COPY ./api_back/src/uploads/avatars/default.png ./uploads/avatars/default.png
|
||||
|
||||
RUN npm ci
|
||||
|
||||
RUN npm i
|
||||
|
||||
CMD [ "npm", "run", "start:dev" ]
|
||||
|
||||
2209
srcs/requirements/nestjs/api_back/package-lock.json
generated
2209
srcs/requirements/nestjs/api_back/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -24,13 +24,13 @@
|
||||
"@nestjs/common": "^9.0.0",
|
||||
"@nestjs/config": "^2.2.0",
|
||||
"@nestjs/core": "^9.0.0",
|
||||
"@nestjs/jwt": "^9.0.0",
|
||||
"@nestjs/mapped-types": "^1.2.0",
|
||||
"@nestjs/passport": "^9.0.0",
|
||||
"@nestjs/platform-express": "^9.0.0",
|
||||
"@nestjs/platform-socket.io": "^9.2.1",
|
||||
"@nestjs/typeorm": "^9.0.1",
|
||||
"@nestjs/websockets": "^9.2.1",
|
||||
"@types/express-session": "^1.17.5",
|
||||
"@types/redis": "^4.0.11",
|
||||
"@types/validator": "^13.7.9",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.13.2",
|
||||
@@ -41,7 +41,6 @@
|
||||
"otplib": "^12.0.1",
|
||||
"passport": "^0.6.0",
|
||||
"passport-42": "^1.2.6",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"pg": "^8.8.0",
|
||||
"qrcode": "^1.5.1",
|
||||
@@ -49,6 +48,7 @@
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.2.0",
|
||||
"socket.io": "^4.5.4",
|
||||
"typeorm": "^0.3.10",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
@@ -59,8 +59,7 @@
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "28.1.8",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^16.0.0",
|
||||
"@types/passport-jwt": "^3.0.7",
|
||||
"@types/node": "^16.18.11",
|
||||
"@types/passport-local": "^1.0.34",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||
|
||||
@@ -7,29 +7,35 @@ import { ConfigModule } from '@nestjs/config';
|
||||
import { FriendshipsModule } from './friendship/friendships.module';
|
||||
import { AuthenticationModule } from './auth/42/authentication.module';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
// import { GameModule } from './game/game/game.module';
|
||||
import { GameModule } from './game/game.module';
|
||||
import { ChatGateway } from './chat/chat.gateway';
|
||||
|
||||
@Module({
|
||||
imports: [UsersModule,
|
||||
AuthenticationModule,
|
||||
PassportModule.register({ session: true }),
|
||||
FriendshipsModule,
|
||||
ConfigModule.forRoot(),
|
||||
TypeOrmModule.forRoot({
|
||||
type: 'postgres',
|
||||
host: process.env.POSTGRES_HOST,
|
||||
port: parseInt(process.env.POSTGRES_PORT),
|
||||
username: process.env.POSTGRES_USER,
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
database: process.env.POSTGRES_DATABASE,
|
||||
autoLoadEntities: true,
|
||||
//ne pas synchroniser quand on est en prod. Trouver un moyen de set ça, sûrement
|
||||
//avec une classe pour le module
|
||||
synchronize: true,
|
||||
}),
|
||||
// GameModule,
|
||||
],
|
||||
imports: [
|
||||
UsersModule,
|
||||
AuthenticationModule,
|
||||
PassportModule.register({ session: true }),
|
||||
FriendshipsModule,
|
||||
GameModule,
|
||||
ConfigModule.forRoot(),
|
||||
TypeOrmModule.forRoot({
|
||||
type: 'postgres',
|
||||
host: process.env.POSTGRES_HOST,
|
||||
port: parseInt(process.env.POSTGRES_PORT),
|
||||
username: process.env.POSTGRES_USER,
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
database: process.env.POSTGRES_DATABASE,
|
||||
autoLoadEntities: true,
|
||||
//ne pas synchroniser quand on est en prod. Trouver un moyen de set ça, sûrement
|
||||
//avec une classe pour le module
|
||||
synchronize: true,
|
||||
}),
|
||||
// GameModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
providers: [
|
||||
AppService,
|
||||
ChatGateway,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -37,10 +37,10 @@ export class AuthenticationController {
|
||||
const user : User = request.user
|
||||
if (user.isEnabledTwoFactorAuth === false || user.isTwoFactorAuthenticated === true){
|
||||
console.log('ON VA VERS PROFILE');
|
||||
return response.status(200).redirect('http://transcendance:8080/#/profile');
|
||||
return response.status(200).redirect('http://' + process.env.WEBSITE_HOST + ':' + process.env.WEBSITE_PORT + '/#/profile');
|
||||
}
|
||||
console.log('ON VA VERS 2FA')
|
||||
return response.status(200).redirect('http://transcendance:8080/#/2fa');
|
||||
return response.status(200).redirect('http://' + process.env.WEBSITE_HOST + ':' + process.env.WEBSITE_PORT + '/#/2fa');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,6 +83,6 @@ export class AuthenticationController {
|
||||
throw new UnauthorizedException('Wrong Code.');
|
||||
await this.userService.authenticateUserWith2FA(request.user.id);
|
||||
console.log('ON REDIRIGE');
|
||||
return response.status(200).redirect('http://transcendance:8080/#/profile');
|
||||
return response.status(200).redirect('http://' + process.env.WEBSITE_HOST + ':' + process.env.WEBSITE_PORT + '/#/profile');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { CreateUsersDto } from "src/users/dto/create-users.dto";
|
||||
async validate(accessToken: string, refreshToken: string, profile: Profile, callbackURL: string) {
|
||||
console.log("Validate inside strategy.ts");
|
||||
console.log(profile.id, profile.username, profile.phoneNumbers[0].value, profile.emails[0].value, profile.photos[0].value);
|
||||
const userDTO: CreateUsersDto = { fortyTwoId: profile.id, username: profile.username, email: profile.emails[0].value, image_url: 'default.png', isEnabledTwoFactorAuth: false , status: "connected" };
|
||||
const userDTO: CreateUsersDto = { fortyTwoId: profile.id, username: profile.username, email: profile.emails[0].value, image_url: 'default.png', isEnabledTwoFactorAuth: false , status: "Connected" };
|
||||
const user = await this.authenticationService.validateUser(userDTO);
|
||||
if (!user)
|
||||
throw new UnauthorizedException();
|
||||
|
||||
44
srcs/requirements/nestjs/api_back/src/chat/chat.gateway.ts
Normal file
44
srcs/requirements/nestjs/api_back/src/chat/chat.gateway.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
WebSocketGateway,
|
||||
SubscribeMessage,
|
||||
WebSocketServer,
|
||||
MessageBody,
|
||||
OnGatewayConnection,
|
||||
OnGatewayDisconnect,
|
||||
} from '@nestjs/websockets';
|
||||
import { UsersService } from 'src/users/users.service';
|
||||
import { PaginationQueryDto } from 'src/common/dto/pagination-query.dto';
|
||||
|
||||
@WebSocketGateway(5000, {
|
||||
path: '/chat',
|
||||
})
|
||||
|
||||
export class ChatGateway
|
||||
implements OnGatewayConnection, OnGatewayDisconnect
|
||||
{
|
||||
constructor
|
||||
(
|
||||
private usersService: UsersService,
|
||||
) {}
|
||||
|
||||
@WebSocketServer()
|
||||
server;
|
||||
|
||||
// how to guard the handleConnection ?
|
||||
// https://github.com/nestjs/nest/issues/882
|
||||
async handleConnection(client) {
|
||||
// const paginationQuery = new PaginationQueryDto();
|
||||
// const users = await this.usersService.findAll(paginationQuery);
|
||||
|
||||
// const users = await this.usersService.findAll(client);
|
||||
// const users = await this.usersService.findAll(client);
|
||||
console.log('---- Client connected :', client.id);
|
||||
// console.log('users :', users);
|
||||
}
|
||||
handleDisconnect(client) {
|
||||
console.log('---- client disconnected :', client.id);
|
||||
}
|
||||
/*
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -8,6 +8,12 @@ const MIME_TYPES = {
|
||||
'image/png': 'png'
|
||||
};
|
||||
|
||||
export enum STATUS {
|
||||
CONNECTED = 'Connected',
|
||||
DISCONNECTED = 'Disconnected',
|
||||
IN_GAME = 'In Game',
|
||||
IN_POOL = 'In Pool',
|
||||
}
|
||||
|
||||
export const storageForAvatar = {
|
||||
storage: diskStorage({
|
||||
@@ -18,5 +24,13 @@ export const storageForAvatar = {
|
||||
const extension : string = MIME_TYPES[file.mimetype];
|
||||
cb(null, `${filename}${extension}`);
|
||||
}
|
||||
})
|
||||
}),
|
||||
fileFilter: (req, file, cb) => {
|
||||
if (file.mimetype === 'image/png' || file.mimetype === 'image/jpg' || file.mimetype === 'image/jpeg') {
|
||||
cb(null, true);
|
||||
}
|
||||
else {
|
||||
cb(null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { IsBoolean, IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class CreateUsersDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
readonly username: string;
|
||||
readonly fortyTwoId: string;
|
||||
@IsEmail()
|
||||
readonly email: string;
|
||||
@IsString()
|
||||
readonly image_url: string;
|
||||
@IsString()
|
||||
readonly status: string;
|
||||
@IsBoolean()
|
||||
readonly isEnabledTwoFactorAuth: boolean;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { IsBoolean, IsNotEmpty, IsNumber, IsString } from "class-validator";
|
||||
|
||||
export class CreateGameDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
gameServerIdOfTheMatch : string
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
gameOptions: number
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
playerOneUsername : string
|
||||
@IsString()
|
||||
playerTwoUsername : string
|
||||
@IsNumber()
|
||||
playerTwoUsernameResult : number
|
||||
@IsNumber()
|
||||
playerOneUsernameResult : number
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { IsBoolean, IsEmpty, IsInt, IsNotEmpty, IsNumber, IsString } from "class-validator";
|
||||
import { IsNull } from "typeorm";
|
||||
|
||||
export class GrantTicketDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
playerOneUsername : string
|
||||
@IsString()
|
||||
playerTwoUsername : string
|
||||
@IsNumber()
|
||||
gameOptions : number
|
||||
@IsBoolean()
|
||||
isGameIsWithInvitation : boolean
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { OmitType } from "@nestjs/mapped-types";
|
||||
import { IsBoolean, IsNotEmpty, IsNumber, IsString } from "class-validator";
|
||||
import { CreateGameDto } from "./createGame.dto";
|
||||
|
||||
export class UpdateGameDto extends OmitType(CreateGameDto, ['playerOneUsername', 'playerTwoUsername', 'gameOptions'] as const){}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { IsBase64, IsBoolean, IsEmpty, IsNotEmpty, IsNumber, IsString } from "class-validator";
|
||||
|
||||
export class ValidateTicketDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
playerOneUsername : string
|
||||
@IsString()
|
||||
playerTwoUsername : string
|
||||
@IsNumber()
|
||||
gameOptions : number
|
||||
@IsBoolean()
|
||||
isGameIsWithInvitation : boolean
|
||||
@IsBase64()
|
||||
@IsNotEmpty()
|
||||
token : string
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity('game')
|
||||
export class Game {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
playerOneUsername: string
|
||||
|
||||
@Column()
|
||||
playerTwoUsername: string
|
||||
|
||||
@Column({default : 0, nullable : true})
|
||||
playerOneUsernameResult : number
|
||||
|
||||
@Column({default : 0, nullable : true})
|
||||
playerTwoUsernameResult : number
|
||||
|
||||
@Column({default : 0})
|
||||
gameOptions: number
|
||||
|
||||
@Column({unique : true})
|
||||
gameServerIdOfTheMatch: string
|
||||
|
||||
@Column({default: false, nullable : true}) //éric pourra trouver un meilleur mot : ongoing ?
|
||||
isMatchIsFinished: boolean
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
@Entity('tokenGame')
|
||||
export class TokenGame {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
@Column()
|
||||
playerOneUsername : string
|
||||
@Column({nullable: true})
|
||||
playerTwoUsername : string
|
||||
@Column()
|
||||
gameOptions : number
|
||||
@Column()
|
||||
isGameIsWithInvitation : boolean
|
||||
@Column({default: 0, nullable: true})
|
||||
numberOfRegisteredUser : number
|
||||
@Column({default : false})
|
||||
isSecondUserAcceptedRequest : boolean
|
||||
@Column()
|
||||
token : string
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
|
||||
|
||||
@Entity('gameParty')
|
||||
export class gameParty {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
playerOne: string
|
||||
|
||||
@Column()
|
||||
playerTwo: string
|
||||
|
||||
@Column()
|
||||
resultOfTheMatch: string
|
||||
|
||||
@Column()
|
||||
gameServerIdOfTheMatch: string
|
||||
}
|
||||
@@ -1,4 +1,104 @@
|
||||
import { Controller } from '@nestjs/common';
|
||||
import { Body, Controller, Get, HttpException, HttpStatus, Post, Req, Res, UseGuards } from '@nestjs/common';
|
||||
import { AuthenticateGuard, TwoFactorGuard } from 'src/auth/42/guards/42guards';
|
||||
import { User } from 'src/users/entities/user.entity';
|
||||
import { Response } from 'express';
|
||||
import { CreateGameDto } from './dto/createGame.dto';
|
||||
import { GrantTicketDto } from './dto/grantTicket.dto';
|
||||
import { UpdateGameDto } from './dto/updateGame.dto';
|
||||
import { ValidateTicketDto } from './dto/validateTicket.dto';
|
||||
import { GameService } from './game.service';
|
||||
|
||||
@Controller('game')
|
||||
export class GameController {}
|
||||
export class GameController {
|
||||
constructor (private readonly gameService : GameService) { }
|
||||
|
||||
|
||||
@Get('match/all')
|
||||
@UseGuards(AuthenticateGuard)
|
||||
@UseGuards(TwoFactorGuard)
|
||||
async getMatchesForSpectator()
|
||||
{
|
||||
return this.gameService.getMatchesForSpectator();
|
||||
}
|
||||
|
||||
@Get('ranking')
|
||||
@UseGuards(AuthenticateGuard)
|
||||
@UseGuards(TwoFactorGuard)
|
||||
async getRankingForAllUsers(@Req() req)
|
||||
{
|
||||
const currentUser : User = req.user
|
||||
return this.gameService.getRankingForAllUsers(currentUser);
|
||||
}
|
||||
|
||||
@Post('ticket')
|
||||
@UseGuards(AuthenticateGuard)
|
||||
@UseGuards(TwoFactorGuard)
|
||||
async grantTicket(@Req() req, @Body() grantTicketDto : GrantTicketDto, @Res() res : Response)
|
||||
{
|
||||
const user : User = req.user
|
||||
if (grantTicketDto.playerOneUsername != user.username)
|
||||
return res.status(HttpStatus.BAD_REQUEST).json({message : 'You can\'t grant a ticket to another user'});
|
||||
return this.gameService.generateToken(user, grantTicketDto, res);
|
||||
}
|
||||
|
||||
@Post('decline')
|
||||
@UseGuards(AuthenticateGuard)
|
||||
@UseGuards(TwoFactorGuard)
|
||||
async declineInvitation(@Body('token') token, @Req() req, @Res() res : Response)
|
||||
{
|
||||
const user : User = req.user;
|
||||
return this.gameService.declineInvitation(user, token, res);
|
||||
}
|
||||
|
||||
@Post('accept')
|
||||
@UseGuards(AuthenticateGuard)
|
||||
@UseGuards(TwoFactorGuard)
|
||||
async acceptInvitation(@Body('token') token, @Req() req, @Res() res : Response)
|
||||
{
|
||||
const user : User = req.user;
|
||||
return this.gameService.acceptInvitation(user, token, res);
|
||||
}
|
||||
|
||||
|
||||
@Get('invitations')
|
||||
@UseGuards(AuthenticateGuard)
|
||||
@UseGuards(TwoFactorGuard)
|
||||
async findInvitations(@Req() request, @Res() res : Response)
|
||||
{
|
||||
const user : User = request.user;
|
||||
return this.gameService.findInvitations(user, res);
|
||||
}
|
||||
|
||||
//
|
||||
//N'est valable que pour le game-serveur.
|
||||
@Post('gameserver/validate')
|
||||
async validateTicket(@Body() validateTicketDto : ValidateTicketDto, @Req() request)
|
||||
{
|
||||
if (await this.gameService.validateToken(validateTicketDto) === false)
|
||||
return new HttpException("The token is not valid", HttpStatus.NOT_FOUND);
|
||||
console.log("200 retourné côté nest")
|
||||
return HttpStatus.OK;
|
||||
}
|
||||
|
||||
@Post('gameserver/creategame')
|
||||
async createGame(@Body() creategameDto : CreateGameDto)
|
||||
{
|
||||
console.log("On est dans create game")
|
||||
console.log(creategameDto)
|
||||
return this.gameService.createGame(creategameDto);
|
||||
}
|
||||
|
||||
@Post('gameserver/updategame')
|
||||
async updateGame(@Body() updateGameDto : UpdateGameDto)
|
||||
{
|
||||
console.log("On est dans update game")
|
||||
console.log(updateGameDto)
|
||||
return this.gameService.updateGame(updateGameDto);
|
||||
}
|
||||
|
||||
@Post('gameserver/destroysession')
|
||||
async destroySession(@Body('token') token)
|
||||
{
|
||||
return this.gameService.destroySession(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Friendship } from 'src/friendship/entities/friendship.entity';
|
||||
import { FriendshipService } from 'src/friendship/friendship.service';
|
||||
import { User } from 'src/users/entities/user.entity';
|
||||
import { UsersService } from 'src/users/users.service';
|
||||
import { Game } from './entity/game.entity';
|
||||
import { TokenGame } from './entity/tokenGame.entity';
|
||||
import { GameController } from './game.controller';
|
||||
import { GameService } from './game.service';
|
||||
|
||||
@Module({
|
||||
controllers: [GameController],
|
||||
providers: [GameService]
|
||||
imports: [TypeOrmModule.forFeature([TokenGame, User, Game, Friendship])],
|
||||
controllers: [GameController],
|
||||
providers: [GameService, UsersService, FriendshipService]
|
||||
})
|
||||
export class GameModule {}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { GameService } from './game.service';
|
||||
// import { Test, TestingModule } from '@nestjs/testing';
|
||||
// // import { GameService } from './game.service';
|
||||
|
||||
describe('GameService', () => {
|
||||
let service: GameService;
|
||||
// describe('GameService', () => {
|
||||
// let service: GameService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [GameService],
|
||||
}).compile();
|
||||
// beforeEach(async () => {
|
||||
// const module: TestingModule = await Test.createTestingModule({
|
||||
// providers: [GameService],
|
||||
// }).compile();
|
||||
|
||||
service = module.get<GameService>(GameService);
|
||||
});
|
||||
// service = module.get<GameService>(GameService);
|
||||
// });
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
// it('should be defined', () => {
|
||||
// expect(service).toBeDefined();
|
||||
// });
|
||||
// });
|
||||
|
||||
@@ -1,4 +1,299 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { HttpException, HttpStatus, Injectable, Res } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { createCipheriv, randomBytes, scrypt } from 'crypto';
|
||||
import { User } from 'src/users/entities/user.entity';
|
||||
import { Repository } from 'typeorm';
|
||||
import { promisify } from 'util';
|
||||
import { Response } from 'express';
|
||||
import { GrantTicketDto } from './dto/grantTicket.dto';
|
||||
import { Game } from './entity/game.entity';
|
||||
import { ValidateTicketDto } from './dto/validateTicket.dto';
|
||||
import { TokenGame } from './entity/tokenGame.entity';
|
||||
import { UsersService } from 'src/users/users.service';
|
||||
import { CreateGameDto } from './dto/createGame.dto';
|
||||
import { UpdateGameDto } from './dto/updateGame.dto';
|
||||
import { FriendshipService } from 'src/friendship/friendship.service';
|
||||
import { STATUS } from 'src/common/constants/constants';
|
||||
|
||||
@Injectable()
|
||||
export class GameService {}
|
||||
export class GameService {
|
||||
constructor (
|
||||
@InjectRepository(Game)
|
||||
private readonly gameRepository : Repository<Game>,
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository : Repository<User>,
|
||||
@InjectRepository(TokenGame)
|
||||
private readonly tokenGameRepository : Repository<TokenGame>,
|
||||
private readonly userService : UsersService,
|
||||
private readonly friendShipService : FriendshipService
|
||||
) { }
|
||||
|
||||
async getMatchesForSpectator() {
|
||||
const games = await this.gameRepository.createQueryBuilder("game")
|
||||
.where('game.isMatchIsFinished = :isMatchIsFinished', {isMatchIsFinished : false})
|
||||
.getMany();
|
||||
const gamesToReturn : Partial<Game>[] = []
|
||||
for (const game of games)
|
||||
{
|
||||
gamesToReturn.push({gameServerIdOfTheMatch : game.gameServerIdOfTheMatch,
|
||||
gameOptions : game.gameOptions, playerOneUsername : game.playerOneUsername,
|
||||
playerTwoUsername : game.playerTwoUsername})
|
||||
console.log("Is match is finished : " + game.isMatchIsFinished)
|
||||
}
|
||||
return gamesToReturn;
|
||||
}
|
||||
|
||||
async getRankingForAllUsers(currentUser : User) {
|
||||
const users = await this.userRepository.createQueryBuilder("user")
|
||||
.leftJoinAndSelect("user.stats", "stats")
|
||||
.orderBy('stats.winGame', "DESC")
|
||||
.getMany();
|
||||
const partialUser : Partial<User>[] = []
|
||||
for (const user of users)
|
||||
{
|
||||
if (await this.friendShipService.findIfUserIsBlockedOrHasBlocked(currentUser.id, user.id) === false)
|
||||
partialUser.push({username : user.username, stats : user.stats })
|
||||
}
|
||||
console.log(...partialUser)
|
||||
return partialUser;
|
||||
}
|
||||
|
||||
|
||||
async encryptToken(toEncrypt : string) : Promise<string> {
|
||||
const iv = randomBytes(16);
|
||||
const password = process.env.TICKET_FOR_PLAYING_GAME_SECRET + new Date();
|
||||
const key = (await promisify(scrypt)(password, 'salt', 32)) as Buffer;
|
||||
const cipher = createCipheriv('aes-256-ctr', key, iv);
|
||||
const encryptedText = Buffer.concat([
|
||||
cipher.update(toEncrypt),
|
||||
cipher.final(),
|
||||
]);
|
||||
const encryptedTextToReturn = encryptedText.toString('base64');
|
||||
return encryptedTextToReturn
|
||||
}
|
||||
|
||||
async deleteToken(user : User){
|
||||
const tokenGame = await this.tokenGameRepository.createQueryBuilder('tokengame')
|
||||
.where('tokengame.playerTwoUsername = :playerTwoUsername', {playerTwoUsername : user.username})
|
||||
.orWhere('tokengame.playerOneUsername = :playerOneUsername', {playerOneUsername : user.username})
|
||||
.getMany();
|
||||
if (tokenGame)
|
||||
return this.tokenGameRepository.remove(tokenGame);
|
||||
}
|
||||
|
||||
async generateToken(user : User, grantTicketDto : GrantTicketDto, @Res() res : Response)
|
||||
{
|
||||
console.log(user.status);
|
||||
if (user.status === STATUS.IN_POOL || user.status === STATUS.IN_GAME)
|
||||
{
|
||||
await this.deleteToken(user);
|
||||
user.status = STATUS.CONNECTED;
|
||||
this.userRepository.save(user);
|
||||
}
|
||||
if (grantTicketDto.isGameIsWithInvitation === true)
|
||||
{
|
||||
const secondUser : Partial<User> = await this.userService.findOne(grantTicketDto.playerTwoUsername)
|
||||
if (!secondUser || secondUser.username === user.username)
|
||||
return res.status(HttpStatus.NOT_FOUND).json({message : "User not found OR you want to play with yourself."});
|
||||
const encryptedTextToReturn = await this.encryptToken(user.username + '_' + secondUser.username + '_'
|
||||
+ grantTicketDto.gameOptions + '_' + grantTicketDto.isGameIsWithInvitation + '_' + new Date())
|
||||
const tok = this.tokenGameRepository.create(grantTicketDto);
|
||||
tok.isSecondUserAcceptedRequest = false;
|
||||
tok.numberOfRegisteredUser = 0;
|
||||
tok.token = encryptedTextToReturn;
|
||||
this.tokenGameRepository.save(tok);
|
||||
this.userService.updateStatus(user.id, "In Pool")
|
||||
return res.status(HttpStatus.OK).json({ token : encryptedTextToReturn });
|
||||
}
|
||||
else if (grantTicketDto.isGameIsWithInvitation === false) {
|
||||
const encryptedTextToReturn = await this.encryptToken(user.username + '_'
|
||||
+ grantTicketDto.gameOptions + '_' + grantTicketDto.isGameIsWithInvitation + '_' + new Date())
|
||||
const tok = this.tokenGameRepository.create(grantTicketDto);
|
||||
tok.numberOfRegisteredUser = 0;
|
||||
tok.token = encryptedTextToReturn;
|
||||
this.tokenGameRepository.save(tok);
|
||||
this.userService.updateStatus(user.id, "In Pool")
|
||||
return res.status(HttpStatus.OK).json({ token : encryptedTextToReturn });
|
||||
}
|
||||
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({message : "Internal Server Error"});
|
||||
}
|
||||
|
||||
async validateToken(validateTicketDto : ValidateTicketDto) {
|
||||
if (validateTicketDto.isGameIsWithInvitation === true)
|
||||
{
|
||||
const tokenGame : TokenGame = await this.tokenGameRepository.createQueryBuilder('tokengame')
|
||||
.where('tokengame.playerOneUsername = :playerOneUsername', {playerOneUsername : validateTicketDto.playerOneUsername})
|
||||
.andWhere('tokengame.playerTwoUsername = :playerTwoUsername', {playerTwoUsername : validateTicketDto.playerTwoUsername})
|
||||
.andWhere('tokengame.gameOptions = :gameOption', {gameOption : validateTicketDto.gameOptions})
|
||||
.andWhere('tokengame.isGameIsWithInvitation = :isGameIsWithInvitation', {isGameIsWithInvitation: true})
|
||||
.andWhere('tokengame.isSecondUserAcceptedRequest = :choice', {choice : true})
|
||||
.andWhere('tokengame.token = :token', {token : validateTicketDto.token})
|
||||
.getOne();
|
||||
if (tokenGame)
|
||||
{
|
||||
tokenGame.numberOfRegisteredUser++;
|
||||
if (tokenGame.numberOfRegisteredUser === 2)
|
||||
{
|
||||
this.tokenGameRepository.remove(tokenGame)
|
||||
const userOne : User = await this.userRepository.createQueryBuilder('user')
|
||||
.where("user.username = :username", {username : tokenGame.playerOneUsername})
|
||||
.getOne();
|
||||
this.userService.updateStatus(userOne.id, "In Game")
|
||||
const userTwo : User = await this.userRepository.createQueryBuilder('user')
|
||||
.where("user.username = :username", {username : tokenGame.playerTwoUsername})
|
||||
.getOne();
|
||||
this.deleteToken(userOne)
|
||||
this.deleteToken(userTwo)
|
||||
this.userService.updateStatus(userTwo.id, "In Game")
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (validateTicketDto.isGameIsWithInvitation === false)
|
||||
{
|
||||
const tokenGame : TokenGame = await this.tokenGameRepository.createQueryBuilder('tokengame')
|
||||
.where('tokengame.playerOneUsername = :playerOneUsername', {playerOneUsername : validateTicketDto.playerOneUsername})
|
||||
.andWhere('tokengame.gameOptions = :gameOption', {gameOption : validateTicketDto.gameOptions})
|
||||
.andWhere('tokengame.isGameIsWithInvitation = :isGameIsWithInvitation', {isGameIsWithInvitation: false})
|
||||
.andWhere('tokengame.token = :token', {token : validateTicketDto.token})
|
||||
.getOne();
|
||||
if (tokenGame)
|
||||
{
|
||||
this.tokenGameRepository.remove(tokenGame)
|
||||
console.log("USERNAME : " + tokenGame.playerOneUsername)
|
||||
const user : User = await this.userRepository.createQueryBuilder('user')
|
||||
.where("user.username = :username", {username : tokenGame.playerOneUsername})
|
||||
.getOne();
|
||||
this.userService.updateStatus(user.id, "In Game")
|
||||
this.deleteToken(user)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async findInvitations(user : User, @Res() res : Response) {
|
||||
const game = await this.tokenGameRepository.createQueryBuilder('tokengame')
|
||||
.where('tokengame.playerTwoUsername = :playerTwoUsername', {playerTwoUsername : user.username})
|
||||
.andWhere('tokengame.isGameIsWithInvitation = :invit', {invit : true})
|
||||
.andWhere('tokengame.isSecondUserAcceptedRequest = :choice', {choice : false})
|
||||
.getMany();
|
||||
if (!game)
|
||||
return res.status(HttpStatus.NOT_FOUND).send({message : "No invitation found"});
|
||||
let partialGame : Partial<TokenGame>[] = [];
|
||||
for (const gameToken of game) {
|
||||
partialGame.push({
|
||||
playerOneUsername : gameToken.playerOneUsername,
|
||||
playerTwoUsername : gameToken.playerTwoUsername,
|
||||
gameOptions : gameToken.gameOptions,
|
||||
token : gameToken.token,
|
||||
});
|
||||
}
|
||||
return res.status(HttpStatus.OK).json(partialGame);
|
||||
}
|
||||
|
||||
async declineInvitation(user : User, token : string, @Res() res : Response)
|
||||
{
|
||||
if (user.status !== "Connected")
|
||||
return res.status(HttpStatus.FORBIDDEN).json({message : "You must not be in game to decline an invitation"});
|
||||
console.log("On décline l'invitation")
|
||||
const tokenGame = await this.tokenGameRepository.createQueryBuilder('tokengame')
|
||||
.andWhere('tokengame.playerTwoUsername = :playerTwoUsername', {playerTwoUsername : user.username})
|
||||
.andWhere('tokengame.token = :token', {token : token})
|
||||
.getOne();
|
||||
if (tokenGame)
|
||||
{
|
||||
this.tokenGameRepository.remove(tokenGame);
|
||||
return res.status(HttpStatus.OK).json({message : "Invitation declined."});
|
||||
}
|
||||
return res.status(HttpStatus.NOT_FOUND).json({message : "No invitation found !"});
|
||||
}
|
||||
|
||||
async destroySession(token : string)
|
||||
{
|
||||
console.log("On détruit le token et la session qui va avec")
|
||||
const tokenGame = await this.tokenGameRepository.createQueryBuilder('tokengame')
|
||||
.where('tokengame.token = :token', {token : token})
|
||||
.getOne();
|
||||
if (tokenGame)
|
||||
{
|
||||
const playerOne = await this.userRepository.findOneBy({username : tokenGame.playerOneUsername})
|
||||
const playerTwo = await this.userRepository.findOneBy({username : tokenGame.playerTwoUsername})
|
||||
if (playerOne.status !== "Disconnected")
|
||||
this.userService.updateStatus(playerOne.id, "Connected")
|
||||
if (playerTwo.status !== "Disconnected")
|
||||
this.userService.updateStatus(playerTwo.id, "Connected")
|
||||
return this.tokenGameRepository.remove(tokenGame);
|
||||
}
|
||||
return new HttpException("Token not found !", HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
async acceptInvitation(user : User, token : string, @Res() res : Response)
|
||||
{
|
||||
if (user.status !== "Connected")
|
||||
return res.status(HttpStatus.FORBIDDEN).send("")
|
||||
const tokenGame = await this.tokenGameRepository.createQueryBuilder('tokenGame')
|
||||
.andWhere('tokenGame.playerTwoUsername = :playerTwoUsername', {playerTwoUsername : user.username})
|
||||
.andWhere('tokenGame.token = :token', {token : token})
|
||||
.getOne();
|
||||
if (tokenGame)
|
||||
{
|
||||
tokenGame.isSecondUserAcceptedRequest = true;
|
||||
this.tokenGameRepository.save(tokenGame)
|
||||
return res.status(HttpStatus.OK).json({message : "Invitation accepted."});
|
||||
}
|
||||
return res.status(HttpStatus.NOT_FOUND).json({message : "No invitation found !"});
|
||||
}
|
||||
|
||||
async createGame(creategameDto : CreateGameDto)
|
||||
{
|
||||
if (creategameDto.playerOneUsername === "" || creategameDto.playerTwoUsername === ""
|
||||
|| creategameDto.playerOneUsername === creategameDto.playerTwoUsername)
|
||||
return HttpStatus.INTERNAL_SERVER_ERROR
|
||||
const game = this.gameRepository.create(creategameDto)
|
||||
game.isMatchIsFinished = false;
|
||||
this.gameRepository.save(game);
|
||||
if (!game)
|
||||
return HttpStatus.INTERNAL_SERVER_ERROR
|
||||
console.log("200 retourné pour la création de partie")
|
||||
return HttpStatus.OK
|
||||
}
|
||||
|
||||
async updateGame(updateGameDto : UpdateGameDto) {
|
||||
console.log("Updata game" + updateGameDto)
|
||||
const game = await this.gameRepository.createQueryBuilder('game')
|
||||
.where("game.gameServerIdOfTheMatch = :gameServerIdOfTheMatch", {gameServerIdOfTheMatch : updateGameDto.gameServerIdOfTheMatch})
|
||||
.getOne();
|
||||
if (!game)
|
||||
throw new HttpException(`The game could not be updated.`,HttpStatus.NOT_FOUND);
|
||||
game.isMatchIsFinished = true;
|
||||
game.playerOneUsernameResult = updateGameDto.playerOneUsernameResult
|
||||
game.playerTwoUsernameResult = updateGameDto.playerTwoUsernameResult
|
||||
this.gameRepository.save(game);
|
||||
console.log("On a sauvegardé la partie. Game :")
|
||||
console.log(game)
|
||||
const playerOne = await this.userRepository.findOneBy({username : game.playerOneUsername})
|
||||
const playerTwo = await this.userRepository.findOneBy({username : game.playerTwoUsername})
|
||||
if (!playerOne || !playerTwo)
|
||||
return new HttpException("Internal Server Error. Impossible to update the database", HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
if (game.playerOneUsernameResult === game.playerTwoUsernameResult)
|
||||
{
|
||||
this.userService.incrementDraws(playerOne.id)
|
||||
this.userService.incrementDraws(playerTwo.id)
|
||||
}
|
||||
else if (game.playerOneUsernameResult < game.playerTwoUsernameResult)
|
||||
{
|
||||
this.userService.incrementDefeats(playerOne.id)
|
||||
this.userService.incrementVictories(playerTwo.id)
|
||||
}
|
||||
else
|
||||
{
|
||||
this.userService.incrementVictories(playerOne.id)
|
||||
this.userService.incrementDefeats(playerTwo.id)
|
||||
}
|
||||
this.userService.updateStatus(playerOne.id, "Connected")
|
||||
this.userService.updateStatus(playerTwo.id, "Connected")
|
||||
return HttpStatus.OK
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as connectRedis from 'connect-redis';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule, { cors: true });
|
||||
const port = process.env.PORT || 3001;
|
||||
const port = process.env.PORT || 3000;
|
||||
const client = redis.createClient(
|
||||
{
|
||||
socket: { host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT) },
|
||||
@@ -50,6 +50,6 @@ async function bootstrap() {
|
||||
);
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
await app.listen(port, () => { console.log(`Listening on port ${port}`); });
|
||||
await app.listen(port, () => { console.log(`Listening on port ${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}}`); });
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// et de les mettre comme optionnelles. De plus on peut hériter
|
||||
// des décorateurs de la classe parente (par exemple @IsString()).
|
||||
|
||||
import { OmitType, PartialType } from "@nestjs/mapped-types";
|
||||
import { OmitType } from "@nestjs/mapped-types";
|
||||
import { CreateUsersDto } from "./create-users.dto";
|
||||
|
||||
export class UpdateUsersDto extends OmitType(CreateUsersDto, ['fortyTwoId', 'email', 'image_url', 'status'] as const){}
|
||||
|
||||
@@ -28,7 +28,7 @@ export class User {
|
||||
@Column({ nullable: true })
|
||||
phone: string;
|
||||
|
||||
@Column({ default: 'disconnected' })
|
||||
@Column({ default: 'Disconnected' })
|
||||
status: string;
|
||||
|
||||
// @Column()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
Body, Controller, Delete, Get, NotFoundException, Param, Patch, Post, Query, Redirect, Req, Res, UploadedFile, UseGuards, UseInterceptors
|
||||
Body, Controller, Delete, Get, NotFoundException,HttpStatus, Param, Patch, Post, Query, Redirect, Req, Res, UploadedFile, UseGuards, UseInterceptors
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { Response } from 'express';
|
||||
@@ -84,12 +84,9 @@ export class UsersController {
|
||||
@UseGuards(TwoFactorGuard)
|
||||
@Patch()
|
||||
async update(@Req() req, @Body(new ValidationPipe()) usersUpdateDto: UpdateUsersDto, @Res() response : Response) {
|
||||
console.log("DANS PATCH USERS");
|
||||
const user = await this.usersService.update(req.user.id, usersUpdateDto);
|
||||
// const user : User = req.user;
|
||||
if (user.isEnabledTwoFactorAuth === false && user.isTwoFactorAuthenticated === true)
|
||||
this.usersService.setIsTwoFactorAuthenticatedWhenLogout(user.id);
|
||||
console.log ("Enbale 2FA " + user.isEnabledTwoFactorAuth + " Is authenticated " + user.isTwoFactorAuthenticated);
|
||||
if (user.isEnabledTwoFactorAuth === true && user.isTwoFactorAuthenticated === false)
|
||||
{
|
||||
response.status(201).send('2FA redirect')
|
||||
@@ -111,9 +108,14 @@ export class UsersController {
|
||||
@UseGuards(TwoFactorGuard)
|
||||
@Post('avatar')
|
||||
@UseInterceptors(FileInterceptor('file', storageForAvatar))
|
||||
uploadAvatar(@UploadedFile() file, @Req() request){
|
||||
uploadAvatar(@UploadedFile() file, @Req() request, @Res() res){
|
||||
const user : User = request.user;
|
||||
this.usersService.updateAvatar(user.id, file.filename);
|
||||
if (file)
|
||||
{
|
||||
this.usersService.updateAvatar(user.id, file.filename);
|
||||
return res.status(HttpStatus.OK).json({message : "Avatar updated"});
|
||||
}
|
||||
return res.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE).json({message : "Unsupported media type. Please use a valid image file."});
|
||||
}
|
||||
|
||||
// GET http://transcendance:8080/user/avatar
|
||||
|
||||
@@ -4,12 +4,9 @@ import { User } from './entities/user.entity';
|
||||
import { Repository, Not } from 'typeorm';
|
||||
import { CreateUsersDto } from './dto/create-users.dto';
|
||||
import { UpdateUsersDto } from './dto/update-users.dto';
|
||||
import { Friendship } from '../friendship/entities/friendship.entity';
|
||||
import { PaginationQueryDto } from 'src/common/dto/pagination-query.dto';
|
||||
import { UserStats } from './entities/userStat.entities';
|
||||
import { FriendshipService } from 'src/friendship/friendship.service';
|
||||
import { stringify } from 'querystring';
|
||||
|
||||
// On va devoir sûrement trouver un moyen plus simple pour passer l'id, sûrement via des pipes
|
||||
// ou des interceptors, mais pour l'instant on va faire comme ça.
|
||||
@Injectable()
|
||||
@@ -86,6 +83,28 @@ export class UsersService {
|
||||
return partialUsers;
|
||||
}
|
||||
|
||||
// async findAbsolutelyAll() {
|
||||
// // const otherUsers = await this.userRepository.find({where: {id: Not(+currentUser.id)}, order: {username: "ASC"}, skip: offset, take: limit,});
|
||||
// const otherUsers = await this.userRepository.find({order: {username: "ASC"}});
|
||||
|
||||
// let partialUsers : Partial<User>[] = [];
|
||||
|
||||
// for (const otherUser of otherUsers) {
|
||||
// console.log('other user: ')
|
||||
// console.log({...otherUser})
|
||||
// let tmp = await this.friendshipService.findIfUserIsBlockedOrHasBlocked(currentUser.id, otherUser.id);
|
||||
// console.log('user.services findIF Blocked... : ')
|
||||
// console.log(tmp)
|
||||
// if (tmp === false) {
|
||||
// // if (await this.friendshipService.findIfUserIsBlockedOrHasBlocked(currentUser.id, otherUser.id) === false) {
|
||||
// partialUsers.push({username: otherUser.username, image_url: otherUser.image_url, status: otherUser.status, stats: otherUser.stats});
|
||||
// }
|
||||
// }
|
||||
// console.log('user.services findAll, partialUsers:')
|
||||
// console.log({...partialUsers})
|
||||
// return partialUsers;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
async create(createUserDto: CreateUsersDto) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
listen [::]:8080 default_server;
|
||||
server_name transcendance;
|
||||
listen 8080;
|
||||
listen [::]:8080;
|
||||
server_name localhost;
|
||||
|
||||
location /api/v2 {
|
||||
proxy_set_header Host $host;
|
||||
@@ -10,6 +10,28 @@ server {
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_pass http://backend_dev:3000;
|
||||
}
|
||||
|
||||
location /chat {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_pass http://backend_dev:5000/chat;
|
||||
}
|
||||
|
||||
location /api/v2/game/gameserver {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location /pong {
|
||||
proxy_pass http://game_server:8042/pong;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
@@ -20,10 +42,9 @@ server {
|
||||
}
|
||||
|
||||
server {
|
||||
|
||||
listen 35729 default_server;
|
||||
listen [::]:35729 default_server;
|
||||
server_name transcendance;
|
||||
server_name localhost;
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
37
srcs/requirements/nginx/conf/nginx.conf
Normal file
37
srcs/requirements/nginx/conf/nginx.conf
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log notice;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
#gzip on;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
@@ -8,3 +8,4 @@
|
||||
!api_front/*.json
|
||||
!api_front/*.html
|
||||
!api_front/*.lock
|
||||
!api_front/.env
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
# This repo is no longer maintained. Consider using `npm init vite` and selecting the `svelte` option or — if you want a full-fledged app framework and don't mind using pre-1.0 software — use [SvelteKit](https://kit.svelte.dev), the official application framework for Svelte.
|
||||
|
||||
---
|
||||
|
||||
# svelte app
|
||||
|
||||
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
|
||||
|
||||
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
|
||||
|
||||
```bash
|
||||
npx degit sveltejs/template svelte-app
|
||||
cd svelte-app
|
||||
```
|
||||
|
||||
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
|
||||
|
||||
|
||||
## Get started
|
||||
|
||||
Install the dependencies...
|
||||
|
||||
```bash
|
||||
cd svelte-app
|
||||
npm install
|
||||
```
|
||||
|
||||
...then start [Rollup](https://rollupjs.org):
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Navigate to [localhost:8080](http://localhost:8080). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
|
||||
|
||||
By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
|
||||
|
||||
If you're using [Visual Studio Code](https://code.visualstudio.com/) we recommend installing the official extension [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If you are using other editors you may need to install a plugin in order to get syntax highlighting and intellisense.
|
||||
|
||||
## Building and running in production mode
|
||||
|
||||
To create an optimised version of the app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
|
||||
|
||||
|
||||
## Single-page app mode
|
||||
|
||||
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
|
||||
|
||||
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
|
||||
|
||||
```js
|
||||
"start": "sirv public --single"
|
||||
```
|
||||
|
||||
## Using TypeScript
|
||||
|
||||
This template comes with a script to set up a TypeScript development environment, you can run it immediately after cloning the template with:
|
||||
|
||||
```bash
|
||||
node scripts/setupTypeScript.js
|
||||
```
|
||||
|
||||
Or remove the script via:
|
||||
|
||||
```bash
|
||||
rm scripts/setupTypeScript.js
|
||||
```
|
||||
|
||||
If you want to use `baseUrl` or `path` aliases within your `tsconfig`, you need to set up `@rollup/plugin-alias` to tell Rollup to resolve the aliases. For more info, see [this StackOverflow question](https://stackoverflow.com/questions/63427935/setup-tsconfig-path-in-svelte).
|
||||
|
||||
## Deploying to the web
|
||||
|
||||
### With [Vercel](https://vercel.com)
|
||||
|
||||
Install `vercel` if you haven't already:
|
||||
|
||||
```bash
|
||||
npm install -g vercel
|
||||
```
|
||||
|
||||
Then, from within your project folder:
|
||||
|
||||
```bash
|
||||
cd public
|
||||
vercel deploy --name my-project
|
||||
```
|
||||
|
||||
### With [surge](https://surge.sh/)
|
||||
|
||||
Install `surge` if you haven't already:
|
||||
|
||||
```bash
|
||||
npm install -g surge
|
||||
```
|
||||
|
||||
Then, from within your project folder:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
surge public my-project.surge.sh
|
||||
```
|
||||
@@ -1,81 +0,0 @@
|
||||
<script lang="ts">
|
||||
// routing
|
||||
// may not need {link} here
|
||||
import Router, { link } from "svelte-spa-router";
|
||||
import { routes } from "../routes.js";
|
||||
|
||||
import LoginPage from "./LoginPage.svelte";
|
||||
import UserPage from "../UserPage.svelte";
|
||||
import NotFound from "../pages/NotFound.svelte";
|
||||
|
||||
// Ideally fuck all this shit in the long run
|
||||
let pages = ['login', 'user', 'account'];
|
||||
// make this a $: currentPage ?
|
||||
let currentPage = 'booba';
|
||||
// prolly change this? yea idk...
|
||||
// let userIndex;
|
||||
// tmp for testing
|
||||
let userId = 0;
|
||||
// horrible naming... can be HOME or ACCOUNT
|
||||
let currentType = 'account';
|
||||
|
||||
// this page should handle the SPA history management...
|
||||
|
||||
// set to false later for actual security
|
||||
let loggedIn = true;
|
||||
|
||||
// not sure if this is how i want to do this...
|
||||
// might do differently cuz URL route manangement...
|
||||
const handleLogin = () => {
|
||||
currentPage = 'user';
|
||||
loggedIn = true;
|
||||
};
|
||||
|
||||
// I don't know if i should but i'm tempted to put this here...
|
||||
|
||||
// import axios from 'axios';
|
||||
// import { onMount } from 'svelte';
|
||||
// import { push } from 'svelte-spa-router';
|
||||
|
||||
// let user = {logedIn: false};
|
||||
|
||||
// onMount(async () => {
|
||||
// // console.log('PROFIL SVELTE');
|
||||
// const {data} = await axios.get('http://transcendance:8080/api/v2/user');
|
||||
// if (data)
|
||||
// user.logedIn = true;
|
||||
// });
|
||||
|
||||
// $: submit = async() => {
|
||||
// window.location.href = 'http://transcendance:8080/api/v2/auth';
|
||||
// }
|
||||
|
||||
// $: logout = async() => {
|
||||
// await fetch('http://transcendance:8080/api/v2/auth/logout',);
|
||||
// user.logedIn = false;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eventually we will do this with routes, but for now use a prop -->
|
||||
<!-- {#if currentPage === 'login'}
|
||||
<LoginPage {pages} {currentPage} {userId} on:loggedIn={handleLogin}/>
|
||||
{:else if currentPage === 'user'}
|
||||
<UserPage {pages} {currentPage} {userId} {currentType}/>
|
||||
{:else}
|
||||
<NotFound />
|
||||
{/if} -->
|
||||
<Router {routes}/>
|
||||
|
||||
<style>
|
||||
|
||||
/* doesn't work... */
|
||||
/* body{
|
||||
background: bisque;
|
||||
} */
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
<!-- <script lang="ts"> -->
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let canvas;
|
||||
|
||||
// $: scaleRatio = window.innerWidth / 10;
|
||||
$: scaleRatio = 30;
|
||||
|
||||
onMount(() => {
|
||||
// we're invoking JS methods of the canvas element
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.width = window.innerWidth;
|
||||
ctx.height = window.innerHeight;
|
||||
|
||||
// ctx.beginPath();
|
||||
// ctx.moveTo(50,50);
|
||||
// ctx.lineTo(70,70);
|
||||
// ctx.stroke();
|
||||
|
||||
// attempting to import an image
|
||||
const img = new Image();
|
||||
// we may have to call an onMount or something to make sure the image has loaded...
|
||||
// doing it in JS for now, ideally in Svelte later..
|
||||
// img.addEventListener('load', () => {
|
||||
// // execute drawImage statements here
|
||||
// }, false);
|
||||
img.src = 'img/potato_logo.png'; // seems like this does need to be above onload()
|
||||
img.onload = () => {
|
||||
// ctx.drawImage(img, 0, 0, img.width * (ctx.width / 15), img.height * (ctx.height / 15));
|
||||
// ctx.drawImage(img, 0, 0, ctx.width / 15, ctx.height / 15);
|
||||
|
||||
// i think i don't want this cuz it'll get in the way?
|
||||
// ctx.drawImage(img, 0, 0, img.width / scaleRatio, img.height / scaleRatio);
|
||||
|
||||
// it would seem you need to redraw the images when you change the Window size, it's not automatically responsive
|
||||
};
|
||||
|
||||
let startX = 200;
|
||||
let startY = 200;
|
||||
let dx = 3;
|
||||
let dy = -1;
|
||||
|
||||
// Time for some math
|
||||
// lets say i want 6 rows
|
||||
// ok so we're gonna draw all the potatos at the same time and each of them gets animated, like i have it now
|
||||
// 6 in a row so # of rows aka x = width * 6/height
|
||||
|
||||
function Potato(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
// ctx.drawImage(img, x, y, img.width / scaleRatio, img.height / scaleRatio);
|
||||
|
||||
this.draw = function() {
|
||||
ctx.drawImage(img, x, y, img.width / scaleRatio, img.height / scaleRatio);
|
||||
}
|
||||
|
||||
this.animate = function() {
|
||||
this.x += dx;
|
||||
this.y += dy;
|
||||
|
||||
this.draw();
|
||||
}
|
||||
}
|
||||
|
||||
// let spacing = canvas.width * ((6 + 1) / canvas.height);
|
||||
let spacing = 70;
|
||||
let Potatos = [];
|
||||
//check the math...
|
||||
// for (let i = 0; i < 6 * spacing; i++) {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
// for (let j = 0; j < 6; j++) {
|
||||
for (let j = 0; i < 2; j++) {
|
||||
Potatos.push(new Potato(spacing + (i * spacing), spacing + (j * spacing)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// now i'm trying to move 1 potato
|
||||
let frame = requestAnimationFrame(loop);
|
||||
// function loop(t) {
|
||||
// ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
// frame = requestAnimationFrame(loop);
|
||||
// ctx.drawImage(img, startX, startY, img.width / scaleRatio, img.height / scaleRatio);
|
||||
// startX += dx;
|
||||
// startY += dy;
|
||||
// }
|
||||
|
||||
// new Potato(70, 70).animate();
|
||||
|
||||
|
||||
function loop(t) {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
frame = requestAnimationFrame(loop);
|
||||
for (let i = 0; i < Potatos.length; i++) {
|
||||
Potatos[i].animate();
|
||||
}
|
||||
// Potatos[0].animate();
|
||||
}
|
||||
|
||||
loop();
|
||||
|
||||
// Lets try again with a single loop
|
||||
|
||||
|
||||
|
||||
|
||||
// THis shit makes the cool Gradient
|
||||
// let frame = requestAnimationFrame(loop);
|
||||
// function loop(t) {
|
||||
// frame = requestAnimationFrame(loop);
|
||||
|
||||
// // const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// for (let p = 0; p < imageData.data.length; p += 4) {
|
||||
// const i = p / 4;
|
||||
// const x = i % canvas.width;
|
||||
// const y = i / canvas.width >>> 0;
|
||||
|
||||
// const r = 64 + (128 * x / canvas.width) + (64 * Math.sin(t / 1000));
|
||||
// const g = 64 + (128 * y / canvas.height) + (64 * Math.cos(t / 1000));
|
||||
// const b = 128;
|
||||
|
||||
// imageData.data[p + 0] = r;
|
||||
// imageData.data[p + 1] = g;
|
||||
// imageData.data[p + 2] = b;
|
||||
// imageData.data[p + 3] = 255;
|
||||
// }
|
||||
|
||||
// ctx.putImageData(imageData, 0, 0);
|
||||
// }
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(frame);
|
||||
// prolly something else...
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<canvas
|
||||
bind:this={canvas}
|
||||
width={window.innerWidth}
|
||||
height={window.innerHeight}
|
||||
></canvas>
|
||||
<!-- widht and height were 32, trying stuff -->
|
||||
|
||||
<!-- I don't have the /svelte-logo-mask.svg, i guess i'll go get something -->
|
||||
<style>
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #666;
|
||||
|
||||
|
||||
/* testing something */
|
||||
/* -webkit-mask-image: radial-gradient(circle, black 50%, rgba(0, 0, 0, 0.5) 50%);
|
||||
mask-image: radial-gradient(circle, black 50%, rgba(0, 0, 0, 0.5) 50%); */
|
||||
/* Holy shit that worked! i got a mask */
|
||||
/* it also works without a mask! i have a canvas! */
|
||||
|
||||
/* -webkit-mask: url(/svelte-logo-mask.svg) 50% 50% no-repeat; */
|
||||
/* -webkit-mask: url(img/cartoon_potato3.jpg) 50% 50% no-repeat; */
|
||||
/* mask: url(/svelte-logo-mask.svg) 50% 50% no-repeat; */
|
||||
/* mask: url(img/cartoon_potato3.jpg) 50% 50% no-repeat; */
|
||||
}
|
||||
</style>
|
||||
@@ -1,45 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount, setContext } from "svelte";
|
||||
|
||||
let canvas;
|
||||
|
||||
// what do i want?
|
||||
// lets start with an image of my potato
|
||||
// then get it to move
|
||||
// then have many displayed in an offset grid
|
||||
|
||||
const drawFunctions = [];
|
||||
|
||||
|
||||
setContext('canvas', {
|
||||
register(drawFn) {
|
||||
drawFunctions.push(drawFn);
|
||||
},
|
||||
unregister(drawFn) {
|
||||
drawFunctions.splice(drawFunctions.indexOf(drawFn), 1);
|
||||
}
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
// not sure what this does...
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// no idea what this does...
|
||||
function update() {
|
||||
|
||||
ctx.clearRect()
|
||||
drawFunctions.forEach(drawFn => {
|
||||
drawFn(ctx);
|
||||
});
|
||||
|
||||
frameId = requestAnimationFrame(update);
|
||||
}
|
||||
|
||||
let frameId = requestAnimationFrame(update);
|
||||
return () => {
|
||||
cancelAnimationFrame(update);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<canvas bind:this={canvas} />
|
||||
@@ -1,112 +0,0 @@
|
||||
<script lang="ts">
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { location } from 'svelte-spa-router';
|
||||
import GenerateUserDisplay from './GenerateUserDisplay.svelte';
|
||||
|
||||
// using location won't work cuz i do a fetch but i don't change the page fragment, so means nothing to location...
|
||||
|
||||
// this is how you access /:first for example
|
||||
// export let params = {}
|
||||
// <p>Your name is: <b>{params.first}</b> <b>{#if params.last}{params.last}{/if}</b></p>
|
||||
|
||||
// If i export these vars, maybe as an nice tidy object, i could pass whatever i like to them
|
||||
// The current user, some other user, whatever, and thus reuse this Componente for the user and their friends or whatever
|
||||
// will have to coordinate with Back, will know more once the Game stats are in the back
|
||||
// wait maybe this won't work, cuz like it's still going through a route, i would have to update a Store Var each time...
|
||||
// not sure if that's what i want...
|
||||
|
||||
|
||||
// maybe the rank is determined dynamically just in the front based on win loss ratio or something no one cares about
|
||||
// why bother storing that shit in the back...
|
||||
// maybe i need a Rank.svelte component
|
||||
// ohhh i could make above a certain rank glitter! like that CSS tutorial showed me!
|
||||
|
||||
export let aUsername;
|
||||
|
||||
let user;
|
||||
let rank = '';
|
||||
let avatar;
|
||||
|
||||
// i think i don't need to do this once i sort out the {wrap} conditions: in theory i could pass values to the Route
|
||||
// once the async authentication check is done
|
||||
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}`)
|
||||
.then( (x) => x.json() );
|
||||
|
||||
console.log('Display a user: ' + user.username)
|
||||
|
||||
|
||||
// console.log('profile display did my fetch')
|
||||
// should i be updating the userStore or is that unnecessary?
|
||||
|
||||
if (user.loseGame > user.winGame) {
|
||||
rank = 'Bitch Ass Loser!'
|
||||
} else if (user.loseGame === user.winGame) {
|
||||
rank = 'Fine i guess...'
|
||||
} else {
|
||||
rank = 'Yea you da Boss!'
|
||||
}
|
||||
|
||||
await fetch("http://transcendance:8080/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);
|
||||
|
||||
|
||||
})
|
||||
|
||||
// Glittery Stars and such for Rank
|
||||
|
||||
let index = 0, interval = 1000;
|
||||
|
||||
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)}%`);
|
||||
|
||||
star.style.animation = "none";
|
||||
star.offsetHeight;
|
||||
star.style.animation = "";
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if user !== undefined}
|
||||
<GenerateUserDisplay {user}/>
|
||||
{:else}
|
||||
<h2>Sorry</h2>
|
||||
<div>Failed to load user {aUsername}</div>
|
||||
{/if}
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -1,24 +0,0 @@
|
||||
<script lang="ts">
|
||||
// might rename...
|
||||
// no idea what i'm doing...
|
||||
|
||||
import { getContext, onMount } from 'svelte';
|
||||
|
||||
// here you have to export the vars you need to draw this shit...
|
||||
|
||||
const { register, unregister } = getContext('canvas');
|
||||
|
||||
onMount(() => {
|
||||
register(draw);
|
||||
|
||||
return () => {
|
||||
unregister(draw);
|
||||
}
|
||||
});
|
||||
|
||||
function draw(ctx) {
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = fill;
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -1,124 +0,0 @@
|
||||
<script lang="ts">
|
||||
|
||||
// Fucking having a header that can change size, i don't really want the larger one
|
||||
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
let dispatch = createEventDispatcher();
|
||||
|
||||
let types: string[] = ['home', 'regular', 'none'];
|
||||
// let currentType: string = 'regular';
|
||||
export let currentType = 'regular';
|
||||
// apparently Regular is the only one i use...
|
||||
|
||||
let handleClickHome = () => {
|
||||
dispatch('clickedHome');
|
||||
};
|
||||
|
||||
let handleClickLogout = () => {
|
||||
dispatch('clickedLogout');
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Make it so you can have a Big Home page header and a regular header or no header -->
|
||||
<!-- So far my CSS is super Gross, i guess i'll get Hugo to help me with it -->
|
||||
|
||||
<header class={currentType}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<img class={currentType} src="/img/potato_logo.png" alt="Potato Pong Logo" on:click={handleClickHome}>
|
||||
<!-- {#if currentType === 'home'} -->
|
||||
<h1 class={currentType}>Potato Pong</h1>
|
||||
<!-- {/if} -->
|
||||
<nav class={currentType}>
|
||||
<!-- <a href=""></a> -->
|
||||
<!-- i might change these to links rather than buttons, i kinda hate the buttons -->
|
||||
<button>My Stats</button>
|
||||
<button>Stream</button>
|
||||
<button on:click={handleClickLogout}>Log Out</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
/* See "possible_fonts.css" for more font options... */
|
||||
@font-face {
|
||||
font-family: 'Bondi';
|
||||
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');
|
||||
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... */
|
||||
|
||||
|
||||
header{
|
||||
/* background: #f7f7f7; */
|
||||
background: #618174;
|
||||
/* padding: 20px; */
|
||||
margin: 0;
|
||||
/* does nothing so far... */
|
||||
/* display: flex; */
|
||||
}
|
||||
|
||||
header.home{
|
||||
/* position: sticky; */
|
||||
}
|
||||
header.regular{
|
||||
/* for some reason this doesn't do shit! */
|
||||
position: sticky;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
/* Headers */
|
||||
h1{
|
||||
font-family: 'Bondi';
|
||||
}
|
||||
h1.home {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
/* max-width: 100px; */
|
||||
}
|
||||
h1.regular{
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
/* max-width: 40px; */
|
||||
/* this helped with the weird extra space under the image... */
|
||||
display: flex;
|
||||
justify-self: center;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
/* Images */
|
||||
img.home{
|
||||
/* text-align: center; */
|
||||
/* get the image squarely in the middle... */
|
||||
cursor: pointer;
|
||||
max-width: 100px;
|
||||
}
|
||||
img.regular{
|
||||
cursor: pointer;
|
||||
max-width: 40px;
|
||||
padding: 7px 20px;
|
||||
justify-self: left;
|
||||
}
|
||||
|
||||
nav{
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
nav button{
|
||||
margin: 7px 20px;
|
||||
/* padding: 5px; */
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* .none{
|
||||
|
||||
} */
|
||||
</style>
|
||||
@@ -1,102 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
let dispatch = createEventDispatcher();
|
||||
|
||||
let types: string[] = ['home', 'regular', 'none'];
|
||||
// let currentType: string = 'regular';
|
||||
export let currentType = 'home';
|
||||
|
||||
let handleClick = () => {
|
||||
dispatch('clickedHome');
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Make it so you can have a Big Home page header and a regular header or no header -->
|
||||
<!-- So far my CSS is super Gross, i guess i'll get Hugo to help me with it -->
|
||||
|
||||
<header class={currentType}>
|
||||
<h1 class={currentType}>
|
||||
<img class={currentType} src="/img/potato_logo.png" alt="Potato Pong Logo" on:click={handleClick}>
|
||||
</h1>
|
||||
<!-- {#if currentType === 'home'} -->
|
||||
<h1 class={currentType}>Potato Pong</h1>
|
||||
<!-- {/if} -->
|
||||
</header>
|
||||
|
||||
<style>
|
||||
/* See "possible_fonts.css" for more font options... */
|
||||
@font-face {
|
||||
font-family: 'Bondi';
|
||||
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');
|
||||
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... */
|
||||
|
||||
|
||||
header{
|
||||
/* background: #f7f7f7; */
|
||||
background: #618174;
|
||||
/* padding: 20px; */
|
||||
margin: 0;
|
||||
font-family: 'Bondi';
|
||||
/* does nothing so far... */
|
||||
/* display: flex; */
|
||||
}
|
||||
|
||||
header.home{
|
||||
/* position: sticky; */
|
||||
}
|
||||
header.regular{
|
||||
position: sticky;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
header.regular > h1:first-child{
|
||||
justify-self: left;
|
||||
}
|
||||
header.regular > h1:nth-child(2){
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
/* Headers */
|
||||
h1.home {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
/* max-width: 100px; */
|
||||
}
|
||||
h1.regular{
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
/* max-width: 40px; */
|
||||
/* this helped with the weird extra space under the image... */
|
||||
display: flex;
|
||||
justify-self: center;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
/* Images */
|
||||
h1 img.home{
|
||||
/* text-align: center; */
|
||||
/* get the image squarely in the middle... */
|
||||
cursor: pointer;
|
||||
max-width: 100px;
|
||||
}
|
||||
h1 img.regular{
|
||||
cursor: pointer;
|
||||
max-width: 40px;
|
||||
padding: 7px 15px;
|
||||
}
|
||||
|
||||
|
||||
/* .none{
|
||||
|
||||
} */
|
||||
</style>
|
||||
@@ -1,172 +0,0 @@
|
||||
<script lang="ts">
|
||||
|
||||
// Now called LoginPage
|
||||
|
||||
// import Header from "./Header.svelte";
|
||||
import Footer from "../components/Footer.svelte";
|
||||
import Login from "./Login.svelte";
|
||||
import Tabs from "../shared/Tabs.svelte"
|
||||
import Card from "../pieces/Card.svelte"
|
||||
// tmp
|
||||
let login = { username: '', password: ''};
|
||||
// let's us track any errors in a submited form
|
||||
let errors = { username: '', password: ''};
|
||||
let valid:boolean = false;
|
||||
const loginHandler = () => {
|
||||
console.log('hi');
|
||||
};
|
||||
const createAccountHandler = () => {
|
||||
console.log('hi');
|
||||
};
|
||||
|
||||
// Tabs
|
||||
let items: string[] = ['Login', 'Create Account'];
|
||||
let activeItem: string = 'Login';
|
||||
|
||||
const tabChange = (e) => {
|
||||
activeItem = e.detail;
|
||||
};
|
||||
|
||||
// TMP for switching page, down the line this will be down by modifying url
|
||||
|
||||
</script>
|
||||
|
||||
<!-- New New Approach -->
|
||||
<!-- if i were to do all this with CSS Grids how would i do it? -->
|
||||
<!-- Things i want -->
|
||||
<!-- A title button, some nav buttons, a giant dope canvas and words over it -->
|
||||
<!-- not sure if i want: login and create account -->
|
||||
<!-- let's start with just the canvas -->
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- New aproach -->
|
||||
<!-- just make the html in order you can move it around all you want into special Compoenents later -->
|
||||
|
||||
<!-- Ok i think all of this needs to go in a Home Page Component -->
|
||||
<!-- and then i make another master component for the main page once you're logged in, no idea what that should look like -->
|
||||
|
||||
<!-- what if i kept the special canvas header in here and made another generic header for the rest of the site as a component -->
|
||||
|
||||
<header class="banner">
|
||||
<!-- top left corner, sticky -->
|
||||
<h1>Potato Pong</h1>
|
||||
<!-- top right but it takes you down the page -->
|
||||
<h2>Login</h2>
|
||||
|
||||
<!-- all this used to be in Welcome Section -->
|
||||
<!-- the amazing backround! not sure yet if it should scroll with us or be the size of the View Port... -->
|
||||
<!-- <canvas></canvas> -->
|
||||
<!-- i think maybe the canvas needs to be in the header -->
|
||||
<!-- using an image for now as a placehodler for the canvase -->
|
||||
<img src="/img/tmp_mario_banner.png" alt="tmp Mario banner">
|
||||
<div class="welcome">
|
||||
<h2>Welcome to <br><span>Potato Pong</span></h2>
|
||||
</div>
|
||||
<!-- I want some sort of arrow pointing down and blinking to indicate you should scroll -->
|
||||
</header>
|
||||
<!-- <section class="banner"> -->
|
||||
<section class="welcome">
|
||||
|
||||
</section>
|
||||
<!-- no nav on home page -->
|
||||
<section class="register">
|
||||
|
||||
<!-- i could have a toggle tab to login or create a new account -->
|
||||
|
||||
<Tabs items={items} {activeItem} on:tabChange={tabChange}/>
|
||||
{#if activeItem === 'Login'}
|
||||
<div class="card">
|
||||
<Card>
|
||||
<h2>Login</h2>
|
||||
<form on:submit|preventDefault={loginHandler}>
|
||||
<div class="form-field">
|
||||
<input type="text" id="username" placeholder="username" bind:value={login.username}>
|
||||
<div class="error">{ errors.username }</div>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<input type="password" id="password" placeholder="password" bind:value={login.password}>
|
||||
<div class="error">{ errors.password }</div>
|
||||
</div>
|
||||
<!-- type="" but flat={} cuz type is a string but flat is a bool-->
|
||||
<!-- <Button type="secondary" flat={true}>Add Poll</Button> -->
|
||||
<button>Login</button>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
{:else if activeItem = 'Create Account'}
|
||||
<!-- Create Account -->
|
||||
<div class="card">
|
||||
<Card>
|
||||
<h3>Create Account</h3>
|
||||
<form on:submit|preventDefault={createAccountHandler}>
|
||||
<div class="form-field">
|
||||
<input type="text" id="username" placeholder="username" bind:value={login.username}>
|
||||
<div class="error">{ errors.username }</div>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<input type="password" id="password" placeholder="password" bind:value={login.password}>
|
||||
<div class="error">{ errors.password }</div>
|
||||
</div>
|
||||
<!-- type="" but flat={} cuz type is a string but flat is a bool-->
|
||||
<!-- <Button type="secondary" flat={true}>Add Poll</Button> -->
|
||||
<button>Login</button>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
</section>
|
||||
<!-- below this i could say, this is where i might have put an explanation of what you can do on this page but fuck you i didn't -->
|
||||
<!-- or maybe in the end i will, something like: Fun, Game, Colors, enjoy Friendship, or don't, it's your choice! -->
|
||||
<Footer />
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
/* hearder stuff */
|
||||
|
||||
/* Clearly i have yet to master floating stuff... */
|
||||
/* i need to put box-sizing in here somewhere for the login... */
|
||||
|
||||
/* starting again with CSS Grid */
|
||||
/* .banner{
|
||||
position: relative;
|
||||
}
|
||||
.banner img{
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.banner h1{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 10px;
|
||||
}
|
||||
.banner h2{
|
||||
position: absolute;
|
||||
left: 90%;
|
||||
top: 10px;
|
||||
}
|
||||
.banner .welcome{
|
||||
background-color: #feb614;
|
||||
color:white;
|
||||
padding: 30px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
}
|
||||
.banner .welcome h2{
|
||||
font-size: 58px;
|
||||
}
|
||||
.banner .welcome h2 span{
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.register{
|
||||
|
||||
} */
|
||||
|
||||
|
||||
</style>
|
||||
@@ -1,90 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { now } from "svelte/internal";
|
||||
import Card from "../pieces/Card.svelte";
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
// import UserStore from './stores/UserStore';
|
||||
|
||||
// prolly Typescript-ify
|
||||
//let fields{question:string, answerA:string, answerB: string} = { question: '', answerA: '', answerB: ''};
|
||||
let login = { username: '', password: ''};
|
||||
// let's us track any errors in a submited form
|
||||
let errors = { username: '', password: ''};
|
||||
let valid:boolean = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const loginHandler = () => {
|
||||
valid = true;
|
||||
|
||||
if (login.username !== $UserStore.username) {
|
||||
valid = false;
|
||||
errors.username = "wrong example username."
|
||||
} else {
|
||||
// reseting the value of errors
|
||||
errors.username = "";
|
||||
}
|
||||
if (login.password !== $UserStore.password) {
|
||||
valid = false;
|
||||
errors.password = "wrong example password."
|
||||
} else {
|
||||
// reseting the value of errors
|
||||
errors.password = "";
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
$UserStore.loggedIn = true;
|
||||
$UserStore.status = 'online';
|
||||
|
||||
dispatch('login');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="login">
|
||||
<Card>
|
||||
|
||||
<form on:submit|preventDefault={loginHandler}>
|
||||
<div class="form-field">
|
||||
<input type="text" id="username" placeholder="username" bind:value={login.username}>
|
||||
<div class="error">{ errors.username }</div>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<input type="password" id="password" placeholder="password" bind:value={login.password}>
|
||||
<div class="error">{ errors.password }</div>
|
||||
</div>
|
||||
<!-- type="" but flat={} cuz type is a string but flat is a bool-->
|
||||
<!-- <Button type="secondary" flat={true}>Add Poll</Button> -->
|
||||
<button>Login</button>
|
||||
</form>
|
||||
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
form{
|
||||
width: 200px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
.form-field{
|
||||
margin: 18px auto;
|
||||
}
|
||||
input{
|
||||
width: 100%;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.login{
|
||||
/* display: grid;
|
||||
grid-template-columns: 1fr; */
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.error{
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
color: #d91b42;
|
||||
}
|
||||
</style>
|
||||
@@ -1,414 +0,0 @@
|
||||
<script lang="ts">
|
||||
// import Header from "./Header.svelte";
|
||||
import Footer from "../components/Footer.svelte";
|
||||
import Tabs from "../shared/Tabs.svelte";
|
||||
import Card from "../pieces/Card.svelte";
|
||||
import Canvas from "../pieces/Canvas.svelte";
|
||||
import ScrollTo from "../shared/ScrollTo.svelte";
|
||||
import UserStore from "./UserStore.js";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import {push} from "svelte-spa-router";
|
||||
|
||||
let dispatch = createEventDispatcher();
|
||||
// Tabs
|
||||
let items: string[] = ['Login', 'Create Account'];
|
||||
let activeItem: string = 'Login';
|
||||
|
||||
const tabChange = (e) => {
|
||||
activeItem = e.detail;
|
||||
};
|
||||
|
||||
|
||||
import axios from 'axios';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let user = {logedIn: false};
|
||||
|
||||
onMount(async () => {
|
||||
// console.log('PROFIL SVELTE');
|
||||
const {data} = await axios.get('http://transcendance:8080/api/v2/user');
|
||||
if (data)
|
||||
user.logedIn = true;
|
||||
});
|
||||
|
||||
const submit = async() => {
|
||||
document.body.scrollIntoView();
|
||||
push
|
||||
window.location.href = 'http://transcendance:8080/api/v2/auth';
|
||||
}
|
||||
|
||||
const logout = async() => {
|
||||
await fetch('http://transcendance:8080/api/v2/auth/logout',);
|
||||
user.logedIn = false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// for toLogin
|
||||
|
||||
let bottomHalf;
|
||||
// console.log(bottomHalf);
|
||||
// const element = document.body;
|
||||
|
||||
|
||||
// in theory this could be a Store, but for now this will do
|
||||
// also in future we'll do this with urls
|
||||
export let pages;
|
||||
export let currentPage;
|
||||
// this shit has overstayed it's welcome, fuck having userId all over the place
|
||||
export let userId;
|
||||
|
||||
|
||||
// maybe we put this in the login Component?
|
||||
// tmp
|
||||
let login = { username: '', password: ''};
|
||||
// let's us track any errors in a submited form
|
||||
let errors = { username: '', password: ''};
|
||||
let valid:boolean = false;
|
||||
|
||||
const loginHandler = () => {
|
||||
console.log('hi from loginHandler');
|
||||
|
||||
|
||||
//
|
||||
// Basic Checks
|
||||
//
|
||||
valid = false;
|
||||
|
||||
// checkin Username
|
||||
if (login.username.length < 1)
|
||||
{
|
||||
valid = false;
|
||||
errors.username = 'please enter a username';
|
||||
} else {
|
||||
valid = true;
|
||||
errors.username = '';
|
||||
}
|
||||
|
||||
// Checking Password
|
||||
if (login.password.length < 1)
|
||||
{
|
||||
valid = false;
|
||||
errors.password = 'please enter your password';
|
||||
} else {
|
||||
valid = true;
|
||||
errors.password = '';
|
||||
}
|
||||
|
||||
//
|
||||
// Advanded Checks
|
||||
//
|
||||
|
||||
// Comparing to UserStore
|
||||
let users;
|
||||
const unsubscribe = UserStore.subscribe(objs => {
|
||||
users = objs;
|
||||
console.log('subscribed');
|
||||
});
|
||||
|
||||
// could i do $users.length ? doesn't look like it...
|
||||
// let len = $users.length;
|
||||
|
||||
// userId = 0;
|
||||
// userId = users.filter(user => user.username === login.username);
|
||||
// this shit returns an array, it would be nice if it weren't an array
|
||||
// let user = users.filter(user => user.username === login.username);
|
||||
let user = users.find(user => user.username === login.username);
|
||||
|
||||
console.log(user);
|
||||
// console.log(user.password);
|
||||
|
||||
|
||||
// all this shit is a bit wordy... maybe a better way to handle this stuff?
|
||||
|
||||
// if (userIndex > users.length) {
|
||||
if (!user) {
|
||||
valid = false;
|
||||
errors.username = 'user not found';
|
||||
// something better?
|
||||
} else {
|
||||
valid = true;
|
||||
errors.username = '';
|
||||
}
|
||||
|
||||
// if (users[userIndex].password !== login.password) {
|
||||
if (user && user.password !== login.password) {
|
||||
valid = false;
|
||||
errors.password = 'Wrong Password';
|
||||
// Maybe clear the fields?
|
||||
} else {
|
||||
valid = true;
|
||||
errors.password = '';
|
||||
}
|
||||
|
||||
//
|
||||
// Validation
|
||||
//
|
||||
unsubscribe();
|
||||
|
||||
if (valid) {
|
||||
// yea don't modify this here...
|
||||
currentPage = 'user';
|
||||
// something like: indicate that userIndex is the one we want...
|
||||
console.log('valid Credentials');
|
||||
|
||||
// making sure we start at the top of the page, way less jarring
|
||||
// leave for now just in case...
|
||||
document.body.scrollIntoView();
|
||||
|
||||
// may not actually want a dispatch?
|
||||
// pass data userIndex?
|
||||
// dispatch('loggedIn');
|
||||
// from svelte-spa-router
|
||||
push("/user");
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
const createAccountHandler = () => {
|
||||
console.log('hi from accunt handler');
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<header class="grid-container">
|
||||
|
||||
<!-- <div on:mouseenter={enter} on:mouseleave={leave} class:active > -->
|
||||
<h1>Potato Pong</h1>
|
||||
<!-- </div> -->
|
||||
<nav>
|
||||
<!-- placeholder links -->
|
||||
<a href="/">Somewhere</a>
|
||||
<!-- <a href="/">SomewhereElse</a> -->
|
||||
{#if !user.logedIn}
|
||||
<ScrollTo element={bottomHalf}/>
|
||||
{:else}
|
||||
<div class="logout" on:click={logout}>Log Out</div>
|
||||
{/if}
|
||||
<!-- one of these will be login and it will scroll you down to the login part -->
|
||||
</nav>
|
||||
<h2>
|
||||
<div>Welcome to</div>
|
||||
<div>Potato Pong</div>
|
||||
</h2>
|
||||
|
||||
<!-- here i want a flashing arrow pointing down to the login part -->
|
||||
|
||||
</header>
|
||||
<!-- <Canvas class=".canvas2"/> -->
|
||||
<Canvas/>
|
||||
|
||||
|
||||
|
||||
<section class="register" bind:this={bottomHalf}>
|
||||
|
||||
<!-- My beautiful tabs are useless now, whatever, kill your darlings... -->
|
||||
<!-- i could have a toggle tab to login or create a new account -->
|
||||
<!-- This shit is kinda unnecessary cuz there is no Create Account option... -->
|
||||
|
||||
<Tabs items={items} {activeItem} on:tabChange={tabChange}/>
|
||||
{#if activeItem === 'Login'}
|
||||
<div class="card">
|
||||
<Card>
|
||||
<h2>Login</h2>
|
||||
<form on:submit|preventDefault={loginHandler}>
|
||||
<div class="form-field">
|
||||
<input type="text" id="username" placeholder="username" bind:value={login.username}>
|
||||
<div class="error">{ errors.username }</div>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<input type="password" id="password" placeholder="password" bind:value={login.password}>
|
||||
<div class="error">{ errors.password }</div>
|
||||
</div>
|
||||
<!-- type="" but flat={} cuz type is a string but flat is a bool-->
|
||||
<!-- <Button type="secondary" flat={true}>Add Poll</Button> -->
|
||||
<button>Login</button>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
{:else if activeItem = 'Create Account'}
|
||||
<!-- Create Account -->
|
||||
<div class="card">
|
||||
<Card>
|
||||
<h2>Create Account</h2>
|
||||
<form on:submit|preventDefault={createAccountHandler}>
|
||||
<div class="form-field">
|
||||
<input type="text" id="username" placeholder="username" bind:value={login.username}>
|
||||
<div class="error">{ errors.username }</div>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<input type="password" id="password" placeholder="password" bind:value={login.password}>
|
||||
<div class="error">{ errors.password }</div>
|
||||
</div>
|
||||
<!-- type="" but flat={} cuz type is a string but flat is a bool-->
|
||||
<!-- <Button type="secondary" flat={true}>Add Poll</Button> -->
|
||||
<button>Login</button>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
</section>
|
||||
<!-- below this i could say, this is where i might have put an explanation of what you can do on this page but fuck you i didn't -->
|
||||
<!-- or maybe in the end i will, something like: Fun, Game, Colors, enjoy Friendship, or don't, it's your choice! -->
|
||||
|
||||
<Footer />
|
||||
|
||||
<!-- </div> -->
|
||||
|
||||
|
||||
<style>
|
||||
/* currently useless */
|
||||
/* No styles get applied to the canvas from here, maybe i should move them to the Canvas Component... */
|
||||
/* tho tbh, why bother, i'm gonna change it anyway... */
|
||||
.canvas{
|
||||
/* grid-column: 1 / 13;
|
||||
grid-row: 1 / 3; */
|
||||
/* don't rely on Z-Index!!!! */
|
||||
z-index: -1;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
white-space: nowrap;
|
||||
|
||||
/* Tmp? */
|
||||
/* background-color: #666; */
|
||||
|
||||
/* somehow this got rid of they annoying white space under the canvas */
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.canvas2{
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
white-space: nowrap;
|
||||
|
||||
/* Tmp? */
|
||||
/* background-color: #666; */
|
||||
|
||||
/* somehow this got rid of they annoying white space under the canvas */
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* .canvas .grid-container{ */
|
||||
.grid-container{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
white-space: nowrap;
|
||||
/* padding-bottom: 0; */
|
||||
margin-bottom: 0px;
|
||||
overflow: hidden;
|
||||
padding: 20px 40px;
|
||||
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
grid-template-rows: 1fr 1fr 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
header h1, header nav a{
|
||||
/* tmp ? well i kinda like it */
|
||||
color: bisque;
|
||||
}
|
||||
header h1{
|
||||
grid-column: 1 / 7;
|
||||
grid-row: 1;
|
||||
/* grid-column: span 6; */
|
||||
/* tmp? */
|
||||
padding: 20px;
|
||||
border: 1px solid bisque;
|
||||
}
|
||||
header nav{
|
||||
/* make it a flexbox? */
|
||||
grid-column: 7 / 13;
|
||||
grid-row: 1;
|
||||
justify-self: end;
|
||||
/* tmp? */
|
||||
padding: 20px;
|
||||
border: 1px solid bisque;
|
||||
}
|
||||
header nav a{
|
||||
margin-left: 10px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* testing */
|
||||
header nav a:hover{
|
||||
font-weight: bold;
|
||||
background-color: blue;
|
||||
}
|
||||
header h2:hover{
|
||||
background: blue;
|
||||
}
|
||||
|
||||
|
||||
header h2{
|
||||
grid-row: 3;
|
||||
grid-column: 5 / span 4;
|
||||
justify-self: center;
|
||||
/* tmp */
|
||||
border: 1px solid black;
|
||||
z-index: 3;
|
||||
}
|
||||
header h2 div{
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
/* the login / register part */
|
||||
|
||||
/* What do i want?
|
||||
I want it to be the same size as a full screen so you don't see the canvas at all anymore */
|
||||
|
||||
/* doesn't work... */
|
||||
/* body{
|
||||
background: bisque;
|
||||
} */
|
||||
|
||||
.bottom-half{
|
||||
/* doesn't quite work... */
|
||||
background: bisque;
|
||||
/* also doesn't work... */
|
||||
/* height: 1vw; */
|
||||
|
||||
/* testing */
|
||||
/* position: absolute; */
|
||||
}
|
||||
|
||||
section.register{
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.error{
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -1,305 +0,0 @@
|
||||
<script lang="ts">
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { location } from 'svelte-spa-router';
|
||||
// this is how you access /:first for example
|
||||
// export let params = {}
|
||||
// <p>Your name is: <b>{params.first}</b> <b>{#if params.last}{params.last}{/if}</b></p>
|
||||
|
||||
// If i export these vars, maybe as an nice tidy object, i could pass whatever i like to them
|
||||
// The current user, some other user, whatever, and thus reuse this Componente for the user and their friends or whatever
|
||||
// will have to coordinate with Back, will know more once the Game stats are in the back
|
||||
// wait maybe this won't work, cuz like it's still going through a route, i would have to update a Store Var each time...
|
||||
// not sure if that's what i want...
|
||||
|
||||
|
||||
// maybe the rank is determined dynamically just in the front based on win loss ratio or something no one cares about
|
||||
// why bother storing that shit in the back...
|
||||
// maybe i need a Rank.svelte component
|
||||
// ohhh i could make above a certain rank glitter! like that CSS tutorial showed me!
|
||||
|
||||
let user;
|
||||
let rank = '';
|
||||
let avatar;
|
||||
|
||||
// i think i don't need to do this once i sort out the {wrap} conditions: in theory i could pass values to the Route
|
||||
// once the async authentication check is done
|
||||
onMount( async() => {
|
||||
// console.log('mounting profile display')
|
||||
user = await fetch('http://transcendance:8080/api/v2/user')
|
||||
.then( (x) => x.json() );
|
||||
|
||||
// console.log('profile display did my fetch')
|
||||
// should i be updating the userStore or is that unnecessary?
|
||||
|
||||
if (user.loseGame > user.winGame) {
|
||||
rank = 'Bitch Ass Loser!'
|
||||
} else if (user.loseGame === user.winGame) {
|
||||
rank = 'Fine i guess...'
|
||||
} else {
|
||||
rank = 'Yea you da Boss!'
|
||||
}
|
||||
|
||||
await fetch("http://transcendance:8080/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);
|
||||
|
||||
|
||||
})
|
||||
|
||||
// Glittery Stars and such for Rank
|
||||
|
||||
let index = 0, interval = 1000;
|
||||
|
||||
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)}%`);
|
||||
|
||||
star.style.animation = "none";
|
||||
star.offsetHeight;
|
||||
star.style.animation = "";
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- is this if excessive? -->
|
||||
<div class="outer">
|
||||
{#if user !== undefined}
|
||||
<main>
|
||||
<!-- <img class="icon" src="img/default_user_icon.png" alt="default user icon"> -->
|
||||
<!-- <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:
|
||||
<span class="glitter">
|
||||
<span bind:this={stars[0]} class="glitter-star">
|
||||
<svg viewBox="0 0 512 512">
|
||||
<path d="M512 255.1c0 11.34-7.406 20.86-18.44 23.64l-171.3 42.78l-42.78 171.1C276.7 504.6 267.2 512 255.9 512s-20.84-7.406-23.62-18.44l-42.66-171.2L18.47 279.6C7.406 276.8 0 267.3 0 255.1c0-11.34 7.406-20.83 18.44-23.61l171.2-42.78l42.78-171.1C235.2 7.406 244.7 0 256 0s20.84 7.406 23.62 18.44l42.78 171.2l171.2 42.78C504.6 235.2 512 244.6 512 255.1z" />
|
||||
</svg>
|
||||
</span>
|
||||
<span bind:this={stars[1]} class="glitter-star">
|
||||
<svg viewBox="0 0 512 512">
|
||||
<path d="M512 255.1c0 11.34-7.406 20.86-18.44 23.64l-171.3 42.78l-42.78 171.1C276.7 504.6 267.2 512 255.9 512s-20.84-7.406-23.62-18.44l-42.66-171.2L18.47 279.6C7.406 276.8 0 267.3 0 255.1c0-11.34 7.406-20.83 18.44-23.61l171.2-42.78l42.78-171.1C235.2 7.406 244.7 0 256 0s20.84 7.406 23.62 18.44l42.78 171.2l171.2 42.78C504.6 235.2 512 244.6 512 255.1z" />
|
||||
</svg>
|
||||
</span>
|
||||
<span bind:this={stars[2]} class="glitter-star">
|
||||
<svg viewBox="0 0 512 512">
|
||||
<path d="M512 255.1c0 11.34-7.406 20.86-18.44 23.64l-171.3 42.78l-42.78 171.1C276.7 504.6 267.2 512 255.9 512s-20.84-7.406-23.62-18.44l-42.66-171.2L18.47 279.6C7.406 276.8 0 267.3 0 255.1c0-11.34 7.406-20.83 18.44-23.61l171.2-42.78l42.78-171.1C235.2 7.406 244.7 0 256 0s20.84 7.406 23.62 18.44l42.78 171.2l171.2 42.78C504.6 235.2 512 244.6 512 255.1z" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="glitter-text">{rank}</span>
|
||||
</span>
|
||||
</div>
|
||||
<section class="main-stats">
|
||||
<h4>Match Statistics</h4>
|
||||
<p>Total: {user.stats.totalGame}</p>
|
||||
<p>Victories: {user.stats.winGame}</p>
|
||||
<p>Losses: {user.stats.loseGame}</p>
|
||||
<p>Draws: {user.stats.drawGame}</p>
|
||||
</section>
|
||||
</main>
|
||||
{/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{
|
||||
max-width: 960px;
|
||||
margin: 40px auto;
|
||||
}
|
||||
|
||||
/* The main part */
|
||||
main{
|
||||
max-width: 960px;
|
||||
margin: 40px auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Normal CSS stuff */
|
||||
.avatar{
|
||||
max-width: 150px;
|
||||
/* padding: 5px; */
|
||||
}
|
||||
|
||||
/* The variable rich section */
|
||||
section.main-stats{
|
||||
max-width: 600px;
|
||||
margin: 40px auto;
|
||||
text-align: center;
|
||||
/* i think i want to use a grid? */
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
/* not sure about this, maybe top should be larger? */
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
/* the stuff in the grid*/
|
||||
section.main-stats h4{
|
||||
grid-column: 1 / span 3;
|
||||
}
|
||||
|
||||
div.username{
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
div.rank {
|
||||
/* color: black; */
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
/* Glittery Star Stuff */
|
||||
|
||||
|
||||
:root {
|
||||
--purple: rgb(123, 31, 162);
|
||||
--violet: rgb(103, 58, 183);
|
||||
--pink: rgb(244, 143, 177);
|
||||
/* make shit gold? */
|
||||
}
|
||||
|
||||
@keyframes background-pan {
|
||||
from {
|
||||
background-position: 0% center;
|
||||
}
|
||||
|
||||
to {
|
||||
background-position: -200% center;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scale {
|
||||
from, to {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
div > .glitter {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div > .glitter > .glitter-star {
|
||||
--size: clamp(20px, 1.5vw, 30px);
|
||||
|
||||
animation: scale 700ms ease forwards;
|
||||
display: block;
|
||||
height: var(--size);
|
||||
left: var(--star-left);
|
||||
position: absolute;
|
||||
top: var(--star-top);
|
||||
width: var(--size);
|
||||
}
|
||||
|
||||
div > .glitter > .glitter-star > svg {
|
||||
animation: rotate 1000ms linear infinite;
|
||||
display: block;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
div > .glitter > .glitter-star > svg > path {
|
||||
fill: var(--violet);
|
||||
}
|
||||
|
||||
div > .glitter > .glitter-text {
|
||||
animation: background-pan 3s linear infinite;
|
||||
/* background-image: linear-gradient( */
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
var(--purple),
|
||||
var(--violet),
|
||||
var(--pink),
|
||||
var(--purple)
|
||||
);
|
||||
background-size: 200%;
|
||||
|
||||
/* Keep these for Safari and chrome */
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
|
||||
/* These are for Firefox */
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,137 +0,0 @@
|
||||
<script lang="ts">
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
import { loginStatus } from '../stores/loginStatusStore';
|
||||
|
||||
// Cherif's code
|
||||
|
||||
let qrCodeImg;
|
||||
let qrCode = "";
|
||||
let wrongCode = "";
|
||||
let maxTry = 3;
|
||||
// const fetchQrCodeImg = (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;
|
||||
// });
|
||||
// })()
|
||||
|
||||
// $: submit = async() => {
|
||||
// const response = await fetch("http://transcendance:8080/api/v2/auth/2fa/turn-on",
|
||||
// {
|
||||
// method : 'POST',
|
||||
// headers : {
|
||||
// "Content-Type": "application/json",
|
||||
// },
|
||||
// body : JSON.stringify({
|
||||
// "twoFaCode" : qrCode,
|
||||
// }),
|
||||
// });
|
||||
// if (response.status === 401)
|
||||
// {
|
||||
// qrCode = "";
|
||||
// wrongCode = `Wrong code, please try again. You have ${maxTry} before end session`;
|
||||
// maxTry--;
|
||||
// }
|
||||
// if (maxTry === 0)
|
||||
// {
|
||||
// await fetch("http://transcendance:8080/auth/logout",
|
||||
// {
|
||||
// method : 'POST',
|
||||
// })
|
||||
// .then(response => response.json())
|
||||
// .then(push("/login"));
|
||||
// }
|
||||
// if (response.status === 200)
|
||||
// {
|
||||
// push("/");
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// My code
|
||||
|
||||
|
||||
|
||||
let auth;
|
||||
// we're expecting secret and otpauth
|
||||
|
||||
onMount( async() => {
|
||||
// auth = await fetch('http://transcendance:8080/api/v2/auth/2fa/generate', {
|
||||
// method: 'POST'
|
||||
// })
|
||||
// .then((resp) => resp.json());
|
||||
// console.log(auth.secret);
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// testing loginStatus Custom Store
|
||||
|
||||
const toggleTFA = () => {
|
||||
loginStatus.toggleTFA();
|
||||
console.log($loginStatus.tfa);
|
||||
}
|
||||
|
||||
|
||||
// testing
|
||||
|
||||
let auth2
|
||||
const TFA = async() => {
|
||||
// ok no idea what goes in here...
|
||||
auth2 = await fetch('http://transcendance:8080/api/v2/auth/2fa/generate', {
|
||||
method: 'POST'
|
||||
})
|
||||
// .then((resp) => resp.json());
|
||||
|
||||
// console.log(auth2.secret);
|
||||
console.log(auth2);
|
||||
};
|
||||
|
||||
// if ($loginStatus.tfa && $loginStatus.fortyTwo)
|
||||
|
||||
</script>
|
||||
|
||||
<h1>2FA Test</h1>
|
||||
|
||||
<div>
|
||||
<button on:click={TFA}>TFA</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{#if auth2}
|
||||
<p>{auth2.json()}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<img src={qrCodeImg} alt="A QRCodeImg you must scan with google authenticator" id="qrcodeImg" />
|
||||
|
||||
|
||||
<!-- <p>FortyTwo: {$loginStatus.fortyTwo}</p>
|
||||
<p>TFA: {$loginStatus.tfa}</p>
|
||||
<p>isLogged: {loginStatus.isLogged}</p>
|
||||
|
||||
<div>
|
||||
<button on:click={toggleTFA}>toggleTFA</button>
|
||||
</div> -->
|
||||
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -1,149 +0,0 @@
|
||||
<script lang="ts">
|
||||
|
||||
// The User Page can have several Flavors ?
|
||||
// like a HomePage vibe, and an AccountPage vibe or whatever, but we'll still be serving this page just with diff props
|
||||
|
||||
import UserStore from "./UserStore";
|
||||
import Header from "../components/Header.svelte";
|
||||
import Footer from "../components/Footer.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { loginStatus } from '../stores/loginStatusStore';
|
||||
import { push } from "svelte-spa-router";
|
||||
|
||||
let dispatch = createEventDispatcher();
|
||||
|
||||
// i fucking hate these vars, the will have to go
|
||||
export let pages;
|
||||
export let currentPage;
|
||||
export let userId;
|
||||
|
||||
// This shit is so redundant...
|
||||
let types = ['home', 'account']
|
||||
export let currentType = 'account';
|
||||
|
||||
// this is also stupid...
|
||||
let sidebar = true;
|
||||
|
||||
|
||||
// would i prefer to forward this?
|
||||
let clickedHome = () => {
|
||||
console.log('clicked home');
|
||||
// do something...
|
||||
currentType = 'home';
|
||||
};
|
||||
|
||||
let clickedLogout = async() => {
|
||||
console.log('clicked logout');
|
||||
await fetch('http://transcendance:8080/api/v2/auth/logout',);
|
||||
// $loginStatus = false;
|
||||
// maybe use replace() ?
|
||||
push('/');
|
||||
};
|
||||
|
||||
// All the variables that will eventually be replaced by the real values
|
||||
|
||||
let username = 'Username';
|
||||
let games = { total: 7, won: 4, lost: 3};
|
||||
let rank = 'gold or whatever the fuck who cares...';
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<!-- remove ={clickedHome} if you want to forward the event to App.svelte-->
|
||||
<!-- god this is some gross code... -->
|
||||
<Header on:clickedHome={clickedHome} currentType="{currentType === 'home' ? 'home' : 'regular'}" on:clickedLogout={clickedLogout}/>
|
||||
|
||||
<!-- The Wave -->
|
||||
<!-- <div class="spacer layer1"></div> -->
|
||||
|
||||
<!-- this is the thing that will let me offset -->
|
||||
<div class='{sidebar ? "main-grid" : "none"}'>
|
||||
{#if sidebar}
|
||||
<section class="sidebar">
|
||||
<p>i am a sidebar</p>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<main class:offset={sidebar}>
|
||||
<!-- what the fuck do we even want in here? messges, about the user, STATISTICS!!! -->
|
||||
<!-- <div>some stuff goes here</div> -->
|
||||
<img class="icon" src="img/default_user_icon.png" alt="default user icon">
|
||||
<div>{username}</div>
|
||||
<div>Rank: {rank}</div>
|
||||
<section class="main-stats">
|
||||
<h4>Match Statistics</h4>
|
||||
<p>Total: {games.total}</p>
|
||||
<p>Victories: {games.won}</p>
|
||||
<p>Losses: {games.lost}</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
|
||||
|
||||
/* 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');
|
||||
}
|
||||
|
||||
div.main-grid{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
/* max-height: calc(100vh - 30vh); */
|
||||
height: 85vh;
|
||||
}
|
||||
|
||||
section.sidebar{
|
||||
grid-column: 1 / span 2;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* The main part */
|
||||
main{
|
||||
max-width: 960px;
|
||||
margin: 40px auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
main.offset{
|
||||
grid-column: 3 / span 10;
|
||||
}
|
||||
|
||||
/* Normal CSS stuff */
|
||||
.icon{
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
/* The variable rich section */
|
||||
section.main-stats{
|
||||
max-width: 600px;
|
||||
margin: 40px auto;
|
||||
text-align: center;
|
||||
/* i think i want to use a grid? */
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
/* not sure about this, maybe top should be larger? */
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
/* the stuff in the grid*/
|
||||
section.main-stats h4{
|
||||
grid-column: 1 / span 3;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,45 +0,0 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
// ok yea this doesn't make a lot of sense, what am i trying to do?
|
||||
// have an array of objects that are all the users?
|
||||
// or an object that is the one user?
|
||||
// For now as a placeholder i'll have one user in one obj
|
||||
|
||||
// should it not be a const? yea seems like it
|
||||
// export const users = writable(
|
||||
const UserStore = writable(
|
||||
[{
|
||||
// this is an example user
|
||||
id: 1,
|
||||
username: 'chaboi',
|
||||
email: 'nope@fu.com',
|
||||
// surely there's a better way to do this!
|
||||
password: '1234',
|
||||
// maybe an object listing friends' usernames?
|
||||
friends: 0,
|
||||
loggedIn: false,
|
||||
// i imagine the user uploading their Avatare and it being put somehwere in the DB which could be referenced like a URL
|
||||
// so if this field is empty then use the default avatar
|
||||
avatar: '',
|
||||
// online, offline, gaming
|
||||
status: 'offline',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: 'itsame',
|
||||
email: 'mario@nintendo.com',
|
||||
// surely there's a better way to do this!
|
||||
password: '1234',
|
||||
// maybe an object listing friends' usernames?
|
||||
friends: 0,
|
||||
loggedIn: false,
|
||||
// i imagine the user uploading their Avatare and it being put somehwere in the DB which could be referenced like a URL
|
||||
// so if this field is empty then use the default avatar
|
||||
avatar: '',
|
||||
// online, offline, gaming
|
||||
status: 'offline',
|
||||
}]
|
||||
);
|
||||
|
||||
export default UserStore;
|
||||
// export default users;
|
||||
@@ -1,24 +0,0 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
// an alternative way of doing things where i have a svelte store connected to localStorage
|
||||
|
||||
// do in need to adapt this to work with 2fa?
|
||||
|
||||
let _user = localStorage.getItem('42User');
|
||||
|
||||
// turns out a simple store is actually the easiest :)
|
||||
// export const userStore = writable(_user ? JSON.parse(_user) : null); // we start with no user, but go get one if one exists
|
||||
// export const userStore = writable(null);
|
||||
|
||||
// ok so this will happen no matter what, basically we are telling it what to do if the store containing the user changes
|
||||
userStore.subscribe((value) => {
|
||||
if (value)
|
||||
localStorage.setItem('42User', JSON.stringify(value));
|
||||
else
|
||||
localStorage.removeItem('42User'); // for logout
|
||||
});
|
||||
|
||||
export const userLogout = () => userStore.set(null);
|
||||
|
||||
|
||||
// export const tmpStore = userStore
|
||||
@@ -1,129 +0,0 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
// This is a "Custom Store" see that chapter in the Svelte Tutorial, should be fine
|
||||
// NVM this is definitely overkill
|
||||
// function createLogin() {
|
||||
// const { subscribe, update } = writable(false);
|
||||
|
||||
// return {
|
||||
// subscribe,
|
||||
// login: () => update(s => s = true),
|
||||
// logout: () => update(s => s = false),
|
||||
// }
|
||||
// }
|
||||
// export const loginStatus = createLogin();
|
||||
|
||||
// export const loginStatus = writable({
|
||||
// 42: false,
|
||||
// tfa: false,
|
||||
// });
|
||||
|
||||
// function createLoginStatus() {
|
||||
|
||||
// //ok it really hated all this
|
||||
|
||||
// // const store = writable({
|
||||
// // fortyTwo: false,
|
||||
// // tfa: false,
|
||||
// // });
|
||||
|
||||
// // return {
|
||||
// // ...store,
|
||||
// // subscribe,
|
||||
// // // toggle42: () => update( l => l.fortyTwo = !l.fortyTwo ),
|
||||
// // toggle42: () => store.update( fortyTwo => !fortyTwo ),
|
||||
// // // toggleTFA: () => update( l => l.tfa = !l.tfa ),
|
||||
// // toggleTFA: () => store.update( tfa => !tfa ),
|
||||
// // isLogged: () => store.fortyTwo && store.tfa,
|
||||
// // // isLogged: this.fortyTwo && this.tfa,
|
||||
// // // it really doesn't like "this."
|
||||
// // // isLogged: () => (this.tfa && this.fortyTwo),
|
||||
// // // this. ? or (l) => l.tfa ... ?
|
||||
// // }
|
||||
|
||||
|
||||
// // doesn't seem to work...
|
||||
// const { subscribe, update } = writable({
|
||||
// fortyTwo: false,
|
||||
// tfa: false,
|
||||
// });
|
||||
|
||||
// return {
|
||||
// subscribe,
|
||||
// // toggle42: () => update( l => l.fortyTwo = !l.fortyTwo ),
|
||||
// toggle42: () => update( fortyTwo => !fortyTwo ),
|
||||
// // toggleTFA: () => update( l => l.tfa = !l.tfa ),
|
||||
// toggleTFA: () => update( tfa => !tfa ),
|
||||
// // isLogged: () => fortyTwo && tfa,
|
||||
// // isLogged: this.fortyTwo && this.tfa,
|
||||
// // it really doesn't like "this."
|
||||
// // isLogged: () => (this.tfa && this.fortyTwo),
|
||||
// // this. ? or (l) => l.tfa ... ?
|
||||
// isLogged() {
|
||||
// return fortyTwo && tfa;
|
||||
// },
|
||||
// }
|
||||
|
||||
// // possible other way of doing this
|
||||
|
||||
// // const store = writable({
|
||||
// // fortyTwo: false,
|
||||
// // tfa: false,
|
||||
// // });
|
||||
|
||||
// // return {
|
||||
// // ...store,
|
||||
// // subscribe,
|
||||
// // // toggle42: () => update( l => l.fortyTwo = !l.fortyTwo ),
|
||||
// // toggle42: () => store.update( l.fortyTwo => !l.fortyTwo ),
|
||||
// // toggleTFA: () => store.update( l => l.tfa = !l.tfa ),
|
||||
// // isLogged: store.fortyTwo && store.tfa,
|
||||
// // // isLogged: () => (this.tfa && this.fortyTwo),
|
||||
// // // this. ? or (l) => l.tfa ... ?
|
||||
// // }
|
||||
|
||||
// }
|
||||
|
||||
function createLoginStatus() {
|
||||
const { subscribe, update } = writable({
|
||||
fortyTwo: false,
|
||||
tfa: false,
|
||||
});
|
||||
|
||||
function toggle42() {
|
||||
update( (old) => ({...old, fortyTwo: !old.fortyTwo}) );
|
||||
};
|
||||
|
||||
function toggleTFA() {
|
||||
// update( () => {
|
||||
// self.tfa = !self.tfa;
|
||||
// return self;
|
||||
// })
|
||||
// console.log("testing");
|
||||
update( (old) => ({...old, tfa: !old.tfa}) );
|
||||
};
|
||||
|
||||
function isLogged() {
|
||||
// return (l) => {l.fortyTwo && l.tfa};
|
||||
// return self.fortyTwo && self.tfa;
|
||||
// return fortyTwo && tfa;
|
||||
};
|
||||
|
||||
return { subscribe, update, toggle42, toggleTFA, isLogged };
|
||||
}
|
||||
|
||||
export const loginStatus = createLoginStatus();
|
||||
|
||||
// OK let's try a totally new approach
|
||||
|
||||
// const _loginStatus = writable({
|
||||
// fortyTwo: false,
|
||||
// tfa: false,
|
||||
// })
|
||||
|
||||
// export const loginStatus = {
|
||||
// subscribe: _loginStatus.subscribe,
|
||||
// set: _loginStatus.set,
|
||||
// update: _loginStatus.update,
|
||||
// toggle42: () =>
|
||||
// }
|
||||
@@ -1,54 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'Monocode-Regular-Demo';
|
||||
src:url('/fonts/Monocode-Regular-Demo.ttf.woff') format('woff'),
|
||||
url('Monocode-Regular-Demo.ttf.svg#Monocode-Regular-Demo') format('svg'),
|
||||
url('Monocode-Regular-Demo.ttf.eot'),
|
||||
url('Monocode-Regular-Demo.ttf.eot?#iefix') format('embedded-opentype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Air-Conditioner';
|
||||
src:url('/fonts/Air-Conditioner.ttf.woff') format('woff'),
|
||||
url('Air-Conditioner.ttf.svg#Air-Conditioner') format('svg'),
|
||||
url('Air-Conditioner.ttf.eot'),
|
||||
url('Air-Conditioner.ttf.eot?#iefix') format('embedded-opentype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: '1968-Odyssey-3D';
|
||||
src:url('/fonts/1968-Odyssey-3D.ttf.woff') format('woff'),
|
||||
url('1968-Odyssey-3D.ttf.svg#1968-Odyssey-3D') format('svg'),
|
||||
url('1968-Odyssey-3D.ttf.eot'),
|
||||
url('1968-Odyssey-3D.ttf.eot?#iefix') format('embedded-opentype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: '1968-Odyssey-Gradient';
|
||||
src:url('/fonts/1968-Odyssey-Gradient.ttf.woff') format('woff'),
|
||||
url('1968-Odyssey-Gradient.ttf.svg#1968-Odyssey-Gradient') format('svg'),
|
||||
url('1968-Odyssey-Gradient.ttf.eot'),
|
||||
url('1968-Odyssey-Gradient.ttf.eot?#iefix') format('embedded-opentype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'AddFatMan';
|
||||
src:url('/fonts/AddFatMan.ttf.woff') format('woff'),
|
||||
url('/fonts/AddFatMan.ttf.svg#AddFatMan') format('svg'),
|
||||
url('/fonts/AddFatMan.ttf.eot'),
|
||||
url('/fonts/AddFatMan.ttf.eot?#iefix') format('embedded-opentype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Bondi';
|
||||
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');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
<script lang="ts">
|
||||
import NotFound from "../src/pages/NotFound.svelte";
|
||||
import ProfilePage from "../src/pages/profile/ProfilePage.svelte";
|
||||
import SplashPage from "../src/pages/SplashPage.svelte";
|
||||
import TwoFactorAuthentication from '../src/pages/TwoFactorAuthentication.svelte';
|
||||
import UnauthorizedAccessPage from '../src/pages/UnauthorizedAccessPage.svelte';
|
||||
import { wrap } from 'svelte-spa-router/wrap'
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
import TestPage from '../src/pages/TmpTestPage.svelte';
|
||||
import { userStore, userLogout } from "../src/stores/loginStatusStore";
|
||||
|
||||
|
||||
|
||||
// "/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,
|
||||
'/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 => {
|
||||
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 !== null) {
|
||||
if (user && user.username) {
|
||||
unsub();
|
||||
return true;
|
||||
} else {
|
||||
unsub();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
]
|
||||
}),
|
||||
// '/test': wrap({
|
||||
// component: TestPage,
|
||||
// conditions: [
|
||||
// async(detail) => {
|
||||
// // THIS SHIT TOTALLY WORKS
|
||||
// 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;
|
||||
// }
|
||||
// ]
|
||||
// }),
|
||||
'/2fa': TwoFactorAuthentication,
|
||||
"/profile": ProfilePage,
|
||||
"/profile/*": ProfilePage,
|
||||
'/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
|
||||
// };
|
||||
|
||||
</script>
|
||||
1308
srcs/requirements/svelte/api_front/package-lock.json
generated
1308
srcs/requirements/svelte/api_front/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@
|
||||
"@rollup/plugin-node-resolve": "^11.0.0",
|
||||
"@rollup/plugin-typescript": "^8.0.0",
|
||||
"@tsconfig/svelte": "^2.0.0",
|
||||
"@types/node": "^18.11.18",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-css-only": "^3.1.0",
|
||||
"rollup-plugin-livereload": "^2.0.0",
|
||||
@@ -25,7 +26,10 @@
|
||||
"typescript": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rollup/plugin-replace": "^5.0.2",
|
||||
"dotenv": "^16.0.3",
|
||||
"sirv-cli": "^2.0.0",
|
||||
"socket.io-client": "^4.5.4",
|
||||
"svelte-spa-router": "^3.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
23467
srcs/requirements/svelte/api_front/public/build/bundle.js
Normal file
23467
srcs/requirements/svelte/api_front/public/build/bundle.js
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
BIN
srcs/requirements/svelte/api_front/public/favicon.ico
Normal file
BIN
srcs/requirements/svelte/api_front/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,16 +0,0 @@
|
||||
|
||||
import * as c from "./constants.js"
|
||||
|
||||
export const soundPongArr: HTMLAudioElement[] = [];
|
||||
export const soundRoblox = new Audio("http://localhost:8080/sound/roblox-oof.ogg");
|
||||
|
||||
export function initAudio(muteFlag: boolean)
|
||||
{
|
||||
for (let i = 0; i <= 32; i++) {
|
||||
soundPongArr.push(new Audio("http://localhost:8080/sound/pong/"+i+".ogg"));
|
||||
soundPongArr[i].volume = c.soundPongVolume;
|
||||
soundPongArr[i].muted = muteFlag;
|
||||
}
|
||||
soundRoblox.volume = c.soundRobloxVolume;
|
||||
soundRoblox.muted = muteFlag;
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
|
||||
import * as en from "../enums.js"
|
||||
|
||||
/* From Server */
|
||||
class ServerEvent {
|
||||
type: en.EventTypes;
|
||||
constructor(type: en.EventTypes = 0) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
class EventAssignId extends ServerEvent {
|
||||
id: string;
|
||||
constructor(id: string) {
|
||||
super(en.EventTypes.assignId);
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
class EventMatchmakingComplete extends ServerEvent {
|
||||
side: en.PlayerSide;
|
||||
constructor(side: en.PlayerSide) {
|
||||
super(en.EventTypes.matchmakingComplete);
|
||||
this.side = side;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
class EventScoreUpdate extends ServerEvent {
|
||||
scoreLeft: number;
|
||||
scoreRight: number;
|
||||
constructor(scoreLeft: number, scoreRight: number) {
|
||||
super(en.EventTypes.scoreUpdate);
|
||||
this.scoreLeft = scoreLeft;
|
||||
this.scoreRight = scoreRight;
|
||||
}
|
||||
}
|
||||
|
||||
class EventMatchEnd extends ServerEvent {
|
||||
winner: en.PlayerSide;
|
||||
constructor(winner: en.PlayerSide) {
|
||||
super(en.EventTypes.matchEnd);
|
||||
this.winner = winner;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* From Client */
|
||||
class ClientEvent {
|
||||
type: en.EventTypes; // readonly ?
|
||||
constructor(type: en.EventTypes = 0) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
class ClientAnnounce extends ClientEvent {
|
||||
role: en.ClientRole;
|
||||
clientId: string;
|
||||
matchOptions: en.MatchOptions;
|
||||
constructor(role: en.ClientRole, matchOptions: en.MatchOptions, clientId: string = "") {
|
||||
super(en.EventTypes.clientAnnounce);
|
||||
this.role = role;
|
||||
this.clientId = clientId;
|
||||
this.matchOptions = matchOptions;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
ServerEvent, EventAssignId, EventMatchmakingComplete,
|
||||
EventGameUpdate, EventScoreUpdate, EventMatchEnd,
|
||||
ClientEvent, ClientAnnounce, EventInput
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
|
||||
import * as c from "./constants.js";
|
||||
import * as en from "../shared_js/enums.js"
|
||||
import { gc, matchOptions, clientInfo } from "./global.js";
|
||||
import { wallsMovements } from "../shared_js/wallsMovement.js";
|
||||
|
||||
let actual_time: number = Date.now();
|
||||
let last_time: number;
|
||||
let delta_time: number;
|
||||
|
||||
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 ) {
|
||||
opponentInterpolation(delta_time);
|
||||
}
|
||||
|
||||
// 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 opponentInterpolation(delta: number)
|
||||
{
|
||||
// interpolation
|
||||
clientInfo.opponent.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]);
|
||||
|
||||
if ((clientInfo.opponent.dir.y > 0 && clientInfo.opponent.pos.y > clientInfo.opponentNextPos.y)
|
||||
|| (clientInfo.opponent.dir.y < 0 && clientInfo.opponent.pos.y < clientInfo.opponentNextPos.y))
|
||||
{
|
||||
clientInfo.opponent.dir.y = 0;
|
||||
clientInfo.opponent.pos.y = clientInfo.opponentNextPos.y;
|
||||
}
|
||||
}
|
||||
|
||||
export {gameLoop}
|
||||
@@ -1,3 +0,0 @@
|
||||
|
||||
export {pong, gc, matchOptions} from "./pong.js"
|
||||
export {socket, clientInfo} from "./ws.js"
|
||||
@@ -1,99 +0,0 @@
|
||||
|
||||
initDom();
|
||||
function initDom() {
|
||||
document.getElementById("play_pong_button").addEventListener("click", init);
|
||||
}
|
||||
|
||||
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 { handleInput } from "./handleInput.js";
|
||||
// import { sendLoop } from "./handleInput.js";
|
||||
import { gameLoop } from "./gameLoop.js"
|
||||
import { drawLoop } from "./draw.js";
|
||||
import { countdown } from "./utils.js";
|
||||
import { initWebSocket } from "./ws.js";
|
||||
import { initAudio } from "./audio.js";
|
||||
|
||||
|
||||
/* Keys
|
||||
Racket: W/S OR Up/Down
|
||||
Grid On-Off: G
|
||||
*/
|
||||
|
||||
/* TODO: A way to delay the init of variables, but still use "const" not "let" ? */
|
||||
export let pong: GameArea;
|
||||
export let gc: GameComponentsClient;
|
||||
export let matchOptions: en.MatchOptions = en.MatchOptions.noOption;
|
||||
|
||||
function init()
|
||||
{
|
||||
console.log("multi_balls:"+(<HTMLInputElement>document.getElementById("multi_balls")).checked);
|
||||
console.log("moving_walls:"+(<HTMLInputElement>document.getElementById("moving_walls")).checked);
|
||||
console.log("sound_on:"+(<HTMLInputElement>document.getElementById("sound_on")).checked);
|
||||
|
||||
let soundMutedFlag = false;
|
||||
if ( (<HTMLInputElement>document.getElementById("sound_off")).checked ) {
|
||||
soundMutedFlag = true;
|
||||
}
|
||||
initAudio(soundMutedFlag);
|
||||
|
||||
if ( (<HTMLInputElement>document.getElementById("multi_balls")).checked ) {
|
||||
matchOptions |= en.MatchOptions.multiBalls;
|
||||
}
|
||||
if ( (<HTMLInputElement>document.getElementById("moving_walls")).checked ) {
|
||||
matchOptions |= en.MatchOptions.movingWalls;
|
||||
}
|
||||
|
||||
document.getElementById("div_game_options").hidden = true;
|
||||
|
||||
pong = new GameArea();
|
||||
gc = new GameComponentsClient(matchOptions, pong.ctx);
|
||||
initWebSocket(matchOptions);
|
||||
}
|
||||
|
||||
function matchmaking()
|
||||
{
|
||||
console.log("Searching an opponent...");
|
||||
gc.text1.clear();
|
||||
gc.text1.pos.assign(c.w/5, c.h_mid);
|
||||
gc.text1.text = "Searching...";
|
||||
gc.text1.update();
|
||||
}
|
||||
|
||||
function matchmakingComplete()
|
||||
{
|
||||
console.log("Match Found !");
|
||||
gc.text1.clear();
|
||||
gc.text1.pos.assign(c.w/8, c.h_mid);
|
||||
gc.text1.text = "Match Found !";
|
||||
gc.text1.update();
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
gc.text1.pos.assign(c.w_mid, c.h_mid+c.h/4);
|
||||
countdown(c.matchStartDelay/1000, (count: number) => {
|
||||
gc.text1.clear();
|
||||
gc.text1.text = `${count}`;
|
||||
gc.text1.update();
|
||||
}, resumeGame);
|
||||
}
|
||||
|
||||
function resumeGame()
|
||||
{
|
||||
gc.text1.text = "";
|
||||
window.addEventListener('keydown', function (e) {
|
||||
pong.addKey(e.key);
|
||||
});
|
||||
window.addEventListener('keyup', function (e) {
|
||||
pong.deleteKey(e.key);
|
||||
});
|
||||
pong.handleInputInterval = window.setInterval(handleInput, c.handleInputIntervalMS);
|
||||
// pong.handleInputInterval = window.setInterval(sendLoop, c.sendLoopIntervalMS);
|
||||
pong.gameLoopInterval = window.setInterval(gameLoop, c.gameLoopIntervalMS);
|
||||
pong.drawLoopInterval = window.setInterval(drawLoop, c.drawLoopIntervalMS);
|
||||
}
|
||||
|
||||
|
||||
export {matchmaking, matchmakingComplete, startGame}
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
import { MovingRectangle } from "./class/Rectangle.js";
|
||||
|
||||
function random(min: number = 0, max: number = 1) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
function sleep (ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
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
|
||||
function assertMovingRectangle(value: unknown): asserts value is MovingRectangle {
|
||||
// if (value !== MovingRectangle) throw new Error("Not a MovingRectangle");
|
||||
return;
|
||||
}
|
||||
|
||||
export {random, sleep, clamp, assertMovingRectangle}
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
html, body {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||
|
||||
<title>Svelte app</title>
|
||||
<title>Potato Pong</title>
|
||||
|
||||
<link rel='icon' type='image/png' href='/favicon.png'>
|
||||
<link rel='icon' type='image/x-icon' href='/favicon.ico'>
|
||||
<link rel='stylesheet' href='/global.css'>
|
||||
<link rel='stylesheet' href='/build/bundle.css'>
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user