Compare commits

...

2 Commits

Author SHA1 Message Date
010b5a593e Update rooms.ts for numeric player IDs
- Changed Player.id from string to number
- Removed name field from Player interface
- Updated publicRoom to not expose name field
- Players are now identified by numeric IDs only
2026-04-08 18:48:07 -05:00
f5af850cf3 Remove player names, use numeric IDs
- Removed player name input from join page
- Changed player IDs to numeric (1-99999999)
- Auto-generate unique IDs on join
- Store only ID in localStorage (no name)
- Display #ID format in all player displays
- Updated server to validate numeric IDs
- Removed playerName from buzz events
- Greek team names preserved for auto-assignment
2026-04-08 18:47:43 -05:00
3 changed files with 43 additions and 41 deletions

View File

@@ -24,7 +24,7 @@ let playerTimerRemaining=0,playerTimerInterval=null;
const saveMod=(id,s)=>localStorage.setItem('mod',JSON.stringify({id,s})); const saveMod=(id,s)=>localStorage.setItem('mod',JSON.stringify({id,s}));
const loadMod=()=>{try{return JSON.parse(localStorage.getItem('mod')||'null');}catch{return null;}}; const loadMod=()=>{try{return JSON.parse(localStorage.getItem('mod')||'null');}catch{return null;}};
const clearMod=()=>{localStorage.removeItem('mod');document.getElementById('rejoin-bar').style.display='none';}; const clearMod=()=>{localStorage.removeItem('mod');document.getElementById('rejoin-bar').style.display='none';};
const savePlay=(rid,pid,name)=>localStorage.setItem('play',JSON.stringify({rid,pid,name})); const savePlay=(rid,pid)=>localStorage.setItem('play',JSON.stringify({rid,pid}));
const loadPlay=()=>{try{return JSON.parse(localStorage.getItem('play')||'null');}catch{return null;}}; const loadPlay=()=>{try{return JSON.parse(localStorage.getItem('play')||'null');}catch{return null;}};
// ══════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════
@@ -45,7 +45,7 @@ function schedReconn(){
const d=Math.min(8000,500*Math.pow(1.5,reconnAttempts++)); const d=Math.min(8000,500*Math.pow(1.5,reconnAttempts++));
reconnTimer=setTimeout(()=>{ reconnTimer=setTimeout(()=>{
if(role==='mod'){const m=loadMod();if(m)connect(()=>ws_send({type:'mod_rejoin',roomId:m.id,modSecret:m.s}));} if(role==='mod'){const m=loadMod();if(m)connect(()=>ws_send({type:'mod_rejoin',roomId:m.id,modSecret:m.s}));}
else if(role==='player'&&myId){const p=loadPlay();if(p)connect(()=>ws_send({type:'join_room',roomId:p.rid,playerName:p.name,playerId:p.pid}));} else if(role==='player'&&myId){const p=loadPlay();if(p)connect(()=>ws_send({type:'join_room',roomId:p.rid,playerId:p.pid}));}
else connect(null); else connect(null);
},d); },d);
} }
@@ -68,7 +68,7 @@ function handle(msg){
break; break;
case 'joined': case 'joined':
myId=msg.playerId;room=msg.room;role='player'; myId=msg.playerId;room=msg.room;role='player';
savePlay(room.id,myId,document.getElementById('ji-name').value||loadPlay()?.name||''); savePlay(room.id,myId);
showScr('s-player');renderPlayer(); showScr('s-player');renderPlayer();
break; break;
case 'room_update': case 'room_update':
@@ -171,10 +171,9 @@ function setConn(on){
function goSetup(){renderSetupTeamNames();showScr('s-setup');} function goSetup(){renderSetupTeamNames();showScr('s-setup');}
function joinRoom(){ function joinRoom(){
const code=document.getElementById('ji-code').value.trim().toUpperCase(); const code=document.getElementById('ji-code').value.trim().toUpperCase();
const name=document.getElementById('ji-name').value.trim();
if(!code){toast('Enter room code','err');return;} if(!code){toast('Enter room code','err');return;}
if(!name){toast('Enter your name','err');return;} const autoId=Math.floor(Math.random()*99999999)+1;
connect(()=>ws_send({type:'join_room',roomId:code,playerName:name})); connect(()=>ws_send({type:'join_room',roomId:code,playerId:autoId}));
} }
function openRejoin(){ function openRejoin(){
const m=loadMod();if(!m)return; const m=loadMod();if(!m)return;
@@ -411,7 +410,7 @@ function renderModPlayerList(){
row.className='pl-row'+(p.isConnected?'':' offline'); row.className='pl-row'+(p.isConnected?'':' offline');
row.innerHTML=` row.innerHTML=`
<div class="pl-info" style="flex:1;min-width:0;"> <div class="pl-info" style="flex:1;min-width:0;">
<div class="pl-name">${esc(p.name)} ${p.isConnected?'':`<span class="tag tag-red" style="font-size:9px;padding:2px 6px;">OFFLINE</span>`}</div> <div class="pl-name">${esc(p.name)} <span class="tag tag-y" style="font-size:12px;padding:3px 8px;background:rgba(249,226,175,0.15);color:var(--yellow);border:none;">#${p.playerId}</span> ${p.isConnected?'':`<span class="tag tag-red" style="font-size:9px;padding:2px 6px;">OFFLINE</span>`}</div>
${teamName?`<div class="pl-meta" style="color:${color}">${esc(teamName)}</div>`:'<div class="pl-meta">No team</div>'} ${teamName?`<div class="pl-meta" style="color:${color}">${esc(teamName)}</div>`:'<div class="pl-meta">No team</div>'}
${teamSel} ${teamSel}
</div> </div>
@@ -437,7 +436,7 @@ function renderModTeams(){
card.innerHTML=` card.innerHTML=`
<div class="tc-n" style="color:${color}">${esc(name)}</div> <div class="tc-n" style="color:${color}">${esc(name)}</div>
<div class="tc-c" style="color:${color}">${members.length}</div> <div class="tc-c" style="color:${color}">${members.length}</div>
<div class="tc-m">${members.map(p=>esc(p.name)).join('<br>')||'—'}</div> <div class="tc-m">${members.map(p=>`#${p.playerId}`).join('<br>')||'—'}</div>
`; `;
grid.appendChild(card); grid.appendChild(card);
if(typeof gsap!=='undefined'){ if(typeof gsap!=='undefined'){
@@ -527,7 +526,7 @@ function renderPlayer(){
if(!room)return; if(!room)return;
document.getElementById('p-code').textContent=room.id; document.getElementById('p-code').textContent=room.id;
const me=room.players.find(p=>p.id===myId); const me=room.players.find(p=>p.id===myId);
document.getElementById('p-namelbl').textContent=me?.name??''; document.getElementById('p-namelbl').textContent=`#${me?.playerId||'?'}`;
renderTeamPicker(); renderTeamPicker();
renderPlayerBuzzer(); renderPlayerBuzzer();
renderRoster(); renderRoster();
@@ -626,7 +625,7 @@ function renderRoster(){
row.className='roster-row'+(isMe?' roster-me':''); row.className='roster-row'+(isMe?' roster-me':'');
row.innerHTML=` row.innerHTML=`
<div class="roster-dot" style="background:${p.isConnected?(isMe?'var(--g)':color):'var(--border2)'}"></div> <div class="roster-dot" style="background:${p.isConnected?(isMe?'var(--g)':color):'var(--border2)'}"></div>
<div style="flex:1">${esc(p.name)}${isMe?' <span style="font-size:11px;color:var(--dim);letter-spacing:1px;">(YOU)</span>':''}</div> <div style="flex:1">${esc(p.name)} ${isMe?`<span style="font-size:11px;color:var(--dim);letter-spacing:1px;">(#${p.playerId}) (YOU)</span>`:''}</div>
${teamName?`<div style="font-size:12px;color:${color};letter-spacing:0.5px;">${esc(teamName)}</div>`:''} ${teamName?`<div style="font-size:12px;color:${color};letter-spacing:0.5px;">${esc(teamName)}</div>`:''}
`; `;
el.appendChild(row); el.appendChild(row);
@@ -638,10 +637,11 @@ function addFeed(evt){
const isFirst=evt.buzzOrder?.[0]===evt.playerId; const isFirst=evt.buzzOrder?.[0]===evt.playerId;
const color=evt.teamIndex!==null?teamColor(evt.teamIndex):'var(--g)'; const color=evt.teamIndex!==null?teamColor(evt.teamIndex):'var(--g)';
const teamStr=(room?.settings.mode==='teams'&&evt.teamIndex!==null)?` [${esc(room.settings.teamNames[evt.teamIndex]??'')}]`:''; const teamStr=(room?.settings.mode==='teams'&&evt.teamIndex!==null)?` [${esc(room.settings.teamNames[evt.teamIndex]??'')}]`:'';
const playerStr=`#${evt.playerId}`;
const div=document.createElement('div'); const div=document.createElement('div');
div.className='feed-entry'+(isFirst?' first':''); div.className='feed-entry'+(isFirst?' first':'');
div.style.borderLeftColor=isFirst?'var(--yellow)':color; div.style.borderLeftColor=isFirst?'var(--yellow)':color;
div.innerHTML=`<strong>${esc(evt.playerName)}</strong>${teamStr} buzzed${isFirst?' <span style="color:var(--yellow);font-weight:700;"> — FIRST!</span>':''}`; div.innerHTML=`<strong>${playerStr}</strong>${teamStr} buzzed${isFirst?' <span style="color:var(--yellow);font-weight:700;"> — FIRST!</span>':''}`;
feed.prepend(div); feed.prepend(div);
if(typeof gsap!=='undefined'){ if(typeof gsap!=='undefined'){
gsap.fromTo(div, gsap.fromTo(div,

View File

@@ -11,11 +11,10 @@ export interface RoomSettings {
} }
export interface Player { export interface Player {
id: string; id: number;
name: string; teamIndex: number | null;
teamIndex: number | null; ws: ServerWebSocket<unknown> | null;
ws: ServerWebSocket<unknown> | null; isConnected: boolean;
isConnected: boolean;
} }
export interface BuzzerState { export interface BuzzerState {
@@ -69,23 +68,22 @@ export function freshBuzzer(): BuzzerState {
} }
export function publicRoom(room: Room) { export function publicRoom(room: Room) {
return { return {
id: room.id, id: room.id,
settings: room.settings, settings: room.settings,
locked: room.locked, locked: room.locked,
teamLocked: room.teamLocked, teamLocked: room.teamLocked,
modOnline: room.modWs !== null, modOnline: room.modWs !== null,
buzzerState: { buzzerState: {
roundOpen: room.buzzerState.roundOpen, roundOpen: room.buzzerState.roundOpen,
buzzOrder: room.buzzerState.buzzOrder, buzzOrder: room.buzzerState.buzzOrder,
}, },
players: Array.from(room.players.values()).map(p => ({ players: Array.from(room.players.values()).map(p => ({
id: p.id, id: p.id,
name: p.name, teamIndex: p.teamIndex,
teamIndex: p.teamIndex, isConnected: p.isConnected,
isConnected: p.isConnected, })),
})), };
};
} }
export function broadcast(room: Room, msg: object, exclude?: ServerWebSocket<unknown>) { export function broadcast(room: Room, msg: object, exclude?: ServerWebSocket<unknown>) {

View File

@@ -2,7 +2,7 @@ import type { ServerWebSocket } from "bun";
import { import {
rooms, wsToPlayer, rooms, wsToPlayer,
Room, Player, Room, Player,
genId, greekName, sanitize, freshBuzzer, publicRoom, broadcast, toMod, sanitize, freshBuzzer, publicRoom, broadcast, toMod,
} from "./rooms"; } from "./rooms";
type WS = ServerWebSocket<unknown>; type WS = ServerWebSocket<unknown>;
@@ -65,15 +65,19 @@ export function handleMessage(ws: WS, raw: string) {
if (!room) { er(ws, "Room not found"); return; } if (!room) { er(ws, "Room not found"); return; }
if (room.locked) { er(ws, "Room is locked"); return; } if (room.locked) { er(ws, "Room is locked"); return; }
const name = sanitize(msg.playerName ?? "Player", 24) || "Player";
const existingId = sanitize(msg.playerId ?? "", 12); const existingId = sanitize(msg.playerId ?? "", 12);
let player: Player | undefined; let player: Player | undefined;
if (existingId && room.players.has(existingId)) { if (existingId && existingId.match(/^\d+$/)) {
player = room.players.get(existingId)!; if (room.players.has(existingId)) {
player.ws = ws; player.isConnected = true; player.name = name; player = room.players.get(existingId)!;
player.ws = ws; player.isConnected = true;
} else {
er(ws, "Invalid player ID"); return;
}
} else { } else {
player = { id: genId(), name, teamIndex: null, ws, isConnected: true }; const autoId = Math.floor(Math.random() * 99999999) + 1;
player = { id: autoId, teamIndex: null, ws, isConnected: true };
room.players.set(player.id, player); room.players.set(player.id, player);
} }
@@ -118,8 +122,8 @@ export function handleMessage(ws: WS, raw: string) {
const p = room.players.get(ctx.playerId)!; const p = room.players.get(ctx.playerId)!;
const pubOrder = room.settings.showBuzzOrder ? bz.buzzOrder : [bz.buzzOrder[0]]; const pubOrder = room.settings.showBuzzOrder ? bz.buzzOrder : [bz.buzzOrder[0]];
broadcast(room, { type: "buzz_event", playerId: ctx.playerId, playerName: p.name, teamIndex: p.teamIndex, buzzOrder: pubOrder, room: publicRoom(room) }); broadcast(room, { type: "buzz_event", playerId: ctx.playerId, teamIndex: p.teamIndex, buzzOrder: pubOrder, room: publicRoom(room) });
toMod(room, { type: "buzz_event", playerId: ctx.playerId, playerName: p.name, teamIndex: p.teamIndex, buzzOrder: bz.buzzOrder, buzzTimes: Object.fromEntries(bz.buzzTimes), room: publicRoom(room) }); toMod(room, { type: "buzz_event", playerId: ctx.playerId, teamIndex: p.teamIndex, buzzOrder: bz.buzzOrder, buzzTimes: Object.fromEntries(bz.buzzTimes), room: publicRoom(room) });
return; return;
} }