Dungeon of Bits
Aprendiendo informática.
Dungeon of Bits

Creación de un bot para Telegram en Python para jugar a Heroquest - Parte 2.

Segunda parte de la creación de un bot para jugar a Heroquest por Telegram. Cómo controlar el mazo de cartas de tesoro.

hacer un bot de Telegram

Requisitos:

Este tutorial es la segunda parte del Tutorial para crear un bot de Telegram para jugar a Heroquest.

Las imagenes de HeroQuest deben estar en el mismo directorio que el script o se debe cambiar la ruta dentro del mismo script de Python.

¿Cómo funcionan las cartas de tesoro en Heroquest?:

En Heroquest existe un mazo de cartas de tesoro. compuesta por estas 33 cartas:

  • 6 cartas de Monstruo errante.
  • 2 cartas de Suelo Falso.
  • 3 cartas de Flecha de la Pared.
  • 2 cartas de Nada.
  • 1 carta de Agua Bendita.
  • 3 cartas de Poción Curativa.
  • 1 carta de Pócima de Velocidad.
  • 1 carta de Poción de Fuerza.
  • 1 carta de Pócima de Resistencia.
  • 1 carta de Brebaje de Héroes.
  • 1 carta de Cofre del Tesoro.
  • 1 carta de Piedras Preciosas.
  • 1 carta de Un Rubí.
  • 1 carta de Sin Noción del Tiempo.
  • 1 carta de Entre Trapos Viejos.
  • 1 carta de Bajo Una Piedra Suelta.
  • 6 cartas de Pequeña Bolsa de Oro.

En general todas las cartas son positivas salvo las 3 primeras, que son perjudiciales para los jugadores.

Un jugador que busca tesoros roba una carta del mazo, si saca una carta de monstruos o trampas vuelve a dejar la carta en el mazo... PERO si saca una de las otras cartas, la carta se descarta, por lo que desaparece del mazo.

Usando arrays en Python - Representar las cartas del mazo:

Para representar las cartas del mazo usaremos 2 arrays, un array contendrá los nombres de las cartas y el otro contendrá las cantidades totales de cartas y de cada tipo de carta actualmente en el mazo.

Así tendremos estos dos arrays:

#Lista de tesoros:
ListaTesoros = ['monstruoErrante','sueloFalso','flechaPared','nada','aguaBendita','pocionCuracion','pocimaVelocidad','pocionFuerza','pocimaResistencia','brebajeHeroes','cofreTesoro','piedrasPreciosas','rubi','sinNocionTiempo','entreTraposViejos','bajoPiedraSuelta','pequenabolsaoro']
#cantidad de tesoros:
ListaA1 = [33,6,2,3,2,1,3,1,1,1,1,1,1,1,1,1,1,6] 
  • ListaTesoros: El nombre lo guardamos porque lo usaremos para mostrar el fichero de imagen de la carta.
  • ListaA1: Es la lista de objetos que hay en total en el mazo (33) y el total de cartas de cada tipo que hay en una partida de Heroquest de Año1 (En años posteriores la lista de cartas cambiaría).

Concurrencia - Guardando el mazo de cada partida:

Como el bot puede ser llamada desde diferentes chats, o sea, desde diferentes partidas, y las partidas pueden no acabarse en una sesión, debemos tener un sistema de guardado del estado actual de cada mazo de cartas de tesoro.

La forma más fácil es la de usar un fichero, y para saber qué fichero se necesita consultar cada vez usaremos el chat_id, un identificador único de cada chat de Telegram, así nos aseguramos de que no se mezclan las cartas de cada partida.

Creamos una función que se llame barajar y que se llamará cuando los usuarios de un chat quieran jugar una nueva partida de Heroquest (con el comando /barajar o /start).

@bot.message_handler(commands=['barajar','start'])
def barajar(message):    
    chat_id = message.chat.id    
    bot.reply_to(message, "Se ha barajado el mazo de cartas de tesoro.")
    #creamos un fichero de nombre la id de chat desde la que se llama a la función y xon extensión txt: 
    nombreFichero = str(chat_id)+".txt"  
    with open(nombreFichero, 'wb') as filehandle:
        #Se guardan los datos en binario:
        pickle.dump(ListaA1, filehandle)

El fichero tan solo contendrá las cartas de cada tipo que quedan en el mazo.

Si se empieza una nueva partida se debe llamar a la función barajar de nuevo.

Sacar una carta de tesoro:

Para sacar una carta de tesoro, el jugador debe escribir el comando /tesoro o /t.

chat_id = message.chat.id
    nombreFichero = str(chat_id)+".txt" 
    if not(os.path.isfile(nombreFichero)):
        barajar(message)

Para asegurarnos de que existe el fichero del chat comprobamos si existe, de no existir llamamos a la función barajar que lo crearía, si no tendríamos un error al buscar un fichero que no existe.

