Integrates static file serving for HTML, CSS, and JS, and sets up a WebSocket server for real-time communication. Replit-Commit-Author: Agent Replit-Commit-Session-Id: f3ac8eb3-f610-4678-ab6e-ebf900098be4 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 182c9671-a459-4b94-a7eb-1c7a0cefe768 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/d4b7863b-f7b2-425c-a9b5-ad7bd1885e9d/f3ac8eb3-f610-4678-ab6e-ebf900098be4/N1qS6qo Replit-Helium-Checkpoint-Created: true
100 lines
2.8 KiB
TypeScript
100 lines
2.8 KiB
TypeScript
import type { WebSocket } from "ws";
|
|
|
|
export interface RoomSettings {
|
|
mode: "individual" | "teams";
|
|
numTeams: number;
|
|
teamNames: string[];
|
|
playerPickTeam: boolean;
|
|
showBuzzOrder: boolean;
|
|
buzzerLockout: boolean;
|
|
timerSeconds: number;
|
|
}
|
|
|
|
export interface Player {
|
|
id: string;
|
|
name: string;
|
|
teamIndex: number | null;
|
|
ws: WebSocket | null;
|
|
isConnected: boolean;
|
|
}
|
|
|
|
export interface BuzzerState {
|
|
roundOpen: boolean;
|
|
buzzOrder: string[];
|
|
buzzTimes: Map<string, number>;
|
|
}
|
|
|
|
export interface Room {
|
|
id: string;
|
|
moderatorSecret: string;
|
|
modWs: WebSocket | null;
|
|
settings: RoomSettings;
|
|
players: Map<string, Player>;
|
|
buzzerState: BuzzerState;
|
|
locked: boolean;
|
|
teamLocked: boolean;
|
|
}
|
|
|
|
export const rooms = new Map<string, Room>();
|
|
export const wsToPlayer = new Map<WebSocket, { roomId: string; playerId: string }>();
|
|
|
|
const GREEK = ["Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega"];
|
|
export function greekName(i: number): string {
|
|
const cycle = Math.floor(i / GREEK.length);
|
|
const name = GREEK[i % GREEK.length];
|
|
return cycle === 0 ? name : `${name} ${toRoman(cycle + 1)}`;
|
|
}
|
|
function toRoman(n: number): string {
|
|
const vals = [1000,900,500,400,100,90,50,40,10,9,5,4,1];
|
|
const syms = ["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"];
|
|
let out = "";
|
|
for (let i = 0; i < vals.length; i++) while (n >= vals[i]) { out += syms[i]; n -= vals[i]; }
|
|
return out;
|
|
}
|
|
|
|
export function genId(len = 8): string {
|
|
const c = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
let s = "";
|
|
for (let i = 0; i < len; i++) s += c[Math.floor(Math.random() * c.length)];
|
|
return s;
|
|
}
|
|
|
|
export function sanitize(v: unknown, max = 32): string {
|
|
return String(v ?? "").trim().replace(/[<>"'`]/g, "").slice(0, max);
|
|
}
|
|
|
|
export function freshBuzzer(): BuzzerState {
|
|
return { roundOpen: false, buzzOrder: [], buzzTimes: new Map() };
|
|
}
|
|
|
|
export function publicRoom(room: Room) {
|
|
return {
|
|
id: room.id,
|
|
settings: room.settings,
|
|
locked: room.locked,
|
|
teamLocked: room.teamLocked,
|
|
modOnline: room.modWs !== null,
|
|
buzzerState: {
|
|
roundOpen: room.buzzerState.roundOpen,
|
|
buzzOrder: room.buzzerState.buzzOrder,
|
|
},
|
|
players: Array.from(room.players.values()).map(p => ({
|
|
id: p.id,
|
|
name: p.name,
|
|
teamIndex: p.teamIndex,
|
|
isConnected: p.isConnected,
|
|
})),
|
|
};
|
|
}
|
|
|
|
export function broadcast(room: Room, msg: object, exclude?: WebSocket) {
|
|
const d = JSON.stringify(msg);
|
|
for (const p of room.players.values())
|
|
if (p.ws && p.isConnected && p.ws !== exclude) try { p.ws.send(d); } catch {}
|
|
if (room.modWs && room.modWs !== exclude) try { room.modWs.send(d); } catch {}
|
|
}
|
|
|
|
export function toMod(room: Room, msg: object) {
|
|
if (room.modWs) try { room.modWs.send(JSON.stringify(msg)); } catch {}
|
|
}
|