Dagor es un framework de Python que permite programar diversos tipos de jugadores que compiten entre sí para ganar un juego combinacional.

La palabra Dagor significa “batalla” en el idioma sindarin (conocido también como élfico gris), creado por el escritor británico J. R. R. Tolkien, autor del “Señor de los anillos”.

En teoría de juegos, un juego combinacional tiene las siguientes características:

  • Siempre hay dos jugadores que toman turnos para tirar de manera alternada.

  • No hay elementos aleatorios como dados o cartas barajadas.

  • Ambos jugadores tienen información perfecta (no hay información oculta).

  • El juego es finito — debe terminar eventualmente.

  • Usualmente el último jugador en tirar gana.

Muchas de las ideas de este framework fueron tomadas del sitio Gamesman elaborado por Dan Garcia de UC Berkeley.

1. Descarga

El módulo de dagor se puede descargar de la siguiente liga:

  • dagor.zip versión 1.0.3, 3 de noviembre, 2022.

Solo se requiere descomprimir el ZIP y copiar el archivo dagor.py al directorio de trabajo.

2. D10 (destino 10)

2.1. Reglas

Piezas y tablero: Este juego se juega en un tablero de 1 por 10. La posición inicial es un tablero vacío.

Para tirar: Los jugadores se alternan colocando una o dos piezas en las localidades desocupadas de más a la izquierda del tablero. En este juego no hay distinción respecto a las piezas de cada jugador. Nos referiremos al primer jugador en tirar como izquierda, y al otro jugador como derecha.

Para ganar: El primer jugador en colocar la décima pieza en el tablero gana.

2.2. Ejemplo

Tablero inicial:
 _ _ _ _ _ _ _ _ _ _
| | | | | | | | | | |
| | | | | | | | | | |
|_|_|_|_|_|_|_|_|_|_|
 1 2 3 4 5 6 7 8 9 10

Izquierda tira 2
 _ _ _ _ _ _ _ _ _ _
| | | | | | | | | | |
|*|*| | | | | | | | |
|_|_|_|_|_|_|_|_|_|_|
 1 2 3 4 5 6 7 8 9 10

Derecha tira 2
 _ _ _ _ _ _ _ _ _ _
| | | | | | | | | | |
|*|*|*|*| | | | | | |
|_|_|_|_|_|_|_|_|_|_|
 1 2 3 4 5 6 7 8 9 10

Izquierda tira 2
 _ _ _ _ _ _ _ _ _ _
| | | | | | | | | | |
|*|*|*|*|*|*| | | | |
|_|_|_|_|_|_|_|_|_|_|
 1 2 3 4 5 6 7 8 9 10

Derecha tira 1
 _ _ _ _ _ _ _ _ _ _
| | | | | | | | | | |
|*|*|*|*|*|*|*| | | |
|_|_|_|_|_|_|_|_|_|_|
 1 2 3 4 5 6 7 8 9 10

Izquierda tira 2
 _ _ _ _ _ _ _ _ _ _
| | | | | | | | | | |
|*|*|*|*|*|*|*|*|*| |
|_|_|_|_|_|_|_|_|_|_|
 1 2 3 4 5 6 7 8 9 10

Derecha tira 1
 _ _ _ _ _ _ _ _ _ _
| | | | | | | | | | |
|*|*|*|*|*|*|*|*|*|*|
|_|_|_|_|_|_|_|_|_|_|
 1 2 3 4 5 6 7 8 9 10

Derecha resulta ganador

2.3. Utilizando Dagor

El siguiente programa permite a un jugador humano jugar contra un jugador estratégico autónomo:

Archivo: ejemplo1.py
from dagor import JuegoD10, JugadorD10Interactivo, JugadorD10Estrategico (1)

jugador1 = JugadorD10Interactivo('Humano') (2)
jugador2 = JugadorD10Estrategico('Máquina') (3)
juego = JuegoD10(jugador1, jugador2) (4)
juego.inicia() (5)
1 Importamos del módulo dagor las clases que estaremos utilizando en nuestro programa.
2 Creamos un jugador de D10 interactivo.
3 Creamos un jugador de D10 estratégico autónomo.
4 Creamos un juego de D10 y le enviamos los dos jugadores previamente creados.
5 Comenzamos el juego.

