Mahjong Code Reviewer
Reviews game code against Fujian Mahjong rules and the implementation plan.
When to Use
- •Review pull requests for game logic
- •Verify implementation matches rules
- •Check scoring calculations
- •Ensure features match the roadmap in FUTURE_FEATURES.md
- •Catch rule violations in code
- •Validate state management logic
Reference Documents
Always check code against these files:
- •
mahjong-fujian-rules.md— Authoritative game rules - •
app/FUTURE_FEATURES.md— Roadmap with planned features
Review Checklist
1. Implemented Features
Currently implemented (verify correctness):
- • Kong implementation (concealed, exposed, upgrade)
- • Kong scoring (+2 concealed, +1 exposed)
- • Golden Pair bonus (+30)
- • All One Suit bonus (+60)
- • Multi-round game with cumulative scoring
- • Dealer rotation and streak tracking
Example - Kong scoring:
// ✅ CORRECT - Kong scoring
score += concealedKongs * 2; // +2 per concealed Kong
score += exposedKongs * 1; // +1 per exposed Kong
// ✅ CORRECT - Golden Pair
if (hasGoldenPair(hand)) {
score += 30; // 2 Golds form the pair
}
2. Tile System
Correct tile counts:
- • 108 suit tiles (dots, bamboo, characters 1-9, 4 copies each)
- • 20 bonus tiles (16 winds, 4 red dragons)
- • Total: 128 tiles
Tile ID format:
// ✅ CORRECT 'dots_1', 'dots_9', 'bamboo_5', 'characters_3' 'wind_east', 'wind_south', 'wind_west', 'wind_north' 'dragon_red' (4 copies) // ❌ INCORRECT 'dot_1', 'Dots_1', 'DOTS_1', '1_dots' 'east', 'wind-east', 'windEast'
Gold tile:
- • Only suit tiles can be Gold (not bonus tiles)
- • Flipped tile removed from play (only 3 remain)
- • Gold type stored in game state
3. Hand Management
Tile counts:
// ✅ CORRECT // During play: 16 tiles player.concealedTiles.length + player.exposedMelds.length * 3 === 16 // After draw (before discard/win): 17 tiles player.concealedTiles.length + player.exposedMelds.length * 3 === 17
Exposed melds:
- • Chow, Pung, or Kong
- • Chow/Pung: 3 tiles, Kong: 4 tiles
- • Melds are visible to all players
4. Win Detection
Standard win:
- • 5 sets + 1 pair = 17 tiles
- • Sets are Chow (3 sequential) or Pung (3 identical)
- • Gold can substitute for any tile
Three Golds:
- • Checked after EVERY draw (normal, replacement, bonus)
- • Instant and automatic (cannot decline)
- • Triggers with exactly 3 Gold tiles
// ✅ CORRECT - Check Three Golds first
function checkWin(hand, goldTileType) {
if (hasThreeGolds(hand, goldTileType)) {
return { win: true, type: 'three_golds', instant: true };
}
// Then check standard win...
}
// ❌ INCORRECT - Missing Three Golds check
function checkWin(hand, goldTileType) {
return isValidWinningHand(hand); // Forgot Three Golds!
}
Gold substitution:
- • Gold can fill any position in Chow
- • Gold can fill any position in Pung
- • Gold can be part of pair
- • Multiple Golds can be used
5. Calling System
Priority order:
// ✅ CORRECT
const PRIORITY = { win: 3, kong: 2, pung: 1, chow: 0 };
Chow restriction:
- • Only from player to your LEFT
- • LEFT = previous player in turn order
- • Turn order is counter-clockwise
// ✅ CORRECT const canChow = (discarderIndex + 1) % 4 === playerIndex; // ❌ INCORRECT const canChow = (playerIndex + 1) % 4 === discarderIndex; // Wrong direction!
Gold restriction:
- • Gold CANNOT be used for calling
- • Only real tiles count for Chow/Pung eligibility
// ✅ CORRECT
function canPung(hand, tile, goldTileType) {
const realMatches = hand.filter(t => t === tile && t !== goldTileType);
return realMatches.length >= 2;
}
// ❌ INCORRECT
function canPung(hand, tile) {
return hand.filter(t => t === tile).length >= 2; // Counts Gold!
}
Manual pass required:
- • No auto-pass for players with no options
- • All players must click a button
- • Invalid options greyed out but visible
6. Scoring
Correct formula:
// ✅ CORRECT
let points = 1; // Base
points += bonusTiles.length; // +1 per bonus
points += goldsInHand; // +1 per Gold
points += concealedKongs * 2; // +2 per concealed Kong
points += exposedKongs * 1; // +1 per exposed Kong
if (isSelfDraw || isThreeGolds) {
points *= 2; // Self-draw multiplier
}
// Special bonuses (added after multiplier)
if (isThreeGolds) points += 20;
if (hasGoldenPair) points += 30;
if (isAllOneSuit) points += 60;
Common mistakes:
// ❌ INCORRECT - Multiplies special bonus
if (isSelfDraw) {
points = (points + threeGoldsBonus) * 2; // Wrong order!
}
// ❌ INCORRECT - Not yet implemented
if (noBonusTiles && noKongs) {
points += 10; // "No Bonus/Kong" not yet implemented
}
Payment:
- • All 3 losers pay the winner
- • Each pays the full point total
- • Regardless of who discarded winning tile
7. Game Flow
Turn order:
- • Counter-clockwise
- • Draw → (expose bonus) → Discard/Win
- • Taking discard skips draw
Bonus tile handling:
- • Auto-expose when drawn
- • Draw replacement from wall
- • Chain until non-bonus drawn
- • Check Three Golds after each replacement
Game end conditions:
- • Someone wins (standard or Three Golds)
- • Wall exhausted = draw game
- • Wall empty during replacement = draw game
8. Multiplayer/State Sync
Room state:
- • Room code generation
- • 4 players required
- • Host can select dealer
Game state sync:
- • All clients see same game state
- • Private hands only visible to owner
- • Exposed melds visible to all
- • Discard pile visible to all
Call handling:
- • Wait for all 4 players to respond
- • No timeout-based auto-pass
- • Resolve by priority after all respond
9. Common Bugs to Watch For
Off-by-one errors:
// ❌ Tile numbers should be 1-9, not 0-8
for (let i = 0; i < 9; i++) { tiles.push(`dots_${i}`); }
// ✅ CORRECT
for (let i = 1; i <= 9; i++) { tiles.push(`dots_${i}`); }
Player index wrapping:
// ❌ Can produce negative numbers const nextPlayer = (currentPlayer - 1) % 4; // ✅ CORRECT const nextPlayer = (currentPlayer + 3) % 4; // Counter-clockwise
Chow sequence validation:
// ❌ Doesn't check same suit
function isChow(t1, t2, t3) {
return t2 - t1 === 1 && t3 - t2 === 1;
}
// ✅ CORRECT
function isChow(t1, t2, t3) {
const [s1, n1] = parseTile(t1);
const [s2, n2] = parseTile(t2);
const [s3, n3] = parseTile(t3);
return s1 === s2 && s2 === s3 && n2 - n1 === 1 && n3 - n2 === 1;
}
Mutable state bugs:
// ❌ Mutates original array
function removeTile(hand, tile) {
const index = hand.indexOf(tile);
hand.splice(index, 1); // Mutates!
return hand;
}
// ✅ CORRECT
function removeTile(hand, tile) {
const index = hand.indexOf(tile);
return [...hand.slice(0, index), ...hand.slice(index + 1)];
}
Review Output Format
When reviewing code, output findings in this format:
## Code Review: [File/Feature Name] ### ✅ Correct - [What's implemented correctly] ### ⚠️ Warnings - [Potential issues or edge cases] ### ❌ Errors - [Rule violations or bugs] - **Rule**: [Which rule is violated] - **Expected**: [Correct behavior] - **Actual**: [What code does] - **Fix**: [Suggested fix] ### 🚫 Out of Scope - [Features that shouldn't be implemented yet]
Usage
To review code, provide:
- •The code to review
- •What feature it implements
- •Any specific concerns
This skill will check against rules and implementation plan.