with open(nombreFichero, 'rb') as filehandle:
        #Se leen los datos como un stream binario
        objetos = pickle.load(filehandle)
        filehandle.close()

Se leen los objetos del fichero y se guardan en un array llamado objetos.

#Se crea un número aleatorio de 1 al valor guardado en objetos[0]:
    random.seed()    
    r = random.randint(1,objetos[0])

Creamos un número aleatorio que vaya de 1 al número total de cartas en el mazo que está en la posición 0 del array objetos.

contador = 0
    i = 1
    while i < len(objetos):
        contador += objetos[i]
        if r <= contador:            
            d = ListaTesoros[i-1]
            #Objetos 1,2 y 3 se devuelven al mazo, tesoros se quitan:
            if i > 3:
                objetos[i]-=1
                objetos[0]-=1  
            break 
        i += 1

Ahora lo que hacemos es contar desde 0 (contador = 0) hasta el número aleatorio que hemos generado r.

Se suma el número de cartas a contador que haya en la posición 1 del array objetos, en nuestro ejemplo serán 6 monstruos errantes.

Si el número aleatorio r es más pequeño o igual a 6 es que el jugador ha sacado un monstruo errante, si r es mayor se le suma al contador el valor de la siguiente posición del array objetos, hasta que el número r sea menor o igual a la suma acumulada de cartas en el contador.

Cuando r sea menor que el acumulado en contador se guarda en d el nombre de la carta que ha salido.

Además si la carta NO ES un Monstruo Errante, Suelo Falso o Flecha en la Pared, se le resta 1 al valor de objetos en esa posición y 1 al total de cartas de tesoro que quedan y se sale del bucle.

imagen = open('/root/'+d+'.jpg', 'rb')
bot.send_photo(chat_id, imagen)

A continuación se genera el nombre del fichero que se corresponde a la carta y se envía un mensaje al chat con la imagen.

#se guarda la lista de cartas:
    with open(nombreFichero, 'wb') as filehandle:
    #Se guardan los datos como un stream binario
        pickle.dump(objetos, filehandle)

Y por último guardamos en el fichero correspondiente al chat desde el que se llamó a la función la cantidad de cartas actualizada (el array objetos).

hacer un bot de Telegram

El código de la función es el siguiente:

@bot.message_handler(commands=['tesoro','t'])
def tesoro(message):
    chat_id = message.chat.id
    nombreFichero = str(chat_id)+".txt" 
    if not(os.path.isfile(nombreFichero)):
        barajar(message)
    with open(nombreFichero, 'rb') as filehandle:
        #Se leen los datos como un stream binario
        objetos = pickle.load(filehandle)
        filehandle.close()
    #Se crea un número aleatorio de 1 al valor guardado en objetos[0]:
    random.seed()    
    r = random.randint(1,objetos[0])
    contador = 0
    i = 1
    while i < len(objetos):
        contador += objetos[i]
        if r <= contador:            
            d = ListaTesoros[i-1]
            #Objetos 1,2 y 3 se devuelven al mazo, tesoros se quitan:
            if i > 3:
                objetos[i]-=1
                objetos[0]-=1  
            break 
        i += 1
    imagen = open('/root/'+d+'.jpg', 'rb')
    bot.send_photo(chat_id, imagen)
    #se guarda la lista de cartas:
    with open(nombreFichero, 'wb') as filehandle:
    #Se guardan los datos como un stream binario
        pickle.dump(objetos, filehandle)

Para que el código funcione se han de añadir al principio del fichero estas dos líneas:

import pickle
import os

Añadir una función de Ayuda al usuario:

Para facilitarle la vida al usuario añadiremos una función llamada ayuda que mostrará los comandos que se pueden usar con el bot:

@bot.message_handler(commands=['help'])
def ayuda(message):
    bot.reply_to(message,"Bienvenido aventurero."+'\n'+"/d1 lanza un dado"+'\n'+"/d2 lanza 2 dados"+"/t o /tesoro buscar tesoro."+'\n'+"/dado lanza un dado de 6 caras"+'\n'+"/start o /barajar reiniciar cartas tesoro.")

hacer un bot de Telegram

Imágenes que se utilizan en el programa:

Las imágenes de los dados y las cartas están en este zip y han sido extraída de Heroquest.es.

Código completo del programa:

El código completo quedaría así:

import telebot 
import random 
import pickle
import os

bot = telebot.TeleBot("TOKEN")

#Lista de tesoros:
ListaTesoros = ['monstruoErrante','sueloFalso','flechaPared','nada','aguaBendita','pocionCuracion','pocimaVelocidad','pocionFuerza','pocimaResistencia','brebajeHeroes','cofreTesoro','piedrasPreciosas','rubi','sinNocionTiempo','entreTraposViejos','bajoPiedraSuelta','pequenabolsaoro']
#cantidad de tesoros:
ListaA1 = [33,6,2,3,2,1,3,1,1,1,1,1,1,1,1,1,1,6]

