The DecisionTreeDangerEstimator is responsible for calculating the hojuProb (deal-in probability) for a specific discard against a target opponent. It utilizes a pre-trained decision tree that evaluates the "danger" of a tile based on game features such as Suji, Kabe, and discard history.
Danger estimation in mjai-manue-go is driven by a binary decision tree. For every legal discard candidate, the AI evaluates the risk of dealing into any opponent who has declared Riichi or is otherwise suspected of being Tenpai.
The estimation process follows these steps:
SafeTiles (Genbutsu), the probability is immediately returned as 0 internal/domain/ai/danger.go36-38dangerScene is built, capturing the relevant context such as visible tiles, target's discards, and Riichi status internal/domain/ai/danger_scene.go27-60DecisionNode tree. At each node, it evaluates a specific feature (e.g., "is this tile Suji?") against the dangerScene internal/domain/ai/danger.go43-64The following diagram shows the relationship between the estimation logic and the data structures.
Danger Estimation Component Map
Sources: internal/domain/ai/danger.go11-30 internal/domain/ai/danger_scene.go14-25 internal/domain/ai/danger.go43-64
dangerScene)The dangerScene struct acts as a snapshot of the game state from the perspective of a specific "danger" evaluation (Self vs. Target). It encapsulates all data required by the feature DSL internal/domain/ai/danger_scene.go14-25
| Field | Description |
|---|---|
selfHand | Current tiles in the AI's hand, including the drawn tile internal/domain/ai/danger_scene.go30-35 |
safeTiles | Tiles that are safe against the target (Genbutsu) internal/domain/ai/danger_scene.go50 |
visibleTiles | All tiles visible on the board (discards, melds, Dora indicators) internal/domain/ai/danger_scene.go51 |
doras | Current Dora indicators internal/domain/ai/danger_scene.go52 |
preRiichiTiles | Tiles discarded by the target before declaring Riichi internal/domain/ai/danger_scene.go42 |
riichiDeclarationTiles | The specific tile discarded to declare Riichi internal/domain/ai/danger_scene.go43 |
Sources: internal/domain/ai/danger_scene.go14-60
The dangerScene.evaluate method implements a Domain Specific Language (DSL) used by the decision tree to query the state of a discard internal/domain/ai/danger_scene.go62-184
suji, weak_suji, reach_suji, chances<=n internal/domain/ai/danger_scene.go87-125urasuji, matagisuji, senkisuji, aida4ken internal/domain/ai/danger_scene.go95-117tsupai (honors), dora, fanpai (Yaku-giving honors) internal/domain/ai/danger_scene.go66-75visible>=n, suji_visible<=n internal/domain/ai/danger_scene.go132-139in_tehais>=n, suji_in_tehais>=n internal/domain/ai/danger_scene.go146-154_outer_prereach_sutehai and _inner_prereach_sutehai for distance-based discard analysis internal/domain/ai/danger_scene.go158-171The traversal logic in estimateDangerTreeProb determines the path through the tree:
Tree Traversal Logic
Sources: internal/domain/ai/danger.go43-64 internal/domain/ai/danger_scene.go62-118
The estimator relies on several helper functions in danger_tiles.go to perform Mahjong-specific calculations:
isSujiOf: Checks if the target tile is a Suji (e.g., 1 or 7 for a 4) relative to a set of tiles (usually safe tiles or Riichi discards) internal/domain/ai/danger_tiles.go63-78isNChanceOrLess: Implements "Kabe" (Wall) logic. It checks if the number of visible tiles that could form a specific sequence (Shuntsu) with the target is low enough (4-n) to make the sequence unlikely internal/domain/ai/danger_tiles.go92-114isAida4Ken: Checks for "Between 4-interval discards" (e.g., discarding 1 and 6, making 3 and 4 dangerous) internal/domain/ai/danger_tiles.go229-248fanpaiValue: Calculates how many Yaku points a honor tile is worth for the target (e.g., Guest Wind = 0, Round Wind = 1, Dragon = 1, Double Wind = 2) internal/domain/ai/danger_tiles.go32-48possibleSujis: Identifies potential Suji intervals that are not yet safe, used for calculating urasuji, matagisuji, and senkisuji internal/domain/ai/danger_tiles.go116-138The decision tree is stored as a JSON file and embedded into the binary.
DangerTreeNode interface allows the estimator to be decoupled from the specific JSON implementation internal/domain/ai/danger.go11-16DecisionTreeDangerEstimator is initialized with a root node that satisfies this interface internal/domain/ai/danger.go22-24ManueAgentDeps internal/domain/ai/deps.go10-13discard.RemoveRed() is called. The danger model treats red and black fives identically for deal-in probability internal/domain/ai/danger.go35state.SafeTiles(winner).ContainsSameSymbol(discard) is true, the function returns 0 immediately, bypassing the tree traversal internal/domain/ai/danger.go36-38Sources:
internal/domain/ai/danger.gointernal/domain/ai/danger_scene.gointernal/domain/ai/danger_tiles.gointernal/domain/ai/deps.gointernal/domain/ai/danger_test.go