Este es un proyecto para mi tesis de maestría. Cualquiera que esté interesado en crear una poderosa IA de Mahjong puede extender mi agente. Para obtener más detalles sobre los algoritmos aplicados y las razones por las que se utilizan dichos algoritmos, comuníquese conmigo por correo electrónico.
En el caso de que quieras desarrollar tu propio agente de Mahjong, este Repo también se puede utilizar como marco para pruebas en tiempo real ( con jugadores humanos reales ). Luego podrá ahorrar todo su tiempo para encontrar la mejor estrategia para su agente. Además, ahora está disponible una biblioteca de desarrollo (rastreo y preprocesamiento de registros de juego, cálculo de shantin y puntuación ganadora, etc.) en: https://github.com/erreurt/MahjongKit
Próxima actualización próximamente :
Se necesita una mejor ingeniería de funciones. Decida si llamar a una fusión/llamar a Riichi o no usar Random Forest (en lugar de reglas de condición).
En curso : Entrenamiento del modelo de predicción de mosaicos en espera con LSTM.
Atención : si prueba su bot en paralelo con más de 4 cuentas bajo la misma dirección IP, tenhou.net prohibirá su dirección IP durante 24 horas. (No sé exactamente las reglas para prohibir jugadores, pero todo eso se infiere de mi observación).
Autor | Jian Yang Tang (Thomas) |
---|---|
Correo electrónico | [email protected] |
Mahjong es un juego de estrategia para cuatro jugadores con información imperfecta. Los principales desafíos a la hora de desarrollar un agente inteligente de Mahjong son, por ejemplo, las complicadas reglas del juego, el inmenso espacio de búsqueda, los múltiples oponentes y la información imperfecta. Varios trabajos existentes han intentado abordar estos problemas mediante la simulación de árboles de Monte Carlo, el ajuste de funciones de utilidad mediante aprendizaje supervisado o el modelo de oponentes mediante algoritmos de regresión. Sin embargo, el desempeño de los agentes inteligentes que juegan Mahjong aún está lejos del de los mejores jugadores humanos. Basado en el análisis estadístico del juego Mahjong y el conocimiento experto humano, en este trabajo se propuso un agente inteligente de Mahjong. Para abordar los problemas del trabajo de última generación, se aplicaron a este trabajo tecnologías heurísticas y un modelo de oponentes mejorado logrado mediante la adopción de ensacado de perceptrones multicapa. Los experimentos muestran que el agente propuesto supera al agente más moderno y que el modelo de oponentes aplicado tiene un efecto positivo significativo en el desempeño del agente. Además, de los experimentos se pueden discernir varios puntos interesantes que son bastante significativos para trabajos futuros.
Consulte https://en.wikipedia.org/wiki/Japanese_Mahjong para conocer las reglas del juego japonés Riichi Mahjong.
El cliente implementado permite ejecutar un agente Mahjong directamente a través del programa, en lugar de hacerlo en el navegador web. El sitio para jugar en línea al Riichi Mahjong japonés es http://tenhou.net/
El lado izquierdo es un escenario típico de la mesa japonesa Riichi Mahjong. Esta imagen es una captura de pantalla de la GUI implementada para uso de depuración.
El agente Mahjong propuesto fue probado en tenhou.net. La prueba se realizó en dos versiones, una con modelo de defensa y otra sin modelo. Los registros del juego sin procesar y los resultados intermedios del juego se pueden encontrar en mi otro repositorio: https://github.com/erreurt/Experiments-result-of-mahjong-bot. Los experimentos se llevaron a cabo con la versión del agente en experiment_ai.py .
Para la versión con modelo de defensa se jugaron 526 partidos, y para la versión sin modelo de defensa se jugaron 532 partidos. Esto no es tanto como dos trabajos relacionados, pero como se muestra en la figura del comportamiento de convergencia del desempeño del agente, 526 juegos son suficientes.
El extenso trabajo de Mizukami puede considerarse actualmente como el mejor y más confiable agente de Mahjong en la literatura inglesa. Aquí se presenta una comparación entre el desempeño de mi agente de Mahjong y el de Mizukami:
[1] | [2] | [3] | [4] | |
---|---|---|---|---|
Juegos jugados | 526 | 532 | 2634 | 1441 |
tasa de 1er lugar | 23,95% | 22,65% | 24,10% | 25,30% |
tasa de 2do lugar | 26,62% | 25,92% | 28,10% | 24,80% |
tasa de 3er lugar | 31,75% | 25,71% | 24,80% | 25,10% |
tasa de 4to lugar | 17,68% | 25,71% | 23,00% | 24,80% |
tasa de ganancia | 24,68% | 26,50% | 24,50% | 25,60% |
perder tasa | 13,92% | 20,21% | 13,10% | 14,80% |
nivel fijo | 2.21 Dan | 0,77 danes | 1.14 Dan | 1.04 Dan |
[1] Mi agente Mahjong con modelo de defensa
[2] Mi agente Mahjong sin modelo de defensa
[3] Trabajo ampliado de Mizukami : Mizukami N., Tsuruoka Y.. Construcción de un jugador de mahjong por computadora basado en la simulación de Monte Carlo y modelos de oponentes. En: Conferencia IEEE de 2015 sobre juegos e inteligencia computacional (CIG), págs. IEEE (2015)
[4] Mizukami y otros. Alabama. : N. Mizukami, R. Nakahari, A. Ura, M. Miwa, Y. Tsuruoka y T. Chikayama. Realización de un programa de mahjong por ordenador para cuatro jugadores mediante aprendizaje supervisado con aspectos multijugador aislados. Sociedad de Transacciones de Procesamiento de Información de Japón, vol. 55, núm. 11, págs. 1 a 11, 2014 (en japonés).
Tenga en cuenta que mientras juega Mahjong, caer en el cuarto lugar es definitivamente un tabú, ya que se reducirían los puntos de nivel. Como resultado, la tasa de caída al cuarto lugar de un jugador es crítica para su desempeño general. Mi bot tiene un nivel fijo mejor precisamente debido a la baja tasa del cuarto lugar.
Para ejecutar el agente Mahjong, hay que especificar algunas configuraciones. Como se muestra en el siguiente ejemplo 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 )
Instancia de IA : una instancia de clase del agente Mahjong. En este repositorio se proporcionan tres versiones del agente Mahjong. El primero está en agentes.random_ai_example.py , esta es una clase de demostración para mostrar a los desarrolladores potenciales cómo implementar sus propios agentes. El segundo está en agentes.experiment_ai.py y esta IA genera los resultados del experimento proporcionados en la parte 4. El tercero es la IA actualizada y se encuentra en agentes.jianyang_ai.py .
Clase de jugador oponente : la clase del jugador oponente. Se puede utilizar la clase predeterminada OpponentPlayer en client.mahjong_player . Si uno ha ampliado la clase OpponentPlayer debido a necesidades adicionales, esta variable debe establecerse en su clase correspondiente.
ID de usuario : un token en el formato que se muestra en el ejemplo que se obtuvo después de registrarse en tenhou.net. ATENCIÓN: Utilice su propia identificación de usuario. Si se utiliza la misma ID con una dirección IP diferente con demasiada frecuencia, tenhou.net bloqueará temporalmente la cuenta.
Nombre de usuario : El nombre de usuario correspondiente que creó al registrarse en tenhou.net. Esta variable es solo para identificar sus registros de prueba.
Tipo de juego : El tipo de juego está codificado como un entero de 8 bits. A continuación se muestra la descripción de cada bit.
Por ejemplos:
- Tenhou.net does not provide all possibility of the above specified combinations. Most online players play on configurations for example "1", "137", "193", "9"
Registrador : Se requieren dos parámetros para inicializar el registrador. El primero es el ID del registrador definido por el usuario, de modo que los desarrolladores puedan nombrar libremente su historial de pruebas.
Después de especificar todas estas configuraciones, simplemente envíe todos estos parámetros a connect_and_play() . ¡¡¡Entonces es hora de ver el show de tu agente de Mahjong!!!
Se deben implementar cuatro funciones para el bot Mahjong, como se muestra en la clase "interfaz" en agentes.ai_interface . Se recomienda que su agente sea heredado de AIInterface. Para obtener una explicación más profunda y un ejemplo simple de estas funciones, consulte la documentación en agentes.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 : basándose en toda la información accesible sobre el estado del juego, esta función devuelve una ficha para descartar. El retorno es un número entero en el rango 0-135. Hay un total de 136 fichas en el juego Mahjong, es decir, 34 tipos de fichas y 4 copias de cada tipo. En diferentes ocasiones utilizamos la forma 34 (cada número corresponde a un tipo de ficha) o la forma 136 (cada número corresponde a una ficha). Tenga en cuenta que aquí la devolución debe estar en el formulario 136.
debería_call_kan : https://en.wikipedia.org/wiki/Japanese_Mahjong#Making_melds_by_calling. Esta función debería decidir si el agente debe llamar a una combinación kan(Quad). Tile136 representa la ficha que algún oponente ha descartado, que puede ser utilizada por el agente para formar la fusión kan. from_opponent indica si el agente forma la combinación kan mediante el descarte del oponente (tres fichas en la mano y el oponente descarta la cuarta) o fichas propias (las cuatro fichas en la mano).
try_to_call_meld : https://en.wikipedia.org/wiki/Japanese_Mahjong#Making_melds_by_calling. Esta función decide si el agente debe llamar a una combinación Pon(Triplet)/Chi(Sequence). Tile136 representa la ficha en forma 136 que algunos oponentes han descartado. might_call_chi indica si el agente podría convocar una combinación de Chi, ya que una combinación de Chi solo se puede convocar con el descarte del oponente en el asiento izquierdo.
can_call_reach : https://en.wikipedia.org/wiki/Japanese_Mahjong#R%C4%ABchi. Esta función decide si el agente debe reclamar a Riichi.
Cuando la clase de agente Mahjong es una subclase de la clase AIInterface , se puede acceder a la información que se enumera a continuación dentro de la clase de agente como se especifica.
Acceso | tipo de datos | Mudable | Descripción |
---|---|---|---|
self.tiles136 | lista de números enteros | Y | azulejos de mano en forma 136 |
auto.mano34 | lista de números enteros | norte | mosaicos de mano en forma 34 (tile34 = mosaico136//4) |
autodescarte136 | lista de números enteros | Y | los descartes del agente en 136-de |
autodescarte34 | lista de números enteros | norte | los descartes del agente en forma 34 |
self.meld136 | lista de instancias de Meld | Y | las fusiones llamadas del agente, instancias de la clase Meld en client.mahjong_meld.py |
self.total_melds34 | lista de lista de números enteros | norte | las fusiones llamadas del agente en forma 34 |
auto.meld34 | lista de lista de números enteros | norte | las llamadas combinaciones pon/chow del agente en forma 34 |
yo.pon34 | lista de lista de números enteros | norte | el pon llamado se fusiona del agente en forma 34 |
yo.chow34 | lista de lista de números enteros | norte | el llamado chow se fusiona del agente en forma 34 |
yo.minkan34 | lista de lista de números enteros | norte | las llamadas combinaciones minkan del agente en forma 34 |
yo.ankan34 | lista de lista de números enteros | norte | las llamadas fusiones ankan del agente en forma 34 |
nombre.propio | cadena | Y | nombre de la cuenta |
autonivel | cadena | Y | nivel de la cuenta |
asiento propio | entero | Y | ID de asiento, el agente siempre tiene 0 |
self.dealer_seat | entero | Y | el ID del asiento del concesionario |
self.is_dealer | booleano | norte | si el agente es distribuidor o no |
self.reach_status | booleano | Y | indica si el agente ha reclamado a Riichi |
self.just_reach() | booleano | norte | si el agente acaba de reclamar a Riichi |
self.tmp_rank | entero | Y | rango del agente en el juego actual |
autopuntuación | entero | Y | puntuación del agente en el juego actual |
self.is_open_hand | booleano | norte | si el agente ya ha llamado a fusiones abiertas |
self.turn_num | entero | norte | el número del turno actual |
self.player_wind | entero | norte | El viento del jugador es un tipo de yaku. |
self.round_wind | entero | norte | El viento redondo es un tipo de yaku. |
self.bonus_honors | lista de números enteros | Y | todas las fichas de personajes que tienen yaku |
Se puede acceder a la instancia de la clase oponente llamando a self.game_table.get_player(i) con i es igual a 1,2,3, que indica la identificación correspondiente del oponente.
Acceso | tipo de datos | Mudable | Descripción |
---|---|---|---|
.descartar136 | lista de números enteros | Y | los descartes del oponente observado en 136-de |
.descartar34 | lista de números enteros | norte | los descartes del oponente observado en forma 34 |
.meld136 | lista de instancias de Meld | Y | las combinaciones convocadas del oponente observado |
.total_melds34 | lista de lista de números enteros | norte | las combinaciones convocadas del oponente observado en forma 34 |
.meld34 | lista de lista de números enteros | norte | las combinaciones pon/chow llamadas del oponente observado en forma 34 |
.pon34 | lista de lista de números enteros | norte | el pon llamado se fusiona del oponente observado en forma 34 |
.chow34 | lista de lista de números enteros | norte | el chow llamado se fusiona del oponente observado en 34 formas |
.minkan34 | lista de lista de números enteros | norte | las llamadas combinaciones minkan del oponente observado en forma 34 |
.ankan34 | lista de lista de números enteros | norte | las fusiones ankan llamadas del oponente observado en forma 34 |
.safe_tiles | lista de números enteros | Y | fichas en formato 34 que son absolutamente seguras para el agente, es decir, el oponente observado no puede ganar con estas fichas |
.nombre | cadena | Y | nombre del oponente |
.nivel | cadena | Y | nivel del oponente |
.asiento | entero | Y | ID del asiento del oponente observado |
.asiento_distribuidor | entero | Y | el ID del asiento del concesionario |
.is_dealer | booleano | norte | si el oponente observado es dealer o no |
.reach_status | booleano | Y | indica si el oponente observado ha reclamado a Riichi |
.just_reach() | booleano | norte | si el oponente observado acaba de reclamar a Riichi |
.tmp_rank | entero | Y | rango del oponente observado en el juego actual |
.puntaje | entero | Y | puntuación del oponente observado en el juego actual |
.is_open_hand | booleano | norte | si el oponente observado ya ha llamado combinaciones abiertas |
.turn_num | entero | norte | el número del turno actual |
.player_wind | entero | norte | El viento del jugador es un tipo de yaku. |
.round_wind | entero | norte | El viento redondo es un tipo de yaku. |
.bonus_honores | lista de números enteros | Y | todas las fichas de personajes que tienen yaku |
Para acceder a información sobre la mesa de juego, se puede llamar a self.game_table
Acceso | tipo de datos | Mudable | Descripción |
---|---|---|---|
.bot | instancia de clase de agente | Y | instancia de clase del agente |
.get_player(yo) | instancia de clase oponente | Y | instancia de clase del oponente, i=1,2,3 |
.asiento_distribuidor | entero | Y | ID de asiento del concesionario |
.bonus_indicator | lista de números enteros | Y | indicadores de bonificación en formato 136 |
.número_redondo | entero | Y | numero redondo |
.reach_sticks | entero | Y | ¿Cuántos palitos de Riichi hay sobre la mesa? El siguiente jugador que gane recibirá todos los puntos de estos palos de Riichi. |
.honba_sticks | entero | Y | ¿Cuántos palos de honba hay sobre la mesa? El jugador tendrá puntos extra según los palos de honba si gana. |
.count_ramaining_tiles | entero | Y | El número de fichas restantes no reveladas. |
.reveló | lista de números enteros | Y | Cada elemento de la lista indica cuántas copias de esta ficha específica se han revelado (descartes, combinaciones abiertas, indicadores de bonificación, etc.) |
.round_win | entero | norte | El viento redondo es un tipo de yaku. |
.bonus_tiles | lista de números enteros | norte | Lista de fichas de bonificación. Cada aparición de fichas de bonificación en fichas de mano cuenta como un yaku. |
.last_discard | entero | norte | El último descarte del oponente, esta ficha es absolutamente segura según las reglas. |
mi profesor Johannes Fürnkranz (Grupo de Ingeniería del Conocimiento de la TU Darmstadt)
mi supervisor Tobias Joppen (TU Darmstadt Knowledge Engineering Group)