feat(tournament): allow the creation of a tournament
A tournament can be created (by the "owner") Any other players can join said tournament. The information is currently not displayed in the frontend, but does exists and is passed to the frontend using a socket.io event
This commit is contained in:
parent
272c6f319c
commit
2195207297
9 changed files with 558 additions and 170 deletions
|
|
@ -8,8 +8,8 @@
|
||||||
|
|
||||||
.btn-style {
|
.btn-style {
|
||||||
@apply
|
@apply
|
||||||
w-25
|
min-w-25
|
||||||
h-8
|
min-h-8
|
||||||
border
|
border
|
||||||
border-gray-500
|
border-gray-500
|
||||||
rounded-3xl
|
rounded-3xl
|
||||||
|
|
@ -188,4 +188,4 @@
|
||||||
inline-flex items-center justify-center
|
inline-flex items-center justify-center
|
||||||
rounded-full w-8 h-8 bg-blue-500
|
rounded-full w-8 h-8 bg-blue-500
|
||||||
border-10 border-blue-500
|
border-10 border-blue-500
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,13 @@
|
||||||
<div id="mainbox" class="mainboxDisplay">
|
<div id="mainbox" class="mainboxDisplay">
|
||||||
<button id="QueueBtn" class="btn-style absolute top-4 left-6">Queue Up</button>
|
<button id="QueueBtn" class="btn-style absolute top-4 left-6">Queue Up</button>
|
||||||
<button id="LocalBtn" class="btn-style absolute top-14 left-6">Local Game</button>
|
<button id="LocalBtn" class="btn-style absolute top-14 left-6">Local Game</button>
|
||||||
<span id="queue-info" class="flex rounded-3xl border-7 border-gray-500 bg-gray-500 absolute top-4 right-6">?👤 ?⏳ ?▮•▮</span> <!-- total | in queue | games-->
|
<span id="queue-info" class="flex rounded-3xl border-7 border-gray-500 bg-gray-500 absolute top-4 right-6">?👤
|
||||||
<button id="play-info" class="pong-how-to-play absolute top-14 right-6">?</button> <!-- how to play?-->
|
?⏳ ?▮•▮</span> <!-- total | in queue | games-->
|
||||||
|
<button id="TourBtn"
|
||||||
|
class="min-25 min-h-8 border border-gray-500 rounded-3xl bg-gray-500 text-white cursor-pointer shadow-[0_2px_0_0_black] transition-all hover:bg-blue-200 active:bg-gray-400 active:translate-y-px active:shadow-[0_2px_0_0_black] absolute top-16 right-6">
|
||||||
|
<span class="mx-2">Create Tournament</span>
|
||||||
|
</button> <!-- Tournament Button -->
|
||||||
|
<button id="play-info" class="pong-how-to-play absolute top-26 right-6">?</button> <!-- how to play?-->
|
||||||
<br>
|
<br>
|
||||||
<h1 class="text-3xl font-bold text-gray-800">
|
<h1 class="text-3xl font-bold text-gray-800">
|
||||||
Pong Box<span id="t-username"></span>
|
Pong Box<span id="t-username"></span>
|
||||||
|
|
@ -25,7 +30,8 @@
|
||||||
down:
|
down:
|
||||||
<kbd class="pong-protips-key">S</kbd>
|
<kbd class="pong-protips-key">S</kbd>
|
||||||
<br>
|
<br>
|
||||||
You are <span class="text-red-500">red</span>. <br> Your goal is to bounce the ball back to the adversary.
|
You are <span class="text-red-500">red</span>. <br> Your goal is to bounce the ball back to the
|
||||||
|
adversary.
|
||||||
<br>
|
<br>
|
||||||
<span class="text-gray-400">local games keys for the left paddle:<br>
|
<span class="text-gray-400">local games keys for the left paddle:<br>
|
||||||
up:
|
up:
|
||||||
|
|
@ -33,7 +39,7 @@
|
||||||
down:
|
down:
|
||||||
<kbd class="pong-protips-key">L</kbd>
|
<kbd class="pong-protips-key">L</kbd>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="pong-rdy-screen" id="readyup-btn">ready!</button>
|
<button class="pong-rdy-screen" id="readyup-btn">ready!</button>
|
||||||
<div class="pong-field">
|
<div class="pong-field">
|
||||||
<div id="batleft" class="pong-batleft top-0"></div>
|
<div id="batleft" class="pong-batleft top-0"></div>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,20 @@
|
||||||
import { addRoute, navigateTo, setTitle, type RouteHandlerParams, type RouteHandlerReturn } from "@app/routing";
|
import {
|
||||||
import authHtml from './pong.html?raw';
|
addRoute,
|
||||||
import io from 'socket.io-client';
|
navigateTo,
|
||||||
|
setTitle,
|
||||||
|
type RouteHandlerParams,
|
||||||
|
type RouteHandlerReturn,
|
||||||
|
} from "@app/routing";
|
||||||
|
import authHtml from "./pong.html?raw";
|
||||||
|
import io from "socket.io-client";
|
||||||
import type { CSocket, GameMove, GameUpdate } from "./socket";
|
import type { CSocket, GameMove, GameUpdate } from "./socket";
|
||||||
import { showError, showInfo } from "@app/toast";
|
import { showError, showInfo, showSuccess } from "@app/toast";
|
||||||
import { getUser, type User } from "@app/auth";
|
import { getUser, type User } from "@app/auth";
|
||||||
import { isNullish } from "@app/utils";
|
import { isNullish } from "@app/utils";
|
||||||
import client from "@app/api";
|
import client from "@app/api";
|
||||||
import "./pong.css";
|
import "./pong.css";
|
||||||
|
|
||||||
declare module 'ft_state' {
|
declare module "ft_state" {
|
||||||
interface State {
|
interface State {
|
||||||
pongSock?: CSocket;
|
pongSock?: CSocket;
|
||||||
}
|
}
|
||||||
|
|
@ -20,96 +26,176 @@ enum QueueState {
|
||||||
Ready = "Ready-ing",
|
Ready = "Ready-ing",
|
||||||
Iddle = "Queue Up",
|
Iddle = "Queue Up",
|
||||||
In_local = "In Local",
|
In_local = "In Local",
|
||||||
};
|
}
|
||||||
|
|
||||||
enum ReadyState {
|
enum ReadyState {
|
||||||
readyUp = "ready ok",
|
readyUp = "ready ok",
|
||||||
readyDown = "not ready",
|
readyDown = "not ready",
|
||||||
};
|
}
|
||||||
|
|
||||||
|
enum TourBtnState {
|
||||||
|
Joined = "Joined",
|
||||||
|
Started = "Started",
|
||||||
|
AbleToJoin = "Join Tournament",
|
||||||
|
AbleToCreate = "Create Tournament",
|
||||||
|
AbleToStart = "Start Tournament",
|
||||||
|
AbeToProcreate = "He would be proud",
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener("ft:pageChange", (newUrl) => {
|
document.addEventListener("ft:pageChange", (newUrl) => {
|
||||||
if (newUrl.detail.startsWith('/app/pong') || newUrl.detail.startsWith('/pong')) return;
|
if (
|
||||||
|
newUrl.detail.startsWith("/app/pong") ||
|
||||||
|
newUrl.detail.startsWith("/pong")
|
||||||
|
)
|
||||||
|
return;
|
||||||
if (window.__state.pongSock !== undefined) window.__state.pongSock.close();
|
if (window.__state.pongSock !== undefined) window.__state.pongSock.close();
|
||||||
window.__state.pongSock = undefined;
|
window.__state.pongSock = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
export function getSocket(): CSocket {
|
export function getSocket(): CSocket {
|
||||||
if (window.__state.pongSock === undefined)
|
if (window.__state.pongSock === undefined)
|
||||||
window.__state.pongSock = io(window.location.host, { path: "/api/pong/socket.io/" }) as any as CSocket;
|
window.__state.pongSock = io(window.location.host, {
|
||||||
|
path: "/api/pong/socket.io/",
|
||||||
|
}) as any as CSocket;
|
||||||
return window.__state.pongSock;
|
return window.__state.pongSock;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn {
|
function pongClient(
|
||||||
setTitle('Pong Game Page');
|
_url: string,
|
||||||
|
_args: RouteHandlerParams,
|
||||||
|
): RouteHandlerReturn {
|
||||||
|
setTitle("Pong Game Page");
|
||||||
return {
|
return {
|
||||||
html: authHtml, postInsert: async (app) => {
|
html: authHtml,
|
||||||
|
postInsert: async (app) => {
|
||||||
const DEFAULT_COLOR = "white";
|
const DEFAULT_COLOR = "white";
|
||||||
const SELF_COLOR = "red";
|
const SELF_COLOR = "red";
|
||||||
|
|
||||||
const user = getUser();
|
const user = getUser();
|
||||||
let currentGame: GameUpdate | null = null;
|
let currentGame: GameUpdate | null = null;
|
||||||
let opponent: User | null = null;
|
let opponent: User | null = null;
|
||||||
const rdy_btn = document.querySelector<HTMLButtonElement>('#readyup-btn');
|
const rdy_btn =
|
||||||
|
document.querySelector<HTMLButtonElement>("#readyup-btn");
|
||||||
const batLeft = document.querySelector<HTMLDivElement>("#batleft");
|
const batLeft = document.querySelector<HTMLDivElement>("#batleft");
|
||||||
const batRight = document.querySelector<HTMLDivElement>("#batright");
|
const batRight =
|
||||||
|
document.querySelector<HTMLDivElement>("#batright");
|
||||||
const ball = document.querySelector<HTMLDivElement>("#ball");
|
const ball = document.querySelector<HTMLDivElement>("#ball");
|
||||||
const score = document.querySelector<HTMLDivElement>("#score-board");
|
const score =
|
||||||
const playerL = document.querySelector<HTMLDivElement>('#player-left');
|
document.querySelector<HTMLDivElement>("#score-board");
|
||||||
const playerR = document.querySelector<HTMLDivElement>('#player-right');
|
const playerL =
|
||||||
const queueBtn = document.querySelector<HTMLButtonElement>("#QueueBtn");
|
document.querySelector<HTMLDivElement>("#player-left");
|
||||||
const LocalGameBtn = document.querySelector<HTMLButtonElement>("#LocalBtn");
|
const playerR =
|
||||||
const gameBoard = document.querySelector<HTMLDivElement>("#pongbox");
|
document.querySelector<HTMLDivElement>("#player-right");
|
||||||
const queue_infos = document.querySelector<HTMLSpanElement>("#queue-info");
|
const queueBtn =
|
||||||
const how_to_play_btn = document.querySelector<HTMLButtonElement>("#play-info");
|
document.querySelector<HTMLButtonElement>("#QueueBtn");
|
||||||
const protips = document.querySelector<HTMLDivElement>("#protips-box");
|
const LocalGameBtn =
|
||||||
|
document.querySelector<HTMLButtonElement>("#LocalBtn");
|
||||||
|
const gameBoard =
|
||||||
|
document.querySelector<HTMLDivElement>("#pongbox");
|
||||||
|
const queue_infos =
|
||||||
|
document.querySelector<HTMLSpanElement>("#queue-info");
|
||||||
|
const how_to_play_btn =
|
||||||
|
document.querySelector<HTMLButtonElement>("#play-info");
|
||||||
|
const protips =
|
||||||
|
document.querySelector<HTMLDivElement>("#protips-box");
|
||||||
|
const tournamentBtn =
|
||||||
|
document.querySelector<HTMLButtonElement>("#TourBtn");
|
||||||
|
|
||||||
let socket = getSocket();
|
let socket = getSocket();
|
||||||
|
|
||||||
if (isNullish(user)) { // if no user (no loggin / other) : GTFO
|
if (isNullish(user)) {
|
||||||
|
// if no user (no loggin / other) : GTFO
|
||||||
navigateTo("/app");
|
navigateTo("/app");
|
||||||
return ;
|
return;
|
||||||
}
|
}
|
||||||
if (!batLeft || !batRight || !ball || !score || !queueBtn || !playerL || !playerR || !gameBoard || !queue_infos || !LocalGameBtn || !rdy_btn) // sanity check
|
if (
|
||||||
return showError('fatal error');
|
!batLeft ||
|
||||||
if (!how_to_play_btn || !protips)
|
!batRight ||
|
||||||
showError('missing protips'); // not a fatal error
|
!ball ||
|
||||||
|
!score ||
|
||||||
|
!queueBtn ||
|
||||||
|
!playerL ||
|
||||||
|
!playerR ||
|
||||||
|
!gameBoard ||
|
||||||
|
!queue_infos ||
|
||||||
|
!LocalGameBtn ||
|
||||||
|
!rdy_btn ||
|
||||||
|
!tournamentBtn
|
||||||
|
)
|
||||||
|
// sanity check
|
||||||
|
return showError("fatal error");
|
||||||
|
if (!how_to_play_btn || !protips) showError("missing protips"); // not a fatal error
|
||||||
|
|
||||||
|
tournamentBtn.addEventListener("click", () => {
|
||||||
|
showInfo(`Button State: ${tournamentBtn.innerText}`);
|
||||||
|
|
||||||
|
switch (tournamentBtn.innerText) {
|
||||||
|
case TourBtnState.AbleToStart:
|
||||||
|
//socket.emit('')
|
||||||
|
break;
|
||||||
|
case TourBtnState.AbleToJoin:
|
||||||
|
socket.emit("tourRegister");
|
||||||
|
break;
|
||||||
|
case TourBtnState.AbleToCreate:
|
||||||
|
socket.emit("tourCreate");
|
||||||
|
break;
|
||||||
|
case TourBtnState.AbeToProcreate:
|
||||||
|
showError("We are developpers, this is impossible !");
|
||||||
|
break;
|
||||||
|
case TourBtnState.Joined:
|
||||||
|
socket.emit("tourUnregister");
|
||||||
|
break;
|
||||||
|
case TourBtnState.Started:
|
||||||
|
showInfo("tournament Started");
|
||||||
|
//socket.emit("tourStart");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
// keys handler
|
// keys handler
|
||||||
// ---
|
// ---
|
||||||
const keys: Record<string, boolean> = {};
|
const keys: Record<string, boolean> = {};
|
||||||
if (how_to_play_btn && protips)
|
if (how_to_play_btn && protips)
|
||||||
how_to_play_btn.addEventListener("click", ()=>{
|
how_to_play_btn.addEventListener("click", () => {
|
||||||
protips.classList.toggle("hidden");
|
protips.classList.toggle("hidden");
|
||||||
how_to_play_btn.innerText = how_to_play_btn.innerText === '?' ? 'x' : '?';
|
how_to_play_btn.innerText =
|
||||||
|
how_to_play_btn.innerText === "?" ? "x" : "?";
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("keydown", (e) => {keys[e.key.toLowerCase()] = true;});
|
document.addEventListener("keydown", (e) => {
|
||||||
document.addEventListener("keyup", (e) => {keys[e.key.toLowerCase()] = false;});
|
keys[e.key.toLowerCase()] = true;
|
||||||
|
});
|
||||||
|
document.addEventListener("keyup", (e) => {
|
||||||
|
keys[e.key.toLowerCase()] = false;
|
||||||
|
});
|
||||||
|
|
||||||
setInterval(() => { // key sender
|
setInterval(() => {
|
||||||
if (keys['escape'] === true && protips && how_to_play_btn) {
|
// key sender
|
||||||
|
if (keys["escape"] === true && protips && how_to_play_btn) {
|
||||||
protips.classList.add("hidden");
|
protips.classList.add("hidden");
|
||||||
how_to_play_btn.innerText = '?';
|
how_to_play_btn.innerText = "?";
|
||||||
}
|
}
|
||||||
if (queueBtn.innerText !== QueueState.InGame)//we're in game ? continue | gtfo
|
if (queueBtn.innerText !== QueueState.InGame)
|
||||||
return ;
|
//we're in game ? continue | gtfo
|
||||||
|
return;
|
||||||
if (currentGame === null) return;
|
if (currentGame === null) return;
|
||||||
|
|
||||||
let packet: GameMove = {
|
let packet: GameMove = {
|
||||||
move: null,
|
move: null,
|
||||||
moveRight: null,
|
moveRight: null,
|
||||||
}
|
};
|
||||||
|
|
||||||
if (queueBtn.innerText !== QueueState.InGame)//we're in game ? continue | gtfo
|
if (queueBtn.innerText !== QueueState.InGame)
|
||||||
return ;
|
//we're in game ? continue | gtfo
|
||||||
|
return;
|
||||||
if (currentGame === null) return;
|
if (currentGame === null) return;
|
||||||
|
|
||||||
if ((keys['w'] !== keys['s']))
|
if (keys["w"] !== keys["s"])
|
||||||
packet.move = keys['w'] ? 'up' : 'down';
|
packet.move = keys["w"] ? "up" : "down";
|
||||||
if (currentGame.local && (keys['o'] !== keys['l']))
|
if (currentGame.local && keys["o"] !== keys["l"])
|
||||||
packet.moveRight = keys['o'] ? 'up' : 'down';
|
packet.moveRight = keys["o"] ? "up" : "down";
|
||||||
socket.emit('gameMove', packet);
|
socket.emit("gameMove", packet);
|
||||||
}, 1000 / 60);
|
}, 1000 / 60);
|
||||||
// ---
|
// ---
|
||||||
// keys end
|
// keys end
|
||||||
|
|
@ -118,15 +204,28 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
// ---
|
// ---
|
||||||
// position logic (client)
|
// position logic (client)
|
||||||
// ---
|
// ---
|
||||||
const DEFAULT_POSITIONS : GameUpdate = {
|
const DEFAULT_POSITIONS: GameUpdate = {
|
||||||
gameId:"",
|
gameId: "",
|
||||||
ball:{size:16, x:800/2, y:450/2},
|
ball: { size: 16, x: 800 / 2, y: 450 / 2 },
|
||||||
left:{id:"", paddle:{x:40, y:185, width:12, height:80}, score:0},
|
left: {
|
||||||
right:{id:"", paddle:{x:748, y:185, width:12, height:80}, score:0},
|
id: "",
|
||||||
local:false
|
paddle: { x: 40, y: 185, width: 12, height: 80 },
|
||||||
|
score: 0,
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
id: "",
|
||||||
|
paddle: { x: 748, y: 185, width: 12, height: 80 },
|
||||||
|
score: 0,
|
||||||
|
},
|
||||||
|
local: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
function resetBoard(batLeft : HTMLDivElement, batRight : HTMLDivElement, playerL : HTMLDivElement, playerR : HTMLDivElement) {
|
function resetBoard(
|
||||||
|
batLeft: HTMLDivElement,
|
||||||
|
batRight: HTMLDivElement,
|
||||||
|
playerL: HTMLDivElement,
|
||||||
|
playerR: HTMLDivElement,
|
||||||
|
) {
|
||||||
render(DEFAULT_POSITIONS);
|
render(DEFAULT_POSITIONS);
|
||||||
batLeft.style.backgroundColor = DEFAULT_COLOR;
|
batLeft.style.backgroundColor = DEFAULT_COLOR;
|
||||||
batRight.style.backgroundColor = DEFAULT_COLOR;
|
batRight.style.backgroundColor = DEFAULT_COLOR;
|
||||||
|
|
@ -154,115 +253,131 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
ball.style.height = `${state.ball.size * 2}px`;
|
ball.style.height = `${state.ball.size * 2}px`;
|
||||||
ball.style.width = `${state.ball.size * 2}px`;
|
ball.style.width = `${state.ball.size * 2}px`;
|
||||||
|
|
||||||
score.innerText = `${state.left.score} | ${state.right.score}`
|
score.innerText = `${state.left.score} | ${state.right.score}`;
|
||||||
}
|
};
|
||||||
socket.on('gameUpdate', (state: GameUpdate) => {
|
socket.on("gameUpdate", (state: GameUpdate) => {
|
||||||
render(state);});
|
render(state);
|
||||||
|
});
|
||||||
// ---
|
// ---
|
||||||
// position logic (client) end
|
// position logic (client) end
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
// queue evt
|
// queue evt
|
||||||
// ---
|
// ---
|
||||||
// utils
|
// utils
|
||||||
function set_pretty(batU : HTMLDivElement, txtU : HTMLDivElement, txtO : HTMLDivElement, colorYou : string) {
|
function set_pretty(
|
||||||
|
batU: HTMLDivElement,
|
||||||
|
txtU: HTMLDivElement,
|
||||||
|
txtO: HTMLDivElement,
|
||||||
|
colorYou: string,
|
||||||
|
) {
|
||||||
batU.style.backgroundColor = colorYou;
|
batU.style.backgroundColor = colorYou;
|
||||||
txtU.style.color = colorYou;
|
txtU.style.color = colorYou;
|
||||||
txtU.innerText = isNullish(user) ? "you" : user.name;
|
txtU.innerText = isNullish(user) ? "you" : user.name;
|
||||||
txtO.innerText = isNullish(opponent) ? "the mechant" : opponent.name;
|
txtO.innerText = isNullish(opponent)
|
||||||
|
? "the mechant"
|
||||||
|
: opponent.name;
|
||||||
}
|
}
|
||||||
async function get_opponent(opponent_id : string) {
|
async function get_opponent(opponent_id: string) {
|
||||||
let t = await client.getUser({user:opponent_id});
|
let t = await client.getUser({ user: opponent_id });
|
||||||
|
|
||||||
switch (t.kind) {
|
switch (t.kind) {
|
||||||
case "success" :
|
case "success":
|
||||||
opponent = t.payload;
|
opponent = t.payload;
|
||||||
break ;
|
break;
|
||||||
default :
|
default:
|
||||||
opponent = null;
|
opponent = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// btn setup
|
// btn setup
|
||||||
queueBtn.addEventListener("click", ()=>{
|
queueBtn.addEventListener("click", () => {
|
||||||
if (queueBtn.innerText !== QueueState.Iddle) {
|
if (queueBtn.innerText !== QueueState.Iddle) {
|
||||||
if (queueBtn.innerText === QueueState.InQueu) {
|
if (queueBtn.innerText === QueueState.InQueu) {
|
||||||
socket.emit("dequeue");
|
socket.emit("dequeue");
|
||||||
queueBtn.innerText = QueueState.Iddle;
|
queueBtn.innerText = QueueState.Iddle;
|
||||||
}
|
}
|
||||||
return ;
|
return;
|
||||||
}
|
}
|
||||||
queueBtn.innerText = QueueState.InQueu;
|
queueBtn.innerText = QueueState.InQueu;
|
||||||
socket.emit('enqueue');
|
socket.emit("enqueue");
|
||||||
});
|
});
|
||||||
LocalGameBtn.addEventListener("click", () => {
|
LocalGameBtn.addEventListener("click", () => {
|
||||||
if (queueBtn.innerText !== QueueState.Iddle || currentGame !== null) {
|
if (
|
||||||
showError("cant launch a local game while in queue/in game");
|
queueBtn.innerText !== QueueState.Iddle ||
|
||||||
return ;
|
currentGame !== null
|
||||||
|
) {
|
||||||
|
showError(
|
||||||
|
"cant launch a local game while in queue/in game",
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
socket.emit("localGame");
|
socket.emit("localGame");
|
||||||
queueBtn.innerText = QueueState.In_local;
|
queueBtn.innerText = QueueState.In_local;
|
||||||
LocalGameBtn.innerText = "playing";
|
LocalGameBtn.innerText = "playing";
|
||||||
});
|
});
|
||||||
rdy_btn.addEventListener("click", ()=>{
|
rdy_btn.addEventListener("click", () => {
|
||||||
showInfo("rdy-evt");
|
showInfo("rdy-evt");
|
||||||
switch (rdy_btn.innerText) {
|
switch (rdy_btn.innerText) {
|
||||||
case ReadyState.readyDown:
|
case ReadyState.readyDown:
|
||||||
socket.emit('readyUp');
|
socket.emit("readyUp");
|
||||||
rdy_btn.innerText = ReadyState.readyUp;
|
rdy_btn.innerText = ReadyState.readyUp;
|
||||||
rdy_btn.classList.remove("text-red-600");
|
rdy_btn.classList.remove("text-red-600");
|
||||||
rdy_btn.classList.add("text-green-600");
|
rdy_btn.classList.add("text-green-600");
|
||||||
break ;
|
break;
|
||||||
case ReadyState.readyUp:
|
case ReadyState.readyUp:
|
||||||
socket.emit('readyDown');
|
socket.emit("readyDown");
|
||||||
rdy_btn.innerText = ReadyState.readyDown;
|
rdy_btn.innerText = ReadyState.readyDown;
|
||||||
rdy_btn.classList.remove("text-green-600");
|
rdy_btn.classList.remove("text-green-600");
|
||||||
rdy_btn.classList.add("text-red-600");
|
rdy_btn.classList.add("text-red-600");
|
||||||
break ;
|
break;
|
||||||
default:
|
default:
|
||||||
showError("error on ready btn");
|
showError("error on ready btn");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('newGame', async (state) => {
|
socket.on("newGame", async (state) => {
|
||||||
render(state);
|
render(state);
|
||||||
|
|
||||||
await get_opponent(state.left.id == user.id ? state.right.id : state.left.id);
|
await get_opponent(
|
||||||
|
state.left.id == user.id ? state.right.id : state.left.id,
|
||||||
|
);
|
||||||
queueBtn.innerText = QueueState.InGame;
|
queueBtn.innerText = QueueState.InGame;
|
||||||
queueBtn.style.color = 'red';
|
queueBtn.style.color = "red";
|
||||||
batLeft.style.backgroundColor = DEFAULT_COLOR;
|
batLeft.style.backgroundColor = DEFAULT_COLOR;
|
||||||
batRight.style.backgroundColor = DEFAULT_COLOR;
|
batRight.style.backgroundColor = DEFAULT_COLOR;
|
||||||
if (state.left.id === user.id) {
|
if (state.left.id === user.id) {
|
||||||
set_pretty(batLeft, playerL, playerR, SELF_COLOR);
|
set_pretty(batLeft, playerL, playerR, SELF_COLOR);
|
||||||
} else if (state.right.id === user.id) {
|
} else if (state.right.id === user.id) {
|
||||||
set_pretty(batRight, playerR, playerL, SELF_COLOR);
|
set_pretty(batRight, playerR, playerL, SELF_COLOR);
|
||||||
} else
|
} else showError("couldn't find your id in game");
|
||||||
showError("couldn't find your id in game");
|
rdy_btn.classList.remove("hidden");
|
||||||
rdy_btn.classList.remove('hidden');
|
|
||||||
rdy_btn.classList.add("text-red-600");
|
rdy_btn.classList.add("text-red-600");
|
||||||
rdy_btn.innerText = ReadyState.readyDown;
|
rdy_btn.innerText = ReadyState.readyDown;
|
||||||
});
|
});
|
||||||
socket.on('rdyEnd', () => {
|
socket.on("rdyEnd", () => {
|
||||||
rdy_btn.classList.remove("text-green-600");
|
rdy_btn.classList.remove("text-green-600");
|
||||||
rdy_btn.classList.remove("text-red-600");
|
rdy_btn.classList.remove("text-red-600");
|
||||||
rdy_btn.classList.add('hidden');
|
rdy_btn.classList.add("hidden");
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("gameEnd", (winner) => {
|
socket.on("gameEnd", (winner) => {
|
||||||
rdy_btn.classList.add('hidden');
|
rdy_btn.classList.add("hidden");
|
||||||
queueBtn.innerHTML = QueueState.Iddle;
|
queueBtn.innerHTML = QueueState.Iddle;
|
||||||
queueBtn.style.color = 'white';
|
queueBtn.style.color = "white";
|
||||||
|
|
||||||
if (!isNullish(currentGame)) {
|
if (!isNullish(currentGame)) {
|
||||||
let new_div = document.createElement('div');
|
let new_div = document.createElement("div");
|
||||||
let end_txt = "";
|
let end_txt = "";
|
||||||
if ((user.id === currentGame.left.id && winner === 'left') ||
|
if (
|
||||||
(user.id === currentGame.right.id && winner === 'right'))
|
(user.id === currentGame.left.id &&
|
||||||
end_txt = 'won! #yippe';
|
winner === "left") ||
|
||||||
else
|
(user.id === currentGame.right.id && winner === "right")
|
||||||
end_txt = 'lost #sadge';
|
)
|
||||||
new_div.innerText = 'you ' + end_txt;
|
end_txt = "won! #yippe";
|
||||||
|
else end_txt = "lost #sadge";
|
||||||
|
new_div.innerText = "you " + end_txt;
|
||||||
new_div.className = "pong-end-screen";
|
new_div.className = "pong-end-screen";
|
||||||
gameBoard.appendChild(new_div);
|
gameBoard.appendChild(new_div);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -270,25 +385,61 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
}, 3 * 1000);
|
}, 3 * 1000);
|
||||||
|
|
||||||
if (currentGame.local) {
|
if (currentGame.local) {
|
||||||
LocalGameBtn.innerText = "Local Game"
|
LocalGameBtn.innerText = "Local Game";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resetBoard(batLeft, batRight, playerL, playerR);
|
resetBoard(batLeft, batRight, playerL, playerR);
|
||||||
})
|
});
|
||||||
socket.on('updateInformation', (e) => {
|
socket.on("updateInformation", (e) => {
|
||||||
queue_infos.innerText = `${e.totalUser}👤 ${e.inQueue}⏳ ${e.totalGames}▮•▮`;
|
queue_infos.innerText = `${e.totalUser}👤 ${e.inQueue}⏳ ${e.totalGames}▮•▮`;
|
||||||
});
|
});
|
||||||
socket.on('queueEvent', (e) => showInfo(`QueueEvent: ${e}`)); // MAYBE: play a sound? to notify user that smthing happend
|
socket.on("queueEvent", (e) => showInfo(`QueueEvent: ${e}`)); // MAYBE: play a sound? to notify user that smthing happend
|
||||||
// ---
|
// ---
|
||||||
// queue evt end
|
// queue evt end
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
|
socket.on("tournamentInfo", (s) => {
|
||||||
|
// no tournament => we can create it !
|
||||||
|
if (s === null) {
|
||||||
|
tournamentBtn.innerText = TourBtnState.AbleToCreate;
|
||||||
|
// create tournament
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let weIn = s.players.some((p) => p.id === user.id);
|
||||||
|
let imOwner = s.ownerId === user.id;
|
||||||
|
switch (s.state) {
|
||||||
|
case "ended":
|
||||||
|
tournamentBtn.innerText = TourBtnState.AbleToCreate;
|
||||||
|
break;
|
||||||
|
case "playing":
|
||||||
|
tournamentBtn.innerText = TourBtnState.Started;
|
||||||
|
break;
|
||||||
|
case "prestart":
|
||||||
|
if (imOwner) {
|
||||||
|
tournamentBtn.innerText = TourBtnState.AbleToStart;
|
||||||
|
} else {
|
||||||
|
tournamentBtn.innerText = weIn
|
||||||
|
? TourBtnState.Joined
|
||||||
|
: TourBtnState.AbleToJoin;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(s.players);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("tournamentRegister", ({ kind, msg }) => {
|
||||||
|
if (kind === "success") showSuccess(msg ?? "Success");
|
||||||
|
if (kind === "failure") showError(msg ?? "An error Occured");
|
||||||
|
});
|
||||||
|
|
||||||
// init
|
// init
|
||||||
rdy_btn.classList.add('hidden');
|
rdy_btn.classList.add("hidden");
|
||||||
queueBtn.innerText = QueueState.Iddle;
|
queueBtn.innerText = QueueState.Iddle;
|
||||||
rdy_btn.innerText = ReadyState.readyUp;
|
rdy_btn.innerText = ReadyState.readyUp;
|
||||||
resetBoard(batLeft, batRight, playerL, playerR);
|
resetBoard(batLeft, batRight, playerL, playerR);
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
};
|
}
|
||||||
addRoute('/pong', pongClient);
|
addRoute("/pong", pongClient);
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,80 @@
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
|
|
||||||
export type UpdateInfo = {
|
export type UpdateInfo = {
|
||||||
inQueue: number,
|
inQueue: number;
|
||||||
totalUser: number,
|
totalUser: number;
|
||||||
totalGames : number
|
totalGames: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type PaddleData = {
|
export type PaddleData = {
|
||||||
x: number,
|
x: number;
|
||||||
y: number,
|
y: number;
|
||||||
|
|
||||||
width: number,
|
width: number;
|
||||||
height: number,
|
height: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GameUpdate = {
|
export type GameUpdate = {
|
||||||
gameId: string;
|
gameId: string;
|
||||||
|
|
||||||
left: { id: string, paddle: PaddleData, score: number };
|
left: { id: string; paddle: PaddleData; score: number };
|
||||||
right: { id: string, paddle: PaddleData, score: number };
|
right: { id: string; paddle: PaddleData; score: number };
|
||||||
|
|
||||||
ball: { x: number, y: number, size: number };
|
ball: { x: number; y: number; size: number };
|
||||||
local: boolean,
|
local: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type GameMove = {
|
export type GameMove = {
|
||||||
move: 'up' | 'down' | null,
|
move: 'up' | 'down' | null;
|
||||||
// only used in local games
|
// only used in local games
|
||||||
moveRight: 'up' | 'down' | null,
|
moveRight: 'up' | 'down' | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export type TourInfo = {
|
||||||
|
ownerId: string;
|
||||||
|
state: 'prestart' | 'playing' | 'ended';
|
||||||
|
players: { id: string; name: string; score: number }[];
|
||||||
|
currentGameInfo: GameUpdate | null;
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: add new evt such as "local play", "ready-up" see: ./pong.ts
|
|
||||||
export interface ClientToServer {
|
export interface ClientToServer {
|
||||||
enqueue: () => void;
|
enqueue: () => void;
|
||||||
dequeue: () => void;
|
dequeue: () => void;
|
||||||
readyUp: () => void;
|
readyUp: () => void;
|
||||||
readyDown:() => void;
|
readyDown: () => void;
|
||||||
debugInfo: () => void;
|
debugInfo: () => void;
|
||||||
gameMove: (up: GameMove) => void;
|
gameMove: (up: GameMove) => void;
|
||||||
connectedToGame: (gameId: string) => void;
|
connectedToGame: (gameId: string) => void;
|
||||||
localGame: () => void,
|
localGame: () => void;
|
||||||
};
|
|
||||||
|
// TOURNAMENT
|
||||||
|
|
||||||
|
tourRegister: () => void;
|
||||||
|
tourUnregister: () => void;
|
||||||
|
|
||||||
|
tourCreate: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ServerToClient {
|
export interface ServerToClient {
|
||||||
forceDisconnect: (reason: string) => void;
|
forceDisconnect: (reason: string) => void;
|
||||||
queueEvent: (msg: 'registered' | 'unregistered') => void;
|
queueEvent: (msg: 'registered' | 'unregistered') => void;
|
||||||
rdyEnd:() => void,
|
rdyEnd: () => void;
|
||||||
updateInformation: (info: UpdateInfo) => void,
|
updateInformation: (info: UpdateInfo) => void;
|
||||||
newGame: (initState: GameUpdate) => void, // <- consider this the gameProc eg not start of game but wait for client to "ready up"
|
newGame: (initState: GameUpdate) => void;
|
||||||
gameUpdate: (state: GameUpdate) => void,
|
gameUpdate: (state: GameUpdate) => void;
|
||||||
gameEnd: (winner: 'left' | 'right') => void;
|
gameEnd: (winner: 'left' | 'right') => void;
|
||||||
};
|
|
||||||
|
// TOURNAMENT
|
||||||
|
tournamentRegister: (res: {
|
||||||
|
kind: 'success' | 'failure';
|
||||||
|
msg?: string;
|
||||||
|
}) => void;
|
||||||
|
tournamentCreateMsg: (res: {
|
||||||
|
kind: 'success' | 'failure';
|
||||||
|
msg?: string;
|
||||||
|
}) => void;
|
||||||
|
tournamentInfo: (info: TourInfo | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export type SSocket = Socket<ClientToServer, ServerToClient>;
|
export type SSocket = Socket<ClientToServer, ServerToClient>;
|
||||||
export type CSocket = Socket<ServerToClient, ClientToServer>;
|
export type CSocket = Socket<ServerToClient, ClientToServer>;
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,30 @@ http {
|
||||||
proxy_ssl_verify off;
|
proxy_ssl_verify off;
|
||||||
proxy_pass https://localhost:8888;
|
proxy_pass https://localhost:8888;
|
||||||
}
|
}
|
||||||
|
location /api/chat/socket.io/ {
|
||||||
|
proxy_ssl_verify off;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
proxy_pass https://localhost:8888;
|
||||||
|
}
|
||||||
|
location /api/pong/socket.io/ {
|
||||||
|
proxy_ssl_verify off;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
proxy_pass https://localhost:8888;
|
||||||
|
}
|
||||||
|
location /api/ttt/socket.io/ {
|
||||||
|
proxy_ssl_verify off;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
proxy_pass https://localhost:8888;
|
||||||
|
}
|
||||||
location /assets {
|
location /assets {
|
||||||
proxy_pass http://localhost:5173;
|
proxy_pass http://localhost:5173;
|
||||||
proxy_ssl_verify off;
|
proxy_ssl_verify off;
|
||||||
|
|
|
||||||
|
|
@ -149,3 +149,20 @@ export function escape(s: string): string {
|
||||||
c => '&#' + c.charCodeAt(0) + ';',
|
c => '&#' + c.charCodeAt(0) + ';',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shuffle<T>(array: T[]) {
|
||||||
|
let currentIndex = array.length;
|
||||||
|
|
||||||
|
// While there remain elements to shuffle...
|
||||||
|
while (currentIndex != 0) {
|
||||||
|
// Pick a remaining element...
|
||||||
|
const randomIndex = Math.floor(Math.random() * currentIndex);
|
||||||
|
currentIndex--;
|
||||||
|
|
||||||
|
// And swap it with the current element.
|
||||||
|
[array[currentIndex], array[randomIndex]] = [
|
||||||
|
array[randomIndex],
|
||||||
|
array[currentIndex],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,81 @@
|
||||||
import { Socket } from 'socket.io';
|
import { Socket } from 'socket.io';
|
||||||
|
|
||||||
export type UpdateInfo = {
|
export type UpdateInfo = {
|
||||||
inQueue: number,
|
inQueue: number;
|
||||||
totalUser: number,
|
totalUser: number;
|
||||||
totalGames : number
|
totalGames: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type PaddleData = {
|
export type PaddleData = {
|
||||||
x: number,
|
x: number;
|
||||||
y: number,
|
y: number;
|
||||||
|
|
||||||
width: number,
|
width: number;
|
||||||
height: number,
|
height: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GameUpdate = {
|
export type GameUpdate = {
|
||||||
gameId: string;
|
gameId: string;
|
||||||
|
|
||||||
left: { id: string, paddle: PaddleData, score: number };
|
left: { id: string; paddle: PaddleData; score: number };
|
||||||
right: { id: string, paddle: PaddleData, score: number };
|
right: { id: string; paddle: PaddleData; score: number };
|
||||||
|
|
||||||
ball: { x: number, y: number, size: number };
|
ball: { x: number; y: number; size: number };
|
||||||
local: boolean,
|
local: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type GameMove = {
|
export type GameMove = {
|
||||||
move: 'up' | 'down' | null,
|
move: 'up' | 'down' | null;
|
||||||
// only used in local games
|
// only used in local games
|
||||||
moveRight: 'up' | 'down' | null,
|
moveRight: 'up' | 'down' | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export type TourInfo = {
|
||||||
|
ownerId: string;
|
||||||
|
state: 'prestart' | 'playing' | 'ended';
|
||||||
|
players: { id: string; name: string; score: number }[];
|
||||||
|
currentGameInfo: GameUpdate | null;
|
||||||
|
};
|
||||||
|
|
||||||
export interface ClientToServer {
|
export interface ClientToServer {
|
||||||
enqueue: () => void;
|
enqueue: () => void;
|
||||||
dequeue: () => void;
|
dequeue: () => void;
|
||||||
readyUp: () => void;
|
readyUp: () => void;
|
||||||
readyDown:() => void;
|
readyDown: () => void;
|
||||||
debugInfo: () => void;
|
debugInfo: () => void;
|
||||||
gameMove: (up: GameMove) => void;
|
gameMove: (up: GameMove) => void;
|
||||||
connectedToGame: (gameId: string) => void;
|
connectedToGame: (gameId: string) => void;
|
||||||
localGame: () => void,
|
localGame: () => void;
|
||||||
};
|
|
||||||
|
// TOURNAMENT
|
||||||
|
|
||||||
|
tourRegister: () => void;
|
||||||
|
tourUnregister: () => void;
|
||||||
|
|
||||||
|
tourCreate: () => void;
|
||||||
|
// tourStart: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ServerToClient {
|
export interface ServerToClient {
|
||||||
forceDisconnect: (reason: string) => void;
|
forceDisconnect: (reason: string) => void;
|
||||||
queueEvent: (msg: 'registered' | 'unregistered') => void;
|
queueEvent: (msg: 'registered' | 'unregistered') => void;
|
||||||
rdyEnd:() => void,
|
rdyEnd: () => void;
|
||||||
updateInformation: (info: UpdateInfo) => void,
|
updateInformation: (info: UpdateInfo) => void;
|
||||||
newGame: (initState: GameUpdate) => void,
|
newGame: (initState: GameUpdate) => void;
|
||||||
gameUpdate: (state: GameUpdate) => void,
|
gameUpdate: (state: GameUpdate) => void;
|
||||||
gameEnd: (winner: 'left' | 'right') => void;
|
gameEnd: (winner: 'left' | 'right') => void;
|
||||||
};
|
|
||||||
|
// TOURNAMENT
|
||||||
|
tournamentRegister: (res: {
|
||||||
|
kind: 'success' | 'failure';
|
||||||
|
msg?: string;
|
||||||
|
}) => void;
|
||||||
|
tournamentCreateMsg: (res: {
|
||||||
|
kind: 'success' | 'failure';
|
||||||
|
msg?: string;
|
||||||
|
}) => void;
|
||||||
|
tournamentInfo: (info: TourInfo | null) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export type SSocket = Socket<ClientToServer, ServerToClient>;
|
export type SSocket = Socket<ClientToServer, ServerToClient>;
|
||||||
export type CSocket = Socket<ServerToClient, ClientToServer>;
|
export type CSocket = Socket<ServerToClient, ClientToServer>;
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ import { UserId } from '@shared/database/mixin/user';
|
||||||
import { newUUID } from '@shared/utils/uuid';
|
import { newUUID } from '@shared/utils/uuid';
|
||||||
import { FastifyInstance } from 'fastify';
|
import { FastifyInstance } from 'fastify';
|
||||||
import { Pong } from './game';
|
import { Pong } from './game';
|
||||||
import { GameMove, GameUpdate, SSocket } from './socket';
|
import { GameMove, GameUpdate, SSocket, TourInfo } from './socket';
|
||||||
import { isNullish } from '@shared/utils';
|
import { isNullish, shuffle } from '@shared/utils';
|
||||||
import { PongGameId, PongGameOutcome } from '@shared/database/mixin/pong';
|
import { PongGameId, PongGameOutcome } from '@shared/database/mixin/pong';
|
||||||
|
import { Tournament } from './tour';
|
||||||
|
|
||||||
type PUser = {
|
type PUser = {
|
||||||
id: UserId;
|
id: UserId;
|
||||||
|
|
@ -23,6 +24,7 @@ class StateI {
|
||||||
private queue: Set<UserId> = new Set();
|
private queue: Set<UserId> = new Set();
|
||||||
private queueInterval: NodeJS.Timeout;
|
private queueInterval: NodeJS.Timeout;
|
||||||
private games: Map<GameId, Pong> = new Map();
|
private games: Map<GameId, Pong> = new Map();
|
||||||
|
private tournament: Tournament | null = null;
|
||||||
|
|
||||||
public constructor(private fastify: FastifyInstance) {
|
public constructor(private fastify: FastifyInstance) {
|
||||||
this.queueInterval = setInterval(() => this.queuerFunction());
|
this.queueInterval = setInterval(() => this.queuerFunction());
|
||||||
|
|
@ -39,8 +41,71 @@ class StateI {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private registerForTournament(sock: SSocket, name: string | null) {
|
||||||
|
const user = this.users.get(sock.authUser.id);
|
||||||
|
if (isNullish(user)) return;
|
||||||
|
|
||||||
|
if (isNullish(this.tournament)) {
|
||||||
|
sock.emit('tournamentRegister', { kind: 'failure', msg: 'No tournament exists' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.tournament.started) {
|
||||||
|
sock.emit('tournamentRegister', { kind: 'failure', msg: 'No tournament already started' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const udb = this.fastify.db.getUser(user.id);
|
||||||
|
if (isNullish(udb)) {
|
||||||
|
sock.emit('tournamentRegister', { kind: 'failure', msg: 'User not found' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tournament.addUser(user.id, name ?? udb.name);
|
||||||
|
sock.emit('tournamentRegister', { kind: 'success', msg: 'Registered to Tournament' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unregisterForTournament(sock: SSocket) {
|
||||||
|
const user = this.users.get(sock.authUser.id);
|
||||||
|
if (isNullish(user)) return;
|
||||||
|
|
||||||
|
if (isNullish(this.tournament)) {
|
||||||
|
sock.emit('tournamentRegister', { kind: 'failure', msg: 'No tournament exists' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.tournament.started) {
|
||||||
|
sock.emit('tournamentRegister', { kind: 'failure', msg: 'No tournament already started' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tournament.removeUser(user.id);
|
||||||
|
sock.emit('tournamentRegister', { kind: 'success', msg: 'Unregistered to Tournament' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createTournament(sock: SSocket) {
|
||||||
|
const user = this.users.get(sock.authUser.id);
|
||||||
|
if (isNullish(user)) return;
|
||||||
|
|
||||||
|
if (this.tournament !== null) {
|
||||||
|
sock.emit('tournamentCreateMsg', { kind: 'failure', msg: 'A tournament already exists' });
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tournament = new Tournament(user.id);
|
||||||
|
this.registerForTournament(sock, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private tournamentStart(sock: SSocket) {
|
||||||
|
if (isNullish(this.tournament)) return;
|
||||||
|
const user = this.users.get(sock.authUser.id);
|
||||||
|
if (isNullish(user)) return;
|
||||||
|
|
||||||
|
this.tournament.start();
|
||||||
|
}
|
||||||
|
|
||||||
private queuerFunction(): void {
|
private queuerFunction(): void {
|
||||||
const values = Array.from(this.queue.values());
|
const values = Array.from(this.queue.values());
|
||||||
|
shuffle(values);
|
||||||
while (values.length >= 2) {
|
while (values.length >= 2) {
|
||||||
const id1 = values.pop();
|
const id1 = values.pop();
|
||||||
const id2 = values.pop();
|
const id2 = values.pop();
|
||||||
|
|
@ -80,7 +145,7 @@ class StateI {
|
||||||
this.gameUpdate(gameId, u1.socket);
|
this.gameUpdate(gameId, u1.socket);
|
||||||
this.gameUpdate(gameId, u2.socket);
|
this.gameUpdate(gameId, u2.socket);
|
||||||
}
|
}
|
||||||
if (g.checkWinner() !== null) {this.cleanupGame(gameId, g); }
|
if (g.checkWinner() !== null) { this.cleanupGame(gameId, g); }
|
||||||
}, 1000 / StateI.UPDATE_INTERVAL_FRAMES);
|
}, 1000 / StateI.UPDATE_INTERVAL_FRAMES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +213,7 @@ class StateI {
|
||||||
socket,
|
socket,
|
||||||
id: socket.authUser.id,
|
id: socket.authUser.id,
|
||||||
windowId: socket.id,
|
windowId: socket.id,
|
||||||
updateInterval: setInterval(() => this.updateClient(socket), 3000),
|
updateInterval: setInterval(() => this.updateClient(socket), 100),
|
||||||
currentGame: null,
|
currentGame: null,
|
||||||
});
|
});
|
||||||
this.fastify.log.info('Registered new user');
|
this.fastify.log.info('Registered new user');
|
||||||
|
|
@ -162,6 +227,12 @@ class StateI {
|
||||||
|
|
||||||
socket.on('gameMove', (e) => this.gameMove(socket, e));
|
socket.on('gameMove', (e) => this.gameMove(socket, e));
|
||||||
socket.on('localGame', () => this.newLocalGame(socket));
|
socket.on('localGame', () => this.newLocalGame(socket));
|
||||||
|
|
||||||
|
// todo: allow passing nickname
|
||||||
|
socket.on('tourRegister', () => this.registerForTournament(socket, null));
|
||||||
|
socket.on('tourUnregister', () => this.unregisterForTournament(socket));
|
||||||
|
|
||||||
|
socket.on('tourCreate', () => this.createTournament(socket));
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateClient(socket: SSocket): void {
|
private updateClient(socket: SSocket): void {
|
||||||
|
|
@ -170,6 +241,21 @@ class StateI {
|
||||||
totalUser: this.users.size,
|
totalUser: this.users.size,
|
||||||
totalGames: this.games.size,
|
totalGames: this.games.size,
|
||||||
});
|
});
|
||||||
|
let tourInfo: TourInfo | null = null;
|
||||||
|
if (this.tournament !== null) {
|
||||||
|
tourInfo = {
|
||||||
|
ownerId: this.tournament.owner,
|
||||||
|
state: this.tournament.started ? 'playing' : 'prestart',
|
||||||
|
players: this.tournament.users.values().toArray(),
|
||||||
|
currentGameInfo: (() => {
|
||||||
|
if (this.tournament.currentGame === null) return null;
|
||||||
|
const game = this.games.get(this.tournament.currentGame);
|
||||||
|
if (isNullish(game)) return null;
|
||||||
|
return StateI.getGameUpdateData(this.tournament.currentGame, game);
|
||||||
|
})(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
socket.emit('tournamentInfo', tourInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private cleanupUser(socket: SSocket): void {
|
private cleanupUser(socket: SSocket): void {
|
||||||
|
|
@ -203,20 +289,20 @@ class StateI {
|
||||||
this.fastify.db.setPongGameOutcome(gameId, { id: game.userLeft, score: game.score[0] }, { id: game.userRight, score: game.score[1] }, outcome, game.local);
|
this.fastify.db.setPongGameOutcome(gameId, { id: game.userLeft, score: game.score[0] }, { id: game.userRight, score: game.score[1] }, outcome, game.local);
|
||||||
this.fastify.log.info('SetGameOutcome !');
|
this.fastify.log.info('SetGameOutcome !');
|
||||||
if (!game.local) {
|
if (!game.local) {
|
||||||
const payload = { 'nextGame':chat_text };
|
const payload = { 'nextGame': chat_text };
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('http://app-chat/api/chat/broadcast', {
|
const resp = await fetch('http://app-chat/api/chat/broadcast', {
|
||||||
method:'POST',
|
method: 'POST',
|
||||||
headers:{ 'Content-type':'application/json' },
|
headers: { 'Content-type': 'application/json' },
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!resp.ok) { throw (resp); }
|
if (!resp.ok) { throw (resp); }
|
||||||
else { this.fastify.log.info('game-end info to chat success'); }
|
else { this.fastify.log.info('game-end info to chat success'); }
|
||||||
}
|
}
|
||||||
// disable eslint for err catching
|
// disable eslint for err catching
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
catch (e : any) {
|
catch (e: any) {
|
||||||
this.fastify.log.error(`game-end info to chat failed: ${e}`);
|
this.fastify.log.error(`game-end info to chat failed: ${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -243,7 +329,7 @@ class StateI {
|
||||||
socket.emit('queueEvent', 'unregistered');
|
socket.emit('queueEvent', 'unregistered');
|
||||||
}
|
}
|
||||||
|
|
||||||
private readydownUser(socket: SSocket) : void {
|
private readydownUser(socket: SSocket): void {
|
||||||
// do we know this user ?
|
// do we know this user ?
|
||||||
if (!this.users.has(socket.authUser.id)) return;
|
if (!this.users.has(socket.authUser.id)) return;
|
||||||
const user = this.users.get(socket.authUser.id)!;
|
const user = this.users.get(socket.authUser.id)!;
|
||||||
|
|
@ -254,7 +340,7 @@ class StateI {
|
||||||
if (game.local === true) return;
|
if (game.local === true) return;
|
||||||
game.readydown(user.id);
|
game.readydown(user.id);
|
||||||
}
|
}
|
||||||
private readyupUser(socket: SSocket) : void {
|
private readyupUser(socket: SSocket): void {
|
||||||
// do we know this user ?
|
// do we know this user ?
|
||||||
if (!this.users.has(socket.authUser.id)) return;
|
if (!this.users.has(socket.authUser.id)) return;
|
||||||
const user = this.users.get(socket.authUser.id)!;
|
const user = this.users.get(socket.authUser.id)!;
|
||||||
|
|
|
||||||
54
src/pong/src/tour.ts
Normal file
54
src/pong/src/tour.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { PongGameId } from '@shared/database/mixin/pong';
|
||||||
|
import { UserId } from '@shared/database/mixin/user';
|
||||||
|
import { shuffle } from '@shared/utils';
|
||||||
|
import { newUUID } from '@shared/utils/uuid';
|
||||||
|
import { Pong } from './game';
|
||||||
|
|
||||||
|
type TourUser = {
|
||||||
|
id: UserId;
|
||||||
|
score: number;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Tournament {
|
||||||
|
public users: Map<UserId, TourUser> = new Map();
|
||||||
|
public currentGame: PongGameId | null = null;
|
||||||
|
public games: Map<PongGameId, Pong> = new Map();
|
||||||
|
public started: boolean = false;
|
||||||
|
public gameUpdate: NodeJS.Timeout | undefined;
|
||||||
|
|
||||||
|
constructor(public owner: UserId) { }
|
||||||
|
|
||||||
|
public addUser(id: UserId, name: string) {
|
||||||
|
if (this.started) return;
|
||||||
|
this.users.set(id, { id, name, score: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeUser(id: UserId) {
|
||||||
|
if (this.started) return;
|
||||||
|
this.users.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public start() {
|
||||||
|
this.started = true;
|
||||||
|
const users = Array.from(this.users.keys());
|
||||||
|
const comb: [UserId, UserId][] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < users.length; i++) {
|
||||||
|
for (let j = i + 1; j < users.length; j++) {
|
||||||
|
comb.push([users[i], users[j]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shuffle(comb);
|
||||||
|
comb.forEach(shuffle);
|
||||||
|
|
||||||
|
comb.forEach(([u1, u2]) => {
|
||||||
|
const gameId = newUUID() as PongGameId;
|
||||||
|
const g = new Pong(u1, u2);
|
||||||
|
|
||||||
|
this.games.set(gameId, g);
|
||||||
|
});
|
||||||
|
this.currentGame = this.games.keys().next().value ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue