[wip] - frontend meh/20 back-end meh/20 still, works. works badly but does work...

This commit is contained in:
bgoulard 2026-01-06 15:09:29 +01:00 committed by Nigel
parent f87a991441
commit 572392f640
5 changed files with 176 additions and 53 deletions

View file

@ -141,4 +141,16 @@
text-black text-black
text-center text-center
bg-white bg-white
z-50
}
.pong-rdy-screen {
@apply
rounded-2xl
absolute
justify-center
text-black
text-center
bg-white
z-50
} }

View file

@ -8,6 +8,8 @@
Pong Box<span id="t-username"></span> Pong Box<span id="t-username"></span>
</h1><br> </h1><br>
<!-- todo: print keys for moving -->
<!-- Horizontal Message Box --> <!-- Horizontal Message Box -->
<div id="score-box" class="grid grid-cols-[1fr_auto_1fr] items-center"> <div id="score-box" class="grid grid-cols-[1fr_auto_1fr] items-center">
<h1 id="player-left"></h1> <h1 id="player-left"></h1>
@ -18,6 +20,7 @@
<div class="flex justify-center mt-2"> <div class="flex justify-center mt-2">
<div id="pongspace" class="flex flex-col"> <div id="pongspace" class="flex flex-col">
<div id="pongbox" class="pongbox-style"> <div id="pongbox" class="pongbox-style">
<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>
<div class="pong-center-line"></div> <div class="pong-center-line"></div>

View file

