Este é um projeto para minha dissertação de mestrado. Qualquer pessoa interessada em criar Mahjong AI poderoso pode estender meu agente. Para obter mais detalhes sobre os algoritmos aplicados e as razões pelas quais tais algoritmos são usados, entre em contato comigo por e-mail.
Caso você queira desenvolver seu próprio agente de Mahjong, este Repo também pode ser usado como Framework para testes em tempo real ( com jogadores humanos reais ). Assim você poderá economizar todo o seu tempo para encontrar a melhor estratégia para o seu agente. Além disso, uma biblioteca de desenvolvimento (rastreamento e pré-processamento de logs de jogos, cálculo de shantin e pontuação vencedora, etc.) está agora disponível em: https://github.com/erreurt/MahjongKit
Próxima atualização em breve :
É necessária uma melhor engenharia de recursos. Decida se deve chamar uma combinação/ligar para Riichi ou não usando Random Forest (em vez de regras de condição).
Em andamento : Treinamento do modelo de previsão de blocos de espera com LSTM.
Atenção : Se você testar seu bot paralelamente com mais de 4 contas no mesmo endereço IP, seu endereço IP será banido pelo tenhou.net por 24 horas. (Não sei exatamente sobre as regras de banimento de jogadores, mas tudo isso foi inferido da minha observação.)
Autor | Jian Yang Tang (Thomas) |
---|---|
[email protected] |
Mahjong é um jogo de estratégia para quatro jogadores com informações imperfeitas. Os principais desafios do desenvolvimento de um agente inteligente de Mahjong são, por exemplo, as complicadas regras do jogo, o imenso espaço de busca, múltiplos oponentes e informações imperfeitas. Vários trabalhos existentes tentaram resolver estes problemas através de simulação de árvore de Monte Carlo, ajuste de função de utilidade por aprendizagem supervisionada ou modelo de oponentes por algoritmos de regressão. No entanto, o desempenho dos agentes inteligentes que jogam Mahjong ainda está longe do desempenho dos melhores jogadores humanos. Com base na análise estatística do jogo Mahjong e no conhecimento especializado humano, um agente inteligente de Mahjong foi proposto neste trabalho. Para resolver os problemas do trabalho de última geração, tecnologias heurísticas e um modelo de oponentes aprimorado obtido pela adoção de ensacamento de perceptrons multicamadas foram aplicados a este trabalho. Os experimentos mostram que o agente proposto supera o agente do estado da arte, e o modelo de oponentes aplicado tem um efeito positivo significativo no desempenho do agente. Além disso, vários pontos interessantes podem ser discernidos a partir dos experimentos, que são bastante significativos para trabalhos futuros.
Consulte https://en.wikipedia.org/wiki/Japanese_Mahjong para ver as regras do jogo do Riichi Mahjong japonês.
O cliente implementado permite executar um agente Mahjong diretamente através do programa, em vez de fazê-lo no navegador web. O site para jogar online o japonês Riichi Mahjong é http://tenhou.net/
O lado esquerdo é um cenário típico da mesa japonesa Riichi Mahjong. Esta imagem é uma captura de tela da GUI implementada para uso de depuração.
O agente Mahjong proposto foi testado em tenhou.net. O teste foi realizado em duas versões, ou seja, uma com modelo de defesa e outra sem. Os registros brutos do jogo e os resultados intermediários do jogo podem ser encontrados em meu outro repositório: https://github.com/erreurt/Experiments-result-of-mahjong-bot. Os experimentos foram realizados com a versão do agente em experiment_ai.py .
Para a versão com modelo de defesa foram disputados 526 jogos, e para a versão sem modelo de defesa foram disputados 532 jogos. Não se trata de dois trabalhos relacionados, mas como mostra a figura do comportamento de convergência do desempenho do agente, 526 jogos são suficientes.
O extenso trabalho de Mizukami pode ser visto como atualmente o melhor e mais confiável agente de Mahjong na literatura inglesa. Aqui é apresentada uma comparação entre o desempenho do meu agente de Mahjong e o de Mizukami:
[1] | [2] | [3] | [4] | |
---|---|---|---|---|
Jogos disputados | 526 | 532 | 2634 | 1441 |
Taxa de 1º lugar | 23,95% | 22,65% | 24,10% | 25,30% |
Taxa de 2º lugar | 26,62% | 25,92% | 28,10% | 24,80% |
Taxa do 3º lugar | 31,75% | 25,71% | 24,80% | 25,10% |
Taxa de 4º lugar | 17,68% | 25,71% | 23,00% | 24,80% |
taxa de vitória | 24,68% | 26,50% | 24,50% | 25,60% |
taxa de perda | 13,92% | 20,21% | 13,10% | 14,80% |
nível fixo | 2.21Dan | 0,77 Dan | 1.14Dan | 1.04 Dan |
[1] Meu agente Mahjong com modelo de defesa
[2] Meu agente Mahjong sem modelo de defesa
[3] Trabalho estendido de Mizukami : Mizukami N., Tsuruoka Y.. Construindo um jogador de mahjong de computador baseado na simulação de Monte Carlo e modelos de oponentes. In: Conferência IEEE 2015 sobre Inteligência Computacional e Jogos (CIG), pp. IEEE (2015)
[4] Mizukami e outros. al. : N. Mizukami, R. Nakahari, A. Ura, M. Miwa, Y. Tsuruoka e T. Chikayama. Realização de um programa de mahjong de computador para quatro jogadores por meio de aprendizagem supervisionada com aspectos multijogador isolados. Transações da Sociedade de Processamento de Informação do Japão, vol. 55, não. 11, pp. 1–11, 2014, (em japonês).
Observe que ao jogar Mahjong, cair para o 4º lugar é definitivamente um tabu, já que os pontos de nível seriam reduzidos. Como resultado, a taxa de queda de um jogador para o 4º lugar é crítica para o seu desempenho geral. Meu bot tem um nível fixo melhor exatamente devido à baixa taxa de 4º lugar.
Para executar o agente Mahjong, é necessário especificar algumas configurações. Conforme mostrado no exemplo a seguir 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 )
Instância AI : Uma instância de classe do agente Mahjong. Neste repositório são fornecidas três versões do agente Mahjong. O primeiro está em agents.random_ai_example.py , esta é uma classe de demonstração para mostrar aos desenvolvedores em potencial como implementar seus próprios agentes. O segundo está em agents.experiment_ai.py e os resultados do experimento fornecidos na parte 4 são gerados por esta IA. A terceira é a IA atualizada e está em agents.jianyang_ai.py .
Classe de jogador oponente : A classe do jogador oponente. Pode-se usar a classe padrão OpponentPlayer em client.mahjong_player . Se alguém estendeu a classe OpponentPlayer devido a necessidades extras, esta variável deve ser definida para sua classe correspondente.
ID do usuário : um token no formato mostrado no exemplo obtido após o registro no tenhou.net. ATENÇÃO: Utilize seu próprio ID de usuário. Se o mesmo ID for usado em endereços IP diferentes com muita frequência, a conta será temporariamente bloqueada pelo tenhou.net.
Nome de usuário : O nome de usuário correspondente que você criou ao se registrar no tenhou.net. Esta variável serve apenas para identificar seus logs de teste.
Tipo de jogo : O tipo de jogo é codificado como um número inteiro de 8 bits. A seguir está a descrição de cada bit.
Por exemplo:
- 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 : Dois parâmetros são necessários para inicializar o logger. O primeiro é o ID do criador de logs definido pelo usuário, de forma que os desenvolvedores possam nomear livremente seu histórico de testes.
Após especificar todas essas configurações, basta lançar todos esses parâmetros para connect_and_play() . Então é hora de assistir ao show do seu agente de Mahjong!!!
Quatro funções devem ser implementadas para o bot Mahjong, conforme mostrado na classe "interface" em agents.ai_interface . É recomendado que seu agente seja uma herança da AIInterface. Para uma explicação mais profunda e um exemplo simples dessas funções, consulte a documentação em 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 : Com base em todas as informações acessíveis sobre o estado do jogo, esta função retorna um bloco para descartar. O retorno é um número inteiro no intervalo de 0 a 135. Existem no total 136 peças no jogo Mahjong, ou seja, 34 tipos de peças e 4 cópias de cada tipo. Em diferentes ocasiões usamos a forma 34 (cada número corresponde a um tipo de peça) ou a forma 136 (cada número corresponde a uma peça). Observe que aqui o retorno deve estar no formato 136.
deveria_call_kan : https://en.wikipedia.org/wiki/Japanese_Mahjong#Making_melds_by_calling. Esta função deve decidir se o agente deve chamar uma combinação kan(Quad). tile136 representa a peça que algum oponente descartou, que pode ser usada pelo agente para formar a combinação kan. from_opponent indica se o agente forma a combinação kan pelo descarte do oponente (três peças na mão e o oponente descarta a quarta) ou pelas próprias peças (todas as quatro peças na mão).
try_to_call_meld : https://en.wikipedia.org/wiki/Japanese_Mahjong#Making_melds_by_calling. Esta função decide se o agente deve chamar uma combinação Pon(Triplet)/Chi(Sequence). tile136 representa a peça no formato 136 que alguns oponentes descartaram. might_call_chi indica se o agente poderia chamar uma combinação de Chi, já que uma combinação de Chi só pode ser chamada com o descarte do oponente no assento esquerdo.
can_call_reach : https://en.wikipedia.org/wiki/Japanese_Mahjong#R%C4%ABchi. Esta função decide se o agente deve reivindicar Riichi.
Quando a classe de agente Mahjong é uma subclasse da classe AIInterface , as informações listadas a seguir podem ser acessadas dentro da classe de agente conforme especificado.
Acesso | Tipo de dados | Mutável | Descrição |
---|---|---|---|
auto.tiles136 | lista de inteiros | S | peças manuais em formato 136 |
self.hand34 | lista de inteiros | N | mão peças em formato 34 (tile34 = tile136//4) |
self.discard136 | lista de inteiros | S | os descartes do agente em 136-de |
self.discard34 | lista de inteiros | N | os descartes do agente na forma 34 |
self.meld136 | lista de instâncias do Meld | S | as combinações chamadas do agente, instâncias da classe Meld em client.mahjong_meld.py |
self.total_melds34 | lista de lista de inteiros | N | as combinações chamadas do agente na forma 34 |
self.meld34 | lista de lista de inteiros | N | as chamadas combinações pon/chow do agente na forma 34 |
self.pon34 | lista de lista de inteiros | N | as combinações pon chamadas do agente na forma 34 |
self.chow34 | lista de lista de inteiros | N | as chamadas combinações de comida do agente na forma 34 |
eu.minkan34 | lista de lista de inteiros | N | as chamadas combinações minkan do agente na forma 34 |
self.ankan34 | lista de lista de inteiros | N | as chamadas combinações ankan do agente na forma 34 |
nome próprio | corda | S | nome da conta |
nível próprio | corda | S | nível da conta |
assento próprio | inteiro | S | ID do assento, o agente sempre tem 0 |
self.dealer_seat | inteiro | S | o ID do assento do revendedor |
self.is_dealer | booleano | N | se o agente é revendedor ou não |
self.reach_status | booleano | S | indica se o agente reivindicou Riichi |
self.just_reach() | booleano | N | se o agente acabou de reivindicar Riichi |
self.tmp_rank | inteiro | S | classificação do agente no jogo atual |
auto.pontuação | inteiro | S | pontuação do agente no jogo atual |
self.is_open_hand | booleano | N | se o agente já convocou combinações abertas |
self.turn_num | inteiro | N | o número do turno atual |
self.player_wind | inteiro | N | o vento do jogador é um tipo de yaku |
self.round_wind | inteiro | N | vento redondo é um tipo de yaku |
self.bonus_honors | lista de inteiros | S | todas as peças de personagem que possuem yaku |
Pode-se acessar a instância da classe oponente chamando self.game_table.get_player(i) com i igual a 1,2,3, que indica o id correspondente do oponente.
Acesso | Tipo de dados | Mutável | Descrição |
---|---|---|---|
.discard136 | lista de inteiros | S | os descartes do oponente observado em 136-de |
.discard34 | lista de inteiros | N | os descartes do oponente observado na forma 34 |
.meld136 | lista de instâncias do Meld | S | as combinações chamadas do oponente observado |
.total_melds34 | lista de lista de inteiros | N | as combinações chamadas do oponente observado na forma 34 |
.meld34 | lista de lista de inteiros | N | as combinações chamadas pon/chow do oponente observado na forma 34 |
.pon34 | lista de lista de inteiros | N | o chamado pon combina do oponente observado na forma 34 |
.chow34 | lista de lista de inteiros | N | as combinações de chow chamadas do oponente observado na forma 34 |
.minkan34 | lista de lista de inteiros | N | as chamadas combinações minkan do oponente observado na forma 34 |
.ankan34 | lista de lista de inteiros | N | as chamadas combinações ankan do oponente observado na forma 34 |
.safe_tiles | lista de inteiros | S | peças no formato 34 que são absolutamente seguras para o agente, ou seja, o oponente observado não pode vencer com essas peças |
.nome | corda | S | nome do adversário |
.nível | corda | S | nível do adversário |
.assento | inteiro | S | ID do assento do oponente observado |
.dealer_seat | inteiro | S | o ID do assento do revendedor |
.is_dealer | booleano | N | se o oponente observado é dealer ou não |
.reach_status | booleano | S | indica se o oponente observado reivindicou Riichi |
.just_reach() | booleano | N | se o oponente observado acabou de reivindicar Riichi |
.tmp_rank | inteiro | S | classificação do oponente observado no jogo atual |
.pontuação | inteiro | S | pontuação do adversário observado no jogo atual |
.is_open_hand | booleano | N | se o oponente observado já convocou combinações abertas |
.turn_num | inteiro | N | o número do turno atual |
.player_wind | inteiro | N | o vento do jogador é um tipo de yaku |
.round_wind | inteiro | N | vento redondo é um tipo de yaku |
.bonus_honors | lista de inteiros | S | todas as peças de personagem que possuem yaku |
Para acessar informações sobre a mesa de jogo, pode-se chamar self.game_table
Acesso | Tipo de dados | Mutável | Descrição |
---|---|---|---|
.robô | instância da classe do agente | S | instância de classe do agente |
.get_player(i) | instância da classe oponente | S | instância de classe do oponente, i=1,2,3 |
.dealer_seat | inteiro | S | ID do assento do revendedor |
.bonus_indicator | lista de inteiros | S | indicadores de bônus no formato 136 |
.número_redondo | inteiro | S | número redondo |
.reach_sticks | inteiro | S | Quantos palitos de Riichi estão na mesa. O próximo jogador que vencer receberá todos os pontos desses bastões Riichi |
.honba_sticks | inteiro | S | Quantos palitos de honba estão na mesa. O jogador terá pontos extras de acordo com os honba sticks se vencer |
.count_ramaining_tiles | inteiro | S | O número de peças não reveladas restantes |
.revelado | lista de inteiros | S | Cada elemento na lista indica quantas cópias desta peça específica foram reveladas (descartes, combinações abertas, indicadores de bônus, etc.) |
.round_win | inteiro | N | vento redondo é um tipo de yaku |
.bonus_tiles | lista de inteiros | N | Lista de peças de bônus. Cada ocorrência de peças de bônus em peças de mão conta como um yaku |
.last_discard | inteiro | N | O último descarte do oponente, esta peça é absolutamente segura pelas regras |
meu professor Johannes Fürnkranz (Grupo de Engenharia do Conhecimento da TU Darmstadt)
meu supervisor Tobias Joppen (Grupo de Engenharia de Conhecimento da TU Darmstadt)