Para correrlo, desde la terminal teclear:

$ python ejemplo1.py

2.4. Clases disponibles

Clase Descripción

JuegoD10

Clase que representa un juego de D10 (Destino 10).

JugadorD10

Toda clase que represente un jugador del juego D10 debe heredar de esta clase.

JugadorD10Aleatorio

Jugador de D10 que tira de manera aleatoria.

JugadorD10Estrategico

Jugador de D10 que tira con una estrategia.

JugadorD10Interactivo

Jugador de D10 controlado a partir de una interfaz de usuario en modo texto.

2.5. Programando un nuevo jugador

Para escribir el código de un nuevo jugador solo se requiere:

  1. Definir una clase que herede del tipo de jugador deseado (JugadorD10 en nuestro caso).

  2. Proporcionar la implementación de los siguientes dos métodos abstractos:

    • heuristica(self, posicion): Función heurística que puede utilizar un jugador para rankear la posición enviada como argumento.

    • tira(self, posicion): Invocada automáticamente por el objeto controlador del juego para determinar el tiro de este jugador. Debe devolver un tiro válido a partir de la posición enviada como argumento.

El siguiente código muestra un jugador de D10 cuya estrategia es tirar un 1 siempre, excepto si existe un tiro ganador con un 2:

Archivo: ejemplo2.py
from dagor import JuegoD10, JugadorD10, JugadorD10Aleatorio


class JugadorD10SiempreTiraUno(JugadorD10): (1)

    def heuristica(self, posicion):
        return self.triunfo(posicion) == self.simbolo (2)

    def tira(self, posicion):
        posibles = self.posiciones_siguientes(posicion) (3)
        for p in posibles: (4)
            if self.heuristica(p):
                return p
        return posibles[0] (5)


jugador1 = JugadorD10SiempreTiraUno('OnlyOne')
jugador2 = JugadorD10Aleatorio('RandomBoy')
juego = JuegoD10(jugador1, jugador2)
juego.inicia()
1 Esta es la manera correcta en Python de definir una clase JugadorD10SiempreTiraUno que hereda de otra JugadorD10.
2 Esta función heurística devuelve True si posicion resulta en un tiro ganador para este jugador. De otra forma regresa False.
3 Obtenemos todas todas las posiciones posibles que puede haber en un juego posteriores a posicion.
4 Busca si en esas posibles posiciones existe un tiro ganador.
5 De lo contrario, tira siempre la primera posición, la cual siempre es el tiro con un 1.

2.6. Sobre posicion

La documentación en línea indica la estructura interna que tiene el parámetro posicion para los métodos definidos en el ejemplo anterior. Para consultarla, hay que correr el interprete de Python. Desde la línea de comando teclear:

python

En el shell de Python, debemos hacer lo siguiente:

>>> from dagor import JugadorD10 (1)
>>> help(JugadorD10) (2)
1 Importamos la clase de la cual heredamos en el ejemplo de arriba.
2 Solicitamos la ayuda en línea.

La salida debe ser algo así:

Help on class JugadorD10 in module dagor:

class JugadorD10(Jugador)
 |  Toda clase que represente un jugador del juego D10
 |  debe heredar de esta clase.
 |
 |  La posición que maneja un juego de D10 es una tupla
 |  de la siguiente forma:
 |
 |      (J, S)
 |
 |  En donde:
 |
 |      J: turno actual (nombre del jugador que se indicó al
 |         momento de crearlo)
 |      S: suma hasta el momento (0 al 10)
 |
 |  Por ejemplo:
 |
 |      ('Alfa', 5)
 |
 |  Esta posición indica que el jugador actual es Alfa y que
 |  la suma actual es 5.

2.7. Opciones de inicia

El método inicia de la clase Juego (y sus subclases, como JuegoD10) comienza un encuentro entre los dos jugadores provistos al momento en el que se creó el juego. El encuentro tendrá un cierto número de juegos determinado por el valor del parámetro opcional veces. Cada jugador tira primero de manera alternada en todos los juegos del encuentro. Si el parámetro opcional delta_max tiene un valor mayor a cero arroja la excepción de TiemploLimiteExcedido si el tiempo que tarda el tiro de algún jugador excede delta_max segundos. Al final despliega un resumen del encuentro si veces es mayor a 1.

