clone

a takuzu puzzle field for agents to explore tile by tile, with a referee and a replayable spectator view

main
mosaic / site / lib / viewer / hues.ts
hues.ts 2.5K
1// Hue means the agent who solved a tile. Hues are assigned in join order2// and never change afterwards (stable per agent id across revisions and3// regardless of who joins later): each agent takes the smallest hue not4// held by an earlier agent whose seed tile is within a few overlap steps5// of its own, so neighbours start distinct; after that, join order mod6// HUES. Two agents that later meet keep their hues: their territories are7// the point, and the merge is visible in the meet marker, not in colour.89import { type AgentInfo, type Geometry, HUES } from "../fold.ts";1011/** Graph distance over the overlap graph, up to `max`, from one tile. */12export function nearTiles(geo: Geometry, from: number, max: number): number[] {13 if (from < 0 || from >= geo.adjacency.length) return [];14 const dist = new Map<number, number>([[from, 0]]);15 const q = [from];16 for (let i = 0; i < q.length; i++) {17 const t = q[i];18 const d = dist.get(t)!;19 if (d >= max) continue;20 for (const u of geo.adjacency[t]) {21 if (!dist.has(u)) {22 dist.set(u, d + 1);23 q.push(u);24 }25 }26 }27 return [...dist.keys()];28}2930/**31 * Hue index per agent id, in join order. `fixed` carries hues already32 * shown for agents (from an earlier geometry, before the field grew):33 * those are kept as they are, whatever the new adjacency says, so a hue34 * never changes on screen; only agents without one are assigned.35 */36export function agentHues(37 agents: AgentInfo[],38 geo: Geometry | null,39 reach = 3,40 fixed?: Map<string, number>,41): Map<string, number> {42 const out = new Map<string, number>();43 const seeds: { id: string; hue: number; near: Set<number> }[] = [];44 agents.forEach((a, i) => {45 const near = new Set(geo ? nearTiles(geo, a.seed_tile, reach) : []);46 const kept = fixed?.get(a.id);47 if (kept !== undefined) {48 out.set(a.id, kept);49 seeds.push({ id: a.id, hue: kept, near });50 return;51 }52 const used = new Set<number>();53 for (const s of seeds) {54 if (55 s.near.has(a.seed_tile) || near.has(geo ? seedOf(agents, s.id) : -1)56 ) {57 used.add(s.hue);58 }59 }60 let hue = i % HUES;61 if (used.has(hue)) {62 for (let k = 0; k < HUES; k++) {63 const h = (i + k) % HUES;64 if (!used.has(h)) {65 hue = h;66 break;67 }68 }69 }70 out.set(a.id, hue);71 seeds.push({ id: a.id, hue, near });72 });73 return out;74}7576function seedOf(agents: AgentInfo[], id: string): number {77 return agents.find((a) => a.id === id)?.seed_tile ?? -1;78}