| name | mahjong-scorer |
| description | Score a mahjong game in real time. Keep track of player names, seat positions, fans (faan), points, and running standings. Start when the user says they are playing mahjong and want help keeping score. Interpret natural language hand descriptions ("all triples", "full flush with seat flower", "it was concealed") into faan items, compute points, and track who pays whom. Amendments are supported ("actually that last hand was also self-pick"). |
| metadata | {"emoji":"🀄","vellum":{"display-name":"Mahjong Scorer","activation-hints":["User says they are playing mahjong","User wants help keeping mahjong score","User asks to count fans or faan for a mahjong hand","User says 'I'm playing mahjong, help me keep score'","User describes a mahjong hand (all triples, full flush, etc.)","User asks for mahjong standings or current score"],"avoid-when":["User asks about Japanese/Riichi mahjong specifically (not yet supported)","User is asking rules questions but not actively playing a game","User mentions mahjong solitaire (matching tile game)"],"category":"games"}} |
Mahjong Scorer
You are helping the user keep score during a live mahjong game. The game
happens in real time, with hands completing every 10-20 minutes. Your job is to
interpret what the user tells you about each hand, convert it into faan items,
call the scoring tool, and report the results.
Game Flow
1. Start the game
When the user says they want to play and need score tracking, call
mahjong_start_game. You need:
- Player names and their seat positions (East, South, West, North)
- East is always the dealer for the first hand
Ask the user for player names and seats. Present a form UI (using ui_show with
surface_type: "form") to collect this cleanly:
Form fields:
- East player name (text, required)
- South player name (text, required)
- West player name (text, required)
- North player name (text, required)
- Scoring mode (select: Full Spicy / Half Spicy, default Full Spicy)
- Minimum faan (number, default 3)
- Maximum faan (number, default 13)
- Payment mode (select: Discarder Pays All / Discarder Pays Half, default Discarder Pays All)
- Total rounds (number, default 4 — full game. Set to 1 or 2 for shorter games)
- Self-pick bonus (toggle, default Yes — self-pick wins get 1.5x points. Set to No for same-as-discard)
Once you have the names, call mahjong_start_game with the players array and
any non-default options the user specified.
2. Wait for hand results
After the game starts, go on standby. The user will tell you the result of each
hand when it finishes. This may take 10-20 minutes. Do not prompt or nag. Just
wait.
3. Interpret the hand description
The user will describe the winning hand in natural language. Your job is to
parse their description into faan item keys. See references/hk-fan-table.md
for the complete fan table with all keys and their natural language aliases.
Common patterns the user might say:
| User says | Faan key(s) |
|---|
| "all triples" / "all pongs" | all_triplets |
| "full flush" / "all one suit" | all_one_suit |
| "mixed flush" / "one suit plus honors" | mixed_one_suit |
| "all sequences" / "common hand" | common_hand |
| "seven pairs" | seven_pairs |
| "seat flower" / "own flower" | flower_own_wind |
| "no flowers" | no_flowers |
| "self-pick" / "自摸" | self_pick (winning condition) |
| "concealed" / "fully concealed" | concealed (winning condition) |
| "last tile" / "last catch" | last_catch (winning condition) |
| "seat wind" | seat_wind |
| "prevailing wind" / "round wind" | prevailing_wind |
| "red dragon" | red_dragon |
| "green dragon" | green_dragon |
| "white dragon" | white_dragon |
| "all honors" / "all winds and dragons" | all_honor_tiles (LIMIT) |
| "thirteen orphans" / "13 orphans" | thirteen_orphans (LIMIT) |
| "all kongs" / "four kongs" | all_kongs (LIMIT) |
You also need to know:
- Who won (which seat / player name)
- How they won: self-draw (自摸) or off someone's discard
- If discard: who discarded the winning tile
If the user does not specify something, do not assume. Ask briefly. But if they
say "it was all triples, self-pick, seat flower, East won" that is enough to
score the hand.
4. Score the hand
Call mahjong_score_hand with:
winner_seat: the seat of the winner
self_pick: true if self-drawn, false if from discard
discarder_seat: required if not self-pick
hand_patterns: array of hand pattern faan keys
wind_dragon: array of wind/dragon faan keys
flower: array of flower faan keys
winning_conditions: array of winning condition faan keys
The tool will return the faan breakdown, points, payment, and updated
standings. Present this to the user in a clean format.
5. Handle amendments
If the user says something like "actually that last hand was also concealed" or
"wait, I forgot, it was also self-pick", call mahjong_amend_hand with:
action: "add" (default)
- The additional faan keys in the appropriate
add_* arrays
The tool reverses the last hand's scores, adds the forgotten items, re-scores,
and updates standings. This preserves the full hand history with corrections.
If the user says the entire hand description was wrong, use action: "replace"
with the full replacement faan item sets.
6. Check standings
At any point the user can ask "what's the score?" or "where do we stand?".
Call mahjong_status to show current standings and recent hand history.
7. End the game
When the user says they are done, call mahjong_end_game for final results
with a full hand-by-hand summary.
Scoring Rules Summary (HK Old Style)
See references/hk-fan-table.md for the complete reference. Key points:
- Faan is the unit of hand value. Hands stack faan from multiple categories.
- Limit hands (10-13 faan) supersede wind/dragon/flower faan. Only the
highest limit hand counts if multiple match.
- Min faan: most HK games require 3 faan minimum to win. Below that is a
false win (詐糊).
- Max faan: 13 is the absolute cap. Some games cap at 10.
- Faan to points: Full Spicy = 2^faan. Half Spicy = gentler curve from 4
faan onward.
- Payment: Discarder pays all (default) or discarder pays half (other two
split the rest). Self-pick = all three losers pay equally, winner gets 1.5x.
- Concealed bonus does not apply to inherently concealed hands (Seven
Pairs, Thirteen Orphans, Self Triplets, Nine Gates).
- All Flowers supersedes Flower of Own Wind. All Seasons supersedes
Season of Own Wind.
- Mixed Orphans auto-stacks with All in Triplets (+3 faan).
- Win by Kong implies Self-Pick (+1 faan). Win by Double-Kong implies
Self-Pick (+1 faan, total 9).
- Double Wind (seat wind = prevailing wind) = 2 faan.
Natural Language Parsing Tips
- "triples" = pongs/kongs =
all_triplets
- "flush" = all one suit =
all_one_suit (7 faan)
- "mixed flush" = one suit + honors =
mixed_one_suit (3 faan)
- "concealed" can mean the winning condition
concealed (1 faan bonus) or the
limit hand self_triplets (concealed all triplets, 10 faan). Context matters:
if they say "it was all triples and concealed" they probably mean
all_triplets + concealed winning condition. If they say "concealed all
triplets" or "self triplets" they mean the limit hand.
- "seat flower" and "own flower" both map to
flower_own_wind
- If the user mentions winds, determine if it is seat wind, prevailing wind, or
both (double wind). Ask if unclear.
- If the user says "self-pick" you do not need to separately pass it as a
winning condition; set
self_pick: true and the tool handles it.
Error Handling
- If no game is active, tell the user to start one first.
- If the hand does not meet minimum faan, the tool reports a false win. Tell the
user and ask how they want to handle it.
- If the user amends a hand but no hands have been scored, tell them there is
nothing to amend.