riichi_engine 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +19 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
- data/.github/pull_request_template.md +15 -0
- data/.github/workflows/ci.yml +22 -0
- data/.github/workflows/release.yml +31 -0
- data/.gitignore +6 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +164 -0
- data/docs/adapter_contract.md +144 -0
- data/docs/api_reference.md +309 -0
- data/docs/concepts.md +134 -0
- data/docs/public_api_policy.md +94 -0
- data/docs/state_machine.md +360 -0
- data/docs/usage_examples.md +265 -0
- data/lib/mahjong/config/rule_config.rb +53 -0
- data/lib/mahjong/cpu/ai/base_ai.rb +50 -0
- data/lib/mahjong/cpu/ai/easy_ai.rb +11 -0
- data/lib/mahjong/cpu/ai/hard_ai.rb +11 -0
- data/lib/mahjong/cpu/ai/normal_ai.rb +11 -0
- data/lib/mahjong/cpu/analysis/shanten.rb +112 -0
- data/lib/mahjong/cpu/analysis/tile_evaluator.rb +54 -0
- data/lib/mahjong/cpu/judges/naki_judge.rb +60 -0
- data/lib/mahjong/cpu/judges/riichi_judge.rb +42 -0
- data/lib/mahjong/cpu/selectors/dahai_selector.rb +97 -0
- data/lib/mahjong/errors/engine_error.rb +5 -0
- data/lib/mahjong/errors/invalid_action_error.rb +5 -0
- data/lib/mahjong/errors/invalid_state_error.rb +5 -0
- data/lib/mahjong/errors/tile_not_found_error.rb +5 -0
- data/lib/mahjong/flow/detectors/naki_detector.rb +82 -0
- data/lib/mahjong/flow/games/game_flow.rb +186 -0
- data/lib/mahjong/flow/resolvers/action_resolver.rb +22 -0
- data/lib/mahjong/flow/rounds/round_flow.rb +588 -0
- data/lib/mahjong/flow/validators/action_validator.rb +110 -0
- data/lib/mahjong/results/final_result.rb +11 -0
- data/lib/mahjong/results/game_flow_result.rb +20 -0
- data/lib/mahjong/results/game_result.rb +26 -0
- data/lib/mahjong/results/round_end_info.rb +18 -0
- data/lib/mahjong/results/round_flow_result.rb +18 -0
- data/lib/mahjong/scoring/calculators/fu_calculator.rb +68 -0
- data/lib/mahjong/scoring/calculators/score_calculator.rb +73 -0
- data/lib/mahjong/scoring/evaluators/win_evaluator.rb +111 -0
- data/lib/mahjong/scoring/judges/agari_judge.rb +68 -0
- data/lib/mahjong/scoring/judges/furiten_judge.rb +34 -0
- data/lib/mahjong/scoring/judges/machi_judge.rb +78 -0
- data/lib/mahjong/scoring/judges/yaku_judge.rb +161 -0
- data/lib/mahjong/scoring/parsers/hand_parser.rb +87 -0
- data/lib/mahjong/scoring/value_objects/score_result.rb +57 -0
- data/lib/mahjong/scoring/value_objects/yaku_context.rb +70 -0
- data/lib/mahjong/scoring/value_objects/yaku_entry.rb +7 -0
- data/lib/mahjong/scoring/value_objects/yaku_judge_result.rb +27 -0
- data/lib/mahjong/scoring/yaku/chankan.rb +6 -0
- data/lib/mahjong/scoring/yaku/chanta.rb +15 -0
- data/lib/mahjong/scoring/yaku/chiihou.rb +7 -0
- data/lib/mahjong/scoring/yaku/chiitoitsu.rb +6 -0
- data/lib/mahjong/scoring/yaku/chinitsu.rb +10 -0
- data/lib/mahjong/scoring/yaku/chinroutou.rb +6 -0
- data/lib/mahjong/scoring/yaku/chuuren_poutou.rb +20 -0
- data/lib/mahjong/scoring/yaku/daisangen.rb +8 -0
- data/lib/mahjong/scoring/yaku/daisuushii.rb +8 -0
- data/lib/mahjong/scoring/yaku/double_riichi.rb +6 -0
- data/lib/mahjong/scoring/yaku/haitei.rb +6 -0
- data/lib/mahjong/scoring/yaku/honitsu.rb +11 -0
- data/lib/mahjong/scoring/yaku/honroutou.rb +9 -0
- data/lib/mahjong/scoring/yaku/houtei.rb +6 -0
- data/lib/mahjong/scoring/yaku/iipeiko.rb +16 -0
- data/lib/mahjong/scoring/yaku/ikkitsuukan.rb +15 -0
- data/lib/mahjong/scoring/yaku/ippatsu.rb +6 -0
- data/lib/mahjong/scoring/yaku/junchan.rb +15 -0
- data/lib/mahjong/scoring/yaku/kokushi_musou.rb +6 -0
- data/lib/mahjong/scoring/yaku/menzen_tsumo.rb +6 -0
- data/lib/mahjong/scoring/yaku/pinfu.rb +16 -0
- data/lib/mahjong/scoring/yaku/riichi.rb +6 -0
- data/lib/mahjong/scoring/yaku/rinshan_kaihou.rb +6 -0
- data/lib/mahjong/scoring/yaku/ryanpeiko.rb +11 -0
- data/lib/mahjong/scoring/yaku/ryuuiisou.rb +6 -0
- data/lib/mahjong/scoring/yaku/san_ankou.rb +15 -0
- data/lib/mahjong/scoring/yaku/san_kantsu.rb +6 -0
- data/lib/mahjong/scoring/yaku/sanshoku_doujun.rb +15 -0
- data/lib/mahjong/scoring/yaku/sanshoku_doukou.rb +14 -0
- data/lib/mahjong/scoring/yaku/shousangen.rb +14 -0
- data/lib/mahjong/scoring/yaku/shousuushii.rb +14 -0
- data/lib/mahjong/scoring/yaku/suuankou.rb +15 -0
- data/lib/mahjong/scoring/yaku/suukantsu.rb +6 -0
- data/lib/mahjong/scoring/yaku/tanyao.rb +7 -0
- data/lib/mahjong/scoring/yaku/tenhou.rb +7 -0
- data/lib/mahjong/scoring/yaku/toitoihou.rb +7 -0
- data/lib/mahjong/scoring/yaku/tsuuiisou.rb +6 -0
- data/lib/mahjong/scoring/yaku/yakuhai.rb +27 -0
- data/lib/mahjong/snapshots/final_result_snapshot.rb +8 -0
- data/lib/mahjong/snapshots/game_progress_snapshot.rb +12 -0
- data/lib/mahjong/snapshots/game_setup_snapshot.rb +12 -0
- data/lib/mahjong/snapshots/win_evaluation_snapshot.rb +11 -0
- data/lib/mahjong/state/fuuro.rb +45 -0
- data/lib/mahjong/state/hand.rb +64 -0
- data/lib/mahjong/state/kawa.rb +68 -0
- data/lib/mahjong/state/mentsu.rb +55 -0
- data/lib/mahjong/state/round_state.rb +193 -0
- data/lib/mahjong/tiles/dora.rb +19 -0
- data/lib/mahjong/tiles/tile.rb +168 -0
- data/lib/mahjong/tiles/tile_set.rb +51 -0
- data/lib/mahjong/tiles/wall.rb +47 -0
- data/lib/mahjong/tiles/wanpai.rb +79 -0
- data/lib/riichi_engine/api.rb +62 -0
- data/lib/riichi_engine/version.rb +3 -0
- data/lib/riichi_engine.rb +15 -0
- data/riichi_engine.gemspec +32 -0
- metadata +207 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0705360e35c7551b07930b9c6faf76514066be31cf6c3de5ec7ffd14ec911ef3
|
|
4
|
+
data.tar.gz: 7216b262185cc200da28ce090f93af3af5305ab44cc335a4492ec2950adc80a3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6403afe314adc11f52fb58650ef8cd5037d917643589b5f05a0b67b82d21a3283f04f39665ea6a3148aaf037e7e90e28210dc73f661385d88b09548c9ff41970
|
|
7
|
+
data.tar.gz: 7b9ae0f5aded16e00b8fd619c538146af2cad725adf1cfb94256c31e024fbcc3107324ee9ddb71eb6fc07794105a6c89f75d28f29733756258e9cb2d360467df
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Propose a new capability or API addition
|
|
4
|
+
title: "[Feature] "
|
|
5
|
+
labels: enhancement
|
|
6
|
+
assignees: ""
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
## Motivation
|
|
12
|
+
|
|
13
|
+
## Proposed API / Behavior
|
|
14
|
+
|
|
15
|
+
## Alternatives
|
|
16
|
+
|
|
17
|
+
## Notes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
## Changes
|
|
4
|
+
|
|
5
|
+
## Verification
|
|
6
|
+
|
|
7
|
+
- [ ] `bundle exec rspec` in `vendor/gems/riichi_engine`
|
|
8
|
+
- [ ] `bundle exec rspec` in host app
|
|
9
|
+
|
|
10
|
+
## Docs
|
|
11
|
+
|
|
12
|
+
- [ ] `README.md` updated if needed
|
|
13
|
+
- [ ] `docs/api_reference.md` updated if needed
|
|
14
|
+
- [ ] `docs/usage_examples.md` updated if needed
|
|
15
|
+
- [ ] `docs/public_api_policy.md` updated if needed
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- uses: ruby/setup-ruby@v1
|
|
17
|
+
with:
|
|
18
|
+
ruby-version: .ruby-version
|
|
19
|
+
bundler-cache: true
|
|
20
|
+
|
|
21
|
+
- name: Run specs
|
|
22
|
+
run: bundle exec rspec
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: ruby/setup-ruby@v1
|
|
18
|
+
with:
|
|
19
|
+
ruby-version: .ruby-version
|
|
20
|
+
bundler-cache: true
|
|
21
|
+
|
|
22
|
+
- name: Run specs
|
|
23
|
+
run: bundle exec rspec
|
|
24
|
+
|
|
25
|
+
- name: Build gem
|
|
26
|
+
run: gem build riichi_engine.gemspec
|
|
27
|
+
|
|
28
|
+
# gem push は手動で行う:
|
|
29
|
+
# gem push riichi_engine-*.gem
|
|
30
|
+
# 事前に RubyGems へのログイン(gem signin)または
|
|
31
|
+
# ~/.gem/credentials への API キー設定が必要。
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
4.0.2
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-04-05)
|
|
4
|
+
|
|
5
|
+
初回リリース
|
|
6
|
+
|
|
7
|
+
### 機能
|
|
8
|
+
|
|
9
|
+
- **局進行** — 自摸・打牌・リーチ・ポン・チー・カン(暗槓・加槓・大明槓)・九種九牌の処理
|
|
10
|
+
- **和了判定** — 通常和了形・七対子・国士無双に対応
|
|
11
|
+
- **役判定** — 一般役から役満まで全役対応(ダブル役満はルール設定で切り替え可)
|
|
12
|
+
- **点数計算** — 符・翻計算、基本点・各プレイヤーへの支払い点算出、本場加算
|
|
13
|
+
- **流局処理** — 荒牌流局・途中流局(四風連打・四家リーチ・三家和・四槓散了)、ノーテン罰符計算
|
|
14
|
+
- **ゲーム進行** — 連荘・親流れ・オーラス終了条件・トビ判定・西入対応
|
|
15
|
+
- **順位計算** — ウマ・オカを含む最終スコア算出
|
|
16
|
+
- **CPU AI** — Easy / Normal / Hard の3段階
|
|
17
|
+
- **状態の永続化** — `RoundState` の JSON シリアライズ・デシリアライズ
|
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HenrikStephensen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# riichi_engine
|
|
2
|
+
|
|
3
|
+
Pure Ruby 実装のリーチ麻雀エンジン Gem です。
|
|
4
|
+
|
|
5
|
+
局進行・和了判定・役判定・点数計算・順位計算を Rails・ActiveRecord 非依存で提供します。
|
|
6
|
+
host app から `RiichiEngine::API` を呼ぶだけで、麻雀の完全なドメインロジックを利用できます。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 機能
|
|
11
|
+
|
|
12
|
+
| カテゴリ | 内容 |
|
|
13
|
+
|---|---|
|
|
14
|
+
| 局進行管理 | 牌山構築・配牌・自摸・打牌・鳴き(ポン/チー/カン)・リーチ処理 |
|
|
15
|
+
| 和了判定 | 通常和了形・七対子・国士無双 |
|
|
16
|
+
| 役判定 | 一般役から役満まで全役対応 |
|
|
17
|
+
| 点数計算 | 符・翻の計算、基本点・各プレイヤーへの支払い点算出 |
|
|
18
|
+
| 順位計算 | ウマ・オカを含む最終スコア計算 |
|
|
19
|
+
| CPU AI | Easy / Normal / Hard の3段階(host app 側での切り替え対応) |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## ドキュメント
|
|
24
|
+
|
|
25
|
+
| ドキュメント | 内容 |
|
|
26
|
+
|---|---|
|
|
27
|
+
| [概念・用語集](docs/concepts.md) | 牌記法・麻雀用語・アクション種別の解説 |
|
|
28
|
+
| [使用例](docs/usage_examples.md) | 局開始〜終了までのコード例 |
|
|
29
|
+
| [局ステートマシン](docs/state_machine.md) | フェーズ遷移・アクション優先順位・特殊シーケンスの解説 |
|
|
30
|
+
| [API リファレンス](docs/api_reference.md) | 公開メソッドの詳細仕様 |
|
|
31
|
+
| [公開 API ポリシー](docs/public_api_policy.md) | 安定保証の範囲と方針 |
|
|
32
|
+
| [Adapter 契約](docs/adapter_contract.md) | host app と gem の責務境界 |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## インストール
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bundle add riichi_engine
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
または Gemfile に直接追加:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
gem "riichi_engine"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## クイックスタート
|
|
51
|
+
|
|
52
|
+
### 1. 局を開始する
|
|
53
|
+
|
|
54
|
+
`RuleConfig` を作成し、局の初期情報を `GameSetupSnapshot` に詰めて `setup_round` を呼びます。
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
rule = Mahjong::Config::RuleConfig.new # デフォルトルール(東南戦・25000点持ち等)
|
|
58
|
+
|
|
59
|
+
snapshot = Mahjong::Snapshots::GameSetupSnapshot.new(
|
|
60
|
+
bakaze: "ton", # 東場
|
|
61
|
+
kyoku: 1, # 1局目
|
|
62
|
+
honba: 0,
|
|
63
|
+
kyoutaku: 0,
|
|
64
|
+
oya_index: 0, # seat 0 が親
|
|
65
|
+
scores: { 0 => 25_000, 1 => 25_000, 2 => 25_000, 3 => 25_000 }
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
state = RiichiEngine::API.setup_round(
|
|
69
|
+
game_snapshot: snapshot,
|
|
70
|
+
rule: rule,
|
|
71
|
+
seed: "round-001" # 牌山シード(省略可)
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2. 自摸・打牌を進める
|
|
76
|
+
|
|
77
|
+
アクションを順に `apply_round_action` へ渡すことで局を進行させます。
|
|
78
|
+
選択可能なアクション一覧は `available_round_actions` で取得できます。
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
# 自摸
|
|
82
|
+
RiichiEngine::API.apply_round_action(
|
|
83
|
+
state: state,
|
|
84
|
+
action: { type: :tsumo },
|
|
85
|
+
rule: rule
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# 現在の手番プレイヤーが選べるアクションを取得
|
|
89
|
+
actions = RiichiEngine::API.available_round_actions(
|
|
90
|
+
state: state,
|
|
91
|
+
seat: state.current_seat,
|
|
92
|
+
rule: rule
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# 打牌アクションを選んで適用
|
|
96
|
+
dahai = actions.find { |a| a[:type] == :dahai }
|
|
97
|
+
|
|
98
|
+
result = RiichiEngine::API.apply_round_action(
|
|
99
|
+
state: state,
|
|
100
|
+
action: { type: :dahai, seat: state.current_seat, tile: dahai[:tile] },
|
|
101
|
+
rule: rule
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`result` は `Mahjong::Results::RoundFlowResult` です。
|
|
106
|
+
`result.round_end?` が `true` のとき局が終了しており、`result.round_end_info` に終了情報が入ります。
|
|
107
|
+
|
|
108
|
+
### 3. 局終了後に次局かゲーム終了かを判定する
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
flow_result = RiichiEngine::API.judge_next_game_round(
|
|
112
|
+
progress_snapshot: progress_snapshot,
|
|
113
|
+
round_end_info: result.round_end_info,
|
|
114
|
+
rule: rule
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# flow_result.next_round? => true なら次局へ
|
|
118
|
+
# flow_result.game_end? => true ならゲーム終了
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## エラー
|
|
124
|
+
|
|
125
|
+
エンジンが発生させる例外は2種類です。host app 側の adapter で rescue してください。
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
RiichiEngine::API::InvalidActionError # 不正なアクション(不正な打牌・存在しない鳴き等)
|
|
129
|
+
RiichiEngine::API::EngineError # エンジン内部エラー
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## ルール設定
|
|
135
|
+
|
|
136
|
+
`RuleConfig.new` にハッシュを渡すことで細かい設定が可能です。
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
rule = Mahjong::Config::RuleConfig.new(
|
|
140
|
+
"game_type" => "hanchan", # "hanchan"(東南戦)or "tonpuu"(東風戦)
|
|
141
|
+
"initial_score" => 25_000,
|
|
142
|
+
"uma" => [20_000, 10_000, -10_000, -20_000],
|
|
143
|
+
"kuitan" => true, # 食い断あり
|
|
144
|
+
"double_ron" => true, # ダブロン
|
|
145
|
+
"tobi" => true # トビあり
|
|
146
|
+
)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
設定項目の詳細は [API リファレンス](docs/api_reference.md#ruleconfigの設定項目) を参照してください。
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## テスト実行
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
bundle install --local
|
|
157
|
+
bundle exec rspec
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## ライセンス
|
|
163
|
+
|
|
164
|
+
MIT
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Adapter 契約
|
|
2
|
+
|
|
3
|
+
`riichi_engine` は pure Ruby のドメインロジックのみを持ち、副作用・永続化は一切持ちません。
|
|
4
|
+
host app 側の「adapter 層」がこの境界で両者をつなぐ役割を担います。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 責務の境界
|
|
9
|
+
|
|
10
|
+
### riichi_engine 側が持つもの
|
|
11
|
+
|
|
12
|
+
- 局進行ロジック
|
|
13
|
+
- 和了判定・役判定
|
|
14
|
+
- 点数計算・順位計算
|
|
15
|
+
- CPU AI の判断ロジック
|
|
16
|
+
|
|
17
|
+
### host app 側が持つもの(gem に持ち込まないもの)
|
|
18
|
+
|
|
19
|
+
- ActiveRecord
|
|
20
|
+
- ActionCable / Turbo
|
|
21
|
+
- ActiveJob
|
|
22
|
+
- キャッシュバックエンド
|
|
23
|
+
- ロガー
|
|
24
|
+
- リクエスト / セッション / ユーザーコンテキスト
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## host app 側 adapter の役割
|
|
29
|
+
|
|
30
|
+
host app には以下の adapter を実装します。各 adapter の責務を分けることで、gem のバージョンアップ時の修正箇所を局所化できます。
|
|
31
|
+
|
|
32
|
+
| Adapter | 責務 |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `MahjongAdapter::RuleBuilder` | DB / room 設定から `RuleConfig` を生成する |
|
|
35
|
+
| `MahjongAdapter::SnapshotBuilder` | ActiveRecord モデルから各種 Snapshot を生成する |
|
|
36
|
+
| `MahjongAdapter::RoundEndInfoBuilder` | 局終了情報を `RoundEndInfo` に正規化する |
|
|
37
|
+
| `MahjongAdapter::FinalResultBuilder` | 最終結果にプレイヤー表示情報を合成する |
|
|
38
|
+
| `MahjongAdapter::RoundStateCache` | `RoundState` のキャッシュ保存・復元・削除を管理する |
|
|
39
|
+
| `MahjongAdapter::CpuAiFactory` | CPU レベルに応じた AI インスタンスを選択する |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## SnapshotBuilder の契約
|
|
44
|
+
|
|
45
|
+
ActiveRecord オブジェクトを pure な Snapshot へ変換します。
|
|
46
|
+
|
|
47
|
+
**生成対象:**
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
Mahjong::Snapshots::GameSetupSnapshot # 局開始時
|
|
51
|
+
Mahjong::Snapshots::GameProgressSnapshot # 局終了後の進行判定時
|
|
52
|
+
Mahjong::Snapshots::FinalResultSnapshot # ゲーム終了時の最終結果
|
|
53
|
+
Mahjong::Snapshots::WinEvaluationSnapshot # 和了評価時
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**制約:**
|
|
57
|
+
|
|
58
|
+
- Snapshot の値に ActiveRecord オブジェクトを含めない
|
|
59
|
+
- key は seat 番号基準で統一する
|
|
60
|
+
- `score` や `bakaze` などの値はドメイン側がそのまま読める形にする
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## RuleBuilder の契約
|
|
65
|
+
|
|
66
|
+
host app の DB 設定や room/game 設定から `Mahjong::Config::RuleConfig` を作成します。
|
|
67
|
+
|
|
68
|
+
**制約:**
|
|
69
|
+
|
|
70
|
+
- `RuleConfig` に渡すのは plain Ruby の `Hash`(ActiveRecord モデルを渡さない)
|
|
71
|
+
- key の正規化は `RuleConfig` 側で行うため、adapter 側で意識しなくてよい
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
# OK: Hash で渡す
|
|
75
|
+
RuleConfig.new("kuitan" => room.rule_kuitan, "uma" => room.rule_uma)
|
|
76
|
+
|
|
77
|
+
# NG: モデルを直接渡す
|
|
78
|
+
RuleConfig.new(room)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## RoundStateCache の契約
|
|
84
|
+
|
|
85
|
+
`RoundState` のシリアライズ・デシリアライズとキャッシュキーを管理します。
|
|
86
|
+
|
|
87
|
+
**制約:**
|
|
88
|
+
|
|
89
|
+
- キャッシュバックエンドの詳細は adapter 側で隠蔽する
|
|
90
|
+
- gem 側へは `json` 文字列のみを渡す
|
|
91
|
+
- バージョン管理・キャッシュ失効(invalidation)は host app 側で担う
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
# 保存
|
|
95
|
+
json = state.to_json
|
|
96
|
+
RoundStateCache.save(round_id: round.id, json:)
|
|
97
|
+
|
|
98
|
+
# 復元
|
|
99
|
+
json = RoundStateCache.load(round_id: round.id)
|
|
100
|
+
state = RiichiEngine::API.deserialize_round_state(json)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## RoundEndInfoBuilder の契約
|
|
106
|
+
|
|
107
|
+
局終了時の host app 文脈の情報を `Mahjong::Results::RoundEndInfo` に正規化します。
|
|
108
|
+
|
|
109
|
+
**担当する処理:**
|
|
110
|
+
|
|
111
|
+
- 流局時のテンパイ座席の算出(`RiichiEngine::API.tenpai_seats` を呼ぶ)
|
|
112
|
+
- ノーテン罰符計算のための API 呼び出し
|
|
113
|
+
- 和了評価のためのスナップショット組み立て
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## FinalResultBuilder の契約
|
|
118
|
+
|
|
119
|
+
`RiichiEngine::API.calculate_final_results` が返す `FinalResult` に、host app 固有の表示情報を合成します。
|
|
120
|
+
|
|
121
|
+
**担当する情報:**
|
|
122
|
+
|
|
123
|
+
- `player_id`
|
|
124
|
+
- プレイヤー名
|
|
125
|
+
- 表示順・装飾に必要な補足情報
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Service / Job 層のルール
|
|
130
|
+
|
|
131
|
+
service / job が守るべきこと:
|
|
132
|
+
|
|
133
|
+
1. gem の呼び出しは `RiichiEngine::API` を経由する
|
|
134
|
+
2. API に渡すオブジェクトは adapter が生成したものだけにする
|
|
135
|
+
3. DB 保存・Job enqueue・broadcast は service / job 側で行う
|
|
136
|
+
4. gem の内部 namespace(`Mahjong::Flow::*` など)を直接 stub / mock しない
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
# OK: API 経由
|
|
140
|
+
result = RiichiEngine::API.apply_round_action(state:, action:, rule:)
|
|
141
|
+
|
|
142
|
+
# NG: 内部クラスを直接呼ぶ
|
|
143
|
+
result = Mahjong::Flow::Rounds::RoundFlow.apply_action(state:, action:, rule:)
|
|
144
|
+
```
|