Il s'agit d'un projet pour mon mémoire de maîtrise. Quiconque souhaite créer une puissante IA de Mahjong est invité à prolonger mon agent. Pour plus de détails sur les algorithmes appliqués et les raisons pour lesquelles ces algorithmes sont utilisés, veuillez me contacter par e-mail.
Dans le cas où vous souhaitez développer votre propre agent Mahjong, ce Repo peut également être utilisé comme framework pour des tests en temps réel ( avec de vrais joueurs humains ). Vous pourrez alors gagner tout votre temps pour trouver la meilleure stratégie pour votre agent. De plus une bibliothèque de développement (exploration et prétraitement des logs de jeu, calcul du shantin et du score gagnant etc.) est désormais disponible sur : https://github.com/erreurt/MahjongKit
Prochaine mise à jour à venir :
Une meilleure ingénierie des fonctionnalités est nécessaire. Décidez si vous devez appeler une fusion/appeler Riichi ou non en utilisant Random Forest (au lieu de règles de condition).
En cours : Formation du modèle de prédiction des tuiles d'attente avec LSTM.
Attention : Si vous testez votre bot en parallèle avec plus de 4 comptes sous la même adresse IP, votre adresse IP sera bannie par tenhou.net pendant 24 heures. (Je ne connais pas exactement les règles d'interdiction de joueurs, mais tout cela découle de mon observation.)
Auteur | Jianyang Tang (Thomas) |
---|---|
[email protected] |
Le Mahjong est un jeu de stratégie à quatre joueurs avec des informations imparfaites. Les principaux défis du développement d'un agent de Mahjong intelligent sont par exemple les règles du jeu compliquées, l'immense espace de recherche, les multiples adversaires et les informations imparfaites. Plusieurs travaux existants ont tenté de résoudre ces problèmes à travers la simulation d'arbres de Monte Carlo, l'ajustement de fonctions d'utilité par apprentissage supervisé ou le modèle d'adversaires par des algorithmes de régression. Néanmoins, les performances des agents intelligents du jeu de Mahjong sont encore loin de celles des meilleurs joueurs humains. Basé sur l'analyse statistique du jeu de Mahjong et les connaissances des experts humains, un agent de Mahjong intelligent a été proposé dans ce travail. Pour résoudre les problèmes des travaux de pointe, des technologies heuristiques et un modèle d'adversaires amélioré obtenu en adoptant l'ensachage de perceptrons multicouches ont été appliqués à ce travail. Les expériences montrent que l’agent proposé surpasse l’agent de pointe et que le modèle des adversaires appliqué a un effet positif significatif sur les performances de l’agent. En outre, plusieurs points intéressants peuvent être dégagés des expériences, qui sont très significatifs pour les travaux futurs.
Reportez-vous à https://en.wikipedia.org/wiki/Japanese_Mahjong pour connaître les règles du jeu du Riichi Mahjong japonais.
Le client implémenté permet d'exécuter un agent Mahjong directement via le programme, au lieu de le faire dans le navigateur Web. Le site pour jouer en ligne au Riichi Mahjong japonais est http://tenhou.net/
Le côté gauche est un décor typique de la table japonaise Riichi Mahjong. Cette image est une capture d'écran de l'interface graphique implémentée pour le débogage.
L'agent Mahjong proposé a été testé sur tenhou.net. Le test a été réalisé en deux versions, à savoir une avec modèle de défense et une autre sans. Les journaux de jeu bruts et les résultats de jeu intermédiaires peuvent être trouvés dans mon autre référentiel : https://github.com/erreurt/Experiments-result-of-mahjong-bot. Les expériences ont été réalisées avec la version agent dans experimental_ai.py .
Pour la version avec modèle de défense, 526 parties ont été jouées, et pour la version sans modèle de défense, 532 parties ont été jouées. Il ne s'agit pas de deux travaux liés, mais comme le montre la figure du comportement de convergence des performances de l'agent, 526 jeux suffisent.
L'œuvre étendue de Mizukami peut être considérée comme actuellement le meilleur et le plus fiable agent de Mahjong de la littérature anglaise. Ici, une comparaison entre les performances de mon agent Mahjong et celles de Mizukami est présentée :
[1] | [2] | [3] | [4] | |
---|---|---|---|---|
Jeux joués | 526 | 532 | 2634 | 1441 |
Tarif 1ère place | 23,95% | 22,65% | 24,10% | 25,30% |
Tarif 2ème place | 26,62% | 25,92% | 28,10% | 24,80% |
Tarif 3ème place | 31,75% | 25,71% | 24,80% | 25,10% |
Tarif 4ème place | 17,68% | 25,71% | 23,00% | 24,80% |
taux de victoire | 24,68% | 26,50% | 24,50% | 25,60% |
taux de perte | 13,92% | 20,21% | 13,10% | 14,80% |
niveau fixe | 2.21 Dan | 0,77 Dan | 1.14 Dan | 1.04 Dan |
[1] Mon agent Mahjong avec modèle de défense
[2] Mon agent Mahjong sans modèle de défense
[3] Travaux étendus de Mizukami : Mizukami N., Tsuruoka Y.. Construction d'un joueur de mahjong informatique basé sur la simulation de Monte Carlo et des modèles d'adversaires. Dans : Conférence IEEE 2015 sur l'intelligence informatique et les jeux (CIG), pp. 275-283. IEEE (2015)
[4] Mizukami et. al. : N. Mizukami, R. Nakahari, A. Ura, M. Miwa, Y. Tsuruoka et T. Chikayama. Réalisation d'un programme de mahjong informatique à quatre joueurs par apprentissage supervisé avec des aspects multi-joueurs isolés. Transactions de la société de traitement de l'information du Japon, vol. 55, non. 11, pp. 1-11, 2014 (en japonais).
Notez qu'en jouant au Mahjong, tomber à la 4ème place est définitivement un tabou, car on perdrait des points de niveau. En conséquence, le taux de chute à la 4ème place d’un joueur est essentiel à sa performance globale. Mon bot a un meilleur niveau fixe précisément en raison du faible taux de 4ème place.
Pour exécuter l'agent Mahjong, il faut spécifier quelques configurations. Comme le montre l'exemple suivant de 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 )
Instance AI : Une instance de classe de l'agent Mahjong. Dans ce référentiel, trois versions de l'agent Mahjong sont fournies. Le premier se trouve dans agents.random_ai_example.py , il s'agit d'une classe de démonstration permettant de montrer aux développeurs potentiels comment implémenter leurs propres agents. Le second se trouve dans agents.experiment_ai.py et les résultats de l'expérience donnés dans la partie 4 sont générés par cette IA. Le troisième est l'IA à jour et se trouve dans agents.jianyang_ai.py .
Classe du joueur adverse : La classe du joueur adverse. On peut utiliser la classe par défaut OpponentPlayer dans client.mahjong_player . Si l'on a étendu la classe OpponentPlayer en raison de besoins supplémentaires, cette variable doit être définie sur votre classe correspondante.
User ID : Un token sous la forme indiqué dans l'exemple que l'on a obtenu après inscription sur tenhou.net. ATTENTION : Veuillez utiliser votre propre identifiant utilisateur. Si le même identifiant est utilisé trop souvent sous une adresse IP différente, le compte sera temporairement bloqué par tenhou.net.
Nom d'utilisateur : Le nom d'utilisateur correspondant que vous avez créé lors de votre inscription sur tenhou.net. Cette variable sert uniquement à identifier vos journaux de test.
Type de jeu : Le type de jeu est codé sous la forme d'un entier de 8 bits. Voici la description de chaque bit.
Pour des exemples :
- 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 : Deux paramètres sont nécessaires à l'initialisation du logger. Le premier est l'ID de l'enregistreur défini par l'utilisateur, de sorte que les développeurs peuvent librement nommer son historique de tests.
Après avoir spécifié toutes ces configurations, lancez simplement tous ces paramètres dans connect_and_play() . Alors il est temps de regarder le spectacle de votre agent Mahjong !!!
Quatre fonctions doivent être implémentées pour le bot Mahjong, comme indiqué dans la classe "interface" dans agents.ai_interface . Il est recommandé que votre agent soit un héritage de AIInterface. Pour une explication plus approfondie et un exemple simple de ces fonctions, veuillez consulter la documentation dans 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 : Sur la base de toutes les informations accessibles sur l'état du jeu, cette fonction renvoie une tuile à défausser. Le retour est un entier compris entre 0 et 135. Il y a au total 136 tuiles dans le jeu de Mahjong, soit 34 sortes de tuiles et 4 exemplaires pour chaque sorte. À différentes occasions, nous utilisons soit la forme 34 (chaque nombre correspond à un type de tuile), soit la forme 136 (chaque nombre correspond à une tuile). Notez qu'ici, la déclaration doit être au format 136.
Should_call_kan : https://en.wikipedia.org/wiki/Japanese_Mahjong#Making_melds_by_calling. Cette fonction doit décider si l'agent doit appeler une fusion kan(Quad). la tuile 136 représente la tuile qu'un adversaire a défaussée, qui peut être utilisée par l'agent pour former la fusion kan. from_opponent indique si l'agent forme le kan fusionné par la défausse de l'adversaire (trois tuiles en main et l'adversaire défausse la quatrième) ou par ses propres tuiles (les quatre tuiles en main).
try_to_call_meld : https://en.wikipedia.org/wiki/Japanese_Mahjong#Making_melds_by_calling. Cette fonction décide si l'agent doit appeler une fusion Pon(Triplet)/Chi(Sequence). la tuile 136 représente la tuile sous forme 136 que certains adversaires ont défaussée. could_call_chi indique si l'agent peut appeler une fusion Chi, puisqu'une fusion Chi ne peut être appelée qu'avec l'élimination de l'adversaire sur le siège gauche.
can_call_reach : https://en.wikipedia.org/wiki/Japanese_Mahjong#R%C4%ABchi. Cette fonction décide si l'agent doit réclamer Riichi.
Lorsque la classe d'agent Mahjong est une sous-classe de la classe AIInterface , les informations répertoriées comme suit sont accessibles à l'intérieur de la classe d'agent comme spécifié.
Accéder | Type de données | Mutable | Description |
---|---|---|---|
soi.tiles136 | liste d'entiers | Oui | tuiles à main en forme 136 |
soi.hand34 | liste d'entiers | N | tuiles à la main en forme de 34 (tile34 = tuile136//4) |
self.discard136 | liste d'entiers | Oui | les rejets de l'agent en 136-de |
self.discard34 | liste d'entiers | N | les rejets de l'agent sous forme 34 |
soi.meld136 | liste des instances Meld | Oui | les fusions appelées de l'agent, instances de la classe Meld dans client.mahjong_meld.py |
self.total_melds34 | liste de liste d'entiers | N | les fusions appelées de l'agent sous la forme 34 |
soi.meld34 | liste de liste d'entiers | N | les fusions pon/chow appelées de l'agent sous la forme 34 |
soi.pon34 | liste de liste d'entiers | N | le pon appelé se fond de l'agent sous la forme 34 |
soi.chow34 | liste de liste d'entiers | N | le chow appelé se fond de l'agent sous la forme 34 |
soi.minkan34 | liste de liste d'entiers | N | le minkan appelé se mélange de l'agent sous la forme 34 |
soi.ankan34 | liste de liste d'entiers | N | les appelés ankan se fondent de l'agent sous la forme 34 |
soi.nom | chaîne | Oui | nom du compte |
niveau de soi | chaîne | Oui | niveau du compte |
siège autonome | entier | Oui | ID de siège, l'agent a toujours 0 |
self.dealer_seat | entier | Oui | l'identifiant du siège du concessionnaire |
self.is_dealer | booléen | N | si l'agent est un concessionnaire ou non |
self.reach_status | booléen | Oui | indique si l'agent a réclamé Riichi |
soi.just_reach() | booléen | N | si l'agent vient de revendiquer Riichi |
soi.tmp_rank | entier | Oui | rang de l'agent dans le jeu en cours |
auto.score | entier | Oui | score de l'agent dans le jeu en cours |
self.is_open_hand | booléen | N | si l'agent a déjà appelé des fusions ouvertes |
self.turn_num | entier | N | le numéro du tour en cours |
self.player_wind | entier | N | le vent du joueur est une sorte de yaku |
self.round_wind | entier | N | le vent rond est une sorte de yaku |
self.bonus_honors | liste d'entiers | Oui | toutes les tuiles de personnages qui ont des yaku |
On peut accéder à l'instance de la classe adversaire en appelant self.game_table.get_player(i) avec i égal à 1,2,3, ce qui indique l'identifiant correspondant de l'adversaire.
Accéder | Type de données | Mutable | Description |
---|---|---|---|
.jeter136 | liste d'entiers | Oui | les écarts de l'adversaire observé en 136-de |
.jeter34 | liste d'entiers | N | les écarts de l'adversaire observé sous la forme 34 |
.meld136 | liste des instances Meld | Oui | les combinaisons appelées de l'adversaire observé |
.total_melds34 | liste de liste d'entiers | N | les combinaisons appelées de l'adversaire observé sous la forme 34 |
.meld34 | liste de liste d'entiers | N | les mélanges pon/chow appelés de l'adversaire observé sous la forme 34 |
.pon34 | liste de liste d'entiers | N | le pon appelé se fond de l'adversaire observé sous la forme 34 |
.chow34 | liste de liste d'entiers | N | le chow appelé se fond de l'adversaire observé sous la forme 34 |
.minkan34 | liste de liste d'entiers | N | le minkan appelé se fond de l'adversaire observé sous la forme 34 |
.ankan34 | liste de liste d'entiers | N | l'ankan appelé se fond de l'adversaire observé sous la forme 34 |
.safe_tiles | liste d'entiers | Oui | des tuiles en forme de 34 qui sont absolument sans danger pour l'agent, c'est-à-dire que l'adversaire observé ne peut pas gagner avec ces tuiles |
.nom | chaîne | Oui | nom de l'adversaire |
.niveau | chaîne | Oui | niveau de l'adversaire |
.siège | entier | Oui | ID de siège de l'adversaire observé |
.dealer_seat | entier | Oui | l'identifiant du siège du concessionnaire |
.is_dealer | booléen | N | si l'adversaire observé est un croupier ou non |
.reach_status | booléen | Oui | indique si l'adversaire observé a revendiqué Riichi |
.just_reach() | booléen | N | si l'adversaire observé vient de revendiquer Riichi |
.tmp_rank | entier | Oui | rang de l'adversaire observé dans la partie en cours |
.score | entier | Oui | score de l'adversaire observé dans le jeu en cours |
.is_open_hand | booléen | N | si l'adversaire observé a déjà suivi des open melds |
.turn_num | entier | N | le numéro du tour en cours |
.player_wind | entier | N | le vent du joueur est une sorte de yaku |
.round_wind | entier | N | le vent rond est une sorte de yaku |
.bonus_honors | liste d'entiers | Oui | toutes les tuiles de personnages qui ont des yaku |
Pour accéder aux informations sur la table de jeu, on peut appeler self.game_table
Accéder | Type de données | Mutable | Description |
---|---|---|---|
.bot | instance de classe d'agent | Oui | instance de classe de l'agent |
.get_player(je) | instance de classe adverse | Oui | instance de classe de l'adversaire, i=1,2,3 |
.dealer_seat | entier | Oui | ID de siège du concessionnaire |
.bonus_indicateur | liste d'entiers | Oui | indicateurs de bonus sous forme 136 |
.numéro_rond | entier | Oui | numéro rond |
.reach_sticks | entier | Oui | Combien de bâtons Riichi y a-t-il sur la table. Le joueur qui gagnera ensuite recevra tous les points de ces bâtons Riichi |
.honba_sticks | entier | Oui | Combien de bâtons de honba y a-t-il sur la table. Le joueur aura des points supplémentaires selon les bâtons honba s'il gagne |
.count_ramaining_tiles | entier | Oui | Le nombre de tuiles non révélées restantes |
.révélé | liste d'entiers | Oui | Chaque élément de la liste indique combien de copies de cette tuile spécifique ont été révélées (défausses, combinaisons ouvertes, indicateurs de bonus, etc.) |
.round_win | entier | N | le vent rond est une sorte de yaku |
.bonus_tiles | liste d'entiers | N | Liste des tuiles bonus. Chaque apparition de tuiles bonus dans les tuiles de main compte comme un yaku. |
.last_discard | entier | N | La toute dernière défausse de l'adversaire, cette tuile est absolument sûre selon les règles |
mon professeur Johannes Fürnkranz (TU Darmstadt Knowledge Engineering Group)
mon superviseur Tobias Joppen (TU Darmstadt Knowledge Engineering Group)