Pen & Paper - Spielsysteme > Savage Worlds

Savage Worlds Char Generator

<< < (4/9) > >>

Thallion:
Danke für dein Feedback. Ich verstehe was du meinst. Ich mach wohl erst einmal so weiter, weil es ja auch irgendwie ein Experiment ist, was KI so hauptsächlich alleine anbietet, ohne viel zu starke Optimierung vom User. Ich selbst hab aber schon ein Menge dazu gelernt.
Vielleicht fange ich danach nochmal neu an mit einer modernen GUI und etwas methodischer.
Macht Spaß, sich nach Jahren wieder mit so etwas zu beschäftigen.

Thallion:
@sma: Der Tipp mit dem MVVM hat mich deutlich voran gebracht. Der Code ist nun viel ordentlicher. Durch die Model Trennung kann ich jetzt auch mal verschiedene GUIs ausprobieren.


--- Code: ---import tkinter as tk
import math
import json
from tkinter import ttk
from tkinter import messagebox
from tkinter import filedialog
from fpdf import FPDF

class CharakterModel:
    def __init__(self):
        self.vermoegen = 1000  # Startkapital, anpassen nach Bedarf
        self.attribute = {
            'Geschicklichkeit': ['d4', 'd6', 'd8', 'd10', 'd12', 'd12+1', 'd12+2'],
            'Verstand': ['d4', 'd6', 'd8', 'd10', 'd12', 'd12+1', 'd12+2'],
            'Willenskraft': ['d4', 'd6', 'd8', 'd10', 'd12', 'd12+1', 'd12+2'],
            'Stärke': ['d4', 'd6', 'd8', 'd10', 'd12', 'd12+1', 'd12+2'],
            'Konstitution': ['d4', 'd6', 'd8', 'd10', 'd12', 'd12+1', 'd12+2']
        }
        self.fertigkeiten = {
            'Allgemeinwissen': ['d4', 'd6', 'd8', 'd10', 'd12', 'd12+1', 'd12+2'],
            'Athletik': ['d4', 'd6', 'd8', 'd10', 'd12', 'd12+1', 'd12+2'],
            'Überreden': ['d4', 'd6', 'd8', 'd10', 'd12', 'd12+1', 'd12+2'],
            'Wahrnehmung': ['d4', 'd6', 'd8', 'd10', 'd12', 'd12+1', 'd12+2'],
            'Heimlichkeit': ['d4', 'd6', 'd8', 'd10', 'd12', 'd12+1', 'd12+2'],
            'Kämpfen': ['d4', 'd6', 'd8', 'd10', 'd12', 'd12+1', 'd12+2'],
            'Schießen': ['d4', 'd6', 'd8', 'd10', 'd12', 'd12+1', 'd12+2']
        }
        self.maechte = [
            'Abwehren',
            'Arkaner Schutz',
            'Arkanes entdecken/verbergen',
            'Aufheben',
            'Barriere'
        ]
        self.talente = [
            'Aristokrat', 
            'Arkane Resistenz',
            'Starke Arkane Resistenz',
            'Arkaner Hintergrund',
            'Attraktiv'
        ]
        self.handicaps = [
            'fies', 
            'alt',
            'loyal',
            'langsam'
        ]
        self.waffen = [
            'Schwert', 
            'Axt',
            'Hammer',
        ]
        self.ausruestung = {
            'Seil': {'Kosten': 300, 'Gewicht': 20},
            'Dietrich': {'Kosten': 300, 'Gewicht': 20},
            'Lederrüstung': {'Kosten': 300, 'Gewicht': 30},
            'Pferd': {'Kosten': 300, 'Gewicht': 0},
            'Streitross': {'Kosten': 750, 'Gewicht': 0},
            'Sattel': {'Kosten': 10, 'Gewicht': 5}
        }
        self.grundfertigkeiten = ['Allgemeinwissen', 'Athletik', 'Heimlichkeit', 'Überreden', 'Wahrnehmung']
        self.charakter_daten = {'Attribute': {}, 'Fertigkeiten': {}, 'Mächte': {}, 'Abgeleitete Werte': {},'Handicaps': {}, 'Talente': {}, 'Ausrüstung': {}, 'Waffen': {}}
        self.charakter_daten_selected = {fertigkeit: False for fertigkeit in self.grundfertigkeiten}

    def speichern_in_datei(self, dateiname):
        with open(dateiname, 'w') as datei:
            json.dump(self.charakter_daten, datei)

    def laden_aus_datei(self, dateiname):
        with open(dateiname, 'r') as datei:
            self.charakter_daten = json.load(datei)

    def berechne_abgeleitete_werte(self):
        # Bewegungsweite ist standardmäßig 6
        bewegungsweite = "6\""
        # Parade ist 2 plus der halbe Kämpfen-Würfel
        kaempfen_wuerfel = self.charakter_daten['Fertigkeiten'].get('Kämpfen', 'd4')
        parade = 2 + math.ceil(int(kaempfen_wuerfel[1:].split('+')[0]) / 2)
        # Robustheit ist 2 plus der halbe Konstitution-Würfel, aufgerundet bei ungeraden Werten
        konstitution_wuerfel = self.charakter_daten['Attribute'].get('Konstitution', 'd4')
        konstitution_basis = int(konstitution_wuerfel[1:].split('+')[0])
        konstitution_mod = sum(int(mod) for mod in konstitution_wuerfel[1:].split('+')[1:])
        robustheit = 2 + math.ceil((konstitution_basis + konstitution_mod) / 2)
        return {'Bewegungsweite': bewegungsweite, 'Parade': parade, 'Robustheit': robustheit}

    def speichern(self):
        self.charakter_daten['Abgeleitete Werte'] = self.berechne_abgeleitete_werte()

    def kaufe_ausruestung(self, ausruest):
        kosten = self.ausruestung[ausruest]['Kosten']
        if self.vermoegen >= kosten:
            self.vermoegen -= kosten
            return True, f"{ausruest} wurde zum Charakterbogen hinzugefügt."
        else:
            return False, "Du hast nicht genug Gold, um diese Ausrüstung zu kaufen."