@bot.message_handler(commands=['dado'])
def dado6Caras(message):
    chat_id = message.chat.id
    random.seed()
    r = random.randint(1,6)
    bot.send_message(chat_id,r)

@bot.message_handler(commands=['d1'])
def dadoHeroquest1(message):
    chat_id = message.chat.id
    random.seed()
    r = random.randint(1,6)
    if r <=2:
        d = "1"
    elif r <= 4:
        d = "2"
    elif r <= 5:
        d = "3"
    else:
        d = "4"
    imagen = open('/root/dado'+str(d)+'.jpg', 'rb')
    bot.send_photo(chat_id, imagen)

@bot.message_handler(commands=['d2'])
def dadoHeroquest2(message):
    chat_id = message.chat.id
    random.seed()
    for x in range(2):
        r = random.randint(1,6)
        if r <=2:
            d = "1"
        elif r <= 4:
            d = "2"
        elif r <= 5:
            d = "3"
        else:
            d = "4"
        imagen = open('/root/dado'+str(d)+'.jpg', 'rb')
        bot.send_photo(chat_id, imagen)

@bot.message_handler(commands=['barajar','start'])
def barajar(message):    
    chat_id = message.chat.id    
    bot.reply_to(message, "Se ha barajado el mazo de cartas de tesoro.")
    #creamos un fichero de nombre la id de chat desde la que se llama a la función y xon extensión txt: 
    nombreFichero = str(chat_id)+".txt"  
    with open(nombreFichero, 'wb') as filehandle:
        #Se guardan los datos en binario:
        pickle.dump(ListaA1, filehandle)

@bot.message_handler(commands=['tesoro','t'])
def tesoro(message):
    chat_id = message.chat.id
    nombreFichero = str(chat_id)+".txt" 
    if not(os.path.isfile(nombreFichero)):
        barajar(message)
    with open(nombreFichero, 'rb') as filehandle:
        #Se leen los datos como un stream binario
        objetos = pickle.load(filehandle)
        filehandle.close()
    #Se crea un número aleatorio de 1 al valor guardado en objetos[0]:
    random.seed()    
    r = random.randint(1,objetos[0])
    contador = 0
    i = 1
    while i < len(objetos):
        contador += objetos[i]
        if r <= contador:            
            d = ListaTesoros[i-1]
            #Objetos 1,2 y 3 se devuelven al mazo, tesoros se quitan:
            if i > 3:
                objetos[i]-=1
                objetos[0]-=1  
            break 
        i += 1
    imagen = open('/root/'+d+'.jpg', 'rb')
    bot.send_photo(chat_id, imagen)
    #se guarda la lista de cartas:
    with open(nombreFichero, 'wb') as filehandle:
    #Se guardan los datos como un stream binario
        pickle.dump(objetos, filehandle)

@bot.message_handler(commands=['help'])
def ayuda(message):
    bot.reply_to(message,"Bienvenido aventurero."+'\n'+"/d1 lanza un dado"+'\n'+"/d2 lanza 2 dados"+"/t o /tesoro buscar tesoro."+'\n'+"/dado lanza un dado de 6 caras"+'\n'+"/start o /barajar reiniciar cartas tesoro.")

bot.polling()

Es importante tener en cuenta que el bot solo funcionará mientras esté corriendo el programa en Python.

Ejecutar el script como un servicio de Linux:

Estos pasos son para Ubuntu, en otras distribuciones puede cambiar alguna ruta de ficheros.

Como hemos comentado el bot solo funcionará mientras esté corriendo el script, así que lo mejor es que habilitemos un servicio para que podamos iniarlo, pararlo y dejar el script funcionando en segundo plano.

Para ello creamos un fichero llamado heroQuestManager.service en el directorio /lib/systemd/system/ que contenga las siguientes líneas:

[Unit]
Description=Heroquest dice Telegram Bot Service
After=multi-user.target
Conflicts=getty@tty1.service

[Service]
Type=simple
ExecStart=/usr/bin/python3 /root/heroQuestManager.py
StandardInput=tty-force

[Install]
WantedBy=multi-user.target
/lib/systemd/system

Teniendo en cuenta que vuestro script estará en un directorio diferente.

Luego ejecutamos:

systemctl daemon-reload
systemctl enable heroQuestManager.service
systemctl start heroQuestManager.service

hacer un bot de Telegram

Y ahora ya tenemos funcional nuestro bot de Python.

Podemos ver si está funcionando con el comando:

service heroQuestManager status

hacer un bot de Telegram

También podemos iniciarlo y pararlo con los comandos:

service heroQuestManager start
service heroQuestManager stop

Ya tienes un bot que automatiza las tareas aleatorias de HeroQuest de forma que todos los jugadores puedan ver el resultado de las acciones aleatorias.