Estoy desarrollando un bot de tarot en Python que simule la experiencia de una lectura real. Utilizando ChatGPT como motor de lenguaje natural, el bot será capaz de manejar una baraja virtual, crear tiradas personalizadas, y ofrecer interpretaciones detalladas basadas en las cartas seleccionadas.
Con una interfaz tanto para Discord como para web, este proyecto busca brindar acceso a lecturas de tarot a un público amplio, incluyendo aquellos que no tienen una baraja física, quienes buscan una lectura rápida y sencilla, o aquellos que desean reducir su dependencia en lecturas constantes.
El objetivo es crear una herramienta intuitiva y confiable que preserve la esencia de una lectura de tarot tradicional, adaptada a la era digital.
Descripción de proyecto
Creación de aplicación de Python con las capacidades técnicas de un tarotista; manejo de baraja, creación de barajas para propósitos específicos, mezclado de baraja, colocación de cartas y dar su significado. Emplear un LLM para generar interpretaciones de las lecturas generadas. Darle a este dos UIs, una como bot de Discord y otra como aplicación web.
Necesidades que cubre el proyecto
Quienes que por algún motivo no pueden tener una baraja de tarot, pero tienen interés en su uso.
Quienes no tienen energía y/o capacidad para hacer sus propias lecturas.
Quienes tienen dependencia a las lecturas y quieren reemplazarlo.
Actividades identificadas
Parte 1: el core
Manejo de baraja/cartas
Crear sistema de identificadores de las cartas
Método de creación de identificadores
Método de creación de barajas
Mezclado de barajas
Aleatorización de cartas
Mezclado en abanico
Aleatorizar sentido
Colocación de cartas
Jalar cantidades específicas de cartas
Mostrar opciones de selección
Opción de tomar por arriba, por abajo y por en medio
Dar su significado/relaciones por carta
Crear sistema de archivos
Rellenar archivos
Crear sistema de extracción de información de archivos
Crear plantilla de texto para la información.
Crear bucle de trabajo base
Crear diagrama de flujo de las interacciones con el bot
Guionizar interacciones con el bot
Crear bucle
Pruebas y documentación
Crear documentación interna
Crear documentación general externa
Crear unittests
Parte 2: simular humanidad
Integrar openai en el bucle de trabajo
Parte 3: Darle GUI
Crear bot de Discord
Crear aplicación web
Sobre el tarot
El tarot, en esencia, es un sistema de símbolos y arquetipos que se utiliza para la introspección y la orientación. Es una herramienta que ofrece perspectivas sobre diversas situaciones de la vida y ayudando a los consultantes a conectar con su intuición y, para algunos, con guías espirituales.
De acuerdo a Wikipedia, el tarot (originalmente conocido como trionfi y posteriormente como tarocchi o tarocks) es una baraja de naipes a menudo utilizada desde por lo menos mediados del siglo XV en varias partes de Europa para jugar juegos de cartas tales como el Tarocchini.
A finales del siglo XVIII, ocultistas franceses hicieron afirmaciones elaboradas, pero no corroboradas, sobre la historia y significado de las cartas, lo que llevó a la aparición de barajas personalizadas para ser usadas en la adivinación usando las cartas del tarot para obtener conocimiento o intuiciones sobre el pasado, el presente o el futuro.
Es usado desde entonces como medio de consulta e interpretación de hechos (presentes, pasados o futuros), sueños, percepciones o estados emocionales, y constituye, pues, un tipo de cartomancia.
Como se lee el tarot.
En las lecturas hay dos partes; la tirada, que puede considerarse una receta y las cartas, que vendrian a ser los ingredientes.
La tirada es un patron de colocacion de cartas, donde a cada lugar se le da un area o se le hace una pregunta.
El trabajo del tarotista es interpretar como la carta responde a la pregunta o describe el area.
Sobre las cartas
El tarot está compuesto de 78 cartas, divididas en 22 arcanos mayores y 56 menores, que a su vez se dividen en 4 palos; espadas, bastos, copas y oros/pentáculos. Cada uno con 14 cartas, numerados del 1 al 10, y después sota, caballero, reina y rey.
El arte de las cartas representa el significado de estas y cuenta una historia.
El significado cambia acorde a la dirección, teniendo un significado al estar normal y otro al estar invertida.
Cada uno de los palos tiene un area de influencia y un elemento relacionado.
De entre los arcanos menores, 36 tienen una relación con un demonio del Ars Goetia. 9 por palo, las numeradas del 2 al 10. Cada una de estas cartas tiene relación con 2 de los 72 demonios. Uno diurno y uno nocturno.
Los arcanos mayores suelen llevar la numeracion romana y algunos de ellos estan relacionados con un arquetipo jungeano.
Como mencioné en la introducción, las barajas del tarot se dividen en 2 grupos: la arcana mayor, que contiene 22 cartas, y la arcana menor con 56 cartas divididas en 4 palos, contando cada palo con 14 cartas.
Entonces, el tarot tiene 5 grupos de cartas. Para facilitar el manejo de las cartas, decidí darles un identificador alfanumérico único con el formato G00. G siendo el identificador del grupo y 00 el número de la carta.
Identificadores de grupos.
Grupo
Id
Min.
Max.
Arcana mayor
M
00
21
Varas / Bastos
B
01
14
Espadas
E
01
14
Copas
C
01
14
Oros / Pentáculos
P
01
14
Al principio consideré 2 opciones para la identificación: la alfanumérica, ganadora, y un potencial identificador numérico, descartado por el pecado de dificultar el mantenimiento, la reutilización del módulo y los archivos con la información de las cartas. Esta decisión, a la larga, demostraría ser la mejor.
La parte "alfa" de alfanumérica fue fácil de definir; una letra distinta para cada grupo, listo. M de arcana mayor, E de espadas, C de copas, B de bastos (V de varas, no, siendo completamente sincera, no me gustaba cómo se veía junto con las otras) y P de pentáculos (porque, nuevamente, no me gustaba cómo se veía la O de oros). Creando la nomenclatura MECBP, pronunciado mec-bep.
La parte "numérica" de alfanumérica presenta aún menos dificultades. El uso del sistema hexadecimal fue descartado inmediatamente, pues considerando que la arcana mayor tiene 22 integrantes, no ahorraría espacio. Por practicidad, todos los identificadores debían tener exactamente la misma cantidad de caracteres; por ello, se determinó el uso de 2 posiciones numéricas.
La otra característica, la posición
Hay otra variable muy importante a considerar durante las lecturas, y esa es la posición en la que se encuentran las cartas, pudiendo ser esta recta o invertida. Recta, normal, para arriba. Invertida, al revés, para abajo.
Para facilitar la posible consulta de los datos, añadí una tercera posición, la neutra. Teniendo entonces tres posiciones Recta, Inversa y Neutra. Creando la nomenclatura RIN, pronunciada "rin".
Identificadores de posición.
Posición
Identificador
Recta
R
Inversa
I
Neutra
N
El sistema: G00P
Llamé al sistema completo G00P, pronunciado goop; G de grupo (MECBP), los dos ceros de las dos posiciones numéricas y la P de posición (RIN). Este sistema es manejable en archivos, fácil de acceder y mantener.
Todo el manejo de la baraja se realiza con la clase Deck. Esta tiene 3 y solamente 3 atributos:
deck (lista, inicialmente vacía):
Un listado de los identificadores G00P que contiene la baraja
hand (lista, inicialmente vacía):
Listado de las cartas seleccionadas para ser colocadas en el spread / tirada
suits (set ("E","C","B","P")):
Los posibles valores de los palos en G00P, omitiendo M, ya que este se maneja aparte.
Considerando que las posiciones R o I se dan al barajear, la posición N se coloca por defecto.
Armando barajas
Muchas tiradas no requieren de la baraja completa; para obtener respuestas específicas, se puede reducir a un grupo específico de cartas.
Algunas requieren únicamente a los arcanos mayores, otras, solo un palo de los arcanos menores, porque se busca consultar la numeración. Las lecturas enfocadas a los goetia, requerirán únicamente las cartas con relación goetia.
Para reducir las barajas, yo suelo hacer las siguientes preguntas:
¿Se incluyen los arcanos mayores?
Sí (pasar a P2)
No (pasar a P3)
¿Se incluyen únicamente los arcanos mayores?
Sí (baraja terminada, solo arcanos mayores)
No (pasar a P3)
¿Se incluyen todos los palos de los arcanos menores?
Sí (pasar a p5)
No (pasar a p4)
¿Qué palos se incluyen?
Selección múltiple, mínimo 1:
Espadas
Copas
Varas
Oros
Random, no importa cual.
Pasar a P5
¿Se incluyen todos los números?
Sí (baraja terminada)
No (pasar a P6)
¿Qué números se incluyen?
Seleccionar 1:
Solo las numeradas (del 1 al 10)
Solo las Goetia (del 2 al 10)
Baraja terminada
Características de barajas
Punto
Posibles
Incluir arcana mayor
Sí / no / únicamente
Palos de la arcana menor que incluyen.
Espadas / copas / varas / oros / la que sea
Números de arcana menor que incluyen.
Todos / numerados/ goetia.
Código para construcción de baraja.
36
def build_deck( self,
37
major: bool = True,
38
all_suits: bool = True,
39
suits: list = False,
40
goetia: bool = False,
41
numbers: bool = False):
42
"""builds deck, to be called from create_deck()
43
44
Args:
45
major (bool, optional):if major arcana is included. Defaults to True.
46
all_suits (bool, optional):if all minor arcana are included. Defaults to True.
47
suits (list, optional): wich minor arcana suits are included. Defaults to False.
48
goetia (bool, optional):if only goetia related cards are included. Defaults to False.
49
numbers (bool, optional):if only numbered cards are included. Defaults to False.
Considerando estos puntos, definí reglas de creación de baraja, que funcionan como variables de comando. Siendo estas:
onlymajor:
Únicamente arcana mayor
nomajor:
La arcana mayor no se incluye
suite:
Se incluye el palo de espadas
suitc:
Se incluye el palo de copas
suitb:
Se incluye el palo de varas
suitp:
Se incluye el palo de oros
suitr:
Se incluye un palo aleatorio, estos pueden acumularse, siendo el máximo reconocido 3. Superando estos, simplemente se añaden todos los palos a la lista de reglas.
numbered:
De la arcana menor, solo las cartas numeradas (del 1 al 10)
goetia:
De la arcana menor, solo las cartas con relación goetia (del 2 al 10)
Código para creación baraja.
72
def create_deck(self, rules: str = ""):
73
"""creates a deck based on given rules
74
75
Args:
76
rules (str, optional): rules to follow. Defaults to "" (no rules).
77
78
Raises:
79
ValueError: in case deck is empty
80
"""
81
if "onlymajor" in rules:
82
self.build_deck(all_suits = False)
83
return
84
85
if "suitr" in rules:
86
tot = rules.count("suitr")
87
if tot > 3:
88
rules += " ".join([f"suit{y.lower()}" for y in self.suits])
89
else:
90
poss = self.suits
91
shuffle(poss)
92
rules += " ".join([f"suit{y.lower()}" for y in poss[:tot]])
93
94
suits = [x[-1]for x in
95
[f"suit{y.lower()}" for y in self.suits]
96
if x in rules]
97
98
self.build_deck("nomajor" not in rules,
99
len(suits) == 0, suits,
100
"goetia" in rules,
101
"numbered" in rules)
102
103
if len(self.deck) == 0:
104
raise ValueError(f"cant work with an empty deck check rules {rules}")
Ya con la baraja hecha, el primer cambio que se le hace es barajearla. Este proceso les da una posición (RI) y al aleatorizarlas se da ingreso a la suerte y se puede realizar la lectura.
Al no tener las cartas físicas, el mezclado y la asignación de posición se realizan por separado. Veamos primero la posición.
Trabajando el RI: generando posición
Para la generación de posición se usa una "lucky string" o cadena de la suerte. Esta, a diferencia de la de Carmen, no se pierde. Se genera una string con una cantidad aleatoria de R e Is, entre 5 y 15 + una pequeña varianza dada por los "tips" que se obtienen del usuario.
Esta cadena de Rs e Is se convierte en lista y se aleatoriza. Con esto, carta por carta, se elige un punto igual aleatorio de la cadena.
RIN: Generar posición.
106
def generate_position( self, tips:list):
107
"""generates card position
108
109
Args:
110
tips (list, optional): numbers that modify luck for positions. Defaults to [1,3].
¿Qué tanto puede variar la cantidad de cartas rectas e invertidas en una baraja usando este algoritmo? Para refinar los datos de creación de baraja, genere las posiciones de una baraja completa 250 mil veces y recopile las proporciones de R e I. En la siguiente tabla puede verse la cantidad de cartas invertidas presentes en esta muestra.
Datos del muestreo de 250,000 barajeadas
Is presentes en %
Is presentes
Ocurrencias
01.39%
1
0007
02.78%
2
0017
04.17%
3
0070
05.56%
4
0166
06.94%
5
0325
08.33%
6
0567
09.72%
7
0819
11.11%
8
1133
12.50%
9
1489
13.89%
10
1782
15.28%
11
2133
16.67%
12
2334
18.06%
13
2815
19.44%
14
3003
20.83%
15
3428
22.22%
16
3823
23.61%
17
4297
25.00%
18
4473
26.39%
19
4750
27.78%
20
5219
29.17%
21
5569
30.56%
22
5967
31.94%
23
6105
33.33%
24
6474
34.72%
25
6827
36.11%
26
7001
37.50%
27
7435
38.89%
28
7770
40.28%
29
7984
41.67%
30
8053
43.06%
31
8439
44.44%
32
8540
45.83%
33
8623
47.22%
34
8740
48.61%
35
8395
50.00%
36
8485
51.39%
37
8442
52.78%
38
8025
54.17%
39
7539
55.56%
40
7155
56.94%
41
6825
58.33%
42
6319
59.72%
43
5732
61.11%
44
5455
62.50%
45
4845
63.89%
46
4353
65.28%
47
3814
66.67%
48
3485
68.06%
49
2986
69.45%
50
2573
70.83%
51
2139
72.22%
52
1782
73.61%
53
1435
75.00%
54
1110
76.39%
55
0872
77.78%
56
0611
79.17%
57
0498
80.56%
58
0359
81.95%
59
0242
83.33%
60
0151
84.72%
61
0091
86.11%
62
0047
87.50%
63
0021
88.89%
64
0020
90.28%
65
0010
91.67%
66
0005
93.06%
67
0002
Gráfico de frecuencia de I
Barajeado por la maquina
Para el barajeado de naturaleza completamente digital, computacional y difícilmente replicable por el humano, decidí usar el método shuffle del módulo random (te amamos random). Si bien muchas teclas se han desgastado discutiendo si la aleatorización computacional es válida o fiable, porque, realmente, no es aleatoria, sino que es generada, el barajeado humano tampoco es tan aleatorio.
Para esta aplicación, los generadores de números pseudoaleatorios (PRNGs) que podemos considerar el "barajeado" de la máquina; a pesar de ser influenciado por los nanosegundos, la entropia y los fotones, es perfecto.
Los humanos barajean por lo general de dos maneras; en abanico y en bloque o corte. Llamé "abanico" el dividir la baraja en dos o más partes y mezclarlas, si bien hay muchísimas técnicas para hacer esto físicamente, solo buscamos emularlo lo más cercano posible. La otra forma es cortar o de bloque. En este, se divide la baraja en varias partes y se cambia la posición en la que estaban, sin mezclar los bloques entre sí.
Estas dos comparten una característica: se tiene que dividir la baraja en varias (mínimo 2) partes. Para esa labor, cree un método estático. Y otro para verificar que la cantidad de partes que se busca cortar sea viable, es decir, si me pidieras cortar una baraja que tiene solo la arcana mayor (de 22 cartas) en 25 partes, sería imposible. Si me pides que la parta en 22 incluso, sería algo más cercano al hard_mix, se perdería por completo la "energía" o entropía que lleva la baraja.
Partir baraja (estatico)
122
@staticmethod
123
def cutted(bunch:list, parts: int = 5):
124
"""cuts a list in given parts
125
126
Args:
127
bunch (list): list to be cutted
128
parts (int, optional): parts to be cutted in. Defaults to 5.
El mínimo de partes es 2, se necesitan mínimo dos partes para que se pueda hablar de partes en plural, pero ¿por qué la cantidad máxima de partes es una cuarta parte de la cantidad de cartas en la baraja? Bueno, por una pequeña limitación que me encontré durante una de las primeras pruebas de la clase ya integrada. Un problema proveniente de mi propia ambición: la creación de barajas goetia.
Si bien en la práctica cualquier lectura que se limite a las cartas goetia incluye todos los palos, por cómo funciona la creación de las barajas, es perfectamente viable pedir una baraja que tenga solo un palo y solo las cartas goetia. Por ello, que la baraja más pequeña posible es una de 8 cartas.
Inicialmente, el número máximo de partes era una quinta parte de la cantidad de cartas. En mi consideración, este número era ideal. La baraja más pequeña de uso común es la numerada de un palo, con 10 integrantes, 10 entre 5 da 2, dos integrantes mínimos por parte. Por supuesto, esto pasa a ser imposible en la baraja más pequeña creable, 8 entre cinco da 1.6 y al momento de crear el rango de posibilidades, el número máximo resulta menor que el mínimo.
Podríamos, por supuesto, hacer imposible la creación de una baraja así en la GUI, pero si alguien modificara los archivos para que pidan una baraja así, el crearla resultaría en un error fatal. El peor tipo de errores.
Verificar si la cantidad de partes es segura
139
def parts_check( self, parts : int):
140
"""checks if given parts is a valid number
141
142
Args:
143
parts (int): parts we want to cut deck into
144
145
Returns:
146
int: actual parts it will be cut into
147
"""
148
if parts >= len(self.deck) // 4 or parts < 2:
149
return randint(2, len(self.deck) // 4)
150
return parts
Abanico
Técnicamente, ocurren 4 mezclados en esta función. Primero, el orden de las partes se revuelve. Segundo, cada una de las partes se revuelve entre sí. Tercero, las partes se unen en abanico, mezclándose entre ellas y creando nuevas partes y, por cuarto y último, las nuevas partes también se revuelve.
Después de mucha prueba y mucho error, quedé con esta función que permite la existencia de "pegados" (cuando dos cartas son consecutivas) esporádicos. Y se asegura de que la cantidad máxima de "pegados" sea 2. El resultado de esto es una mezcla de tarjetas que se siente natural y se mantiene aleatoria.
Mezclado de abanico.
152
def fan_mix( self, parts:int = 5):
153
"""mix deck in fan
154
155
Args:
156
parts (int, optional): parts deck will be cutted into. Defaults to 5.
"""pull a certain amount of cards from top of deck to hand
181
182
Args:
183
num (int): num of cards to pull
184
"""
185
if num > len(self.deck):
186
raise ValueError(f"Cant pull {num} from deck, theres only {len(self.deck)} cards")
187
if pull_from[0].lower() == "b":
188
self.hand += [self.deck.pop(-1)for x in range(num)]
189
elif pull_from[0].lower() == "m":
190
self.hand += [ self.deck.pop (len(self.deck) // 2)for x in range(num)]
191
else:
192
self.hand += [self.deck.pop(0)for x in range(num)]
Las cartas
Creando una guía de las cartas del Tarot
Al momento de redactar esta parte, te puedo decir, con profundo pesar y completa certeza, que esta fue la parte más agobiante, repetitiva y poco interesante de todo el proyecto.
En parte, me temo, porque en esto no recibí muchísimos conocimientos novedosos y cuando lo hice, tuve que usar cada milinewton de mi fuerza de voluntad para no caer en un agujero de conejo, que si bien me haría más sabia, podría descarrilar el proyecto por completo.
Más abajo puedes navegar cómodamente la información reunida sobre las cartas y los demonios del Ars Goetia.
Pero esa es la versión para consulta de humanos, la tercera versión para consulta humana, generada a partir de la versión para consulta del programa, que, a su vez, se derivó de la segunda versión para consulta humana, la cual es... complicada de navegar.
La versión para consulta desde el código es un conjunto de archivos json, con diferencias para la arcana mayor y la menor y un archivo extra por cada uno de los palos.
A continuación te muestro ejemplos de los 3 tipos de archivos para las cartas; E01 (menor sin goetia), E02 (menor con goetia) y M00 (mayor).
Json de E01.
0
{
1
"id": "E01",
2
"name_card": "As de Espadas",
3
"card_power": "Es un indicador de nuevos comienzos, oportunidades y potencial.",
4
"normal":[
5
"Conquista", "Triunfo logrado a pesar de los problemas", "Actividad intensa", "Gestación o parto"
6
],
7
"invert":[
8
"Desastre o conquista seguida por un desastre", "Gran pérdida", "Muerte violenta", "Infertilidad"
9
]
10
}
Json de E02.
0
{
1
"id": "E02",
2
"name_card": "Dos de Espadas",
3
"card_power": "Simboliza dualidad, equilibrio y colaboración.",
"Deslealtad", "Cambios, a veces en la dirección equivocada", "Peleas"
9
],
10
"days":{
11
"start":"09/23",
12
"end": "10/02"
13
},
14
"d_goetia":{
15
"name":"Sallos",
16
"number": 19,
17
"title": "Duque",
18
"planet": "Venus",
19
"metal": "Cobre",
20
"complete_title": "Es un gran y poderoso duque",
21
"descrip": "Aparece en la forma de un valiente soldado montado en un cocodrilo, con una corona ducal en la cabeza. Es muy pacífico.",
22
"skills": "Causa amor",
23
"legions": "30 legiones de espíritus."
24
},
25
"n_goetia":{
26
"name":"Orbas",
27
"number": 55,
28
"title": "Príncipe",
29
"planet": "Jupiter",
30
"metal": "Estaño",
31
"complete_title": "Es un príncipe grande y poderoso",
32
"descrip": "Al principio parece un caballo; pero después de la orden del exorcista adopta la imagen de un hombre.",
33
"skills": "Su oficio es descubrir todas las cosas. pasado, presente y por venir; también para dar Dignidades, y Prelaturas, y el favor de amigos y de enemigos. Da verdaderas respuestas de la divinidad, y de la creación del mundo. Es muy fiel al Exorcista, y a la voluntad. No permitas que sea tentado por ningún Espíritu.",
34
"legions": "20 Legiones de Espíritus."
35
}
36
}
Json de M00.
0
{
1
"id": "M00",
2
"roman_num": "O",
3
"name_card": "El Loco",
4
"card_power": "Simboliza el inicio de un ciclo nuevo, la confianza en el proceso y la valentía con la que te embarcas en un viaje desconocido.",
5
"descrip": "El feliz viajero que ve el mundo a través de los ojos de un niño",
6
"jung": "El inocente",
7
"powers":[
8
"Libertad", "Búsqueda de nuevas experiencias", "Espontaneidad"
9
],
10
"normal":[
11
"Representa inocencia", "Señala el comienzo auspicioso de nuevas aventuras y oportunidades", "También simboliza la voluntad entusiasta de hacer el ridículo y alcanzar el propio ser superior."
12
],
13
"invert":[
14
"Indica que se han tomado o son inminentes las decisiones equivocadas."
15
]
16
}
Datos recopilados sobre las cartas.
Los datos contenidos en este libro de calculo son derivados de los archivos json creados para el bot.
Puedes acceder al documento con mas comodidad y comentarlo aquí.