Pen & Paper - Spielsysteme > Savage Worlds
Savage Worlds Char Generator
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