class CharakterView:
    def __init__(self, root, model):
        self.root = root
        self.model = model

        # Notebook (Tab-Container) erstellen
        self.notebook = ttk.Notebook(root)
        self.notebook.pack(expand=True, fill='both')

        # Tabs erstellen
        self.tabs = {name: ttk.Frame(self.notebook) for name in ['Attribute', 'Fertigkeiten', 'Mächte', 'Talente', 'Handicaps', 'Ausrüstung', 'Waffen', 'Hintergrund', 'Charakterbogen']}
        for name, tab in self.tabs.items():
            self.notebook.add(tab, text=name)
        self.notebook.pack(expand=True, fill='both')

        self.charakter_daten_values_attribute = {}
        self.charakter_daten_values_fertigkeiten = {}
        self.charakter_daten_selected_fertigkeiten = {}

        self.eingabe_attribute()
        self.eingabe_fertigkeiten()
        self.eingabe_eigenschaften('Mächte', self.model.maechte)
        self.eingabe_eigenschaften('Talente', self.model.talente)
        self.eingabe_eigenschaften('Handicaps', self.model.handicaps)
        self.eingabe_ausruestung()
        self.eingabe_waffen()
        self.ausgabe_fenster(self.model.charakter_daten)

    def speichern_in_datei(self):
        dateiname = filedialog.asksaveasfilename(defaultextension=".json")
        if dateiname:
            self.model.speichern_in_datei(dateiname)
            messagebox.showinfo("Erfolg", "Charakter gespeichert!")

    def laden_aus_datei(self):
        dateiname = filedialog.askopenfilename(defaultextension=".json")
        if dateiname:
            self.model.laden_aus_datei(dateiname)
            self.ausgabe_fenster(self.model.charakter_daten)
            self.update_eingabe_felder()
            messagebox.showinfo("Erfolg", "Charakter geladen!")

    def update_eingabe_felder(self):
        for attr, combobox in self.charakter_daten_values_attribute.items():
            combobox.set(self.model.charakter_daten['Attribute'].get(attr, 'd4'))
        for fert, combobox in self.charakter_daten_values_fertigkeiten.items():
            if fert in self.model.charakter_daten['Fertigkeiten']:
                combobox.set(self.model.charakter_daten['Fertigkeiten'][fert])
                self.charakter_daten_selected_fertigkeiten[fert].set(True)
            else:
                self.charakter_daten_selected_fertigkeiten[fert].set(False)
        for macht in self.model.maechte:
            self.charakter_daten_selected_fertigkeiten[macht].set(macht in self.model.charakter_daten['Mächte'])
        for talent in self.model.talente:
            self.charakter_daten_selected_fertigkeiten[talent].set(talent in self.model.charakter_daten['Talente'])
        for handicap in self.model.handicaps:
            self.charakter_daten_selected_fertigkeiten[handicap].set(handicap in self.model.charakter_daten['Handicaps'])
        for ausruest in self.model.ausruestung:
            self.charakter_daten_selected_fertigkeiten[ausruest].set(ausruest in self.model.charakter_daten['Ausrüstung'])
        for waffe in self.model.waffen:
            self.charakter_daten_selected_fertigkeiten[waffe].set(waffe in self.model.charakter_daten['Waffen'])

    def drucken_als_pdf(self):
        dateiname = filedialog.asksaveasfilename(defaultextension=".pdf")
        if dateiname:
            pdf = FPDF()
            pdf.add_page()
            pdf.set_font("Arial", size=12)
            pdf.cell(200, 10, txt="Charakterbogen", ln=True, align='C')
            for kategorie, daten in self.model.charakter_daten.items():
                pdf.cell(200, 10, txt=kategorie, ln=True, align='L')
                for key, value in daten.items():
                    pdf.cell(200, 10, txt=f"{key}: {value}", ln=True, align='L')
            pdf.output(dateiname)
            messagebox.showinfo("Erfolg", "Charakterbogen gedruckt!")

    def eingabe_attribute(self):
        tab = self.tabs['Attribute']
        for i, (attr, werte) in enumerate(self.model.attribute.items()):
            label = tk.Label(tab, text=attr)
            label.grid(row=i, column=0, sticky='w')
            combobox = ttk.Combobox(tab, values=werte)
            combobox.current(0)
            combobox.grid(row=i, column=1, sticky='w')
            self.charakter_daten_values_attribute[attr] = combobox

        tk.Button(tab, text="Speichern", command=self.speichern_attribute).grid(row=len(self.model.attribute), columnspan=2)

    def speichern_attribute(self):
        self.model.charakter_daten['Attribute'] = {attr: combobox.get() for attr, combobox in self.charakter_daten_values_attribute.items()}
        self.model.speichern()
        self.ausgabe_fenster(self.model.charakter_daten)
        messagebox.showinfo("Erfolg", "Attribute gespeichert!")

    def eingabe_fertigkeiten(self):
        tab = self.tabs['Fertigkeiten']
        for i, (fert, werte) in enumerate(self.model.fertigkeiten.items()):
            label = tk.Label(tab, text=fert)
            label.grid(row=i, column=0, sticky='w')
            combobox = ttk.Combobox(tab, values=werte)
            combobox.current(0)
            combobox.grid(row=i, column=1, sticky='w')
            self.charakter_daten_values_fertigkeiten[fert] = combobox

            selected = tk.BooleanVar(value=fert in self.model.grundfertigkeiten)
            self.charakter_daten_selected_fertigkeiten[fert] = selected
            checkbox = tk.Checkbutton(tab, variable=selected)
            checkbox.grid(row=i, column=2, sticky='w')

        tk.Button(tab, text="Speichern", command=self.speichern_fertigkeiten).grid(row=len(self.model.fertigkeiten), columnspan=3)

    def speichern_fertigkeiten(self):
        self.model.charakter_daten['Fertigkeiten'] = {fert: combobox.get() for fert, combobox in self.charakter_daten_values_fertigkeiten.items() if self.charakter_daten_selected_fertigkeiten[fert].get()}
        self.model.speichern()
        self.ausgabe_fenster(self.model.charakter_daten)
        messagebox.showinfo("Erfolg", "Fertigkeiten gespeichert!")

    def eingabe_eigenschaften(self, tab_name, eigenschaften):
        tab = self.tabs[tab_name]
        for i, eigenschaft in enumerate(eigenschaften):
            selected = tk.BooleanVar(value=False)
            self.charakter_daten_selected_fertigkeiten[eigenschaft] = selected
            checkbox = tk.Checkbutton(tab, text=eigenschaft, variable=selected)
            checkbox.grid(row=i, column=0, sticky='w')

        tk.Button(tab, text="Speichern", command=self.speichern_eigenschaften(tab_name, eigenschaften)).grid(row=len(eigenschaften), column=0)

    def speichern_eigenschaften(self, tab_name, eigenschaften):
        def speichern():
            self.model.charakter_daten[tab_name] = {eigenschaft: True for eigenschaft in eigenschaften if self.charakter_daten_selected_fertigkeiten[eigenschaft].get()}
            self.model.speichern()
            self.ausgabe_fenster(self.model.charakter_daten)
            messagebox.showinfo("Erfolg", f"{tab_name} gespeichert!")
        return speichern

    def eingabe_ausruestung(self):
        tab = self.tabs['Ausrüstung']
        for i, (ausruest, details) in enumerate(self.model.ausruestung.items()):
            label = tk.Label(tab, text=f"{ausruest} (Kosten: {details['Kosten']}, Gewicht: {details['Gewicht']})")
            label.grid(row=i, column=0, sticky='w')
            selected = tk.BooleanVar(value=False)
            self.charakter_daten_selected_fertigkeiten[ausruest] = selected
            checkbox = tk.Checkbutton(tab, variable=selected, command=lambda a=ausruest: self.kaufe_ausruestung(a))
            checkbox.grid(row=i, column=1, sticky='w')

        tk.Button(tab, text="Speichern", command=self.speichern_ausruestung).grid(row=len(self.model.ausruestung), columnspan=2)

    def kaufe_ausruestung(self, ausruest):
        success, message = self.model.kaufe_ausruestung(ausruest)
        if success:
            self.ausgabe_fenster(self.model.charakter_daten)
            messagebox.showinfo("Erfolg", message)
        else:
            messagebox.showwarning("Fehler", message)

    def speichern_ausruestung(self):
        self.model.charakter_daten['Ausrüstung'] = {ausruest: details for ausruest, details in self.model.ausruestung.items() if self.charakter_daten_selected_fertigkeiten[ausruest].get()}
        self.model.speichern()
        self.ausgabe_fenster(self.model.charakter_daten)
        messagebox.showinfo("Erfolg", "Ausrüstung gespeichert!")

    def eingabe_waffen(self):
        tab = self.tabs['Waffen']
        for i, waffe in enumerate(self.model.waffen):
            selected = tk.BooleanVar(value=False)
            self.charakter_daten_selected_fertigkeiten[waffe] = selected
            checkbox = tk.Checkbutton(tab, text=waffe, variable=selected)
            checkbox.grid(row=i, column=0, sticky='w')

        tk.Button(tab, text="Speichern", command=self.speichern_waffen).grid(row=len(self.model.waffen), column=0)

    def speichern_waffen(self):
        self.model.charakter_daten['Waffen'] = {waffe: True for waffe in self.model.waffen if self.charakter_daten_selected_fertigkeiten[waffe].get()}
        self.model.speichern()
        self.ausgabe_fenster(self.model.charakter_daten)
        messagebox.showinfo("Erfolg", "Waffen gespeichert!")

    def ausgabe_fenster(self, charakter_daten):
        tab = self.tabs['Charakterbogen']

        # Alte Inhalte des Tabs löschen
        for widget in tab.winfo_children():
            widget.destroy()

        # Scrollbar für das Eingabefenster
        scrollbar = tk.Scrollbar(tab)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # Canvas für das Eingabefenster
        canvas = tk.Canvas(tab, yscrollcommand=scrollbar.set, bg='white')
        canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.config(command=canvas.yview)

        # Frame im Canvas erstellen, um die Widgets zu halten
        frame = tk.Frame(canvas, bg='white')
        canvas.create_window((0, 0), window=frame, anchor='nw')

        tk.Label(frame, text="Charakterbogen", font=("Arial", 16), bg='white').pack()

        # Create 5 columns
        columns = []
        for i in range(5):
            column_frame = tk.Frame(frame, bg='white')
            column_frame.pack(side=tk.LEFT, fill='y', padx=5, pady=5)
            columns.append(column_frame)

        # Column 1: Attribute and derived values
        tk.Label(columns[0], text="Attribute:", font=("Arial", 14, "bold"), bg='white').pack(anchor='w')
        attr_frame = tk.Frame(columns[0], bg='#F0F0F0', bd=1, relief='solid')  # Gray frame
        attr_frame.pack(fill='x', padx=5, pady=5)
        for attr, value in charakter_daten['Attribute'].items():
            tk.Label(attr_frame, text=f"{attr}: {value}", bg='#F0F0F0').pack(anchor='w')
        tk.Label(columns[0], text="Abgeleitete Werte:", font=("Arial", 14, "bold"), bg='white').pack(anchor='w')
        derived_frame = tk.Frame(columns[0], bg='#F0F0F0', bd=1, relief='solid')  # Gray frame
        derived_frame.pack(fill='x', padx=5, pady=5)
        for key, value in charakter_daten['Abgeleitete Werte'].items():
            tk.Label(derived_frame, text=f"{key}: {value}", bg='#F0F0F0').pack(anchor='w')

        # Column 2: Skills
        tk.Label(columns[1], text="Fertigkeiten:", font=("Arial", 14, "bold"), bg='white').pack(anchor='w')
        skills_frame = tk.Frame(columns[1], bg='#F0F0F0', bd=1, relief='solid')  # Gray frame
        skills_frame.pack(fill='x', padx=5, pady=5)
        for fert, value in charakter_daten['Fertigkeiten'].items():
            tk.Label(skills_frame, text=f"{fert}: {value}", bg='#F0F0F0').pack(anchor='w')

        # Column 3: Talents & Handicaps
        tk.Label(columns[2], text="Handicaps:", font=("Arial", 14, "bold"), bg='white').pack(anchor='w')
        handicaps_frame = tk.Frame(columns[2], bg='#F0F0F0', bd=1, relief='solid')  # Gray frame
        handicaps_frame.pack(fill='x', padx=5, pady=5)
        for handicap, selected in charakter_daten['Handicaps'].items():
            if selected:
                tk.Label(handicaps_frame, text=handicap, bg='#F0F0F0').pack(anchor='w')
       
        tk.Label(columns[2], text="Talente:", font=("Arial", 14, "bold"), bg='white').pack(anchor='w')
        talents_frame = tk.Frame(columns[2], bg='#F0F0F0', bd=1, relief='solid')  # Gray frame
        talents_frame.pack(fill='x', padx=5, pady=5)
        for talent, selected in charakter_daten['Talente'].items():
            if selected:
                tk.Label(talents_frame, text=talent, bg='#F0F0F0').pack(anchor='w')

        # Column 4: Powers
        tk.Label(columns[3], text="Mächte:", font=("Arial", 14, "bold"), bg='white').pack(anchor='w')
        powers_frame = tk.Frame(columns[3], bg='#F0F0F0', bd=1, relief='solid')  # Gray frame
        powers_frame.pack(fill='x', padx=5, pady=5)
        for macht, selected in charakter_daten['Mächte'].items():
            if selected:
                tk.Label(powers_frame, text=macht, bg='#F0F0F0').pack(anchor='w')

        # Column 5: Wealth, equipment, and weapons
        tk.Label(columns[4], text="Ausrüstung:", font=("Arial", 14, "bold"), bg='white').pack(anchor='w')
        equipment_frame = tk.Frame(columns[4], bg='#F0F0F0', bd=1, relief='solid')  # Gray frame
        equipment_frame.pack(fill='x', padx=5, pady=5)
        tk.Label(equipment_frame, text=f"{self.model.vermoegen} Gold", bg='#F0F0F0').pack(anchor='w')
        for ausruest, details in charakter_daten['Ausrüstung'].items():
            tk.Label(equipment_frame, text=ausruest, bg='#F0F0F0').pack(anchor='w')
        for waffe, selected in charakter_daten['Waffen'].items():
            if selected:
                tk.Label(equipment_frame, text=waffe, bg='#F0F0F0').pack(anchor='w')

        # Frame-Größe aktualisieren und Scrollregion setzen
        frame.update_idletasks()
        canvas.config(scrollregion=canvas.bbox('all'))

