anvilsign in

collin/mahjong

1import { DEFAULT_NAMES, DEFAULT_RULES, type Rules } from './types';
2
3const KEY = 'taiwanese-mahjong/prefs';
4
5/**
6 * Preferences belong to the table, not to a hand: they outlive a save, survive
7 * 開新局, and are what a fresh `Game` is built from.
8 */
9export interface Prefs {
10 /** Master mute. Off by default — a shared screen should not surprise a room. */
11 sound: boolean;
12 /** 0..1 */
13 volume: number;
14 names: string[];
15 rules: Rules;
16}
17
18export const DEFAULT_PREFS: Prefs = {
19 sound: false,
20 volume: 0.7,
21 names: DEFAULT_NAMES,
22 rules: DEFAULT_RULES,
23};
24
25export function loadPrefs(): Prefs {
26 try {
27 const raw = localStorage.getItem(KEY);
28 if (!raw) return DEFAULT_PREFS;
29 const p = JSON.parse(raw) as Partial<Prefs>;
30 return {
31 sound: typeof p.sound === 'boolean' ? p.sound : DEFAULT_PREFS.sound,
32 volume: clamp(typeof p.volume === 'number' ? p.volume : DEFAULT_PREFS.volume, 0, 1),
33 names:
34 Array.isArray(p.names) && p.names.length === 4 && p.names.every((n) => typeof n === 'string')
35 ? p.names
36 : DEFAULT_NAMES,
37 rules: { ...DEFAULT_RULES, ...(p.rules ?? {}) },
38 };
39 } catch {
40 return DEFAULT_PREFS;
41 }
42}
43
44export function savePrefs(p: Prefs) {
45 try {
46 localStorage.setItem(KEY, JSON.stringify(p));
47 } catch {
48 // Storage unavailable — the settings just don't outlive the tab.
49 }
50}
51
52export const clamp = (n: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, n));