import { useMemo, useRef } from 'react'; import { ready, ink, rgb } from '../../lib/mrly.js'; import { mount, Page, Pick, Slider, Check, Stats, Stat, Note, Group } from '../../lib/app.jsx'; import { Sketch } from '../../lib/draw.jsx'; import { useQuery } from '../../lib/query.js'; import { useSeeds, seeded, Picker } from '../../lib/select.jsx'; const m = await ready(); const SIZE = 768; const PAD = 10; const SIDES = [2, 3, 5, 7]; const GROWTHS = [['prime', 'MrlyUlam: the primes grow'], ['every', 'MrlySpiral: every number grows']]; const FIRST = { growth: 'every', code: '7', base: 2, side: 2, top: 300, path: true }; const first = (seeds) => ({ ...FIRST, code: seeded(seeds, 2, FIRST.base, FIRST.code) }); function inked(grid, color) { const [w, h] = [grid.width, grid.height]; const [r, g, b] = rgb(color); const rgba = new Uint8ClampedArray(w * h * 4); for (let i = 0; i < w * h; i += 1) { if (!grid.types[i]) continue; rgba[i * 4] = r; rgba[i * 4 + 1] = g; rgba[i * 4 + 2] = b; rgba[i * 4 + 3] = 255; } const canvas = document.createElement('canvas'); canvas.width = w; canvas.height = h; canvas.getContext('2d').putImageData(new ImageData(rgba, w, h), 0, 0); return canvas; } function App() { const s = useSeeds(); const [look, save] = useQuery(first(s)); const kept = useRef({ cells: null, read: null, art: null }); const view = useMemo(() => { try { const cells = m.snail_cells(look.side, look.top, look.growth); const read = JSON.parse(m.snail_read(look.side, look.top, look.growth)); const art = []; for (let k = 1; k <= read.peak; k += 1) { const grid = m.two_grid(look.code, look.side, k, 0, look.base); art[k] = { lit: inked(grid, ink.blue), plain: inked(grid, ink.dim) }; } kept.current = { cells, read, art }; return { ...kept.current, error: null }; } catch (error) { return { ...kept.current, error }; } }, [look.growth, look.side, look.top, look.code, look.base]); const draw = (canvas) => { const { cells, read, art } = view; if (!cells) return; canvas.width = SIZE; canvas.height = SIZE; const ctx = canvas.getContext('2d'); ctx.imageSmoothingEnabled = false; ctx.fillStyle = ink.deep; ctx.fillRect(0, 0, SIZE, SIZE); const inner = SIZE - 2 * PAD; const scale = inner / Math.max(read.width, read.height); const at = (x, y) => [ PAD + (inner - read.width * scale) / 2 + (x - read.low[0]) * scale, SIZE - PAD - (inner - read.height * scale) / 2 - (y - read.low[1]) * scale, ]; if (look.path) { ctx.strokeStyle = ink.line; ctx.lineWidth = 1; ctx.beginPath(); for (let i = 0; i < cells.length; i += 5) { const half = cells[i + 2] / 2; const [px, py] = at(cells[i] + half, cells[i + 1] + half); if (i) ctx.lineTo(px, py); else ctx.moveTo(px, py); } ctx.stroke(); } for (let i = 0; i < cells.length; i += 5) { const side = cells[i + 2]; const level = cells[i + 3]; const prime = cells[i + 4]; const wide = side * scale; const [px, py] = at(cells[i], cells[i + 1] + side); if (level && wide >= 3) { ctx.drawImage(prime ? art[level].lit : art[level].plain, px, py, wide, wide); } else { ctx.globalAlpha = prime ? 1 : 0.5; ctx.fillStyle = prime ? ink.blue : ink.dim; ctx.fillRect(px, py, Math.max(wide, 1), Math.max(wide, 1)); ctx.globalAlpha = 1; } } }; const read = view.read; const tally = read ? read.levels.map((count, k) => `${count} at ${look.side}^${k}`).join(', ') : ''; const controls = ( <> save({ growth: v })} /> save({ top: v })} /> save({ path: v })} /> [q, q])} onChange={(v) => save({ side: +v })} /> ); return ( The rule, exactly. The level of n is the count of its digits in the base less one, so k(n) = 0 below the base, 1 from the base to base² - 1, and on; a tile at level k is the chosen design grown k times, of side base^k, and level 0 is the bare unit cell. MrlyUlam grows a cell only when n is prime and leaves one and every composite a unit cell; MrlySpiral grows every n by the same law and names no prime. The layout is one deterministic step: tile 1 has its lower-left corner at the origin, and the corner of tile n + 1 is the corner of tile n plus the unit step the square winding takes from n to n + 1, scaled by the side of tile n. Tiles overlap wherever the growth outruns the winding, so the drawn area is the sum of the tile squares and counts an overlap twice. This page is an exhibit: it states no theorem, and the two rules above are ours, fixed here and nowhere else. The same numbers on the plain unit grid are the ulam page, and the sieve that decides which of them grow is on the primes page.} controls={controls}>

The shell {read && `${read.tiles} numbers, ${read.grown} grown, base ${look.side}`}

{read?.primes} {read?.grown} {tally} {read && `${read.side} by ${read.side}`} {read?.area} {read && `${read.width} by ${read.height}`}
); } mount();