@ -24,6 +24,11 @@ enum QueueState {
In_local = "In Local", In_local = "In Local",
}; };
enum ReadyState {
readyUp = "ready up?",
readyDown = "ready down",
};
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();
@ -49,6 +54,7 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
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 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");
@ -66,7 +72,7 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
navigateTo("/app"); navigateTo("/app");
return ; return ;
} }
if (!batLeft || !batRight || !ball || !score || !queueBtn || !playerL || !playerR || !gameBoard || !queue_infos || !LocalGameBtn) // sanity check if (!batLeft || !batRight || !ball || !score || !queueBtn || !playerL || !playerR || !gameBoard || !queue_infos || !LocalGameBtn || !rdy_btn) // sanity check
return showError('fatal error'); return showError('fatal error');
// --- // ---
@ -112,6 +118,18 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
local:false local:false
}; };
function resetBoard(batLeft : HTMLDivElement, batRight : HTMLDivElement, playerL : HTMLDivElement, playerR : HTMLDivElement) {
render(DEFAULT_POSITIONS);
batLeft.style.backgroundColor = DEFAULT_COLOR;
batRight.style.backgroundColor = DEFAULT_COLOR;
playerR.style.color = "";
playerL.style.color = "";
playerR.innerText = "";
playerL.innerText = "";
currentGame = null;
opponent = null;
}
const render = (state: GameUpdate) => { const render = (state: GameUpdate) => {
currentGame = state; currentGame = state;
batLeft.style.top = `${state.left.paddle.y}px`; batLeft.style.top = `${state.left.paddle.y}px`;
@ -130,7 +148,10 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
score.innerText = `${state.left.score} | ${state.right.score}` score.innerText = `${state.left.score} | ${state.right.score}`
} }
socket.on('gameUpdate', (state: GameUpdate) => render(state)); socket.on('gameUpdate', (state: GameUpdate) => {
// if (rdy_btn)
// rdy_btn.classList.add('hidden');
render(state);});
// --- // ---
// position logic (client) end // position logic (client) end
// --- // ---
@ -138,35 +159,13 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
// --- // ---
// queue evt // queue evt
// --- // ---
// 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;
} }
queueBtn.addEventListener("click", ()=>{
if (queueBtn.innerText !== QueueState.Iddle) {
if (queueBtn.innerText === QueueState.InQueu) {
socket.emit("dequeue");
queueBtn.innerText = QueueState.Iddle;
}
return ;
}
queueBtn.innerText = QueueState.InQueu;
socket.emit('enqueue');
});
LocalGameBtn.addEventListener("click", () => {
if (queueBtn.innerText !== QueueState.Iddle || currentGame !== null) {
showError("cant launch a local game while in queue/in game");
return ;
}
socket.emit("localGame");
queueBtn.innerText = QueueState.In_local;
LocalGameBtn.innerText = "playing";
});
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});
@ -179,9 +178,49 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
} }
} }
// btn setup
queueBtn.addEventListener("click", ()=>{
if (queueBtn.innerText !== QueueState.Iddle) {
if (queueBtn.innerText === QueueState.InQueu) {
socket.emit("dequeue");
queueBtn.innerText = QueueState.Iddle;
}
return ;
}
queueBtn.innerText = QueueState.InQueu;
socket.emit('enqueue');
});
LocalGameBtn.addEventListener("click", () => {
if (queueBtn.innerText !== QueueState.Iddle || currentGame !== null) {
showError("cant launch a local game while in queue/in game");
return ;
}
socket.emit("localGame");
queueBtn.innerText = QueueState.In_local;
LocalGameBtn.innerText = "playing";
});
rdy_btn.addEventListener("click", ()=>{
showInfo("rdy-evt");
switch (rdy_btn.innerText) {
case ReadyState.readyUp:
showInfo("sent:rdyup");
socket.emit('readyUp');
rdy_btn.innerText = ReadyState.readyDown;
break ;
case ReadyState.readyDown:
showInfo("sent:rdydwn");
socket.emit('readyDown');
rdy_btn.innerText = ReadyState.readyUp;
break ;
default:
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';
@ -193,7 +232,13 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
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");
}); // TODO: notif user of new game w "ready up" btn
rdy_btn.classList.remove('hidden');
rdy_btn.innerText = ReadyState.readyUp;
setTimeout(() => {
rdy_btn.classList.add('hidden');}, 2000); // 1500 : pong.CONCEDED_TIMEOUT
});
socket.on("gameEnd", (winner) => { socket.on("gameEnd", (winner) => {
queueBtn.innerHTML = QueueState.Iddle; queueBtn.innerHTML = QueueState.Iddle;
@ -218,33 +263,24 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
LocalGameBtn.innerText = "Local Game" LocalGameBtn.innerText = "Local Game"
} }
} }
render(DEFAULT_POSITIONS); resetBoard(batLeft, batRight, playerL, playerR);
batLeft.style.backgroundColor = DEFAULT_COLOR;
batRight.style.backgroundColor = DEFAULT_COLOR;
playerR.style.color = "";
playerL.style.color = "";
playerR.innerText = "";
playerL.innerText = "";
currentGame = null;
opponent = null;
}) })
// --- // pretty info for queue :3
// queue evt end
// ---
queueBtn.innerText = QueueState.Iddle;
render(DEFAULT_POSITIONS);
currentGame = null;
batLeft.style.backgroundColor = DEFAULT_COLOR;
batRight.style.backgroundColor = DEFAULT_COLOR;
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}`)); socket.on('queueEvent', (e) => showInfo(`QueueEvent: ${e}`));
// ---
// queue evt end
// ---
// init
rdy_btn.classList.add('hidden');
queueBtn.innerText = QueueState.Iddle;
rdy_btn.innerText = ReadyState.readyUp;
resetBoard(batLeft, batRight, playerL, playerR);
showInfo("butter"); showInfo("butter");
showInfo("butter-toast"); showInfo("butter-toast");
// socket.emit('localGame');
} }
} }
}; };

View file

@ -1,5 +1,7 @@
import { UserId } from '@shared/database/mixin/user'; import { UserId } from '@shared/database/mixin/user';
export class Paddle { export class Paddle {
public static readonly DEFAULT_SPEED = 10; public static readonly DEFAULT_SPEED = 10;
public static readonly DEFAULT_HEIGHT = 80; public static readonly DEFAULT_HEIGHT = 80;
@ -94,8 +96,16 @@ function makeAngle(i: number): [number, number, number, number] {
-Math.PI / i + Math.PI, -Math.PI / i + Math.PI,
]; ];
} }
const LEFT :number = 0;
const RIGHT :number = 1;
enum ReadyState {
noState,
readyUp,
readyDown,
}
export class Pong { export class Pong {
public gameUpdate: NodeJS.Timeout | null = null; public gameUpdate: NodeJS.Timeout | null = null;
public static readonly CONCEDED_TIMEOUT: number = 1500; public static readonly CONCEDED_TIMEOUT: number = 1500;
@ -126,6 +136,8 @@ export class Pong {
Pong.BALL_START_ANGLES[this.ballAngleIdx++], Pong.BALL_START_ANGLES[this.ballAngleIdx++],
); );
public ready_checks: [ReadyState, ReadyState] = [ReadyState.noState, ReadyState.noState];
public score: [number, number] = [0, 0]; public score: [number, number] = [0, 0];
public local: boolean = false; public local: boolean = false;
@ -145,7 +157,44 @@ export class Pong {
public userRight: UserId, public userRight: UserId,
) { } ) { }
public readyup(uid : UserId)
{
// debug
console.log(this.userLeft + " | " + this.userRight);
if (uid === this.userLeft) {
console.log("rdy.up : lft " + uid);
} else if (uid === this.userRight) {
console.log("rdy.up : rgt " + uid);
}
if (uid === this.userLeft) {
this.ready_checks[LEFT] = ReadyState.readyUp;
} else if (uid === this.userRight) {
this.ready_checks[RIGHT] = ReadyState.readyUp;
}
}
public readydown(uid : UserId)
{
// debug
console.log(this.userLeft + " | " + this.userRight);
if (uid === this.userLeft) {
console.log("rdy.down : lft " + uid);
} else if (uid === this.userRight) {
console.log("rdy.down : rgt " + uid);
}
// is everyone already ready?
if (this.ready_checks[LEFT] === ReadyState.readyUp && this.ready_checks[RIGHT] === ReadyState.readyUp) return ;
if (uid === this.userLeft)
this.ready_checks[LEFT] = ReadyState.readyDown;
else if (uid === this.userRight)
this.ready_checks[RIGHT] = ReadyState.readyDown;
}
public tick() { public tick() {
if (this.ready_checks[LEFT] !== ReadyState.readyUp || this.ready_checks[RIGHT] !== ReadyState.readyUp)
return;
if (this.paddleCollision(this.leftPaddle, 'left')) { if (this.paddleCollision(this.leftPaddle, 'left')) {
this.ball.collided( this.ball.collided(
'left', 'left',
@ -254,8 +303,8 @@ export class Pong {
public checkWinner(): 'left' | 'right' | null { public checkWinner(): 'left' | 'right' | null {
const checkInner = () => { const checkInner = () => {
if (this.score[0] >= 5) return 'left'; if (this.score[LEFT] >= 5) return 'left';
if (this.score[1] >= 5) return 'right'; if (this.score[RIGHT] >= 5) return 'right';
if ( if (
this.leftLastSeen !== -1 && this.leftLastSeen !== -1 &&

View file

@ -69,11 +69,10 @@ class StateI {
u1.currentGame = gameId; u1.currentGame = gameId;
u2.currentGame = gameId; u2.currentGame = gameId;
// ---
// wait for ready up
// ---
g.gameUpdate = setInterval(() => { g.gameUpdate = setInterval(() => {
// ---
// wait for ready up
// ---
g.tick(); g.tick();
this.gameUpdate(gameId, u1.socket); this.gameUpdate(gameId, u1.socket);
this.gameUpdate(gameId, u2.socket); this.gameUpdate(gameId, u2.socket);
@ -152,6 +151,9 @@ class StateI {
socket.on('enqueue', () => this.enqueueUser(socket)); socket.on('enqueue', () => this.enqueueUser(socket));
socket.on('dequeue', () => this.dequeueUser(socket)); socket.on('dequeue', () => this.dequeueUser(socket));
socket.on('readyUp', () => this.readyupUser(socket));
socket.on('readyDown', () => this.readydownUser(socket));
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));
} }
@ -215,6 +217,27 @@ class StateI {
socket.emit('queueEvent', 'unregistered'); socket.emit('queueEvent', 'unregistered');
} }
private readydownUser(socket: SSocket) : void { //
// do we know this user ?
if (!this.users.has(socket.authUser.id)) return;
const user = this.users.get(socket.authUser.id)!;
// does the user have a game and do we know such game ?
if (user.currentGame === null || !this.games.has(user.currentGame)) return;
const game = this.games.get(user.currentGame)!;
// is this a local game?
if (game.local === true) return;
game.readydown(user.id);
}
private readyupUser(socket: SSocket) : void { //
// do we know this user ?
if (!this.users.has(socket.authUser.id)) return;
const user = this.users.get(socket.authUser.id)!;
// does the user have a game and do we know such game ?
if (user.currentGame === null || !this.games.has(user.currentGame)) return;
const game = this.games.get(user.currentGame)!;
if (game.local === true) return;
game.readyup(user.id);
}
} }