-
Notifications
You must be signed in to change notification settings - Fork 27
/
Copy pathindex.html
670 lines (592 loc) · 24.5 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mahjong, using standard HTML, CSS, and JavaScript</title>
<link rel="shortcut icon" type="image/jpeg" href="img/tiles/31.jpg">
<link rel="stylesheet" href="src/css/base-colors.css">
<link rel="stylesheet" href="src/css/colors.css">
<link rel="stylesheet" href="src/css/index.css">
<script src="src/js/page/tiles-locknum.js" async></script>
<script src="src/js/page/play-or-watch.js" type="module"></script>
</head>
<body>
<br> <!-- because in rare cases, padding-top is overkill -->
<main class="board" tabindex="0">
<div class="players">
<div class="player" id="0" data-wincount="0" data-score="2000"></div>
<div class="player" id="1" data-wincount="0" data-score="2000"></div>
<div class="player" id="2" data-wincount="0" data-score="2000"></div>
<div class="player" id="3" data-wincount="0" data-score="2000"></div>
</div>
<div class="corner wall data"></div>
<div class="corner settings">settings...</div>
<div class="corner theming">theme</div>
<div class="discards"></div>
<div class="sidebar">
<div class="knowledge"></div>
</div>
<div class="windicator">
<div class="player-wind tc e">西</div>
<div class="player-wind rc">南</div>
<div class="player-wind bc">東</div>
<div class="player-wind lc">北</div>
<div class="wind-of-the-round">東 南 西 北</div>
<div class="hand-counter"></div>
</div>
<div class="hidden modal"></div>
</main>
<footer>
Written by <a target="_blank" nofollow href="https://mastodon.social/@TheRealPomax">Pomax</a>, project page
over on <a target="_blank" nofollow href="https://github.com/Pomax/mahjong">https://github.com/Pomax/mahjong</a>
<span class="fund">
[
<a target="_blank" nofollow href="https://github.com/Pomax/mahjong/issues">bugs/feedback</a>
] [
<a target="_blank" nofollow
href="https://www.paypal.com/donate/?cmd=_s-xclick&hosted_button_id=3BNHGHZAS3DP6&locale.x=en_CA">support this project</a>
]
</span>
</footer>
<section id="tutorial" style="display: none">
<h1><a href="#tutorial">This is a programming tutorial.</a></h1>
<p>
This is a fully functional implementation of the game of mahjong,
with competitive computer players, made for two reasons. The first
being that I like mahjong and I wanted to be able to play it on
a computer without having to install any software for that, and
the second being that it's a great example by which to explain how
to go about writing software that isn't trivial. We've all seen
tutorials for writing a "todo app", or "a single web page", but
those aren't things you'd actually want to write if you were to
make something: they're simple toy exercises to show off "how
easily you can make something". The problem is that tutorials
like this gloss over the fact that actually making something rather
than doing a simple exercise isn't easy. Writing software is hard.
</p>
<p>
And because it's hard, being told that it's easy also makes it
really easy to forget to plan for hardship: once you find something
you <em>actually</em> want to do, you soon discover that just
following the same methodology of that easy, toy implementation
makes you run into all kinds of obstacles, and if you're not
careful, or you don't have someone to help you get on track,
that thing you wanted to make just grinds to a halt before it
gets abandoned altogether. Tutorials don't teach you how to
plan for work you know is going to take more than a few days
to get done, so what if there was a tutorial that did? A
tutorial that covered the space <em>between</em> toy problems
and product programming as a day job? That's where this comes in.
</p>
<p style="display: none">
And not just because of the amount of code involved, it's also
because you need to understand how much you can break up the work,
when to recognise you're going too many different things, and
when to abandon something that just doens't seem to want to work
so that you can try a different approach. Programming is hard,
you're going to fail at <em>some</em> point, and it would be super
nice if there was a tutorial that didn't just tell you want to do,
but also told you what failure looks like, so you can spot it,
and maybe even avoid it.
</p>
<p>
The game you can play, above, took about two months to write in
my spare time, and I'm going to assume you'll be doing the same,
because you probably already have a job, or you're attending school,
and you don't have the luxury of spending 16 hours a day on personal
projects. So: if you want to learn to program something non-trivial,
and you understand that this means you'll probably be working on it
for a couple of weeks, or even a month or two: this might just be
the tutorial for you.
</p>
<p>
Of course, if you just came here to <em>play</em> mahjong, then
as an added bonus, you can! Keep reading for an explanation of
how mahjong works, and specifically which rule set and scoring
system this particular implementation of mahjong supports.
</p>
</section>
<section id="explanation">
<h1><a href="#explanation">The how of Mahjong</a></h1>
<p>
Mahjong is a four player tile game where players compete to form a
winning "hand" of 14 tiles, arranged (in general) as four triplets
(either consecutive tiles within the same suit, or genuine triplets with
the same tile three times), and a pair (one tile, twice).
</p>
<p>
The traditional game uses 144 tiles, consisting of three numbered suits,
seven types of "honour" tile split into "winds" and "dragons", and two
sets of bonus tiles. Specifically:
</p>
<ul>
<li>
<p>
Four sets of the numerical tiles 1 through 9 with "dots" ornamentation (traditionally called 筒, "coin"):
</p>
<div>
<dots-one></dots-one>
<dots-two></dots-two>
<dots-three></dots-three>
<dots-four></dots-four>
<dots-five></dots-five>
<dots-six></dots-six>
<dots-seven></dots-seven>
<dots-eight></dots-eight>
<dots-nine></dots-nine>
</div>
</li>
<li>
<p>
Four sets of the numerical tiles 1 through 9 with "bamboo"/"sticks" ornamentation (traditionally called 索, "coin string"):
</p>
<div>
<bamboo-one></bamboo-one>
<bamboo-two></bamboo-two>
<bamboo-three></bamboo-three>
<bamboo-four></bamboo-four>
<bamboo-five></bamboo-five>
<bamboo-six></bamboo-six>
<bamboo-seven></bamboo-seven>
<bamboo-eight></bamboo-eight>
<bamboo-nine></bamboo-nine>
</div>
</li>
<li>
<p>
Four sets of the numerical tiles 1 through 9 with "Chinese character" ornamentation (traditionally called 萬, "10,000"):
</p>
<div>
<characters-one></characters-one>
<characters-two></characters-two>
<characters-three></characters-three>
<characters-four></characters-four>
<characters-five></characters-five>
<characters-six></characters-six>
<characters-seven></characters-seven>
<characters-eight></characters-eight>
<characters-nine></characters-nine>
</div>
</li>
<li>
<p>
Four sets of the four cardinal "wind" directions:
</p>
<div>
<east-wind></east-wind>
<south-wind></south-wind>
<west-wind></west-wind>
<north-wind></north-wind>
</div>
</li>
<li>
<p>
Four sets of "dragon" tiles:
</p>
<div>
<green-dragon></green-dragon>
<red-dragon></red-dragon>
<white-dragon></white-dragon>
</div>
</li>
</ul>
<p>
With eight bonus tiles (not part of play - they are set aside
immediately when drawn, with a new tile drawn to replace it):
</p>
<ul>
<li>
<p>
The four seasons (spring, summer, fall, winter, or 春, 夏, 秋, 冬)
</p>
<div>
<season-one></season-one>
<season-two></season-two>
<season-three></season-three>
<season-four></season-four>
</div>
</li>
<li>
<p>
The four seasonal flowers (orchid, plum blossom, bamboo,
chrysanthenum, or 蘭, 梅, 竹, 菊)
</p>
<div>
<flower-one></flower-one>
<flower-two></flower-two>
<flower-three></flower-three>
<flower-four></flower-four>
</div>
</li>
</ul>
</section>
<section id="gameplay">
<h2><a href="#gameplay">Game play</a></h2>
<p>
Core game play is about as simple as it gets: everyone starts with 13
tiles, and players take turns in which they draw a tile from the set of
undealt tiles, check to see if they can form a winning pattern, and if
not, discard one of their tiles so they're back at 13, after which it
is the next player's turn.
</p>
<p>
The "actual game" part, rather than "random chance" aspect comes in the
form of "claims", where players may claim a tile as it is being
discarded if they can use that tile to form certain sets. In this case,
it immediately becomes their turn, irrespective of whose turn it
"should" have been, and rather than drawing a tile from the undealt
tiles, they take the discarded tile as their 14<sup>th</sup> tile.
</p>
</section>
<section id="rules">
<h2><a href="#rules">Rules around claiming discards</a></h2>
<ul>
<li>
if a player has two identical tiles in their hand, they may claim a
third tile when another player discards it. This is called a "pung".
</li>
<li>
if a player has three identical tiles in their hand, they may claim
the fourth tile when another player discards it. This is called a
"kong".
</li>
<li>
if a player is waiting for a specific tile to win, they may claim that
winning tile when another player discards it regardless of what the
tile is needed for and whether that would be a legal claim if the
player were not winning on it.
</li>
</ul>
<p>
If none of the above claims are made, then the player whose turn it is
next is allowed to pick up the discard tile rather than drawing a tile
from the undealt tiles, in order to form a set consisting of three
consecutive numbers within the same suit, called a "chow". Note that
these sets are typically worth no points, and will typically even
negatively impact the hand score by ruling out scores based on true-set
hand patterns.
</p>
<p>
In the case when a tile has claims by multiple players, a winning claims
trump any other claims, and a real claim trumps a player wishing to form
a set of consecutive tiles.
</p>
<p>
When honouring a claim, the receiving player <strong>must</strong>
reveal all tiles in the set they formed, and put those tiles aside,
face-up. These tiles are considered "locked", and can no longer be used
for anything, with a single exception:
</p>
<p>
If a player has a locked, true set of three tiles already on the table,
and they draw (not claim) the fourth tile, they are allowed to add that
tile to their locked set of three, forming a locked set of four. If a
player elects to do this, they receive a new tile from the undealt set
of tiles to compensate for the "loss" of a tile. If a player forgets to
do this, they they will not have enough tiles left to form a winning
hand, and may even be penalized for playing a hand with the wrong number
of tiles.
</p>
<h2 id="rule-clarification"><a href="#rule-clarification">Rule clarifications</a></h2>
<p>
A player is <em>not required</em> to declare sets if they do not need to
claim a tile for them. If a player has a set of three or four tiles in
their hand, due to having started the game with them, or having drawn
tiles over a number of turns to end up with them in hand, they are under
no obligation to reveal that fact. Winning with unrevealed tiles
typically incurs a bonus.
</p>
<p>
Note that there are special considerations when a player has four of the
same tile in their hand. They may declare this and lock the four tiles
as a set that is no longer in their hand, receiving an extra tile to
prevent them from having too few tiles to form a winning hand (revealing
the tiles depending on the exact rules being played. Some rules do not
require this kind of set to be revealed, meaning other players will not
know which tile has become unavailable, and so may try to win on a tile
that cannot ever appear during that round of play). They may also elect
to keep all four tiles in their hand so that three of the tiles can be
used as one set, and the fourth tile can be used to form a numerical
set. Note that because a winning hand has to consist of four sets and a
pair, a player who wishes to count all four tiles as a single set will
<em>have</em> to declare that set, or they will not have enough tiles
with which to form the necessary number of sets and pair.
</p>
</section>
<section id="scoring">
<h2><a href="#scoring">Scoring</a></h2>
<p>
Scoring of hands is entirely dependent on the style of mahjong played,
but can be split into two ways to count points:
</p>
<ul>
<li>
<em>points and doublings</em>, in which players gain points for
individual sets, and score doublings for particular combinations of
sets. Typically this scoring system has an "upper limit", limiting how
many points players can score per hand.
</li>
<li>
<em>faan</em>, in which player's hands are assigned a "hand score"
based on specific sets and patterns of sets, and this hand score is
then translated into a player score based on a rule-dependent mapping.
Typically this scoring system has both an upper limit, to fix how many
points players can score per hand, as well as a "lower limit", in
which getting a higher hand score does not alter the resulting player
score for several steps. E.g. a hand worth 5 or 6 faan may both score
as 5 faan, with a hand worth 7 faan assigned a higher score.
</li>
</ul>
<p>
Additionally, rules typically use one of two "score settling" methods:
</p>
<ul>
<li>
Winner takes all, in which the winner is paid their player score by
the other players. In this system, winning is more important than
forming high scoring hand patterns.
</li>
<li>
Everyone pays everyone, in which the winning player is paid their
score by the other players, and the other players all pay each other
based on the difference in scoring. In this system, forming high
scoring hand patterns is more important than just winning.
</li>
</ul>
</section>
<section id="this-game">
<h1><a href="#this-game">About this particular implementation</a></h1>
<p>
The game on this page can be played using two different rulesets:
Chinese Classical rules, and Cantonese rules.
</p>
<h2 id="chinese-classical-rules"><a href="#chinese-classical-rules">Chinese Classical (default)</a></h2>
<ul>
<li>scoring is based on points and doubles</li>
<li>players start with 2000 points</li>
<li>the losers settle their scores after paying the winner</li>
<li>East is paid double their winnings, and double-pays any losses</li>
<li>the deal does <em>not</em> pass to the next player if East wins.</li>
</ul>
<p>
Points are awarded to any player for the following (open/concealed):
</p>
<ul>
<li>each bonus tile: 4</li>
<li>pair of dragons: 2</li>
<li>pair of own winds: 2</li>
<li>pair of wind of the round: 2</li>
<li>pung of simples (numbers 2 through 8): 2/4</li>
<li>pung of terminals (numbers 1 and 9): 4/8</li>
<li>pung of winds: 4/8</li>
<li>pung of dragons: 4/8</li>
<li>kong of simples: 8/16</li>
<li>kong of terminals: 16/32</li>
<li>kong of winds: 16/32</li>
<li>kong of dragonss: 16/32</li>
</ul>
<p>
The following points are awarded to the winner only:
</p>
<ul>
<li>self-drawn win: 2</li>
<li>winning on a non-scoring pair: 2</li>
<li>winning on a scoring pair: 4</li>
</ul>
<p>
The following doubles are awarded to any player:
</p>
<ul>
<li>having your own flower and season: 1</li>
<li>having all flowers: +1</li>
<li>having all seasons: +1</li>
<li>pung/kong of own wind: 1</li>
<li>pung/kong of wind of the round: 1</li>
<li>pung/kong of dragons: 1</li>
</ul>
<p>
The following doubles are awarded to the winner only:
</p>
<ul>
<li>chow hand (four chows and a non-scoring pair): 1</li>
<li>one suit and honour (numbered tiles all from the same suit) : 1</li>
<li>one suit (only number tiles, all the same suit): 3</li>
<li>all pung hand: 1</li>
<li>fully concealed (and self drawn) hand: 1</li>
<li>(self-drawn) win on the last tile from the wall: 1</li>
<li>claimed win on the last discard: 1</li>
<li>winning by robbing a kong: 1</li>
</ul>
<p>
The following limit hands (scoring a flat 1000 points) exist:
</p>
<ul>
<li>fully concealed pung hand</li>
<li>all terminals: pungs and pairs of only 1 and 9 tiles</li>
<li>all honours: pungs and pairs of winds and dragons</li>
<li>all kong: four kongs and a pair</li>
<li>three great scholars: pung/kong of each dragon</li>
<li>little four winds: pung/kong of three winds, pair of last wind</li>
<li>big four winds: pung/kong of all winds</li>
<li>thirteen orphans: a 1 and 9 of each suit, one of each wind and dragon, and a pairing tile</li>
<li>nine gates: 1,1,1, 2,3,4,5,6,7,8, 9,9,9, all of the same suit, all concealed, and a pairing tile</li>
</ul>
<p>
The player winds rotate counter-clockwise, so that (E,S,W,N) become
(S,W,N,E), and the wind of the round rotates clockwise, starting at
East, then South, then West, then North.
</p>
<h2 id="cantonese-rules"><a href="#cantonese-rules">Cantonese</a></h2>
<ul>
<li>scoring is based on points ("faan") and tiered limits ("laak"), which are mapped to a real score</li>
<li>limit hands score at laak 2 (see table below)</li>
<li>players start with 500 points</li>
<li>only the winner scores points</li>
<li>winners are paid double by all players on a self-drawn win, otherwise only the discarding player pays double
</li>
</ul>
<p>
Points are awarded for for the following:
</p>
<ul>
<li>pung/kong of own wind: 1</li>
<li>pung/kong of wind of the round: 1</li>
<li>pung/kong of dragons: 1</li>
<li>having no flowers or seasons: 1</li>
<li>having your own flower: 1</li>
<li>having your own season: 1</li>
<li>having all flowers: 1 extra point</li>
<li>having all seasons: 1 extra point</li>
<li>self-drawn win: 1</li>
<li>chow hand (four chows and a non-scoring pair): 1</li>
<li>one suit and honour (numbered tiles all from the same suit) : 3</li>
<li>one suit (only number tiles, all the same suit): 6</li>
<li>all honours hand: 5</li>
<li>all pung hand: 3</li>
<li>fully concealed hand: 1</li>
<li>winning by robbing a kong: 1</li>
</ul>
<p>
The following limit hands exist:
</p>
<ul>
<li>all terminals: pungs and pairs of only 1 and 9 tiles</li>
<li>all honours: pungs and pairs of only winds and dragons</li>
<li>three great scholars: pung/kong of each dragon</li>
<li>little four winds: pung/kong of three winds, pair of last wind</li>
<li>big four winds: pung/kong of all winds</li>
<li>thirteen orphans: a 1 and 9 of each suit, one of each wind and dragon, and a pairing tile</li>
<li>nine gates: 1,1,1, 2,3,4,5,6,7,8, 9,9,9, all of the same suit, all concealed, and a pairing tile</li>
</ul>
<p>
The conversion table for turning faan into a hand score is:
</p>
<table>
<tr>
<th>faan</th>
<th>hand score</th>
</tr>
<tr>
<td>0</td>
<td>0.5</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>2</td>
<td>2</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
</tr>
<tr>
<td>4</td>
<td>8</td>
</tr>
<tr>
<td>5-6 (laak 1)</td>
<td>16</td>
</tr>
<tr>
<td>7+ (laak 2)</td>
<td>32</td>
</tr>
</table>
<p>
Because of the payment rules explained above, a hand score leads to
a total winnings of either 4x the points when winning off of a discard
(discarding player pays double, the other two players pay the base
score), or 6x the points when declaring a self-drawn win (all other
players pay double).
</p>
</section>
<section id="controls">
<h2><a href="#controls">Using the game interface</a></h2>
<p>
This game can be played with either a mouse or keyboard (or combination of the two).
The interface may also give you hints while playing:
</p>
<ul>
<li>When playing with suggestions turned on (<em>these are on by default</em>) any tile that you
can claim will be highlighted in green when someone else plays it.</li>
<li>When playing with bot recommendations turned on (<em>these are on by default</em>) any tile
that it recommends you claim will be highlighted in pink when someone else plays it.</li>
<li>When playing with bot recommendations turned on, recommended discards will be highlighted
with a single black bar at the top of the tile.</li>
<li>Regardless of suggestion settings, the tile you drew during your turn will be highlighted
using a green bar at the bottom of the tile, and your currently selected tile will have a
full green border.</li>
</ul>
<h3>Mouse controls</h3>
<p>
After receiving a new tile either from the wall or from claiming a set:
</p>
<ul>
<li>click any tile in your tilebank to discard that tile.</li>
<li>long-click to bring up the dialog to declare a kong or win.</li>
</ul>
<p>
While looking at discards from other players:
</p>
<ul>
<li>ignore this discard: click anywhere on the discard "table", except for the discarded tile.</li>
<li>claim discard: click the discarded tile.</li>
</ul>
<p>
While in a modal dialog, click any button to select that option.
</p>
<h3>Keyboard controls</h3>
<p>
After receiving a new tile, and after claiming a set:
</p>
<ul>
<li>cycle left through your tiles: left cursor key or "a" key</li>
<li>cycle right through your tiles: right cursor key or "d" key</li>
<li>jump to first discardable tile: home key</li>
<li>jump to last discardable tile: end key</li>
<li>discard highlighted tile: space, enter, up cursor key, or "w" key</li>
<li>declare a fully concealed, or melded kong with the currently highlighted tile: down cursor key or "s" key</li>
<li>declare a self-drawn win after dismissing the claim dialog: down cursor key or "s" key</li>
</ul>
<p>
While looking at discards from other players:
</p>
<ul>
<li>ignore this discard: left or right cursor key, or "a" or "d" key</li>
<li>claim discard: space, enter, up cursor key, or "w" key</li>
</ul>
<p>
While in a modal dialog:
</p>
<ul>
<li>move focus up one button: down cursor key, or "s" key</li>
<li>move focus up one button: up cursor key, or "w" key</li>
<li>select this button's option: space or enter.</li>
</ul>
</section>
</body>
</html>