En el siguiente ejemplo se inicia un encuentro de 10 juegos en donde el tiro de cada jugador debe durar 2 segundos o menos.

juego.inicia(veces=10, delta_max=2)

2.8. Propiedades y funciones de un jugador

Cualquier instancia de las subclases de la clase Jugador (incluyendo JugadorD10 y sus subclases) tienen los siguientes métodos y propiedades:

Nombre Descripción

__init__(self, nombre)

Constructor que inicializa un jugador con nombre.

__str__(self)

Método que convierte este jugador a una cadena de caracteres. El formato usado es: "nombre (alias simbolo) [clase]".

posiciones_siguientes(self, posicion)

Método que devuelve todas todas las posiciones posibles que puede haber en un juego posteriores a posicion.

triunfo(self, posicion)

Método que devuelve el símbolo del jugador que resulta ganador a partir de posicion, o None si no hay un jugador ganador en posicion.

nombre

Propiedad con el nombre de este jugador.

simbolo

Propiedad con el símbolo (o nombre si el símbolo no existe) de este jugador.

contrario

Propiedad con la referencia al oponente de este jugador. Útil para obtener el nombre y símbolo del jugador contrario, por ejemplo: self.contrario.nombre o self.contrario.simbolo.

3. SuperGato

3.1. Reglas

Piezas y tablero: Este juego se juega en un tablero rectangular de n renglones por m columnas, donde 3 ≤ n ≤ 10, 3 ≤ m ≤ 10.

Para tirar: De manera similar al juego de gato, los jugadores alternan su turno colocando su símbolo (X o O) en cualquier localidad vacía del tablero.

Para ganar: El jugador que logre colocar su símbolo en tres localidades consecutivas (horizontal o verticalmente) gana. Si nadie ha ganado y el tablero está completamente lleno, entonces es un empate.

3.2. Clases disponibles

Clase Descripción

JuegoSuperGato

Clase que representa un juego de SuperGato. Al crear una instancia de esta clase, es necesario indicar el número de renglones y columnas del tablero como tercer y cuarto argumento.

JugadorSuperGato

Toda clase que represente un jugador del juego SuperGato debe heredar de esta clase.

JugadorSuperGatoAleatorio

Jugador de SuperGato que tira de manera aleatoria.

JugadorSuperGatoEstrategico

Jugador de SuperGato que tira con una estrategia.

JugadorSuperGatoInteractivo

Jugador de SuperGato controlado a partir de una interfaz de usuario en modo texto.

4. Orugas

4.1. Reglas

Piezas y tablero: Este juego se juega en un tablero rectangular de n renglones por m columnas, donde 4 ≤ n ≤ 10, 4 ≤ m ≤ 10. Inicialmente cada jugador ocupa una localidad del tablero (la cabeza de la oruga), determinada de manera aleatoria.

Para tirar: Cada jugador controla una oruga que crece rápidamente a partir de su cabeza. Durante el turno de un jugador, éste debe seleccionar una localidad contigua vacía en la que pueda crecer la cabeza de su oruga. La oruga puede crecer hacia la localidad de arriba, abajo, izquierda o derecha, mas no en diagonal. Es posible crecer saliéndose de una orilla del tablero y llegar a su correspondiente localidad opuesta.

Para ganar: Un jugador gana cuando su oponente ya no tenga una localidad donde crecer.

En el tablero, el primer jugador usa el símbolo B (blanco) como cabeza y b para el resto del cuerpo. El segundo jugador usa el símbolo N (negro) como cabeza y n para el resto del cuerpo.

4.2. Clases disponibles

Clase Descripción

JuegoOrugas

Clase que representa un juego de Orugas. Al crear una instancia de esta clase, es necesario indicar el número de renglones y columnas del tablero como tercer y cuarto argumento.

JugadorOrugas

Toda clase que represente un jugador del juego Orugas debe heredar de esta clase.

JugadorOrugasAleatorio

Jugador de Orugas que tira de manera aleatoria.

JugadorOrugasInteractivo

Jugador de Orugas controlado a partir de una interfaz de usuario en modo texto.