index.jsx
5.3 kB · jsx · 144 lines
1import { useMemo } from 'react';2import { ready, ink, fit } from '../../../lib/mrly.js';3import { mount, Page, Group, Slider, Check, Stats, Stat, Note } from '../../../lib/app.jsx';4import { Sketch } from '../../../lib/draw.jsx';5import { useQuery } from '../../../lib/query.js';6import { useSeeds, seeded, Picker } from '../../../lib/select.jsx';78const m = await ready();910const FIRST = { 2: '7', 3: '495', 4: '57343', 5: '33550335' };11const TALL = 640;12const PAD = 12;13const QUARTER = Math.PI / 2;1415function sheet(code, base, level) {16 const grid = m.arcs_grid(code, base, level);17 const out = { side: grid.width, types: grid.types };18 grid.free();19 return out;20}2122function arcs({ side: n, types }, x0, y0, cell) {23 const r = cell / 2;24 const loops = new Path2D();25 const strands = new Path2D();26 const fills = new Path2D();27 const sweep = (path, cx, cy, from) => {28 path.moveTo(cx + r * Math.cos(from), cy + r * Math.sin(from));29 path.arc(cx, cy, r, from, from + QUARTER);30 };31 for (let y = 0; y < n; y++) {32 const top = y0 + (n - 1 - y) * cell;33 const bottom = top + cell;34 for (let x = 0; x < n; x++) {35 const byte = types[y * n + x];36 const left = x0 + x * cell;37 const right = left + cell;38 const lower = byte & 2 ? loops : strands;39 const upper = byte & 4 ? loops : strands;40 if (byte & 1) {41 fills.rect(left, top, cell, cell);42 sweep(lower, left, bottom, -QUARTER);43 sweep(upper, right, top, QUARTER);44 } else {45 sweep(lower, right, bottom, Math.PI);46 sweep(upper, left, top, 0);47 }48 }49 }50 return { loops, strands, fills };51}5253function App() {54 const s = useSeeds();55 const [q, set] = useQuery({ code: seeded(s, 2, 2, '7'), base: 2, level: 5, cells: true });5657 const cap = m.arcs_cap(q.base);58 const level = Math.max(1, Math.min(q.level, cap));59 const code = q.code.trim();6061 const view = useMemo(() => {62 try {63 return {64 name: m.name_of(code, 2, q.base),65 read: JSON.parse(m.arcs_read(code, q.base, level)),66 grid: sheet(code, q.base, level),67 };68 } catch (error) {69 return { error };70 }71 }, [code, q.base, level]);7273 const choose = (patch) => {74 if (patch.base !== undefined && patch.code === undefined) {75 s.drop();76 set({ base: patch.base, code: FIRST[patch.base] });77 return;78 }79 set(patch);80 };8182 const draw = (canvas) => {83 const [ctx, w, h] = fit(canvas, Math.min(canvas.clientWidth, TALL));84 ctx.clearRect(0, 0, w, h);85 if (!view.grid) return;86 const span = Math.min(w, h) - 2 * PAD;87 const cell = span / view.grid.side;88 const paths = arcs(view.grid, (w - span) / 2, (h - span) / 2, cell);89 if (q.cells) {90 ctx.fillStyle = ink.line;91 ctx.fill(paths.fills);92 }93 ctx.lineWidth = Math.max(0.6, Math.min(3, cell * 0.16));94 ctx.strokeStyle = ink.blue;95 ctx.stroke(paths.strands);96 ctx.strokeStyle = ink.orange;97 ctx.stroke(paths.loops);98 };99100 const read = view.read;101 const law = read?.law;102103 const controls = (104 <Group name="The design">105 <Picker dimension={2} bases={[2, 3, 4, 5]} code={q.code} base={q.base} seeds={s} onChange={choose} />106 <Slider label="level" value={level} min={1} max={cap} onChange={(v) => set({ level: v })} />107 <Check label="shade the filled cells" checked={q.cells} onChange={(v) => set({ cells: v })} />108 </Group>109 );110111 return (112 <Page crumb="arcs" title="Loops in arcs" controls={controls}113 sub="Every cell of a design carries two quarter arcs, each joining the midpoints of two neighbouring edges: a filled cell takes the arcs around its lower-left and upper-right corners, an empty cell the other two. The arcs join into curves. The strands, in blue, run from border to border, and there are always twice the side of them. The loops, in orange, close on themselves, and how many there are is up to the design."114 foot={<>Every count on this page comes from the crate through wasm, by union-find over the edge midpoints; the page only draws. Row 0 is at the bottom, so code 7 at base 2 leaves its upper-right quarter empty. A law shows when one is proved: the carpet, base 3 code 495, and all sixteen codes at base 2. Nearby: <a href="../universe/">the universe</a> lists the designs, <a href="../tile/">the tile</a> repeats one.</>}>115 <Sketch draw={draw} deps={[view, q.cells]} role="img" aria-label={`${view.name ?? 'a design'} at level ${level} drawn in arcs`} />116 <Note error={view.error} />117 {read && (118 <Stats>119 <Stat label="name">{view.name}</Stat>120 <Stat label="side">{read.side}</Stat>121 <Stat label="filled">{`${read.filled} of ${read.cells}`}</Stat>122 <Stat label="loops">{read.loops}</Stat>123 <Stat label="strands">{read.strands}</Stat>124 <span className="chip proved">strands = 2 side</span>125 </Stats>126 )}127 {read && (128 <Stats>129 {law ? (130 <>131 <Stat label="law">{`L(n) = ${law.formula}`}</Stat>132 <Stat label={`L(${level})`}>{law.loops}</Stat>133 <span className="chip proved">{law.loops === read.loops ? 'the law meets the count' : 'the law misses the count'}</span>134 </>135 ) : (136 <Stat label="law">no closed form proved for this code</Stat>137 )}138 </Stats>139 )}140 </Page>141 );142}143144mount(<App />);