if __name__ == "__main__":
    root = tk.Tk()
    root.title("Charaktererstellung Savage Worlds")
    model = CharakterModel()
    view = CharakterView(root, model)
    tk.Button(root, text="Speichern in Datei", command=view.speichern_in_datei).pack()
    tk.Button(root, text="Laden aus Datei", command=view.laden_aus_datei).pack()
    tk.Button(root, text="Drucken als PDF", command=view.drucken_als_pdf).pack()
    root.mainloop()

--- Ende Code ---

sma:
Prima.

Jetzt überzeuge die KI, dass es keine gute Idee ist, sog. Geschäftswerte als Strings zu repräsentieren, sprich, es soll die Würfel als Exemplare einer `Die`-Klasse modellieren und auch nur einmal eine Liste der möglichen Exemplare erstellen.

Und wenn jeder Würfel seinen Wert kennt, den er zur Parade und Robustheit bei trägt, muss da nicht so komisch mit Strings hantiert werden. Ein Würfel könnte auch wissen, was seine nächstgrößere oder kleinere Form ist, was sinnvoll sein könnte, wenn irgendwo was von Hoch- oder Runterstufen in den Regeln steht.

Mir scheint auch, dein Modell enthält sowohl die konkreten Werte als auch die möglichen Werte. Da fehlt eine Trennung der  Ebenen. Du willst noch ein Metamodell haben, das beschreibt, wie deine Modelle aussehen können.

Thallion:
Vielen Dank für die wertvollen Tipps. Werde ich berücksichtigen.

Thallion:
Ulisses haben ihr ok zur Veröffentlichung gegeben. Ich habe den bisherigen Code nun bei github hochgeladen.
https://github.com/Thallion/Savage-Worlds-Charakter-Generator-deutsch

Navigation

[0] Themen-Index

[#] Nächste Seite

[*] Vorherige Sete

Zur normalen Ansicht wechseln