Dies ist ein Projekt für meine Masterarbeit. Jeder, der daran interessiert ist, leistungsstarke Mahjong-KI zu entwickeln, ist herzlich willkommen, meinen Agenten zu erweitern. Für weitere Einzelheiten zu den verwendeten Algorithmen und den Gründen, warum solche Algorithmen verwendet werden, kontaktieren Sie mich bitte per E-Mail.
Für den Fall, dass Sie Ihren eigenen Mahjong-Agenten entwickeln möchten, kann dieses Repo auch als Framework für Echtzeittests ( mit echten menschlichen Spielern ) verwendet werden. Dann können Sie Ihre ganze Zeit sparen, um die beste Strategie für Ihren Agenten zu finden. Darüber hinaus ist jetzt eine Entwicklungsbibliothek (Crawling und Vorverarbeitung von Spielprotokollen, Berechnung von Shantin und Gewinnpunktzahl usw.) verfügbar unter: https://github.com/erreurt/MahjongKit
Das nächste Update folgt bald :
Besseres Feature-Engineering erforderlich. Entscheiden Sie, ob ein Meld/Call-Riichi aufgerufen werden soll oder nicht, indem Sie Random Forest (anstelle von Bedingungsregeln) verwenden.
Laufend : Training des Vorhersagemodells für wartende Kacheln mit LSTM.
Achtung : Wenn Sie Ihren Bot parallel mit mehr als 4 Konten unter derselben IP-Adresse testen, wird Ihre IP-Adresse von tenhou.net für 24 Stunden gesperrt. (Ich kenne die Regeln für das Sperren von Spielern nicht genau, aber das ergibt sich alles aus meiner Beobachtung.)
Autor | Jianyang Tang (Thomas) |
---|---|
[email protected] |
Mahjong ist ein Strategiespiel für vier Spieler mit unvollständigen Informationen. Die größten Herausforderungen bei der Entwicklung eines intelligenten Mahjong-Agenten sind beispielsweise die komplizierten Spielregeln, der riesige Suchraum, mehrere Gegner und unvollständige Informationen. Mehrere bestehende Arbeiten haben versucht, diese Probleme durch Monte-Carlo-Baumsimulation, Nutzenfunktionsanpassung durch überwachtes Lernen oder Gegnermodell durch Regressionsalgorithmen anzugehen. Dennoch ist die Leistung intelligenter Mahjong-Spielagenten noch weit von der der besten menschlichen Spieler entfernt. Basierend auf statistischen Analysen für das Mahjong-Spiel und menschlichem Expertenwissen wurde in dieser Arbeit ein intelligenter Mahjong-Agent vorgeschlagen. Um die Probleme der hochmodernen Arbeit anzugehen, wurden in dieser Arbeit heuristische Technologien und ein verbessertes Gegnermodell angewendet, das durch die Einführung des Einsackens von mehrschichtigen Perzeptronen erreicht wurde. Die Experimente zeigen, dass der vorgeschlagene Agent den modernen Agenten übertrifft und das angewandte Gegnermodell einen deutlich positiven Einfluss auf die Leistung des Agenten hat. Darüber hinaus lassen sich aus den Experimenten einige interessante Punkte erkennen, die für zukünftige Arbeiten von großer Bedeutung sind.
Die Spielregeln für japanisches Riichi Mahjong finden Sie unter https://en.wikipedia.org/wiki/Japanese_Mahjong.
Der implementierte Client ermöglicht es, einen Mahjong-Agenten direkt über das Programm auszuführen, anstatt dies im Webbrowser zu tun. Die Website zum Online-Spielen von japanischem Riichi Mahjong ist http://tenhou.net/
Die linke Seite zeigt eine typische Szenerie des japanischen Riichi-Mahjong-Tisches. Dieses Bild ist ein Screenshot der für Debugzwecke implementierten GUI.
Der vorgeschlagene Mahjong-Agent wurde auf tenhou.net getestet. Der Test wurde in zwei Versionen durchgeführt, einmal mit Verteidigungsmodell und einmal ohne. Die rohen Spielprotokolle und Zwischenergebnisse des Spiels finden Sie in meinem anderen Repository: https://github.com/erreurt/Experiments-result-of-mahjong-bot. Die Experimente wurden mit der Agent-Version in experiment_ai.py durchgeführt.
Für die Version mit Verteidigungsmodell wurden 526 Spiele gespielt, für die Version ohne Verteidigungsmodell wurden 532 Spiele gespielt. Das ist nicht so viel wie bei zwei verwandten Arbeiten, aber wie die Abbildung des Konvergenzverhaltens der Agentenleistung zeigt, sind 526 Spiele ausreichend.
Mizukamis umfangreiches Werk kann als das derzeit beste und zuverlässigste Mahjong-Mittel in der englischen Literatur angesehen werden. Hier ein Vergleich zwischen der Leistung meines Mahjong-Agenten und der von Mizukami:
[1] | [2] | [3] | [4] | |
---|---|---|---|---|
Gespielte Spiele | 526 | 532 | 2634 | 1441 |
1. Platz-Rate | 23,95 % | 22,65 % | 24,10 % | 25,30 % |
2. Platz-Rate | 26,62 % | 25,92 % | 28,10 % | 24,80 % |
Preis für den 3. Platz | 31,75 % | 25,71 % | 24,80 % | 25,10 % |
4. Platz | 17,68 % | 25,71 % | 23,00 % | 24,80 % |
Gewinnrate | 24,68 % | 26,50 % | 24,50 % | 25,60 % |
Rate verlieren | 13,92 % | 20,21 % | 13,10 % | 14,80 % |
festes Niveau | 2.21 Dan | 0,77 Dan | 1.14 Dan | 1.04 Dan |
[1] Mein Mahjong-Agent mit Verteidigungsmodell
[2] Mein Mahjong-Agent ohne Verteidigungsmodell
[3] Mizukamis erweiterte Arbeit : Mizukami N., Tsuruoka Y.. Aufbau eines Computer-Mahjong-Spielers basierend auf Monte-Carlo-Simulation und Gegnermodellen. In: 2015 IEEE Conference on Computational Intelligence and Games (CIG), S. 275–283. IEEE (2015)
[4] Mizukami et. al. : N. Mizukami, R. Nakahari, A. Ura, M. Miwa, Y. Tsuruoka und T. Chikayama. Realisierung eines Computer-Mahjong-Programms für vier Spieler durch überwachtes Lernen mit isolierten Mehrspieler-Aspekten. Transaktionen der Information Processing Society of Japan, vol. 55, nein. 11, S. 1–11, 2014, (auf Japanisch).
Beachten Sie, dass es beim Spielen von Mahjong definitiv tabu ist, auf den 4. Platz zu fallen, da die Levelpunkte reduziert würden. Daher ist die Häufigkeit, mit der ein Spieler auf den 4. Platz zurückfällt, entscheidend für seine Gesamtleistung. Mein Bot hat gerade aufgrund der niedrigen 4.-Platz-Rate ein besseres Fixlevel.
Um den Mahjong-Agenten auszuführen, müssen einige Konfigurationen angegeben werden. Wie im folgenden Beispiel aus main.py gezeigt:
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 )
KI-Instanz : Eine Klasseninstanz des Mahjong-Agenten. In diesem Repository werden drei Versionen des Mahjong-Agenten bereitgestellt. Die erste befindet sich in Agents.random_ai_example.py . Hierbei handelt es sich um eine Demoklasse, die potenziellen Entwicklern zeigt, wie sie ihre eigenen Agenten implementieren. Der zweite befindet sich in Agents.experiment_ai.py und die in Teil 4 angegebenen Experimentergebnisse werden von dieser KI generiert. Der dritte ist die aktuelle KI und befindet sich in Agents.jianyang_ai.py .
Klasse des gegnerischen Spielers : Die Klasse des gegnerischen Spielers. Man kann die Standardklasse OpponentPlayer in client.mahjong_player verwenden. Wenn man die OpponentPlayer-Klasse aufgrund zusätzlicher Anforderungen erweitert hat, sollte diese Variable auf die entsprechende Klasse gesetzt werden.
Benutzer-ID : Ein Token in der Form wie im Beispiel gezeigt, das man nach der Registrierung auf tenhou.net erhalten hat. ACHTUNG: Bitte verwenden Sie Ihre eigene Benutzerkennung. Wenn dieselbe ID zu oft unter verschiedenen IP-Adressen verwendet wird, wird das Konto von tenhou.net vorübergehend gesperrt.
Benutzername : Der entsprechende Benutzername, den Sie bei der Registrierung auf tenhou.net erstellt haben. Diese Variable dient nur zur Identifizierung Ihrer Testprotokolle.
Spieltyp : Der Spieltyp wird als 8-Bit-Ganzzahl codiert. Im Folgenden finden Sie die Beschreibung für jedes Bit.
Zum Beispiel:
- 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 : Zur Initialisierung des Loggers sind zwei Parameter erforderlich. Die erste ist die ID des benutzerdefinierten Loggers, sodass Entwickler seinen Testverlauf frei benennen können.
Nachdem Sie alle diese Konfigurationen angegeben haben, übergeben Sie einfach alle diese Parameter an connect_and_play() . Dann ist es Zeit, sich die Show Ihres Mahjong-Agenten anzusehen!!!
Für den Mahjong-Bot müssen vier Funktionen implementiert werden, wie in der Klasse „interface“ in Agents.ai_interface gezeigt. Es wird empfohlen, dass Ihr Agent eine Vererbung des AIInterface ist. Eine ausführlichere Erklärung und ein einfaches Beispiel dieser Funktionen finden Sie in der Dokumentation in 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 : Basierend auf allen verfügbaren Informationen über den Spielstatus gibt diese Funktion eine Kachel zum Verwerfen zurück. Die Rückgabe ist eine Ganzzahl im Bereich von 0-135. Es gibt insgesamt 136 Spielsteine im Mahjong-Spiel, also 34 Arten von Spielsteinen und 4 Kopien für jede Art. In verschiedenen Fällen verwenden wir entweder die 34er-Form (jede Zahl entspricht einer Art Kachel) oder die 136er-Form (jede Zahl entspricht einer Kachel). Beachten Sie, dass die Rückgabe hier in der 136-Form erfolgen sollte.
Should_call_kan : https://en.wikipedia.org/wiki/Japanese_Mahjong#Making_melds_by_calling. Diese Funktion sollte entscheiden, ob der Agent eine Kan(Quad)-Meldung auslösen soll. Tile136 steht für das Plättchen, das ein Gegner abgeworfen hat und das vom Agenten zur Bildung der Kan-Meldung verwendet werden kann. from_opponent gibt an, ob der Agent die Kan-Meldung durch den Abwurf des Gegners (drei Spielsteine auf der Hand und der Gegner wirft den vierten ab) oder durch eigene Spielsteine (alle vier Spielsteine auf der Hand) bildet.
try_to_call_meld : https://en.wikipedia.org/wiki/Japanese_Mahjong#Making_melds_by_calling. Diese Funktion entscheidet, ob der Agent eine Pon(Triplet)/Chi(Sequence)-Meldung aufrufen soll. Tile136 steht für das Plättchen in 136er-Form, das einige Gegner abgeworfen haben. might_call_chi gibt an, ob der Agent eine Chi-Meldung auslösen könnte, da eine Chi-Meldung nur ausgelöst werden kann, wenn der Gegner auf dem linken Platz abgelegt wird.
can_call_reach : https://en.wikipedia.org/wiki/Japanese_Mahjong#R%C4%ABchi. Diese Funktion entscheidet, ob der Agent Riichi beanspruchen soll.
Wenn die Mahjong-Agentenklasse eine Unterklasse der AIInterface- Klasse ist, kann innerhalb der Agentenklasse wie angegeben auf die unten aufgeführten Informationen zugegriffen werden.
Zugang | Datentyp | Veränderlich | Beschreibung |
---|---|---|---|
self.tiles136 | Liste von ganzen Zahlen | Y | Handfliesen in 136er-Form |
selbst.hand34 | Liste von ganzen Zahlen | N | Handplättchen in 34er-Form (Kachel34 = Kachel136//4) |
self.discard136 | Liste von ganzen Zahlen | Y | die Abwürfe des Agenten in 136-von |
self.discard34 | Liste von ganzen Zahlen | N | die Abwürfe des Agenten in 34er-Form |
self.meld136 | Liste der Meld-Instanzen | Y | die aufgerufenen Meldungen des Agenten, Instanzen der Klasse Meld in client.mahjong_meld.py |
self.total_melds34 | Liste der ganzen Zahlen | N | Die genannten Meldungen des Agenten in 34er-Form |
self.meld34 | Liste der ganzen Zahlen | N | die genannten Pon/Chow-Melden des Agenten in 34er-Form |
self.pon34 | Liste der ganzen Zahlen | N | Die aufgerufenen PON-Meldungen des Agenten in 34er-Form |
self.chow34 | Liste der ganzen Zahlen | N | die sogenannten Chow-Meldungen des Agenten in 34er-Form |
self.minkan34 | Liste der ganzen Zahlen | N | die sogenannten Minkan-Formationen des Agenten in 34er-Form |
self.ankan34 | Liste der ganzen Zahlen | N | die genannten Ankan-Meldungen des Agenten in 34er-Form |
Selbstname | Zeichenfolge | Y | Name des Kontos |
Selbstebene | Zeichenfolge | Y | Ebene des Kontos |
Selbstsitz | ganze Zahl | Y | Sitz-ID, der Agent hat immer 0 |
self.dealer_seat | ganze Zahl | Y | die Sitzplatz-ID des Händlers |
self.is_dealer | Boolescher Wert | N | ob der Agent Händler ist oder nicht |
self.reach_status | Boolescher Wert | Y | gibt an, ob der Agent Riichi beansprucht hat |
self.just_reach() | Boolescher Wert | N | ob der Agent gerade Riichi beansprucht hat |
self.tmp_rank | ganze Zahl | Y | Rang des Agenten im aktuellen Spiel |
self.score | ganze Zahl | Y | Punktestand des Agenten im aktuellen Spiel |
self.is_open_hand | Boolescher Wert | N | ob der Agent bereits offene Meldungen aufgerufen hat |
self.turn_num | ganze Zahl | N | die Nummer der aktuellen Runde |
self.player_wind | ganze Zahl | N | Spielerwind ist eine Art Yaku |
self.round_wind | ganze Zahl | N | Runder Wind ist eine Art Yaku |
self.bonus_honors | Liste von ganzen Zahlen | Y | alle Charakterplättchen, die Yaku haben |
Man kann auf die Instanz der Gegnerklasse zugreifen, indem man self.game_table.get_player(i) aufruft, wobei i gleich 1,2,3 ist, was die entsprechende ID des Gegners angibt.
Zugang | Datentyp | Veränderlich | Beschreibung |
---|---|---|---|
.discard136 | Liste von ganzen Zahlen | Y | die Abwürfe des beobachteten Gegners in 136-ab |
.discard34 | Liste von ganzen Zahlen | N | die Abwürfe des beobachteten Gegners in 34er-Form |
.meld136 | Liste der Meld- Instanzen | Y | die ausgerufenen Meldungen des beobachteten Gegners |
.total_melds34 | Liste der ganzen Zahlen | N | die ausgerufenen Meldungen des beobachteten Gegners in 34er-Form |
.meld34 | Liste der ganzen Zahlen | N | die angerufenen Pon/Chow-Meldes des beobachteten Gegners in 34er-Form |
.pon34 | Liste der ganzen Zahlen | N | Der aufgerufene Pon meldet den beobachteten Gegner in 34er-Form |
.chow34 | Liste der ganzen Zahlen | N | die angerufenen Chow-Meldes des beobachteten Gegners in 34er-Form |
.minkan34 | Liste der ganzen Zahlen | N | die aufgerufenen Minkan-Melden des beobachteten Gegners in 34er-Form |
.ankan34 | Liste der ganzen Zahlen | N | die aufgerufenen Ankan-Meldungen des beobachteten Gegners in 34er-Form |
.safe_tiles | Liste von ganzen Zahlen | Y | Plättchen in 34er-Form, die für den Agenten absolut sicher sind, dh der beobachtete Gegner kann mit diesen Plättchen nicht gewinnen |
.Name | Zeichenfolge | Y | Name des Gegners |
.Ebene | Zeichenfolge | Y | Level des Gegners |
.Sitz | ganze Zahl | Y | Platz-ID des beobachteten Gegners |
.dealer_seat | ganze Zahl | Y | die Sitzplatz-ID des Händlers |
.is_dealer | Boolescher Wert | N | ob der beobachtete Gegner Dealer ist oder nicht |
.reach_status | Boolescher Wert | Y | gibt an, ob der beobachtete Gegner Riichi beansprucht hat |
.just_reach() | Boolescher Wert | N | ob der beobachtete Gegner gerade Riichi beansprucht hat |
.tmp_rank | ganze Zahl | Y | Rang des beobachteten Gegners im aktuellen Spiel |
.Punktzahl | ganze Zahl | Y | Punktestand des beobachteten Gegners im aktuellen Spiel |
.is_open_hand | Boolescher Wert | N | ob der beobachtete Gegner bereits offene Meldungen angesagt hat |
.turn_num | ganze Zahl | N | die Nummer der aktuellen Runde |
.player_wind | ganze Zahl | N | Spielerwind ist eine Art Yaku |
.round_wind | ganze Zahl | N | Runder Wind ist eine Art Yaku |
.bonus_honors | Liste von ganzen Zahlen | Y | alle Charakterplättchen, die Yaku haben |
Um auf Informationen über den Spieltisch zuzugreifen, kann man self.game_table aufrufen
Zugang | Datentyp | Veränderlich | Beschreibung |
---|---|---|---|
.bot | Instanz der Agentenklasse | Y | Klasseninstanz des Agenten |
.get_player(i) | Instanz der Gegnerklasse | Y | Klasseninstanz des Gegners, i=1,2,3 |
.dealer_seat | ganze Zahl | Y | Sitzplatz-ID des Händlers |
.bonus_indicator | Liste von ganzen Zahlen | Y | Bonusindikatoren in 136-Form |
.round_number | ganze Zahl | Y | runde Zahl |
.reach_sticks | ganze Zahl | Y | Wie viele Riichi-Sticks liegen auf dem Tisch? Der Spieler, der als nächstes gewinnt, erhält alle Punkte dieser Riichi-Stäbchen |
.honba_sticks | ganze Zahl | Y | Wie viele Honba-Sticks liegen auf dem Tisch? Der Spieler erhält gemäß den Honba-Sticks zusätzliche Punkte, wenn er gewinnt |
.count_ramaining_tiles | ganze Zahl | Y | Die Anzahl der verbleibenden nicht aufgedeckten Kacheln |
.enthüllt | Liste von ganzen Zahlen | Y | Jedes Element in der Liste gibt an, wie viele Kopien dieses bestimmten Plättchens aufgedeckt wurden (Abwürfe, offene Meldungen, Bonusindikatoren usw.). |
.round_win | ganze Zahl | N | Runder Wind ist eine Art Yaku |
.bonus_tiles | Liste von ganzen Zahlen | N | Liste der Bonusplättchen. Jedes Vorkommen von Bonusplättchen in Handplättchen zählt als Yaku |
.last_discard | ganze Zahl | N | Dieses Plättchen ist der allerneueste Abwurf des Gegners und nach den Regeln absolut sicher |
mein Professor Johannes Fürnkranz (TU Darmstadt Knowledge Engineering Group)
mein Betreuer Tobias Joppen (TU Darmstadt Knowledge Engineering Group)