這是我的碩士論文的一個專案。歡迎任何有興趣製作強大的麻將人工智慧的人擴展我的代理商。有關所應用演算法的更多詳細資訊以及使用此類演算法的原因,請透過電子郵件與我聯絡。
如果您想開發自己的麻將代理,這個 Repo 也可以用作即時測試的框架(與真人玩家一起)。然後您就可以節省所有時間來為您的代理商尋找最佳策略。此外,現在提供了一個開發庫(遊戲日誌的抓取和預處理、shantin和獲勝分數的計算等):https://github.com/erreurt/MahjongKit
下次更新即將推出:
需要更好的特徵工程決定是否使用隨機森林(而不是條件規則)來呼叫融合/呼叫 Riichi。
正在進行中:使用 LSTM 訓練等待磁磚預測模型。
注意:如果您在同一 IP 位址下使用超過 4 個帳戶並行測試您的機器人,您的 IP 位址將被 tenhou.net 禁止 24 小時。 (我不太清楚禁止玩家的具體規則,但這都是根據我的觀察推斷的。)
作者 | 湯建陽(托馬斯) |
---|---|
電子郵件 | [email protected] |
麻將是一種資訊不完全的四人策略遊戲。開發智慧麻將代理的主要挑戰是複雜的遊戲規則、巨大的搜尋空間、多個對手和不完美的資訊。一些現有的工作試圖透過蒙特卡羅樹模擬、監督學習的效用函數擬合或回歸演算法的對手模型來解決這些問題。儘管如此,智能麻將玩家的表現與最優秀的人類玩家仍然相差甚遠。基於對麻將遊戲的統計分析和人類專家知識,本工作提出了一種智慧麻將代理。為了解決最先進的工作中的問題,本工作應用了啟發式技術和採用多層感知器裝袋實現的增強對手模型。實驗表明,所提出的代理優於最先進的代理,並且所應用的對手模型對代理的性能具有顯著的積極影響。此外,從實驗中可以看出一些有趣的點,這對未來的工作非常有意義。
日本立直麻將的遊戲規則請參考 https://en.wikipedia.org/wiki/ Japanese_Mahjong 。
所實現的客戶端允許人們直接透過程式運行麻將代理,而不是在網頁瀏覽器中執行此操作。線上玩日本立直麻將的網址是http://tenhou.net/
左邊是日本立直麻將桌的典型場景。這張圖片是為調試使用而實現的 GUI 的螢幕截圖。
提議的 Mahjong 代理人在 tenhou.net 上進行了測試。此次測試分為兩種版本,一種有防禦模型,另一種則不帶防禦模型。原始遊戲日誌和中間遊戲結果可以在我的另一個儲存庫中找到:https://github.com/erreurt/Experiments-result-of-mahjong-bot。實驗是使用experiment_ai.py中的代理版本進行的。
有防禦模型的版本進行了526場比賽,無防禦模型的版本進行了532場比賽。這雖然沒有兩個相關作品那麼多,但從智能體表現收斂行為圖中可以看出,526 場遊戲就足夠了。
水上的延伸作品可視為目前英語文獻中最好、最可靠的麻將代理人。以下是我的麻將代理與 Mizukami 的麻將代理的性能比較:
[1] | [2] | [3] | [4] | |
---|---|---|---|---|
玩過的遊戲 | 第526章 | 第532章 | 2634 | 第1441章 |
第一名率 | 23.95% | 22.65% | 24.10% | 25.30% |
第二名率 | 26.62% | 25.92% | 28.10% | 24.80% |
第三名率 | 31.75% | 25.71% | 24.80% | 25.10% |
第四名率 | 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] Mizukami 的擴展工作:Mizukami N.,Tsuruoka Y..構建基於蒙特卡羅模擬和對手模型的計算機麻將機。請參閱:2015 年 IEEE 計算智能與遊戲會議 (CIG),第 275-283 頁。 IEEE(2015)
[4]水上等。等人。 :N. Mizukami、R. Nakahari、A. Ura、M. Miwa、Y. Tsuruoka 和 T. Chikayama。透過隔離多人方面的監督學習來實現四人電腦麻將程式。日本資訊處理協會學報,卷。 55,沒有。 11,第 1-11 頁,2014 年,(日文)。
注意,在打麻將時,掉到第四名絕對是大忌,因為會降低等級點數。因此,一名選手跌入第四名的幾率對其整體表現至關重要。由於第四名的比率較低,我的機器人具有更好的固定水準。
要執行 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實例:麻將代理程式的類別實例。在這個儲存庫中提供了三個版本的麻將代理程式。第一個位於agents.random_ai_example.py中,這是一個演示類,用於向潛在開發人員展示如何實現他/她自己的代理。第二個是在agents.experiment_ai.py中,第4部分給出的實驗結果是由這個AI產生的。第三個是最新的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"
記錄器:初始化記錄器需要兩個參數。第一個是使用者定義的記錄器ID,以便開發人員可以自由命名他/她的測試歷史記錄。
指定所有這些配置後,只需將所有這些參數丟給connect_and_play()即可。那麼是時候看你的麻將經紀人的表演了!
麻將機器人必須實作四個功能,如agents.ai_interface中的「interface」類別所示。建議您的代理商繼承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 格式(每個數字對應一種牌)或 136 格式(每個數字對應一種牌)。注意這裡的返回應該是136形式。
should_call_kan :https://en.wikipedia.org/wiki/ Japanese_Mahjong#Making_melds_by_calling。此函數應決定代理是否應呼叫 kan(Quad) 融合。 tile136代表某個對手已經棄掉的牌,可以用來讓智能體形成看牌組合。 from_opponent表示代理人是否透過對手的出牌(手中的三張牌,對手出第四張)或自己的牌(手中的所有四張牌)形成kan 組合。
try_to_call_meld :https://en.wikipedia.org/wiki/ Japanese_Mahjong#Making_melds_by_calling。此函數決定代理程式是否應呼叫 Pon(Triplet)/Chi(Sequence) 融合。 tile136代表一些對手已經打掉的 136 式牌。 might_call_chi指示代理是否可以調用 Chi 融合,因為 Chi 融合只能在左側座位上的對手棄牌的情況下調用。
can_call_reach :https://en.wikipedia.org/wiki/ Japanese_Mahjong#R%C4%ABchi。該函數決定特工是否應該認領立直。
當麻將代理類別是AIInterface類別的子類別時,可以依照指定在代理類別內部存取下列資訊。
使用權 | 資料類型 | 可變的 | 描述 |
---|---|---|---|
自我磁磚136 | 整數列表 | 是 | 136 型手牌 |
自手34 | 整數列表 | 氮 | 34 格式的手牌 (tile34 =tile136//4) |
自我丟棄136 | 整數列表 | 是 | 136-from 中代理的丟棄 |
自我丟棄34 | 整數列表 | 氮 | 34 格式的代理程式丟棄 |
自我融合136 | Meld 實例列表 | 是 | 代理的被呼叫的融合, client.mahjong_meld.py中 Meld 類別的實例 |
self.total_melds34 | 整數列表的列表 | 氮 | 34 種形式的代理的呼叫組合 |
自我融合34 | 整數列表的列表 | 氮 | 被稱為 pon/chow 的代理的 34 形式融合 |
自我pon34 | 整數列表的列表 | 氮 | 被稱為 pon 的 34 形式的代理融合體 |
自我chow34 | 整數列表的列表 | 氮 | 被稱為 34 形式的代理的 chow 融合 |
自我民看34 | 整數列表的列表 | 氮 | 被稱為 34 種形式的代理的 minkan 融合 |
自我.ankan34 | 整數列表的列表 | 氮 | 被稱為 34 種形式的代理的 ankan 融合 |
自己的名字 | 細繩 | 是 | 帳戶名稱 |
自我水平 | 細繩 | 是 | 帳戶等級 |
自坐 | 整數 | 是 | 座席ID,座席始終為0 |
self.dealer_seat | 整數 | 是 | 莊家席位ID |
self.is_dealer | 布林值 | 氮 | 代理商是否為經銷商 |
self.reach_status | 布林值 | 是 | 表示代理人是否已認領立直 |
self.just_reach() | 布林值 | 氮 | 代理人是否剛聲稱立直 |
self.tmp_rank | 整數 | 是 | 代理在當前遊戲中的排名 |
自我評分 | 整數 | 是 | 目前遊戲中代理的得分 |
self.is_open_hand | 布林值 | 氮 | 代理是否已經呼叫開放融合 |
self.turn_num | 整數 | 氮 | 當前匝數 |
self.player_wind | 整數 | 氮 | 玩家風是一種yaku |
self.round_wind | 整數 | 氮 | 圓風是屋久的一種 |
自我獎勵榮譽 | 整數列表 | 是 | 所有有yaku的角色牌 |
透過呼叫self.game_table.get_player(i)可以存取對手類別的實例,i 等於 1,2,3,表示對手對應的 id。
使用權 | 資料類型 | 可變的 | 描述 |
---|---|---|---|
.discard136 | 整數列表 | 是 | 136-from 中觀察到的對手的棄牌 |
.discard34 | 整數列表 | 氮 | 觀察對手的 34 式棄牌 |
.meld136 | Meld實例列表 | 是 | 觀察到的對手的叫牌組合 |
.total_melds34 | 整數列表的列表 | 氮 | 觀察到的對手的 34 式組合 |
.meld34 | 整數列表的列表 | 氮 | 觀察對手的 34 式被叫 pon/chow 融合 |
.pon34 | 整數列表的列表 | 氮 | 所觀察到的對手的 pon 融合以 34 式表示 |
.chow34 | 整數列表的列表 | 氮 | 觀察到的對手的叫牌組合為 34 式 |
.minkan34 | 整數列表的列表 | 氮 | 觀察到的對手的稱為 34 式的 minkan 融合 |
.ankan34 | 整數列表的列表 | 氮 | 觀察到的對手的稱為 34 式的 ankan 融合 |
.safe_tiles | 整數列表 | 是 | 34 種形式的牌對代理商來說絕對安全,即觀察到的對手無法用這些牌獲勝 |
。 | 細繩 | 是 | 對手的名字 |
。 | 細繩 | 是 | 對手的水平 |
。 | 整數 | 是 | 觀察對手的座位ID |
.dealer_seat | 整數 | 是 | 莊家席位ID |
.is_dealer | 布林值 | 氮 | 觀察到的對手是否為莊家 |
.reach_status | 布林值 | 是 | 指示觀察到的對手是否已認領立直 |
.just_reach() | 布林值 | 氮 | 觀察到的對手是否剛聲稱立直 |
.tmp_rank | 整數 | 是 | 在目前遊戲中觀察到的對手的排名 |
。 | 整數 | 是 | 在目前比賽中觀察到的對手的得分 |
.is_open_hand | 布林值 | 氮 | 觀察到的對手是否已經叫了開放組合 |
.turn_num | 整數 | 氮 | 當前匝數 |
.player_wind | 整數 | 氮 | 玩家風是一種yaku |
.round_wind | 整數 | 氮 | 圓風是屋久的一種 |
.bonus_honors | 整數列表 | 是 | 所有有yaku的角色牌 |
要存取有關遊戲桌的信息,可以呼叫self.game_table
使用權 | 資料類型 | 可變的 | 描述 |
---|---|---|---|
.bot | 代理類別的實例 | 是 | 代理的類別實例 |
.get_player(i) | 對手類別的實例 | 是 | 對手的類別實例,i=1,2,3 |
.dealer_seat | 整數 | 是 | 經銷商席位ID |
.bonus_indicator | 整數列表 | 是 | 136 形式的獎金指標 |
.round_number | 整數 | 是 | 整數 |
.reach_sticks | 整數 | 是 | 桌上有多少根立直棒。接下來獲勝的玩家將獲得這些立直棒的所有積分 |
.honba_sticks | 整數 | 是 | 桌上有多少根本巴棍。如果獲勝,玩家將根據本巴棒獲得額外積分 |
.count_ramaining_tiles | 整數 | 是 | 剩餘未揭開的磁磚數量 |
.透露 | 整數列表 | 是 | 清單中的每個元素指示已顯示該特定圖塊的數量(棄牌、開放組合、獎勵指示器等) |
.round_win | 整數 | 氮 | 圓風是屋久的一種 |
.bonus_tiles | 整數列表 | 氮 | 獎勵圖塊列表。手牌中每次出現獎勵牌都算是一個yaku |
.last_discard | 整數 | 氮 | 對手最新出的牌,依照規則絕對安全 |
我的 Johannes Fürnkranz 教授(達姆施塔特工業大學知識工程小組)
我的導師 Tobias Joppen(達姆施塔特工業大學知識工程組)