// ── Which monument labels a map draws (PLAN_REDESIGNS §4, D196) ─────────── // // The admin card's half of the rule `model/map/map.model.js` applies on the // server: a switch per monument LABEL, the minor labels off unless somebody // turns them on, every other label on. A server's switch wins over the fleet's, // the fleet's over the built-in list. // // The form holds only the EXPLICIT switches, as the server does — `{ key: bool }` // — so a label nobody touched keeps following the default above it. Setting the // fleet back to what the built-in list says clears the row rather than storing a // copy of the default: the next change to the list then reaches that label. // // Pure, and free of React, so it is tested in Node. /** A label as its switch's key: trimmed, single-spaced, lower case. The server's rule exactly. */ export function markerKey(label) { return String(label == null ? '' : label).trim().replace(/\s+/g, ' ').toLowerCase() } /** The built-in answer for one key: hidden for the minor labels, drawn for everything else. */ export function builtIn(minorLabels, key) { return !(minorLabels || []).some((l) => markerKey(l) === key) } const has = (o, k) => Boolean(o) && Object.prototype.hasOwnProperty.call(o, k) /** Whether the fleet draws one key: its own switch, or the built-in list. */ export function fleetShows(minorLabels, fleetMarkers, key) { return has(fleetMarkers, key) ? fleetMarkers[key] : builtIn(minorLabels, key) } /** Whether one server draws one key: its switch, or the fleet's answer. */ export function serverShows(minorLabels, fleetMarkers, serverMarkers, key) { return has(serverMarkers, key) ? serverMarkers[key] : fleetShows(minorLabels, fleetMarkers, key) } /** * Every label the fleet list offers, sorted by name: each server's current * labels and the minor list, each once. A minor label no map has yet is still * listed, so it can be decided before a wipe brings it. */ export function fleetLabels(minorLabels, servers) { const byKey = new Map() // A capitalised spelling wins over the game's lower-case one ("jungle swamp"). const add = (key, label, count) => { const seen = byKey.get(key) if (!seen) byKey.set(key, { key, label, count }) else { seen.count += count if (seen.label === seen.label.toLowerCase() && label !== label.toLowerCase()) seen.label = label } } for (const s of servers || []) { for (const l of s.markerLabels || []) add(l.key, l.label, l.count || 0) } for (const label of minorLabels || []) add(markerKey(label), label, 0) return [...byKey.values()].sort((a, b) => a.label.localeCompare(b.label)) } /** The fleet form after ticking one key: an explicit switch, or none where it matches the built-in list. */ export function setFleet(minorLabels, fleetMarkers, key, shown) { const next = { ...(fleetMarkers || {}) } if (shown === builtIn(minorLabels, key)) delete next[key] else next[key] = shown return next } /** A server form after choosing one key: `null` follows the fleet, otherwise an explicit switch. */ export function setServer(serverMarkers, key, shown) { const next = { ...(serverMarkers || {}) } if (shown === null) delete next[key] else next[key] = shown return next } /** * What changed between two explicit maps, as the PUT takes it: `{ key: bool }` * for a switch set or changed, `{ key: null }` for one cleared. Null when * nothing changed. */ export function markerDiff(before, now) { const out = {} const keys = new Set([...Object.keys(before || {}), ...Object.keys(now || {})]) for (const key of keys) { const was = has(before, key) ? before[key] : undefined const is = has(now, key) ? now[key] : undefined if (was === is) continue out[key] = is === undefined ? null : is } return Object.keys(out).length ? out : null }