anvilsign in

collin/mahjong · c2c28d83

The call is written down the tile, and the strip has room for it

Collin Richards · 2026-08-25 09:02 UTC · c2c28d8329dc5dd3ea08797d91c67dfc4ba199b2 · parent 963c4b92 · browse files

modifiedREADME.md+16 −2
⋯ 125 unchanged lines
126126 加槓 offers everyone else a 搶槓 chance.
127127 - **Where the buttons go** — on the same row as your tiles, at the right-hand end
128128 of it, where the tile you are about to throw already is. The bar is out of the
129- strip's flow, so a button arriving moves nothing: 打出 五萬 appearing when you
129+ strip's flow, so a button arriving moves nothing: 打出 appearing when you
130130 lift a tile used to shove the melds and flowers above it up the strip. Every
131- button on the bar is given one height for the same reason.
131+ button on the bar is given one height for the same reason. A bar with more on
132+ it than the room beside the hand stacks up the strip in tile-wide lines, most
133+ urgent call at the bottom, rather than running off the end of it. And the room
134+ for the first line is in the tile size itself: a strip is sized to hold sixteen
135+ tiles, the slot it keeps for the tile it is about to draw, and one button —
136+ which is why the tiles down the sides of a short window are the size they are.
137+- **What the buttons say** — the call is written down the tile the way the
138+ character on a tile's face is, one on top of the next in the middle of the
139+ face, with the English along the bottom edge. Two characters stacked are the
140+ size a tile's own character is; two side by side had to be shrunk to fit
141+ across it. A call too long for the height (電腦代打) takes a second column to
142+ the left of the first, which is where the next column of vertical Chinese
143+ goes. 打出 names no tile: the one it would throw is lifted out of the row
144+ beside it wearing a gold ring, so saying 五萬 on the button as well only
145+ crowds the face.
132146 - **On the keys** — ← and → walk the selection along your hand and space throws
133147 the tile that is lifted. How long you *hold* space is how hard it goes: the
134148 tile draws back out of the hand while the key is down and leaves when you let
⋯ 703 unchanged lines
modifiedsrc/styles.css+106 −11
⋯ 56 unchanged lines
5757 being dropped away from you rather than skidded across a flat table.
5858
5959 Taken from whichever way round is tighter, so all four hands match rather
60- than the side seats getting their own size off `--v-len`. */
60+ than the side seats getting their own size off `--v-len`.
61+
62+ What a strip has to fit along its length, written out: sixteen tiles and
63+ fifteen gaps of hand, the slot it keeps for the tile it is about to draw
64+ (a gap, a `--drawn-gap` and a tile), the `--drawn-gap` on either side of
65+ the hand, a tile of button at the end of the row — and, because the hand
66+ is centred rather than pushed along, that same button's width again on the
67+ other side. That comes to 20.35 tiles and 48px, and the row is 16px inside
68+ the strip's own padding.
69+
70+ Sized off 18 tiles it was not enough: a hand down the side of a 765px
71+ window ran off both ends of its own strip, and 碰 and 看牌 stood in felt
72+ that was not on the screen. Nothing about the numbers below changes when
73+ the buttons wrap — the room for the first one is always there. */
6174 --tile-w: min(
62- calc((var(--h-len) - 40px) / 18),
63- calc((var(--v-len) - 40px) / 18),
75+ calc((var(--h-len) - 48px) / 20.35),
76+ calc((var(--v-len) - 48px) / 20.35),
6477 var(--tile-fit),
6578 72px
6679 );
⋯ 235 unchanged lines
302315 Every hand, not only your own: the four strips are the same strip, and each
303316 of them is one draw away from needing it. Not on a phone, where the row wraps
304317 to as many lines as it needs and a draw re-flows it anyway — there the slot
305- would only push the whole hand off its own middle. */
306-:root:not(.compact) .hand:not(:has(.drawn-slot)) {
318+ would only push the whole hand off its own middle.
319+
320+ A face-down hand is the reason for the seventeenth-tile test. Its tiles are
321+ all one tile — which one was drawn is exactly what a covered hand is not
322+ saying — so it has no `.drawn-slot` to go by, and a dealt seventeen was
323+ keeping room for an eighteenth tile that cannot exist. Seventeen is the most
324+ a hand ever holds (a kong takes three out and gives one back), so a row that
325+ long is a row already holding its draw, and it is the same width either way:
326+ sixteen tiles and the slot, or seventeen tiles and none. */
327+:root:not(.compact) .hand:not(:has(.drawn-slot)):not(:has(.hand-cell:nth-child(17))) {
307328 padding-right: calc(var(--hand-gap) + var(--drawn-gap) + var(--tile-w));
308329 }
330+/* And the seventeenth tile of a covered hand still stands the daylight away
331+ from the rest that a drawn tile stands, so that the sixteen under it are at
332+ the same sixteen places they were before the draw. It is at the end of the
333+ row rather than beside the tile, since a covered hand is not saying which of
334+ them arrived last. */
335+:root:not(.compact) .hand:not(:has(.drawn-slot)):has(.hand-cell:nth-child(17)) {
336+ padding-right: var(--drawn-gap);
337+}
309338 /* Your own hand, face down at a shared screen until you tap it. The backs are
310339 already the picture; all this adds is that it is *askable* — a hand that
311340 lifts a little under the finger, the way a card laid over a strip does. The
⋯ 204 unchanged lines
516545 :root:not(.compact) .hand-row > .hand {
517546 grid-column: 2;
518547 }
548+/* Out of the flow, in the column the grid gave it: the grid decides *where* the
549+ bar starts (the start of the right-hand column, which is where the tiles
550+ stop) and the bar then costs the row nothing. So the hand keeps the middle of
551+ the strip whatever is on the bar, and a crowded bar — 碰 槓 胡 過 all at once,
552+ or a hand with two kongs to declare — stacks up the strip in tile-wide lines
553+ instead of running off the end of it. The room for the first line is in
554+ `--tile-w` itself; the lines above it are over the felt, which on the seat
555+ being waited on is the emptiest part of the strip. */
519556 :root:not(.compact) .hand-row > .actionbar {
520557 grid-column: 3;
521- justify-self: start;
558+ position: absolute;
559+ left: 0;
560+ bottom: 0;
561+ width: 100%;
562+ /* Bottom line first, later ones above it: the buttons grow away from the
563+ hand rather than down through it. */
564+ flex-wrap: wrap-reverse;
565+ align-content: flex-start;
522566 }
523567 .actionbar {
524568 display: flex;
⋯ 173 unchanged lines
698742 transform: translateY(1px);
699743 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
700744 }
701-/* The call, the size a tile's own character is drawn at. Two of them fit
702- across a tile; 過.摸牌 is four, and wraps rather than being cut. */
745+/* The call is written down the tile, the way the character on a tile's face is
746+ written: one on top of the next, in the middle of the face, with the English
747+ along the bottom edge under it.
748+
749+ A row of these is a row of tiles, and a tile is taller than it is wide — so
750+ two characters side by side had to be drawn small enough to sit across a
751+ 38px face, while the same two stacked are the size the tile's own character
752+ is. Four (過.摸牌) run out of height and take a second column to the left of
753+ the first, which is where the next column of vertical Chinese goes.
754+
755+ The grid is what puts them there: one row that takes all the height left over
756+ and centres what is in it, one row that is exactly the English. The reading
757+ (打出) is the right-hand column, and the tile it is about (五萬) the column to
758+ its left, because vertical Chinese reads right to left. */
703759 :root:not(.compact) .actionbar .btn .bi {
760+ display: grid;
761+ grid-template-columns: auto auto;
762+ grid-template-rows: 1fr auto;
763+ justify-content: center;
764+ align-items: center;
704765 gap: 0;
705- line-height: 1.02;
766+ line-height: 1;
767+ height: 100%;
768+ width: 100%;
706769 }
707770 :root:not(.compact) .actionbar .btn .bi .zh {
771+ grid-row: 1;
772+ grid-column: 2;
773+ /* Its own width, centred in the column, rather than stretched to fill it —
774+ a stretched box in `vertical-rl` puts its column of characters against the
775+ right-hand edge, which is a second way of ending up off centre. */
776+ justify-self: center;
777+ writing-mode: vertical-rl;
778+ /* The room above the English, so a long call wraps into a second column
779+ rather than growing out through the top of the tile. */
780+ max-height: calc(var(--bar-h) - var(--tile-w) * 0.36);
708781 font-size: calc(var(--tile-w) * 0.44);
709782 letter-spacing: 0;
710- word-break: break-all;
711783 }
784+/* Under the call, in the call's own column — not spanning both.
785+ Spanning was what pushed 打出 off the middle of the face: the English is
786+ wider than one column of characters, and a spanning item shares the width it
787+ needs out across every track it covers, so the empty 五萬 column took half of
788+ the difference and carried the reading right along with it. In one column it
789+ sizes that column and nothing else, and the pair of them centre together. */
712790 :root:not(.compact) .actionbar .btn .bi .en {
791+ grid-row: 2;
792+ grid-column: 2;
793+ justify-self: center;
794+ text-align: center;
713795 font-size: calc(var(--tile-w) * 0.185);
714796 letter-spacing: 0.03em;
715797 white-space: normal;
716798 opacity: 0.6;
717799 }
800+/* An English-only table has nothing standing above it, so the name is the face
801+ rather than the caption under one. */
802+:root:not(.compact) .actionbar .btn .bi .en:first-child {
803+ grid-row: 1 / -1;
804+ font-size: calc(var(--tile-w) * 0.22);
805+ opacity: 0.85;
806+}
718807 /* Which tile it is about — 打出 五萬, 吃 三萬 四萬. Small, because the tile it
719- names is lifted out of the row right beside it. */
808+ names is lifted out of the row right beside it, and written the same way
809+ down the face as the call it belongs to. */
720810 :root:not(.compact) .actionbar .btn .bi .bi-sub {
811+ grid-row: 1;
812+ grid-column: 1;
813+ justify-self: center;
814+ writing-mode: vertical-rl;
815+ max-height: calc(var(--bar-h) - var(--tile-w) * 0.36);
721816 font-size: calc(var(--tile-w) * 0.22);
722817 white-space: normal;
723818 opacity: 0.75;
⋯ 1974 unchanged lines
modifiedsrc/ui/Seat.tsx+5 −1
⋯ 382 unchanged lines
383383 ))}
384384 {selTile !== null && sel !== null && (
385385 <button className="btn discard" onClick={() => doDiscard(sel)}>
386- <Bi zh="打出" en="Discard" sub={labelOf(selTile)} />
386+ {/* No tile named on it: the tile it would throw is the one
387+ lifted out of the row an inch to its left, wearing a gold
388+ ring, and saying 五萬 on the button as well only crowds
389+ the face of it. */}
390+ <Bi zh="打出" en="Discard" />
387391 </button>
388392 )}
389393 </>
⋯ 268 unchanged lines