これは私の修士論文のプロジェクトです。強力な麻雀 AI の作成に興味のある方は、私のエージェントを延長することを歓迎します。適用されているアルゴリズムやそのアルゴリズムが使用されている理由の詳細については、電子メールで私に問い合わせてください。
独自の麻雀エージェントを開発したい場合、このリポジトリはリアルタイム テスト (実際の人間のプレイヤーによる) のフレームワークとしても使用できます。そうすれば、エージェントにとって最適な戦略を見つけるために費やす時間をすべて節約できます。さらに、開発ライブラリ (ゲーム ログのクロールと前処理、シャンティンと勝利スコアの計算など) が https://github.com/erreurt/MahjongKit で利用できるようになりました。
次のアップデートは近日公開予定です:
より優れた特徴エンジニアリングが必要です(条件ルールの代わりに) ランダム フォレストを使用して、メルドを呼び出すか Riichi を呼び出すかどうかを決定します。
進行中: LSTM を使用した待機タイル予測モデルのトレーニング。
注意: 同じ IP アドレスで 4 つ以上のアカウントを使用してボットを並行してテストすると、あなたの IP アドレスは tenhou.net によって 24 時間禁止されます。 (プレイヤーを禁止するルールについては正確には知りませんが、それはすべて私の観察から推測したものです。)
著者 | ジャンヤン・タン(トーマス) |
---|---|
電子メール | [email protected] |
麻雀は、情報が不完全な 4 人プレイの戦略ゲームです。インテリジェントな麻雀エージェントを開発する際の主な課題は、たとえば、複雑なゲーム ルール、広大な検索スペース、複数の対戦相手、不完全な情報などです。いくつかの既存の研究は、モンテカルロツリーシミュレーション、教師あり学習による効用関数フィッティング、または回帰アルゴリズムによる対戦相手モデルを通じて、これらの問題に取り組むことを試みています。それにもかかわらず、インテリジェントな麻雀プレーヤーのパフォーマンスは、人間の最高のプレーヤーのパフォーマンスにはまだ遠く及ばない。この作品では、麻雀ゲームの統計分析と人間の専門知識に基づいて、インテリジェントな麻雀エージェントが提案されています。最先端の研究の問題に取り組むために、ヒューリスティック技術と、多層パーセプトロンのバギングの採用によって達成された強化された対戦相手モデルがこの研究に適用されました。実験では、提案されたエージェントが最先端のエージェントよりも優れており、適用された対戦相手モデルがエージェントのパフォーマンスに大きなプラスの効果をもたらしていることが示されています。さらに、実験からいくつかの興味深い点が確認でき、将来の研究にとって非常に有意義です。
日本の理市麻雀のゲームルールについては、https://en.wikipedia.org/wiki/Japanese_Mahjong を参照してください。
実装されたクライアントにより、Web ブラウザで実行する代わりに、プログラムを通じて直接麻雀エージェントを実行できるようになります。日本理一麻雀のオンライン対戦サイトは http://tenhou.net/
左側は日本の理一麻雀卓の典型的な風景です。この画像は、デバッグ用に実装された GUI のスクリーンショットです。
提案された麻雀エージェントは tenhou.net でテストされました。テストは 2 つのバージョンで実行されました。1 つは防御モデルを使用し、もう 1 つは防御モデルを使用しませんでした。生のゲーム ログと中間ゲーム結果は、私の別のリポジトリ https://github.com/erreurt/Experiments-result-of-mahjong-bot にあります。実験は、 Experiment_ai.pyのエージェント バージョンを使用して実行されました。
守備モデルありのバージョンでは 526 試合がプレイされ、守備モデルなしのバージョンでは 532 試合がプレイされました。これは関連作品 2 つほどではありませんが、エージェントのパフォーマンスの収束挙動の図に示されているように、526 ゲームあれば十分です。
水上氏の拡張された業績は、現在、英語文献の中で最も優れた、最も信頼できる麻雀エージェントと見なすことができます。ここでは、私の麻雀エージェントのパフォーマンスと水上の麻雀エージェントのパフォーマンスの比較を示します。
[1] | [2] | [3] | [4] | |
---|---|---|---|---|
プレイしたゲーム | 526 | 532 | 2634 | 1441 |
1位率 | 23.95% | 22.65% | 24.10% | 25.30% |
2位率 | 26.62% | 25.92% | 28.10% | 24.80% |
3位率 | 31.75% | 25.71% | 24.80% | 25.10% |
4位率 | 17.68% | 25.71% | 23.00% | 24.80% |
勝率 | 24.68% | 26.50% | 24.50% | 25.60% |
損失率 | 13.92% | 20.21% | 13.10% | 14.80% |
固定レベル | 2.21ダン | 0.77ダン | 1.14ダン | 1.04ダン |
[1]防御モデル付き私の麻雀エージェント
[2]防御力のない私の麻雀エージェントモデル
[3]水上氏の拡張研究: 水上直樹、鶴岡裕也.モンテカルロシミュレーションと対戦相手モデルに基づいたコンピュータ麻雀プレイヤーの構築。参照: 2015 IEEE Conference on Computational Intelligence and Games (CIG)、275–283 ページ。 IEEE (2015)
[4]水上ら。アル。 : 水上直樹、中張隆也、浦亜紀、三輪正人、鶴岡裕也、近山哲也。分離されたマルチプレイヤーの側面を持つ教師あり学習により、4 人プレイヤーのコンピューター麻雀プログラムを実現します。情報処理学会論文集、vol. 55、いいえ。 11, pp. 1-11, 2014.
麻雀をプレイしている間、4 位に落ちることはレベル ポイントを減らされるため、絶対にタブーであることに注意してください。結果として、プレーヤーが 4 位に入る割合は、そのプレーヤーの全体的なパフォーマンスにとって非常に重要です。私のボットは、まさに 4 位率が低いため、より良い固定レベルを持っています。
Mahjong エージェントを実行するには、いくつかの構成を指定する必要があります。 main.py の次の例に示すように:
def run_example_ai ():
ai_module = importlib . import_module ( "agents.random_ai_example" )
ai_class = getattr ( ai_module , "RandomAI" )
ai_obj = ai_class () # [1]
player_module = importlib . import_module ( "client.mahjong_player" )
opponent_class = getattr ( player_module , "OpponentPlayer" ) # [2]
user = "ID696E3BCC-hLHNE8Wf" # [3]
user_name = "tst_tio" # [4]
game_type = '1' # [5]
logger_obj = Logger ( "log1" , user_name ) # [6]
connect_and_play ( ai_obj , opponent_class , user , user_name , '0' , game_type , logger_obj ) # play one game
def run_jianyang_ai ():
ai_module = importlib . import_module ( "agents.jianyang_ai" )
waiting_prediction_class = getattr ( ai_module , "EnsembleCLF" )
ensemble_clfs = waiting_prediction_class ()
ai_class = getattr ( ai_module , "MLAI" )
ai_obj = ai_class ( ensemble_clfs ) # [1]
opponent_class = getattr ( ai_module , "OppPlayer" ) # [2]
user = "ID696E3BCC-hLHNE8Wf" # [3]
user_name = "tst_tio" # [4]
game_type = '1' # [5]
logger_obj = Logger ( "log_jianyang_ai_1" , user_name ) # [6]
connect_and_play ( ai_obj , opponent_class , user , user_name , '0' , game_type , logger_obj )
AIインスタンス: 麻雀エージェントのクラスインスタンス。このリポジトリでは、麻雀エージェントの 3 つのバージョンが提供されています。最初のクラスは、 Agents.random_ai_example.pyにあります。これは、潜在的な開発者に独自のエージェントを実装する方法を示すためのデモ クラスです。 2 つ目はAgents.experiment_ai.pyにあり、パート 4 で示された実験結果はこの AI によって生成されます。 3 つ目は最新の AI で、 Agents.jianyang_ai.pyにあります。
相手プレイヤークラス: 相手プレイヤーのクラス。 client.mahjong_playerでデフォルトのクラス OpponentPlayer を使用できます。追加の必要性のために OpponentPlayer クラスを拡張した場合は、この変数を対応するクラスに設定する必要があります。
ユーザーID : tenhou.netに登録後に取得した例のような形式のトークン。注意: 自分のユーザー ID を使用してください。同じIDを別のIPアドレスで頻繁に使用すると、tenhou.netによりアカウントが一時的にブロックされます。
ユーザー名: tenhou.netへの登録時に作成した対応するユーザー名。この変数は、テスト ログを識別するためだけに使用されます。
ゲーム タイプ: ゲーム タイプは 8 ビット整数としてエンコードされます。以下に各ビットについて説明します。
例:
- Tenhou.net does not provide all possibility of the above specified combinations. Most online players play on configurations for example "1", "137", "193", "9"
Logger : ロガーを初期化するには 2 つのパラメータが必要です。 1 つ目はユーザー定義のロガー ID で、開発者がテスト履歴に自由に名前を付けることができます。
これらすべての設定を指定したら、これらすべてのパラメータをconnect_and_play()にスローするだけです。それなら、麻雀エージェントのショーを見る時間です!!!
Agents.ai_interfaceの「interface」クラスに示すように、麻雀ボットには 4 つの関数を実装する必要があります。エージェントは AIInterface の継承であることをお勧めします。これらの関数の詳しい説明と簡単な例については、 agents.random_ai_example.pyのドキュメントを参照してください。
class AIInterface ( MainPlayer ):
def to_discard_tile ( self ):
raise NotImplementedError
def should_call_kan ( self , tile136 , from_opponent ):
raise NotImplementedError
def try_to_call_meld ( self , tile136 , might_call_chi ):
raise NotImplementedError
def can_call_reach ( self ):
raise NotImplementedError
to_discard_tile : ゲーム状態に関するすべてのアクセス可能な情報に基づいて、この関数は破棄するタイルを返します。戻り値は 0 ~ 135 の範囲の整数です。麻雀ゲームには合計 136 個のタイルがあります。つまり、34 種類のタイルと各種類のコピーが 4 枚あります。さまざまな場合に、34 形式 (各数字が 1 種類のタイルに対応する) または 136 形式 (各数字が 1 つのタイルに対応する) のいずれかを使用します。ここでの戻り値は 136 形式である必要があることに注意してください。
should_call_kan : https://en.wikipedia.org/wiki/Japanese_Mahjong#Making_melds_by_calling.この関数は、エージェントが kan(Quad) メルドを呼び出す必要があるかどうかを決定する必要があります。 tile136 は、対戦相手が捨てたタイルを表し、エージェントがカン メルドを形成するために使用できます。 from_opponent は、エージェントが対戦相手の捨て札 (手札に 3 枚のタイルがあり、対戦相手が 4 枚目のタイルを捨てる) によってカン メルドを形成するか、自分のタイル (手札の 4 枚すべてのタイル) によってカン メルドを形成するかを示します。
try_to_call_meld : https://en.wikipedia.org/wiki/Japanese_Mahjong#Making_melds_by_calling。この関数は、エージェントが Pon(Triplet)/Chi(Sequence) メルドを呼び出す必要があるかどうかを決定します。 tile136 は、一部の対戦相手が捨てた 136 形式のタイルを表します。 might_call_chi は、エージェントがチャイ メルドをコールできるかどうかを示します。これは、チャイ メルドは左席の対戦相手を破棄する場合にのみコールできるためです。
can_call_reach : https://en.wikipedia.org/wiki/Japanese_Mahjong#R%C4%ABchi.この機能は、エージェントが Riichi を要求するかどうかを決定します。
麻雀エージェントクラスがAIInterfaceクラスのサブクラスである場合、指定に従ってエージェントクラス内で次の情報にアクセスできます。
アクセス | データ型 | 可変 | 説明 |
---|---|---|---|
self.tiles136 | 整数のリスト | Y | 136形の手牌 |
self.hand34 | 整数のリスト | N | 34 形式の手牌 (tile34 = tile136//4) |
self.discard136 | 整数のリスト | Y | 136-からのエージェントの破棄 |
self.discard34 | 整数のリスト | N | 34 形式のエージェントの破棄 |
self.meld136 | Meld インスタンスのリスト | Y | エージェントの呼び出されたメルド、 client.mahjong_meld.pyのクラス Meld のインスタンス |
self.total_melds34 | 整数のリストのリスト | N | 34 形式のエージェントのコールされたメルド |
self.meld34 | 整数のリストのリスト | N | エージェントの呼び出された pon/chow メルド (34 形式) |
self.pon34 | 整数のリストのリスト | N | エージェントの呼び出された pon メルド (34 形式) |
self.chow34 | 整数のリストのリスト | N | 34 形式のエージェントのコールされたチャウ メルド |
self.minkan34 | 整数のリストのリスト | N | エージェントの呼び出されたミンカン メルド (34 形式) |
self.ankan34 | 整数のリストのリスト | N | エージェントの 34 形式のアンカン メルドと呼ばれる |
自分の名前 | 弦 | Y | アカウントの名前 |
自己レベル | 弦 | Y | アカウントのレベル |
セルフシート | 整数 | Y | シート ID、エージェントは常に 0 を持ちます |
self.dealer_seat | 整数 | Y | ディーラーのシートID |
self.is_dealer | ブール値 | N | 代理店がディーラーかどうか |
self.reach_status | ブール値 | Y | エージェントが理一を主張したかどうかを示します |
self.just_reach() | ブール値 | N | エージェントが理一を主張したばかりかどうか |
self.tmp_rank | 整数 | Y | 現在のゲームにおけるエージェントのランク |
自己スコア | 整数 | Y | 現在のゲームでのエージェントのスコア |
self.is_open_hand | ブール値 | N | エージェントがすでにオープン メルドを呼び出しているかどうか |
self.turn_num | 整数 | N | 現在のターンの番号 |
self.player_wind | 整数 | N | プレイヤー風は役の一種です |
self.round_wind | 整数 | N | 丸い風は役の一種です |
self.bonus_honors | 整数のリスト | Y | 役を持つすべての文字タイル |
i を 1、2、3 に設定してself.game_table.get_player(i)を呼び出すことで、対戦相手クラスのインスタンスにアクセスできます。これは対戦相手の対応する ID を示します。
アクセス | データ型 | 可変 | 説明 |
---|---|---|---|
.discard136 | 整数のリスト | Y | 136-からの観察された相手の捨て札 |
.discard34 | 整数のリスト | N | 34 形式で観察された相手の捨て札 |
.meld136 | Meldインスタンスのリスト | Y | 観察された相手のコールされたメルド |
.total_melds34 | 整数のリストのリスト | N | 34 形式で観察された相手のコールされたメルド |
.meld34 | 整数のリストのリスト | N | 34 形式で観察された相手の呼び出されたポン/チョウ メルド |
.pon34 | 整数のリストのリスト | N | 34 形式で観察された相手の呼び出されたポン メルド |
.chow34 | 整数のリストのリスト | N | 34 形式で観察された相手のコールされたチャウ メルド |
.minkan34 | 整数のリストのリスト | N | 34 形式で観察された相手の呼び出されたミンカン メルド |
.ankan34 | 整数のリストのリスト | N | 呼び出されたアンカンは観察された相手を34の形で融合させる |
.safe_tiles | 整数のリスト | Y | エージェントにとって絶対に安全な 34 形式のタイル。つまり、観察された対戦相手はこれらのタイルでは勝つことができません。 |
。名前 | 弦 | Y | 相手の名前 |
。レベル | 弦 | Y | 相手のレベル |
。シート | 整数 | Y | 観測相手のシートID |
.dealer_seat | 整数 | Y | ディーラーのシートID |
.is_dealer | ブール値 | N | 観察した相手がディーラーかどうか |
.reach_status | ブール値 | Y | 観察された相手が利一を主張したかどうかを示します |
.just_reach() | ブール値 | N | 観察された相手が利一を主張したばかりかどうか |
.tmp_rank | 整数 | Y | 現在のゲームで観察されている対戦相手のランク |
。スコア | 整数 | Y | 現在のゲームで観察された対戦相手のスコア |
.is_open_hand | ブール値 | N | 観察された対戦相手がすでにオープン メルドをコールしているかどうか |
.turn_num | 整数 | N | 現在のターンの番号 |
.player_wind | 整数 | N | プレイヤー風は役の一種です |
.round_wind | 整数 | N | 丸い風は役の一種です |
.bonus_honors | 整数のリスト | Y | 役を持つすべての文字タイル |
ゲームテーブルに関する情報にアクセスするには、 self.game_tableを呼び出します。
アクセス | データ型 | 可変 | 説明 |
---|---|---|---|
.ボット | エージェントクラスのインスタンス | Y | エージェントのクラスインスタンス |
.get_player(i) | 相手クラスのインスタンス | Y | 対戦相手のクラスインスタンス、i=1,2,3 |
.dealer_seat | 整数 | Y | ディーラーのシートID |
.bonus_indicator | 整数のリスト | Y | 136形式のボーナスインジケーター |
.round_number | 整数 | Y | 概数 |
.reach_sticks | 整数 | Y | テーブルの上に理一の棒が何本ありますか。次に勝ったプレイヤーは、この理市棒のポイントをすべて受け取ります。 |
.honba_sticks | 整数 | Y | テーブルの上に本刃が何本あるか。プレイヤーは勝った場合、本場棒に応じて追加のポイントを獲得します。 |
.count_ramaining_tiles | 整数 | Y | 残りの未公開タイルの数 |
。明らかにした | 整数のリスト | Y | リストの各要素は、この特定のタイルのコピーが何枚公開されたかを示します (破棄、オープンメルド、ボーナスインジケーターなど)。 |
.round_win | 整数 | N | 丸い風は役の一種です |
.bonus_tiles | 整数のリスト | N | ボーナスタイルのリスト。手牌にボーナス牌が出現するたびに役としてカウントされます。 |
.last_discard | 整数 | N | 対戦相手の最新の捨て札、このタイルはルールにより絶対に安全です |
私の教授 Johannes Fürnkranz (ダルムシュタット工科大学知識工学グループ)
私の上司である Tobias Joppen (ダルムシュタット工科大学知識工学グループ)