Saltar al contenido

Hora del código #13

Estoy con la curiosidad de crear un librojuego para Android usando Godot. Entonces decidí comenzar con el backend para guardar los datos de la historia, se me ocurrió usar Sqlite3 porque investigué y Godot tiene un librería que lee esa base de datos. Este es el esquema inicial que se me ocurrió:

BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS `requisito_tipo` (
    `id`    INTEGER PRIMARY KEY AUTOINCREMENT,
    `nombre`    TEXT
);
CREATE TABLE IF NOT EXISTS `requisito` (
    `id`    INTEGER PRIMARY KEY AUTOINCREMENT,
    `tipo`  INTEGER,
    `nombre`    TEXT,
    `valor` TEXT,
    FOREIGN KEY(`tipo`) REFERENCES `requisito_tipo`(`id`)
);
CREATE TABLE IF NOT EXISTS `pasaje` (
    `id`    INTEGER PRIMARY KEY AUTOINCREMENT,
    `contenido` TEXT
);
CREATE TABLE IF NOT EXISTS `conexion` (
    `origen`    INTEGER,
    `destino`   INTEGER,
    `requisito` INTEGER,
    FOREIGN KEY(`origen`) REFERENCES `pasaje`(`id`),
    FOREIGN KEY(`destino`) REFERENCES `pasaje`(`id`),
    FOREIGN KEY(`requisito`) REFERENCES `requisito`(`id`)
);
CREATE VIEW conexiones as
select
c.origen, c.destino, rt.nombre as tipo_requisito, r.nombre nombre_requisito, r.valor
from conexion as c
join requisito as r on c.requisito = r.id join requisito_tipo as rt on r.tipo = rt.id;
COMMIT;

Los pasajes son los textos de una ubicación o una conversación o simplemente un nodo. Las conexiones son las vías que hay de un pasaje a otro. Los requisitos son las cosas como variables (posee un objeto, ha visitado un lugar, etc.) o tiradas de dados que debe cumplir el lector-jugador para poder acceder al destino de una conexión.

Ya que me funcionó tan bien el Tkinter en mi último proyecto decidí volver a emplearlo, pensé en una interfaz con un menú que permitiera cambiar entre los mantenimientos, en un principio son:

  1. De pasajes y conexiones.
  2. De requisitos.
  3. De tipos de requisitos.

Así es como luce ahora:

Primer código del backend para editar los datos de un librojuego en sqlite3

Primer código del backend para editar los datos de un librojuego en sqlite3

En la columna donde dice requisitos irían los mantenimientos del menú actual. En la columna derecha que es una imagen, pues instalé [Graphviz] y una librería para usar Graphviz en Python y con eso puedo generar un grafo dirigido que me permite ver las conexiones entre pasajes y sus requisitos, por ejemplo; para ir de 2 a 5 el lector-jugador necesita que su estadística de encanto sea como mínimo cinco. O para ir de 4 a 5 necesita haber conocido al personaje no jugador conocido como el borracho. Lo de dado vendría a significar; el lector-jugador necesita lanzar 2 dados de 6 caras y obtener un mínimo de 8 ó se puede usar un rango de 4 a 6, por ejemplo.

Había olvidado que tkinter proveía ttk para darle más estilo (jajaja) a ciertos controles, será de meterle mano. Este es el código que escribí hoy:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
#! /usr/bin/python3

"""
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""

import tkinter as tk
from tkinter import ttk
from graphviz import Digraph
import sqlite3
import base64

conn = sqlite3.connect('librojuego.db')

c = conn.cursor()

c.execute('SELECT * FROM conexiones')
conexiones = c.fetchall()

g = Digraph(format='png')
for i in conexiones:
    if i[2] is None:
        g.edge(str(i[0]), str(i[1]))
    else:
        g.edge(str(i[0]), str(i[1]), label='[{}] {}={}'.format(i[2], i[3], i[4]))

grafico = base64.b64encode(g.pipe())

window = tk.Tk()
window.title("Editor de datos del Librojuego")
window.rowconfigure(0, weight=1)
window.columnconfigure(1, weight=1)

def mostrar_pasajes():
    fr_pasajes.grid(row=0, column=0, sticky="nws")
    fr_requisitos.grid_forget()
    fr_tipos_requisitos.grid_forget()
    window.title("Editor de datos del Librojuego - Pasajes")

def mostrar_requisitos():
    fr_pasajes.grid_forget()
    fr_requisitos.grid(row=0, column=0, sticky="nws")
    fr_tipos_requisitos.grid_forget()
    window.title("Editor de datos del Librojuego - Requisitos")

def mostrar_tipos_requisitos():
    fr_pasajes.grid_forget()
    fr_requisitos.grid_forget()
    fr_tipos_requisitos.grid(row=0, column=0, sticky="nws")
    window.title("Editor de datos del Librojuego - Tipos de requisitos")

menubar = tk.Menu(window)
menubar.add_command(label="Pasajes", command=mostrar_pasajes)
menubar.add_command(label="Requisitos", command=mostrar_requisitos)
menubar.add_command(label="Tipos de requisitos", command=mostrar_tipos_requisitos)

window.config(menu=menubar)

fr_pasajes = ttk.Frame(window)
label_pasajes = ttk.Label(fr_pasajes, text="pasajes")
label_pasajes.grid(row=0, column=0, sticky="nws")

fr_requisitos = ttk.Frame(window)
label_requisitos = ttk.Label(fr_requisitos, text="requisitos")
label_requisitos.grid(row=0, column=0, sticky="nws")

fr_tipos_requisitos = ttk.Frame(window)
label_tipos_requisitos = ttk.Label(fr_tipos_requisitos, text="tipos de requisitos")
label_tipos_requisitos.grid(row=0, column=0, sticky="nws")

imagen_nodos = tk.PhotoImage(data=grafico) 

fr_nodos = ttk.Frame(window)
fr_nodos.grid_columnconfigure(1, weight=1)
fr_nodos.rowconfigure(0, weight=1)

canvas = tk.Canvas(fr_nodos, width=imagen_nodos.width(), height=imagen_nodos.height(), scrollregion=(0,0,500,500))
canvas.create_image(0, 0, image=imagen_nodos, anchor="nw")

scrollbarx = ttk.Scrollbar(fr_nodos, orient="horizontal")
scrollbarx.grid(row=1, column=1, sticky="nes", padx=5, pady=5)
scrollbarx.config(command=canvas.xview)
scrollbary = ttk.Scrollbar(fr_nodos, orient="vertical")
scrollbary.grid(row=0, column=2, sticky="wes", padx=5, pady=5)
scrollbary.config(command=canvas.yview)

canvas.config(xscrollcommand=scrollbarx.set, yscrollcommand=scrollbary.set)
canvas.grid(row=0, column=1, sticky="news", padx=5, pady=5)

fr_nodos.grid(row=0, column=1, sticky="nes")

window.mainloop()