collin/mahjong · 582ad803
Build the wall as a hash, and give the whole table one tile size
Collin Richards · 2026-08-19 14:55 UTC · 582ad803c208eae202fc2ffa9355b8281dc02b25 · parent 63e2e36a · browse files
modifiedsrc/styles.css+51 −17
| ⋯ 30 unchanged lines | |||
| 31 | 31 | contents to be cut off. */ | |
| 32 | 32 | --tile-fit: calc((var(--depth) - 122px) / 2.57); | |
| 33 | 33 | ||
| 34 | + | /* One size for every tile on the table — in a hand, in the wall, and lying in | |
| 35 | + | the middle. They are the same tiles, so drawing them at different sizes was | |
| 36 | + | always a lie, and a visible one: a tile that shrank as it was thrown read as | |
| 37 | + | being dropped away from you rather than skidded across a flat table. | |
| 38 | + | ||
| 39 | + | Taken from whichever way round is tighter, so all four hands match rather | |
| 40 | + | than the side seats getting their own size off `--v-len`. */ | |
| 41 | + | --tile-w: min( | |
| 42 | + | calc((var(--h-len) - 40px) / 18), | |
| 43 | + | calc((var(--v-len) - 40px) / 18), | |
| 44 | + | var(--tile-fit), | |
| 45 | + | 72px | |
| 46 | + | ); | |
| 47 | + | ||
| 34 | 48 | --felt: #0f4534; | |
| 35 | 49 | --felt-2: #0a3126; | |
| 36 | 50 | --ivory: #f6f0e2; | |
| ⋯ 118 unchanged lines | |||
| 155 | 169 | overflow: hidden; | |
| 156 | 170 | transition: background 0.2s; | |
| 157 | 171 | } | |
| 172 | + | /* `--tile-w` is the table's, set once in :root — a seat only says how long its | |
| 173 | + | own strip is. */ | |
| 158 | 174 | .slot-bottom .seat, | |
| 159 | 175 | .slot-top .seat { | |
| 160 | 176 | width: var(--h-len); | |
| 161 | - | --tile-w: min(calc((var(--h-len) - 40px) / 18), var(--tile-fit), 72px); | |
| 162 | 177 | } | |
| 163 | 178 | .slot-left .seat, | |
| 164 | 179 | .slot-right .seat { | |
| 165 | 180 | width: var(--v-len); | |
| 166 | - | --tile-w: min(calc((var(--v-len) - 40px) / 18), var(--tile-fit), 72px); | |
| 167 | 181 | } | |
| 168 | 182 | .seat.active { | |
| 169 | 183 | background: linear-gradient(to top, rgba(217, 180, 92, 0.22), transparent 70%); | |
| ⋯ 430 unchanged lines | |||
| 600 | 614 | } | |
| 601 | 615 | /* The wall: a square of four staggered sides, 18 stacks of two each, drawn | |
| 602 | 616 | with the tileset's own blank tile. Sits behind the centre readout. */ | |
| 617 | + | /* The wall, built the way it is built on a table: four walls of eighteen stacks | |
| 618 | + | laid out like a # rather than mitred into a closed square. Each one runs the | |
| 619 | + | full eighteen and overhangs the corner of the next, which leaves the corners | |
| 620 | + | open and — the point of it — means the opening in the middle no longer has to | |
| 621 | + | be as long as a wall. That is what lets every tile on the table be the same | |
| 622 | + | size: a closed square of eighteen full-size stacks a side needs about 560px in | |
| 623 | + | the middle, which most windows have not got, so something had to shrink. Here | |
| 624 | + | the tails simply run out under the players' strips instead. | |
| 625 | + | ||
| 626 | + | `.wall-ring` is the opening, not the outside: the sides hang off its edges. */ | |
| 603 | 627 | .wall-ring { | |
| 604 | 628 | position: absolute; | |
| 605 | 629 | top: 50%; | |
| 606 | 630 | left: 50%; | |
| 607 | - | --ring: min(calc(100vh - 2 * var(--depth) - 16px), calc(100vw - 2 * var(--depth) - 16px), 560px); | |
| 608 | - | --ws: calc(var(--ring) / 19.375); | |
| 631 | + | --ws: var(--tile-w); | |
| 609 | 632 | --wd: calc(var(--ws) * 1.375); | |
| 633 | + | --wall-len: calc(var(--ws) * 18); | |
| 634 | + | --ring: min( | |
| 635 | + | calc(100vh - 2 * var(--depth) - 16px), | |
| 636 | + | calc(100vw - 2 * var(--depth) - 16px), | |
| 637 | + | var(--wall-len) | |
| 638 | + | ); | |
| 639 | + | /* How far a wall runs past the opening. Nothing at all once the middle is big | |
| 640 | + | enough to take a whole wall, which is when a plain square is right anyway. */ | |
| 641 | + | --over: calc(var(--wall-len) - var(--ring)); | |
| 610 | 642 | width: var(--ring); | |
| 611 | 643 | height: var(--ring); | |
| 612 | 644 | transform: translate(-50%, -50%); | |
| ⋯ 4 unchanged lines | |||
| 617 | 649 | position: absolute; | |
| 618 | 650 | display: flex; | |
| 619 | 651 | } | |
| 620 | - | /* Pinwheel: each side starts where the previous one ended. */ | |
| 652 | + | /* Each wall sits outside its edge of the opening and overhangs one end of it, | |
| 653 | + | the same way round each time — which is what turns four straight walls into a | |
| 654 | + | pinwheel with its corners open. */ | |
| 621 | 655 | .wall-side.top { | |
| 622 | - | top: 0; | |
| 623 | - | left: var(--wd); | |
| 624 | - | right: 0; | |
| 656 | + | top: calc(-1 * var(--wd)); | |
| 657 | + | left: calc(-1 * var(--over)); | |
| 658 | + | width: var(--wall-len); | |
| 625 | 659 | height: var(--wd); | |
| 626 | 660 | } | |
| 627 | 661 | .wall-side.right { | |
| 628 | - | top: var(--wd); | |
| 629 | - | right: 0; | |
| 630 | - | bottom: 0; | |
| 662 | + | right: calc(-1 * var(--wd)); | |
| 663 | + | top: calc(-1 * var(--over)); | |
| 631 | 664 | width: var(--wd); | |
| 665 | + | height: var(--wall-len); | |
| 632 | 666 | flex-direction: column; | |
| 633 | 667 | } | |
| 634 | 668 | .wall-side.bottom { | |
| 635 | - | bottom: 0; | |
| 636 | - | left: 0; | |
| 637 | - | right: var(--wd); | |
| 669 | + | bottom: calc(-1 * var(--wd)); | |
| 670 | + | right: calc(-1 * var(--over)); | |
| 671 | + | width: var(--wall-len); | |
| 638 | 672 | height: var(--wd); | |
| 639 | 673 | flex-direction: row-reverse; | |
| 640 | 674 | } | |
| 641 | 675 | .wall-side.left { | |
| 642 | - | top: 0; | |
| 643 | - | left: 0; | |
| 644 | - | bottom: var(--wd); | |
| 676 | + | left: calc(-1 * var(--wd)); | |
| 677 | + | bottom: calc(-1 * var(--over)); | |
| 645 | 678 | width: var(--wd); | |
| 679 | + | height: var(--wall-len); | |
| 646 | 680 | flex-direction: column-reverse; | |
| 647 | 681 | } | |
| 648 | 682 | .wall-stack { | |
| ⋯ 1288 unchanged lines | |||
modifiedsrc/table/geometry.ts+9 −9
| ⋯ 68 unchanged lines | |||
| 69 | 69 | const stacks = wallStacks(state); | |
| 70 | 70 | const walls: Rect[] = []; | |
| 71 | 71 | // A stack element is --ws by --wd (or the other way round on the left and | |
| 72 | - | // right sides), which gives us both the tile size and the wall's thickness | |
| 73 | - | // without knowing either number. | |
| 72 | + | // right sides), so its narrower side is the tile's width whichever wall it is | |
| 73 | + | // in — the fallback for sizing a pool tile before any hand has been dealt. | |
| 74 | 74 | let short = 0; | |
| 75 | - | let long = 0; | |
| 76 | 75 | for (const el of ring.querySelectorAll<HTMLElement>('[data-stack]')) { | |
| 77 | 76 | const i = Number(el.dataset.stack); | |
| 78 | 77 | const r = local(el.getBoundingClientRect()); | |
| 79 | 78 | short = Math.max(short, Math.min(r.w, r.h)); | |
| 80 | - | long = Math.max(long, Math.max(r.w, r.h)); | |
| 81 | 79 | const stack = stacks[i]; | |
| 82 | 80 | if (stack && isBarrier(stack)) walls.push(r); | |
| 83 | 81 | } | |
| 84 | 82 | if (short === 0) return null; | |
| 85 | 83 | ||
| 86 | - | // The pool is the square the wall encloses, less the wall's own thickness. | |
| 84 | + | // `.wall-ring` is the opening itself — the walls hang off its edges — so the | |
| 85 | + | // pool is simply its box. (It used to be the wall's outside, which is why this | |
| 86 | + | // had to subtract the wall's own thickness.) | |
| 87 | 87 | const ringRect = local(ring.getBoundingClientRect()); | |
| 88 | 88 | const pool: Rect = { | |
| 89 | - | x: ringRect.x + long, | |
| 90 | - | y: ringRect.y + long, | |
| 91 | - | w: Math.max(short, ringRect.w - long * 2), | |
| 92 | - | h: Math.max(short, ringRect.h - long * 2), | |
| 89 | + | x: ringRect.x, | |
| 90 | + | y: ringRect.y, | |
| 91 | + | w: Math.max(short, ringRect.w), | |
| 92 | + | h: Math.max(short, ringRect.h), | |
| 93 | 93 | }; | |
| 94 | 94 | ||
| 95 | 95 | const launch: Partial<Record<SeatId, Point>> = {}; | |
| ⋯ 155 unchanged lines | |||