added ball + bounces; started on queu system for user to join game

This commit is contained in:
bgoulard 2025-12-14 16:12:19 +01:00 committed by Maix0
parent 7571927097
commit 8a6394786c
7 changed files with 258 additions and 182 deletions

View file

@ -1,25 +1,26 @@
<div class="displaybox">
<div id="mainbox" class="mainboxDisplay">
<button id="b-whoami" class="btn-style absolute top-4 left-6">Who am i</button>
<h1 class="text-3xl font-bold text-gray-800">
Pong Box<span id="t-username"></span>
</h1><br>
<!-- Horizontal Message Box -->
<div id="system-box" class="system-info">System: connecting ... </div>
<!-- Pong Box -->
<div class="flex justify-center mt-2">
<div id="pongspace" class="flex flex-col">
<div id="pongbox" class="pongbox-style">
<div class="pong-field">
<div id="batleft" class="pong-batleft bg-amber-400 top-0"></div>
<div class="pong-center-line"></div>
<div id="batright" class="pong-batright bg-amber-400 top-0"></div>
</div>
</div>
</div>
</div>
<div id="mainbox" class="mainboxDisplay">
<button id="b-whoami" class="btn-style absolute top-4 left-6">Who am i</button>
<br>
<button id="b-joinQueu" class="btn-style absolute top-16 left-6">Queu Up</button>
<br>
<h1 class="text-3xl font-bold text-gray-800">
Pong Box<span id="t-username"></span>
</h1><br>
<!-- Horizontal Message Box -->
<div id="system-box" class="system-info">System: connecting ... </div>
<!-- Pong Box -->
<div class="flex justify-center mt-2">
<div id="pongspace" class="flex flex-col">
<div id="pongbox" class="pongbox-style">
<div class="pong-field">
<div id="batleft" class="pong-batleft bg-amber-400 top-0"></div>
<div class="pong-center-line"></div>
<div id="batright" class="pong-batright bg-amber-400 top-0"></div>
<div id="ball" class="w-16 h-16 rounded-full border-4 bg-white border-gray-400"></div>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -7,6 +7,7 @@ import io, { Socket } from 'socket.io-client';
import { addPongMessage } from './addPongMessage';
import { isLoggedIn } from './isLoggedIn';
import type { ClientMessage, ClientProfil } from './types_front';
import { isNullish } from "@app/utils";
export const color = {
red: 'color: red;',
@ -21,7 +22,8 @@ const machineHostName = window.location.hostname;
console.log('connect to login at %chttps://' + machineHostName + ':8888/app/login',color.yellow);
export let __socket: Socket | undefined = undefined;
document.addEventListener('ft:pageChange', () => {
document.addEventListener('ft:pageChange', () => { // dont regen socket on page change from forward/backward navigation arrows
if (__socket !== undefined)
__socket.close();
__socket = undefined;
@ -30,9 +32,8 @@ document.addEventListener('ft:pageChange', () => {
export function getSocket(): Socket {
let addressHost = `wss://${machineHostName}:8888`;
// let addressHost = `wss://localhost:8888`;
if (__socket === undefined)
if (__socket === undefined)
__socket = io(addressHost, {
path: "/api/pong/socket.io/",
secure: false,
@ -41,19 +42,16 @@ export function getSocket(): Socket {
return __socket;
};
function waitSocketConnected(socket: Socket): Promise<void> {
return new Promise(resolve => {
if (socket.connected) return resolve();
socket.on("connect", () => resolve());
});
return new Promise(resolve => {
if (socket.connected) return resolve();
socket.on("connect", () => resolve());
});
};
async function whoami(socket: Socket) {
try {
const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement;
const loggedIn = isLoggedIn();
const res = await client.guestLogin();
switch (res.kind) {
@ -80,148 +78,172 @@ async function whoami(socket: Socket) {
}
};
function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn {
function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn {
let socket = getSocket();
/**
* on connection plays this part
*/
socket.on("connect", async () => {
const systemWindow = document.getElementById('system-box') as HTMLDivElement;
await waitSocketConnected(socket);
console.log("I AM Connected to the server:", socket.id);
const message = {
command: "",
destination: 'system-info',
type: "chat",
user: getUser()?.name,
token: document.cookie ?? "",
text: " has Just ARRIVED in the chat",
timestamp: Date.now(),
SenderWindowID: socket.id,
};
socket.emit('message', JSON.stringify(message));
// const message = {
// command: "",
// destination: 'system-info',
// type: "chat",
// user: getUser()?.name,
// token: document.cookie ?? "",
// text: " has Just ARRIVED in the chat",
// timestamp: Date.now(),
// SenderWindowID: socket.id,
// };
// socket.emit('message', JSON.stringify(message));
const messageElement = document.createElement("div");
messageElement.textContent = `${message.user}: is connected au server`;
systemWindow.appendChild(messageElement);
// messageElement.textContent = `${message.user}: is connected au server`;
messageElement.textContent = `${getUser()?.name ?? "unkown user"}: is connected au server`;
systemWindow.appendChild(messageElement);
systemWindow.scrollTop = systemWindow.scrollHeight;
});
// Queu handler
async function joinQueu(socket : Socket) {
try {
const res = await client.guestLogin();
switch (res.kind) {
case 'success': {
let user = await updateUser();
if (user === null)
return showError('Failed to get user: no user ?');
socket.emit('queuJoin', user.id);
console.log('queu join sent for : ', user.id);
break;
}
case 'failed': {
showError(`Failed to Join Game Queu: ${res.msg}`);
console.log('Failed to Join Game Queu');
}
}
} catch (err ) {
showError(`Failed to Join Game Queu`);
console.log('Failed to Join Game Queu');
}
}
// keys handler
const keys: Record<string, boolean> = {};
document.addEventListener("keydown", (e) => {
keys[e.key.toLowerCase()] = true;
});
document.addEventListener("keyup", (e) => {
keys[e.key.toLowerCase()] = false;
});
setInterval(() => {
if (keys['w']) {
socket.emit("batmove_Left", "up");
console.log('north key pressed - emit batmove_Left up');
setInterval(() => { // key sender
if ((keys['w'] || keys['s']) && !(keys['w'] && keys['s'])) { // exclusive or to filter requests
if (keys['w']) {
socket.emit("batmove_Left", "up");
console.log('north key pressed - emit batmove_Left up');
}
if (keys['s']) {
socket.emit("batmove_Left", "down");
console.log('south key pressed - emit batmove_Left down');
}
}
if (keys['s']) {
socket.emit("batmove_Left", "down");
console.log('south key pressed - emit batmove_Left down');
}
if (keys['p']) {
socket.emit("batmove_Right", "up");
console.log('north key pressed - emit batmove_Right up');
}
if (keys['l']) {
socket.emit("batmove_Right", "down");
console.log('south key pressed - emit batmove_Right down');
if ((keys['p'] || keys['l']) && !(keys['p'] && keys['l'])) { // exclusive or to filter requests
if (keys['p']) {
socket.emit("batmove_Right", "up");
console.log('north key pressed - emit batmove_Right up');
}
if (keys['l']) {
socket.emit("batmove_Right", "down");
console.log('south key pressed - emit batmove_Right down');
}
}
}, 16);
// Listen for Left bat updates
socket.on("batLeft_update", (y: number) => {
console.log('batLeft_update received y: ', y);
// Pong Objects updators
socket.on("batLeft_update", (y: number) => {
console.log('batLeft_update received y: ', y);
const bat = document.getElementById("batleft") as HTMLDivElement | null;
if (!bat) {
console.error("FATAL ERROR: Bat element with ID 'bat-left' not found. Check HTML.");
if (!bat) {
console.error("FATAL ERROR: Bat element with ID 'bat-left' not found. Check HTML.");
return ;
}
if (typeof y === 'number' && !isNaN(y)) {
bat.style.transform = `translateY(${y}px)`;
} else {
console.warn(`Received invalid Y value: ${y}`);
}
});
// Listen for Right bat updates
socket.on("batRight_update", (y: number) => {
console.log('batRight_update received y: ', y);
}
if (typeof y === 'number' && !isNaN(y)) {
bat.style.transform = `translateY(${y}px)`;
} else {
console.warn(`Received invalid Y value: ${y}`);
}
});
socket.on("batRight_update", (y: number) => {
console.log('batRight_update received y: ', y);
const bat = document.getElementById("batright") as HTMLDivElement | null;
if (!bat) {
console.error("FATAL ERROR: Bat element with ID 'bat-Right' not found. Check HTML.");
if (!bat) {
console.error("FATAL ERROR: Bat element with ID 'bat-Right' not found. Check HTML.");
return ;
}
if (typeof y === 'number' && !isNaN(y)) {
bat.style.transform = `translateY(${y}px)`;
} else {
console.warn(`Received invalid Y value: ${y}`);
}
});
}
if (typeof y === 'number' && !isNaN(y)) {
bat.style.transform = `translateY(${y}px)`;
} else {
console.warn(`Received invalid Y value: ${y}`);
}
});
socket.on("ballPos_update", (x:number, y : number) => {
console.log('ballPos_update recieved: ', x, ' / ', y);
const ball = document.getElementById("ball") as HTMLDivElement | null;
socket.once('welcome', (data) => {
console.log('%cWelcome PONG PAGE', color.yellow );
addPongMessage('socket.once \'Welcome\' called')
if (!ball) {
console.error("FATAL ERROR: Bat element with ID 'bat-Right' not found. Check HTML.");
return ;
}
if (typeof y !== 'number' || isNaN(y) || typeof x !== 'number' || isNaN(x)) {
console.warn(`Received invalid X/Y value: ${x} / ${y}`);
return ;
}
ball.style.transform = `translateY(${y}px)`;
ball.style.transform += `translateX(${x}px)`;
});
// socket.once('welcome', (data) => {
// console.log('%cWelcome PONG PAGE', color.yellow );
// addPongMessage('socket.once \'Welcome\' called')
// });
// Listen for messages from the server "MsgObjectServer"
// Listen for messages from the server "MsgObjectServer"
socket.on("MsgObjectServer", (data: { message: ClientMessage}) => {
// Display the message in the chat window
const systemWindow = document.getElementById('system-box') as HTMLDivElement;
const MAX_SYSTEM_MESSAGES = 10;
if (systemWindow && data.message.destination === "system-info") {
const messageElement = document.createElement("div");
messageElement.textContent = `${data.message.user}: ${data.message.text}`;
systemWindow.appendChild(messageElement);
const messageElement = document.createElement("div");
messageElement.textContent = `${data.message.user}: ${data.message.text}`;
systemWindow.appendChild(messageElement);
// keep only last 10
while (systemWindow.children.length > MAX_SYSTEM_MESSAGES) {
systemWindow.removeChild(systemWindow.firstChild!);
}
systemWindow.scrollTop = systemWindow.scrollHeight;
// keep only last 10
while (systemWindow.children.length > MAX_SYSTEM_MESSAGES) {
systemWindow.removeChild(systemWindow.firstChild!);
}
systemWindow.scrollTop = systemWindow.scrollHeight;
}
console.log("Getuser():", getUser());
});
setTitle('Pong Page');
setTitle('Pong Game Page');
return {
html: authHtml, postInsert: async (app) => {
const bwhoami = document.getElementById('b-whoami') as HTMLButtonElement;
const bqueu = document.getElementById('b-joinQueu') as HTMLButtonElement;
bwhoami?.addEventListener('click', async () => {
whoami(socket);
});
bqueu?.addEventListener('click', async () => {
joinQueu(socket);
});
}
}
};
addRoute('/pong', handleChat, { bypass_auth: true });
addRoute('/pong', pongClient, { bypass_auth: true });

View file

@ -6,7 +6,6 @@ export type ClientMessage = {
SenderWindowID: string;
};
export type ClientProfil = {
command: string,
destination: string,

View file

@ -100,7 +100,6 @@
justify-center;
}
.text-style {
@apply
text-black
@ -123,7 +122,6 @@
@apply absolute right-4 w-[12px] h-[80px] top-[0px];
}
.pong-center-line {
@apply
absolute

View file

@ -30,7 +30,7 @@
"typebox": "^1.0.62"
},
"devDependencies": {
"@types/node": "^22.19.2",
"@types/node": "^22.19.3",
"rollup-plugin-node-externals": "^8.1.2",
"vite": "^7.2.7",
"vite-tsconfig-paths": "^5.1.4"

View file

@ -10,6 +10,9 @@ import { broadcast } from './broadcast';
import type { ClientProfil, ClientMessage } from './chat_types';
import { sendInvite } from './sendInvite';
import { setGameLink } from './setGameLink';
import { emit } from 'process';
import { Record } from 'typebox/type';
import { UserId } from '@shared/database/mixin/user';
@ -80,7 +83,9 @@ declare module 'fastify' {
batmove_Right: (direction: "up" | "down") => void;
batLeft_update: (y:number) => void;
batRight_update: (y:number) => void;
ballPos_update: (x:number, y:number) => void;
MsgObjectServer: (data: { message: ClientMessage }) => void;
queuJoin: (userID : UserId) => void;
}>;
}
}
@ -96,66 +101,116 @@ async function onReady(fastify: FastifyInstance) {
}
const SPEED = 20; // bat speed
const BOTTOM_EDGE = 370; // bottom edge of the field;
const TOP_EDGE = 0; // top edge of the field
const BOTTOM_EDGE = 450; // bottom edge of the field;
const LEFT_EDGE = 0;
const RIGHT_EDGE = 800;
const MAX_PADDLE_Y = 370; // BOTTOM_EDGE - padle_height
const START_POS_Y = 178; // bat y in the middle of the field
let batLeft = START_POS_Y; //shared bat position
let batRight = START_POS_Y; //shared bat position
const START_BALLX = 364; //(RIGHT_EDGE / 2) - (ballradius*2 + ballBorder);
const START_BALLY = 189; //(BOTTOM_EDGE / 2) - (ballradius*2 + ballBorder);
const ACCELERATION_FACTOR = 1.15;
let batLeft = START_POS_Y; //shared start bat position
let batRight = START_POS_Y; //shared start bat position
let ballPosX = START_BALLX;
let ballPosY = START_BALLY;
let ballSpeedX = 1;
let ballSpeedY = 1;
let games : Record<UserId, string> = {}; // uuid, game uid - if not in game empty string
fastify.io.on('connection', (socket: Socket) => {
socket.emit("batLeft_update", batLeft);
socket.emit("batRight_update", batRight);
socket.emit("ballPos_update", ballPosX, ballPosY);
socket.on('batmove_Left', (direction: "up" | "down") => {
if (direction === "up") {
batLeft -= SPEED;
console.log('w pressed UP');
}
if (direction === "down") {
console.log('s pressed DOWN');
batLeft += SPEED;
}
// position of bat leftplokoplpl
batLeft = Math.max(TOP_EDGE, Math.min(BOTTOM_EDGE, batLeft));
console.log('batLeft_update is called y:',batLeft);
socket.emit("batLeft_update", batLeft);
});
socket.on('batmove_Right', (direction: "up" | "down") => {
if (direction === "up") {
batRight -= SPEED;
console.log('p pressed UP');
}
if (direction === "down") {
console.log('l pressed DOWN');
batRight += SPEED;
}
// position of bat left
batRight = Math.max(TOP_EDGE, Math.min(BOTTOM_EDGE, batRight));
console.log('batRight_update is called y:',batRight);
socket.emit("batRight_update", batRight);
});
// GAME
// paddle handling
socket.on('batmove_Left', (direction: "up" | "down") => {
if (direction === "up") {
batLeft -= SPEED;
console.log('w pressed UP');
}
if (direction === "down") {
console.log('s pressed DOWN');
batLeft += SPEED;
}
// position of bat leftplokoplpl
batLeft = Math.max(TOP_EDGE, Math.min(MAX_PADDLE_Y, batLeft));
console.log('batLeft_update is called y:',batLeft);
socket.emit("batLeft_update", batLeft);
});
socket.on('batmove_Right', (direction: "up" | "down") => {
if (direction === "up") {
batRight -= SPEED;
console.log('p pressed UP');
}
if (direction === "down") {
console.log('l pressed DOWN');
batRight += SPEED;
}
// position of bat left
batRight = Math.max(TOP_EDGE, Math.min(MAX_PADDLE_Y, batRight));
console.log('batRight_update is called y:',batRight);
socket.emit("batRight_update", batRight);
});
// ball handling:
// TODO 1: l/r bat hit
// TODO 2: l/r wall hit : score
setInterval(async () => {
const new_ballPosX = ballPosX + ballSpeedX;
const new_ballPosY = ballPosY + ballSpeedY;
if (new_ballPosX < 0 || new_ballPosX + 36*2 > RIGHT_EDGE) {
ballSpeedX *= -1;
ballSpeedX *= ACCELERATION_FACTOR;
ballSpeedY *= ACCELERATION_FACTOR;
// TODO: score point + ball reset + spd reset
}
if (new_ballPosY < 0 || new_ballPosY + 36*2 > BOTTOM_EDGE) {
ballSpeedY *= -1;
ballSpeedX *= ACCELERATION_FACTOR;
ballSpeedY *= ACCELERATION_FACTOR;
}
ballSpeedX = Math.max(-10, Math.min(ballSpeedX, 10));
ballSpeedY = Math.max(-10, Math.min(ballSpeedY, 10));
ballPosX += ballSpeedX;
ballPosY += ballSpeedY;
socket.emit("ballPos_update", ballPosX, ballPosY);
}, 1024)
// QUEU HANDL
socket.on('queuJoin', async (uuid: UserId) => {
console.log('queu join recieved for : ', uuid);
if (!games.hasOwnProperty(uuid)) {
console.log("new user in game search queu");
games[uuid] = "";
} else if (games.hasOwnProperty(uuid) && games[uuid] == "") {
console.log('already searching for game');
} else { // (games.hasOwnProperty(uuid) && games[uuid] != "") {
console.log('user alredy in game');
return ;
}
// TODO: step2 : sesrch in record<> find guid w/ "" &/ pair them up
// TODO: step3 : move game logic to lifecycle of queu'ed game
})
// other:
socket.on('message', (message: string) => {
const obj: ClientMessage = JSON.parse(message) as ClientMessage;
clientChat.set(socket.id, { user: obj.user, lastSeen: Date.now() });
socket.emit('welcome', {msg: 'Welcome to the chat! : '});
broadcast(fastify, obj, obj.SenderWindowID);
});
socket.on('inviteGame', async (data: string) => {
const clientName: string = clientChat.get(socket.id)?.user || '';
const profilInvite: ClientProfil = JSON.parse(data) || '';
@ -164,5 +219,6 @@ async function onReady(fastify: FastifyInstance) {
sendInvite(fastify, inviteHtml, profilInvite);
}
});
});
}