The Round Services layer provides the computational engine for analyzing mahjong hands. It is responsible for calculating the distance to a winning hand (shanten), identifying valid winning tiles (waits), determining the value of a hand (yaku/fu/han), and calculating final points. This layer is stateless and operates on domain primitives like VisibleHand, Meld, and Tile.
The system uses a block-based decomposition approach to determine the shanten number and the "Goal" states (possible winning hand compositions).
The core logic resides in AnalyzeShanten, which utilizes a pruning Depth-First Search (DFS) to find the minimum number of swaps needed to reach a standard winning form (4 melds and 1 pair) internal/domain/game/round/service/shanten.go91-140
Goal Struct: Represents a potential winning hand. It contains the target blocks, the RequiredVector (tiles needed), and the ThrowableVector (tiles to discard) internal/domain/game/round/service/shanten.go15-27analyzeShantenInternal recursively adds triplets and sequences from a pre-calculated set of all possible melds (allMelds) internal/domain/game/round/service/shanten.go45-57 It prunes branches where the distance to the target meld exceeds the current best upper bound internal/domain/game/round/service/shanten.go144-218WaitsFor function determines which tiles would complete a hand by iterating through all 34 tile types, temporarily "drawing" each tile, and checking if it results in a winning form internal/domain/game/round/service/waits.go14-27The following diagram bridges the abstract mahjong concepts to the specific code entities used in the shanten service.
Shanten Analysis Data Flow
Sources: internal/domain/game/round/service/shanten.go15-67 internal/domain/game/round/service/shanten.go101-118
The CalculateFuHan function identifies the yaku present in a hand and calculates the corresponding Han and Fu values. It is important to note that this engine follows the original mjai-manue specification, which uses simplified Fu calculations internal/domain/game/round/service/yaku.go25-26
The engine supports standard yaku, identified by short internal keys in CalculateFuHan internal/domain/game/round/service/yaku.go51-63:
reach: Riichi internal/domain/game/round/service/yaku.go52tyc: Tanyao (All Simples) internal/domain/game/round/service/yaku.go53pf: Pinfu internal/domain/game/round/service/yaku.go55cty: Chantaiyao internal/domain/game/round/service/yaku.go54ykh: Yakuhai (Fanpai) internal/domain/game/round/service/yaku.go56ipk: Iipeikou internal/domain/game/round/service/yaku.go57ssj: Sanshoku Doujun internal/domain/game/round/service/yaku.go58ikt: Ikki Tsuukan internal/domain/game/round/service/yaku.go59tth: Toitoihou internal/domain/game/round/service/yaku.go60his/cis: Honitsu / Chinitsu internal/domain/game/round/service/yaku.go61-62dr/adr: Dora and Red Dora internal/domain/game/round/service/yaku.go71-76Yaku calculation relies on the decomposition of the hand into block.Block objects. Melds are converted to blocks via NewBlockFromMeld internal/domain/game/round/service/yaku.go37-40
Yaku Calculation Architecture
Sources: internal/domain/game/round/service/yaku.go36-94 internal/domain/game/round/service/block/convert.go7-20
The scoring service handles the final translation of Fu/Han into game points for both winning events and exhaustive draws.
The implementation uses a base point calculation fu * (1 << (han + 2)) for non-limit hands internal/domain/game/round/service/point.go17 Tests validate these against standard tables for both Dealer (Oya) and Non-Dealer (Ko) internal/domain/game/round/service/point_test.go18-43
WaitsFor: Returns a WaitSet (uint64 bitmask) representing all tiles that would complete the hand internal/domain/game/round/service/waits.go8-27Has1Han: A utility used to check if a potential winning hand meets the minimum 1-Han requirement (yaku) under current conditions (e.g., Riichi, Tsumo, or specific WinEvents like Robbing a Kan) internal/domain/game/round/service/yaku.go130-226Hand types provide transition methods for the service layer:
Draw(tile): Returns a new hand with the tile added internal/domain/game/round/service/waits.go18ToTileCounts34(): Converts a hand into a fixed-size array of tile counts for efficient calculation internal/domain/game/round/service/shanten.go101Sources:
Refresh this wiki