anvilsign in

collin/mahjong · fba98f79

The corners meet because there is no sum left to get wrong

Collin Richards · 2026-08-21 20:58 UTC · fba98f792458ec509e9725818781f07a39ae3f1e · parent 6e2784f0 · browse files

modifiedREADME.md+8 −4
⋯ 572 unchanged lines
573573 back off the element, so the colliders turn with it, and `WallRing` reads it too
574574 so the square is built smaller by however much the turn costs it (stood on its
575575 corner a square needs its diagonal where it used to need its side). Set it to
576-zero and everything squares up again. `--wall-lap` is how far each wall runs past its own corner, in wall-depths —
577-that overlap is the whole of what makes four straight walls a pinwheel, and at 0
578-they merely meet. `--wall-room`, `--wall-x` and `--wall-y` put it where you want
579-it, and `npm run dev` serves **`/wall.html`** for turning
576+zero and everything squares up again. The lap — how far a wall runs past its own corner, the overlap that makes four
577+straight walls a pinwheel — is not a number anybody gets to choose. For four
578+equal walls to close it has to be exactly one wall-depth, and any other value is
579+either a gap or an overshoot. So it isn't one: `--wall-len` is a side's length,
580+eighteen stacks exactly, set from `WallRing` alongside `--ws`; the opening is
581+that less one depth; and each wall is pinned at the corner it is built from. The
582+corners meet because there is no arithmetic left for them to disagree about.
583+`--wall-room`, `--wall-x` and `--wall-y` put it where you want it, and `npm run dev` serves **`/wall.html`** for turning
580584 all four by hand — the real `WallRing` in a real `.center`, with every collider
581585 the physics would be handed drawn over the top, because "it looks right" and "a
582586 tile would bounce off it there" are two different claims.
⋯ 75 unchanged lines
modifiedsrc/game/wall.ts+14 −20
⋯ 115 unchanged lines
116116 * `ratio` is the stack's long side over its short one, measured off the element
117117 * rather than restated here: it is CSS's business what shape a stack is.
118118 */
119-export function ringCell(
120- space: { w: number; h: number },
121- ratio: number,
122- lap = ratio,
123-): number {
124- // Outer footprint: the opening, plus a wall hanging off each side of it.
125- // opening = n*s - lap*s walls = 2 * ratio*s
126- // outer = (n + 2*ratio - lap) * s
127- return Math.max(1, Math.min(space.w, space.h) / (STACKS_PER_SIDE + 2 * ratio - lap));
119+export function ringCell(space: { w: number; h: number }, ratio: number): number {
120+ // A side is its eighteen stacks; the opening is that less one wall-depth, and
121+ // a wall hangs off each side of the opening. So across the middle:
122+ // opening = (n - ratio) * s walls = 2 * ratio * s
123+ // outer = (n + ratio) * s
124+ return Math.max(1, Math.min(space.w, space.h) / (STACKS_PER_SIDE + ratio));
128125 }
129126
130127 /**
131128 * The opening the square leaves in the middle — where the discards go.
132129 *
133- * A side holds its stacks and then runs `lap` past its own corner: that overlap
134- * is the whole of what makes four straight walls a pinwheel, and it is what the
135- * opening is short by. One wall-depth is the square everyone knows; less draws
136- * the arms in, more throws them out.
130+ * A side holds its stacks and then runs one wall-depth past its own corner —
131+ * over the outside of the next wall along, which is the pinwheel — so what the
132+ * opening is short by is exactly that depth. It is not a free number: any other
133+ * value leaves the four walls either short of each other or thrown out past
134+ * each other, and both of those are the gap.
137135 */
138-export const ringOpening = (
139- cap: RingCapacity,
140- cell: { s: number; d: number },
141- lap = cell.d,
142-) => ({
143- w: Math.max(cell.s, cap.h * cell.s - lap),
144- h: Math.max(cell.s, cap.v * cell.s - lap),
136+export const ringOpening = (cap: RingCapacity, cell: { s: number; d: number }) => ({
137+ w: Math.max(cell.s, cap.h * cell.s - cell.d),
138+ h: Math.max(cell.s, cap.v * cell.s - cell.d),
145139 });
146140
147141 /** A stack and which of the 72 it is — the index is what the physics reads. */
⋯ 51 unchanged lines
modifiedsrc/styles.css+15 −10
⋯ 675 unchanged lines
676676 element, so the colliders turn with it and nothing has to be kept in step
677677 by hand. Zero squares it up again. */
678678 --wall-turn: 45deg;
679- /* How far a wall runs past its own corner, in wall-depths. That overlap is
680- the whole of what makes four straight walls a pinwheel: at 1 it is the
681- square everyone knows, at 0 the four of them just meet, and above 1 the
682- arms are thrown out past each other. */
683- --wall-lap: 1;
684- --lap: calc(var(--wd) * var(--wall-lap));
679+/* A side's length, set from ui/WallRing: eighteen stacks, exactly. The lap —
680+ how far a wall runs past its own corner, the overlap that makes four
681+ straight walls a pinwheel — is not a number anybody gets to choose. For
682+ four equal walls to close it has to be one wall-depth, no more and no less,
683+ and any other value is a gap or an overshoot. So it is not set here: the
684+ side is its stacks, the opening is the side less one depth, and the corners
685+ meet because there is no arithmetic left for them to disagree about. */
686+ --wall-len: 0px;
685687 /* How much of the room it has the square actually takes, and where its middle
686688 sits relative to the middle of the table. All three are turned by hand in
687689 `/wall.html` and pasted back here. */
⋯ 28 unchanged lines
716718 display: flex;
717719 justify-content: flex-end;
718720 }
721+/* Each wall is pinned at the corner it is built from and runs its own length
722+ from there, which carries it one depth past the far one — over the outside of
723+ the next wall along, which is the pinwheel. Every side the same way round. */
719724 .wall-side.top {
720725 top: calc(-1 * var(--wd));
721726 left: 0;
722- right: calc(-1 * var(--lap));
727+ width: var(--wall-len);
723728 height: var(--wd);
724729 }
725730 .wall-side.right {
726731 right: calc(-1 * var(--wd));
727732 top: 0;
728- bottom: calc(-1 * var(--lap));
733+ height: var(--wall-len);
729734 width: var(--wd);
730735 flex-direction: column;
731736 }
732737 .wall-side.bottom {
733738 bottom: calc(-1 * var(--wd));
734739 right: 0;
735- left: calc(-1 * var(--lap));
740+ width: var(--wall-len);
736741 height: var(--wd);
737742 flex-direction: row-reverse;
738743 }
739744 .wall-side.left {
740745 left: calc(-1 * var(--wd));
741746 bottom: 0;
742- top: calc(-1 * var(--lap));
747+ height: var(--wall-len);
743748 width: var(--wd);
744749 flex-direction: column-reverse;
745750 }
⋯ 1573 unchanged lines
modifiedsrc/tuneWall.tsx+2 −5
⋯ 31 unchanged lines
3232 interface Placing {
3333 turn: number;
3434 room: number;
35- lap: number;
3635 x: number;
3736 y: number;
3837 }
3938
40-const START: Placing = { turn: 45, room: 0.95, lap: 1, x: 0, y: 0 };
39+const START: Placing = { turn: 45, room: 0.95, x: 0, y: 0 };
4140
4241 const KNOBS: Knob[] = [
4342 { key: 'turn', label: 'Turn', min: 0, max: 90, step: 1, note: '° clockwise', unit: 'deg' },
4443 { key: 'room', label: 'Room it takes', min: 0.3, max: 1.3, step: 0.01, note: 'of the middle', unit: '' },
45- { key: 'lap', label: 'Pinwheel lap', min: 0, max: 2, step: 0.05, note: 'wall-depths past the corner', unit: '' },
4644 { key: 'x', label: 'Across', min: -200, max: 200, step: 1, note: 'from the middle', unit: 'px' },
4745 { key: 'y', label: 'Down', min: -200, max: 200, step: 1, note: 'from the middle', unit: 'px' },
4846 ];
⋯ 25 unchanged lines
7472 if (!ring) return;
7573 ring.style.setProperty('--wall-turn', `${place.turn}deg`);
7674 ring.style.setProperty('--wall-room', `${place.room}`);
77- ring.style.setProperty('--wall-lap', `${place.lap}`);
7875 ring.style.setProperty('--wall-x', `${place.x}px`);
7976 ring.style.setProperty('--wall-y', `${place.y}px`);
8077 }, [place]);
⋯ 66 unchanged lines
147144 };
148145 }, [place, drawn, showColliders, state]);
149146
150- const css = `.wall-ring {\n --wall-turn: ${place.turn}deg;\n --wall-room: ${place.room};\n --wall-lap: ${place.lap};\n --wall-x: ${place.x}px;\n --wall-y: ${place.y}px;\n}`;
147+ const css = `.wall-ring {\n --wall-turn: ${place.turn}deg;\n --wall-room: ${place.room};\n --wall-x: ${place.x}px;\n --wall-y: ${place.y}px;\n}`;
151148
152149 return (
153150 <div className="tune">
⋯ 84 unchanged lines
modifiedsrc/ui/WallRing.tsx+9 −6
⋯ 41 unchanged lines
4242 opening
4343 ? // `--ws` is set from here rather than from the tile, because the
4444 // square is the fixed thing now and the tile is what gives.
45- ({ width: opening.w, height: opening.h, '--ws': `${cell}px` } as React.CSSProperties)
45+ ({
46+ width: opening.w,
47+ height: opening.h,
48+ '--ws': `${cell}px`,
49+ // A side is its stacks and nothing else, so there is no sum for
50+ // the corners to come out of by a pixel.
51+ '--wall-len': `${RING.h * cell}px`,
52+ } as React.CSSProperties)
4653 : undefined
4754 }
4855 >
⋯ 58 unchanged lines
107114 const turn = (parseFloat(style.getPropertyValue('--wall-turn')) || 0) * (Math.PI / 180);
108115 const spread = Math.abs(Math.cos(turn)) + Math.abs(Math.sin(turn));
109116 const room = parseFloat(style.getPropertyValue('--wall-room')) || 1;
110- // How far a wall runs past its corner, as a share of a stack's short side.
111- const lapOf = parseFloat(style.getPropertyValue('--wall-lap'));
112- const lap = ratio * (Number.isFinite(lapOf) ? lapOf : 1);
113117 // Eighteen stacks and one depth of overlap is `(18 + ratio) * s` across,
114118 // and turned that covers `spread` times as much — so the room it has to
115119 // be solved in is the middle divided by the turn.
⋯ 3 unchanged lines
119123 h: (middle.clientHeight * room) / spread,
120124 },
121125 ratio,
122- lap,
123126 );
124127 // The square is always eighteen a side; what fits it is the tile.
125128 const cell = { s, d: s * ratio };
126- const opening = ringOpening(RING, cell, s * lap);
129+ const opening = ringOpening(RING, cell);
127130 setSquare((was) =>
128131 was.cell === s && was.opening?.w === opening.w && was.opening?.h === opening.h
129132 ? was
⋯ 15 unchanged lines