feat: restructure client-side code
This commit is contained in:
18
src/server/game/deck.js
Normal file
18
src/server/game/deck.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { parseFile as mmParseFile } from 'music-metadata';
|
||||
import { DATA_DIR } from '../config.js';
|
||||
import { loadYearsIndex } from '../years.js';
|
||||
import { shuffle } from './state.js';
|
||||
|
||||
export async function loadDeck() {
|
||||
const years = loadYearsIndex();
|
||||
const files = fs.readdirSync(DATA_DIR).filter((f) => /\.(mp3|wav|m4a|ogg)$/i.test(f));
|
||||
const tracks = await Promise.all(files.map(async (f) => {
|
||||
const fp = path.join(DATA_DIR, f);
|
||||
let year = null, title = path.parse(f).name, artist = '';
|
||||
try { const meta = await mmParseFile(fp, { duration: false }); title = meta.common.title || title; artist = meta.common.artist || artist; year = meta.common.year || null; } catch {}
|
||||
const y = years[f]?.year ?? year; return { id: f, file: f, title, artist, year: y };
|
||||
}));
|
||||
return shuffle(tracks);
|
||||
}
|
||||
71
src/server/game/state.js
Normal file
71
src/server/game/state.js
Normal file
@@ -0,0 +1,71 @@
|
||||
export const rooms = new Map();
|
||||
|
||||
export function shuffle(arr) {
|
||||
const a = [...arr];
|
||||
for (let i = a.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[a[i], a[j]] = [a[j], a[i]];
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
export function nextPlayer(turnOrder, currentId) {
|
||||
if (!turnOrder.length) return null;
|
||||
if (!currentId) return turnOrder[0];
|
||||
const idx = turnOrder.indexOf(currentId);
|
||||
return turnOrder[(idx + 1) % turnOrder.length];
|
||||
}
|
||||
|
||||
export function createRoom(name, host) {
|
||||
const id = (Math.random().toString(36).slice(2, 8)).toUpperCase();
|
||||
const room = {
|
||||
id,
|
||||
name: name || `Room ${id}`,
|
||||
hostId: host.id,
|
||||
players: new Map([[host.id, host]]),
|
||||
deck: [],
|
||||
discard: [],
|
||||
revealTimer: null,
|
||||
syncTimer: null,
|
||||
state: {
|
||||
status: 'lobby',
|
||||
turnOrder: [],
|
||||
currentGuesser: null,
|
||||
currentTrack: null,
|
||||
timeline: {},
|
||||
tokens: {},
|
||||
ready: { [host.id]: false },
|
||||
spectators: {},
|
||||
phase: 'guess',
|
||||
lastResult: null,
|
||||
trackStartAt: null,
|
||||
paused: false,
|
||||
pausedPosSec: 0,
|
||||
goal: 10,
|
||||
},
|
||||
};
|
||||
rooms.set(id, room);
|
||||
return room;
|
||||
}
|
||||
|
||||
export function broadcast(room, type, payload) {
|
||||
for (const p of room.players.values()) {
|
||||
try { p.ws.send(JSON.stringify({ type, ...payload })); } catch {}
|
||||
}
|
||||
}
|
||||
|
||||
export function roomSummary(room) {
|
||||
return {
|
||||
id: room.id,
|
||||
name: room.name,
|
||||
hostId: room.hostId,
|
||||
players: [...room.players.values()].map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
connected: p.connected,
|
||||
ready: !!room.state.ready?.[p.id],
|
||||
spectator: !!room.state.spectators?.[p.id] || !!p.spectator,
|
||||
})),
|
||||
state: room.state,
|
||||
};
|
||||
}
|
||||
13
src/server/game/sync.js
Normal file
13
src/server/game/sync.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { broadcast } from './state.js';
|
||||
|
||||
export function startSyncTimer(room) {
|
||||
if (room.syncTimer) clearInterval(room.syncTimer);
|
||||
room.syncTimer = setInterval(() => {
|
||||
if (room.state.status !== 'playing' || !room.state.currentTrack || !room.state.trackStartAt || room.state.paused) return;
|
||||
broadcast(room, 'sync', { startAt: room.state.trackStartAt, serverNow: Date.now() });
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
export function stopSyncTimer(room) {
|
||||
if (room.syncTimer) { clearInterval(room.syncTimer); room.syncTimer = null; }
|
||||
}
|
||||
Reference in New Issue
Block a user