@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.
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()