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({
126 seat,
127 compact,
128 onClose,
129}: {
130 seat: SeatId;
131 compact?: boolean;
132 onClose: () => void;
133}) {
134 return (
135 <div className={`help-slot ${SEAT_SLOT[seat]}`} onClick={onClose}>
136 <div
137 className="help"
138 style={{
139 transform: compact
140 ? 'translate(-50%, -50%)'
141 : `translate(-50%, -50%) rotate(${SEAT_ROT[seat]}deg)`,
142 }}
143 onClick={(e) => e.stopPropagation()}
144 >
145 <div className="help-head">
146 <h3>台灣麻將規則 · How to play</h3>
147 <button className="btn ghost" onClick={onClose}>
148 關閉 Close
149 </button>
150 </div>
151 <div className="help-body">
152 {SECTIONS.map((sec) => (
153 <section key={sec.zh}>
154 <h4>
155 {sec.zh} <em>{sec.en}</em>
156 </h4>
157 {sec.body.map(([zh, en], i) => (
158 <p key={i}>
159 <span>{zh}</span>
160 <em>{en}</em>
161 </p>
162 ))}
163 </section>
164 ))}
165 <section className="credit">
166 <h4>
167 牌面 <em>Tile art</em>
168 </h4>
169 <p>
170 <span>gnome-mahjongg「postmodern」牌組</span>
171 <em>From gnome-mahjongg’s postmodern theme, GPL-2.0-or-later.</em>
172 </p>
173 </section>
174 </div>
175 </div>
176 </div>
177 );
178}