anvilsign in

collin/mahjong

1import type { SeatId } from '../game/types';
2import { SEAT_ROT, SEAT_SLOT } from './rotation';
3
4interface Section {
5 zh: string;
6 en: string;
7 body: [string, string][];
8}
9
10/** Rules reference, shown inside one player's own strip so only they read it. */
11const SECTIONS: Section[] = [
12 {
13 zh: '目標',
14 en: 'The goal',
15 body: [
16 ['十六張牌,湊成五組加一對眼。', 'Build five sets plus one pair from 16 tiles.'],
17 ['一組=順子(三張同花色連號)或刻子(三張相同)。', 'A set is a run of three in one suit, or three of a kind.'],
18 ['摸進第十七張後打出一張。', 'You draw a 17th tile each turn, then discard one.'],
19 ],
20 },
21 {
22 zh: '牌',
23 en: 'The tiles',
24 body: [
25 ['萬 · 條 · 筒 一到九,各四張。', 'Characters, bamboo and dots, 1–9, four of each.'],
26 ['字牌:東南西北 中發白,各四張。', 'Honours: four winds and three dragons, four of each.'],
27 ['花牌八張,摸到自動補牌,還能算台。', 'Eight flowers: drawn ones replace themselves and score.'],
28 ],
29 },
30 {
31 zh: '輪到你',
32 en: 'Your turn',
33 body: [
34 ['點一張牌抬起,再點一次打出。', 'Tap a tile to lift it, tap again to discard it.'],
35 ['剛摸的牌會分開放在最右邊,標著「摸」。', 'The tile you just drew sits apart on the right, marked 摸.'],
36 ['牌可以拖曳重排,或按「理牌」自動排序。', 'Drag tiles to rearrange, or press 理牌 to sort.'],
37 ],
38 },
39 {
40 zh: '吃碰槓',
41 en: 'Claiming a discard',
42 body: [
43 ['碰:手上兩張相同,可碰任何人打的牌。', 'Pung: two matching tiles, from anyone’s discard.'],
44 ['吃:只能吃上家(左手邊)打的牌。', 'Chow: a run, and only from the player to your left.'],
45 ['優先序:胡 > 碰/槓 > 吃。', 'Priority: win beats pung/kong, which beat chow.'],
46 ['吃碰之後不用摸牌,直接打一張。', 'After claiming you do not draw — just discard.'],
47 [
48 '要碰要快:下家一摸牌就來不及了,桌子不會等你。',
49 'Call fast — the window shuts when the next player draws. Nobody waits.',
50 ],
51 ],
52 },
53 {
54 zh: '槓',
55 en: 'Kongs',
56 body: [
57 ['四張相同:明槓、暗槓、或碰後加槓。', 'Four of a kind, claimed, concealed, or added to a pung.'],
58 ['槓完從牌尾補一張,胡了算「槓上開花」。', 'You draw from the tail; winning on it scores 槓上開花.'],
59 ['別人加槓時你剛好能胡,可以搶槓。', 'You may rob an added kong if that tile completes your hand.'],
60 ],
61 },
62 {
63 zh: '胡牌',
64 en: 'Winning',
65 body: [
66 ['自摸:自己摸到,三家各付一份。', 'Self-draw: all three opponents pay you.'],
67 ['放槍:胡別人打的牌,放槍者一家付。', 'Off a discard: the discarder alone pays.'],
68 ['一份 = 底 + 台數 × 台值。', 'One share is the base plus your tai count.'],
69 ['牌牆剩十六張還沒人胡就流局。', 'If 16 tiles remain undrawn, the hand is a draw.'],
70 ],
71 },
72 {
73 zh: '常見台數',
74 en: 'Common tai',
75 body: [
76 ['自摸 1 · 門清 1 · 門清自摸 +1', 'Self-draw 1 · concealed 1 · both together +1'],
77 ['正花每張 1 · 圈風 1 · 門風 1 · 三元牌每組 1', 'Own flower 1 · round wind 1 · seat wind 1 · dragon pung 1'],
78 ['碰碰胡 4 · 混一色 4 · 清一色 8 · 字一色 16', 'All pungs 4 · half flush 4 · full flush 8 · all honours 16'],
79 ['大三元 8 · 小四喜 8 · 大四喜 16', 'Big three dragons 8 · little four winds 8 · big four winds 16'],
80 ['莊家 1 台,連莊每次再加 2 台。', 'Dealer scores 1, plus 2 more for each repeat deal.'],
81 ],
82 },
83 {
84 zh: '房規',
85 en: 'House rules',
86 body: [
87 [
88 '過水:不胡的牌,到自己再摸牌前都不能胡。',
89 'Sacred discard: pass on a winning tile and it is dead to you until your next draw.',
90 ],
91 [
92 '一炮多響:一張放槍牌,幾家胡就付幾家。',
93 'Multiple winners: one discard can pay out to every seat that calls on it.',
94 ],
95 [
96 '包牌:放第三張三元/四喜刻子的人,一人付全部。',
97 'Liability: feed the third dragon or wind pung to a big hand and you pay for it alone.',
98 ],
99 ['以上都可以在設定裡開關。', 'All three are switches on the settings screen.'],
100 ],
101 },
102 {
103 zh: '拿錯了',
104 en: 'Mis-taps',
105 body: [
106 ['桌子中央有「復原」,可以退回上一步。', 'The 復原 button in the middle takes back the last action.'],
107 ['誰看到誰按,不限於按錯的那個人。', 'Anyone can press it — not just whoever made the mistake.'],
108 ],
109 },
110 {
111 zh: '莊家',
112 en: 'The dealer',
113 body: [
114 ['莊家胡牌或流局就連莊。', 'The dealer keeps the deal on a win or a draw.'],
115 ['否則換下家做莊,四家輪完換一圈風。', 'Otherwise it passes on; the round wind turns every four passes.'],
116 ],
117 },
118];
119
120/**
121 * Rules panel for one seat. Rendered at table level rather than inside the
122 * seat strip so it can be deep enough to read, but still rotated to face only
123 * that player.
124 */
125export function Help({ seat, onClose }: { seat: SeatId; onClose: () => void }) {
126 return (
127 <div className={`help-slot ${SEAT_SLOT[seat]}`} onClick={onClose}>
128 <div
129 className="help"
130 style={{ transform: `translate(-50%, -50%) rotate(${SEAT_ROT[seat]}deg)` }}
131 onClick={(e) => e.stopPropagation()}
132 >
133 <div className="help-head">
134 <h3>台灣麻將規則 · How to play</h3>
135 <button className="btn ghost" onClick={onClose}>
136 關閉 Close
137 </button>
138 </div>
139 <div className="help-body">
140 {SECTIONS.map((sec) => (
141 <section key={sec.zh}>
142 <h4>
143 {sec.zh} <em>{sec.en}</em>
144 </h4>
145 {sec.body.map(([zh, en], i) => (
146 <p key={i}>
147 <span>{zh}</span>
148 <em>{en}</em>
149 </p>
150 ))}
151 </section>
152 ))}
153 <section className="credit">
154 <h4>
155 牌面 <em>Tile art</em>
156 </h4>
157 <p>
158 <span>gnome-mahjongg「postmodern」牌組</span>
159 <em>From gnome-mahjongg’s postmodern theme, GPL-2.0-or-later.</em>
160 </p>
161 </section>
162 </div>
163 </div>
164 </div>
165 );
166}