Autor Thema: Savage Worlds Char Generator  (Gelesen 2597 mal)

0 Mitglieder und 1 Gast betrachten dieses Thema.

Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Savage Worlds Char Generator
« am: 9.05.2024 | 17:58 »
Ich baue gerade mit Hilfe von ChatGPT einen Savage Worlds Charakter Generator in Python. Ich habe zwar grundlegende Programmier Kenntnisse aber hauptsächlich Erfahrung mit VBA. Der Code stammt also mehr oder weniger zu 100% von der KI. Funktioniert bisher super. Ich halte euch auf dem laufenden und hoffe irgendwann mit Erlaubnis von Ulisses / Peginc etwas brauchbares veröffentlichen zu können.
Grüße Thallion

Offline Radulf St. Germain

  • Famous Hero
  • ******
  • Beiträge: 2.086
  • Geschlecht: Männlich
  • Username: Radulf St. Germain
    • Studio St. Germain
Re: Savage Worlds Char Generator
« Antwort #1 am: 9.05.2024 | 19:40 »
Cool. Geht es Dir um den Spaß oder suchtst Du einen Character Generator und hast keinen gefunden?

Es gibt nämlich das hier: https://savaged.us/

Aber wenn Du es einfach aus Spaß machst, dann ist das ein cooles Projekt. Ich spiele schon lange mit dem Gedanken, einen Zufalls-Monstergenerator zu schreiben, komme aber nie dazu.

Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Re: Savage Worlds Char Generator
« Antwort #2 am: 9.05.2024 | 20:23 »
Ich habe den Betreiber von savaged.us angeboten, seine Seite zu übersetzen. Das wurde leider abgelehnt. Ich selbst und meine Spieler hätten halt gerne eine deutsches Tool. Es geht mir aber auch um den Spaß an der Sache und die Neugier, ob eine KI tatsächlich ordentlich programmieren kann, was bisher erstaunlich gut funktioniert. Ich spiele auch mit dem Gedanken sämtliche Settings, die auf deutsch erschienen sind, also auch die GER Bücher, zu implementieren. Wir spielen zum Beispiel gerade Sundered Skies und das Wilde Aventurien.

Offline Radulf St. Germain

  • Famous Hero
  • ******
  • Beiträge: 2.086
  • Geschlecht: Männlich
  • Username: Radulf St. Germain
    • Studio St. Germain
Re: Savage Worlds Char Generator
« Antwort #3 am: 10.05.2024 | 09:23 »
Stimmt, Englisch und Deutsch. Da habe ich jetzt gar nicht dran gedacht.

Ich bin ja sehr neugierig, wie der Code aussieht, falls Du Lust hast mal ein Beispiel zu posten...

Side Note: Ich glaube der Betreiber von Savaged.us hat gesundheitliche Probleme und ist deshalb vielleicht nicht so erpicht auf Projekte, und sei sein Teil auch noch so klein.

Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Re: Savage Worlds Char Generator
« Antwort #4 am: 10.05.2024 | 09:51 »
Bitteschön :

  ', 'd4')
    robustheit = 2 + int(konstitution_wuerfel[1:]) // 2
    return {'Bewegungsweite': bewegungsweite, 'Parade': parade, 'Robustheit': robustheit}

def ausgabe_fenster(charakter_daten, notebook, bogen_tab):

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

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

    # Canvas für das Eingabefenster
    canvas = tk.Canvas(bogen_tab, yscrollcommand=scrollbar.set)
    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)
    canvas.create_window((0, 0), window=frame, anchor='nw')

    tk.Label(frame, text="Charakterbogen", font=("Arial", 16)).grid(row=0, columnspan=4)

    # Spalte 1 für Attribute und abgeleitete Werte
    tk.Label(frame, text="Attribute:", font=("Arial", 14, "bold")).grid(row=1, column=0, sticky="w")
    row = 2
    for attr, value in charakter_daten['Attribute'].items():
        tk.Label(frame, text=f"{attr}: {value}").grid(row=row, column=0, sticky="w")
        row += 1

    # Abgeleitete Werte anzeigen
    tk.Label(frame, text="Abgeleitete Werte:", font=("Arial", 14, "bold")).grid(row=row, column=0, sticky="w")
    row += 1
    for key, value in charakter_daten['Abgeleitete Werte'].items():
        tk.Label(frame, text=f"{key}: {value}").grid(row=row, column=0, sticky="w")
        row += 1

    # Ausrüstung anzeigen
    tk.Label(frame, text="Ausrüstung:", font=("Arial", 14, "bold")).grid(row=row, column=0, sticky="w")
    row += 1
    for ausruest, selected in charakter_daten['Ausrüstung'].items():
        if selected:
            tk.Label(frame, text=ausruest).grid(row=row, column=0, sticky="w")
            row += 1

    # Waffen anzeigen
    tk.Label(frame, text="Waffen:", font=("Arial", 14, "bold")).grid(row=row, column=0, sticky="w")
    row += 1
    for waffe, selected in charakter_daten['Waffen'].items():
        if selected:
            tk.Label(frame, text=waffe).grid(row=row, column=0, sticky="w")
            row += 1

    # Spalte 2 für Fertigkeiten
    tk.Label(frame, text="Fertigkeiten:", font=("Arial", 14, "bold")).grid(row=1, column=1, sticky="w")
    row = 2
    for fert, value in charakter_daten['Fertigkeiten'].items():
        tk.Label(frame, text=f"{fert}: {value}").grid(row=row, column=1, sticky="w")
        row += 1

    # Spalte 3 für Mächte
    tk.Label(frame, text="Mächte:", font=("Arial", 14, "bold")).grid(row=1, column=2, sticky="w")
    row = 2
    for macht, value in charakter_daten['Mächte'].items():
        tk.Label(frame, text=f"{macht}: {value}").grid(row=row, column=2, sticky="w")
        row += 1

    # Spalte 4 für Talente
    tk.Label(frame, text="Talente:", font=("Arial", 14, "bold")).grid(row=1, column=3, sticky="w")
    row = 2
    for talent, selected in charakter_daten['Talente'].items():
        if selected:
            tk.Label(frame, text=talent).grid(row=row, column=3, sticky="w")
            row += 1

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

 

Offline Radulf St. Germain

  • Famous Hero
  • ******
  • Beiträge: 2.086
  • Geschlecht: Männlich
  • Username: Radulf St. Germain
    • Studio St. Germain
Re: Savage Worlds Char Generator
« Antwort #5 am: 11.05.2024 | 11:11 »
Interessant, interessant.

Ich frage mich, warum er den Komstitutionswürfel als Array abbildet. Ist da der Wild Die als zweiter Wert dabei?

(Musst nicht antworten, falls Du keine Lust auf diese Details hast.  :d)

Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Re: Savage Worlds Char Generator
« Antwort #6 am: 11.05.2024 | 16:57 »
Das Array entspricht den Würfeltypen als Text, könnte man natürlich auch als integer definieren.

# Attribute und Fertigkeiten mit möglichen Werten
deutsche_attribute = {
    'Geschicklichkeit': ['d4', 'd6', 'd8', 'd10', 'd12'],
    'Verstand': ['d4', 'd6', 'd8', 'd10', 'd12'],
    'Willenskraft': ['d4', 'd6', 'd8', 'd10', 'd12'],
    'Stärke': ['d4', 'd6', 'd8', 'd10', 'd12'],
    'Konstitution': ['d4', 'd6', 'd8', 'd10', 'd12']
}

- Versions-Protokoll:
- 0_0_5: Attribute und Fertigkeiten und abgeleitetet Werte & Ausgabe im Charakterbogen
- 0_1_0: Mächte ergänzt
- 0_2_0: Scrollbar Charakterbogen ergänzt
- 0_2_1: Liste Mächte & Fertigkeiten vervollständigt
- 0_2_2: Scrollbar Eingabemasken ergänzt
- 0_2_3: Nur aktivierte Mächte & Fertigkeiten ausgeben und auswählbar machen
- 0_2_4: Beispieltalente angelegt
- 0_2_5: Fix Talentausgabe
- 0_2_6: Checkboxen für Talente
- 0_2_7: Talentliste erweitert
- 0_2_8: Ausrüstung & Waffen ergänzt
- 0_3_0: Zusammenfassung aller Fenster in Tabs


- Roadmap:

- Grundfertigkeiten immer aktivieren
- Waffenliste vervollständigen
- Waffen Zusatzattribute pflegen
- Talentliste vervollständigen
- Aufspaltung Talent-Taps (Kampf, Anführer etc.)
- Handicaps anlegen und vervollständigen
- Rang pflegen
- Völker pflegen
- Sprachen pflegen
- Charakter Konzept & Details
- Panzerung implementieren
- + / - Buttons zum Erhöhen / Senken von Attributen etc.
- Sortierung der Fertigkeiten nach Attribut
- Gewicht / Traglast-Regeln implementieren
- Preislisten und Vermögen implementieren
- Charakterbogen Layout verbessern
- Freie Eingabe von Mächten, Fertigkeiten und Talenten
- Hintergrund Textfeld
- Notizen
- Charaktere in Datei speichern & laden
- Generierungspunkte festhalten
- Liste Aufstiege
- Voraussetzungen Talente & Mächte prüfen
- Export nach PDF, XML, BB-Code, Stat-Block etc.
- Settingregeln implementieren
- Auswahl geltender Setting- und Regel-Bücher
- Verbündeten Bogen
- Fahrzeug Bogen
- Eigene Völker erstellen
- Ausführbare EXE bereit stellen
- Support alte Version Savage GER & Settings
- Hausregeln implementieren

Offline Silent

  • Famous Hero
  • ******
  • Beiträge: 2.269
  • Geschlecht: Männlich
  • Username: Althena
Re: Savage Worlds Char Generator
« Antwort #7 am: 11.05.2024 | 17:49 »
Du könntest dir noch Gedanken machen, wie du Attribute über w12 (also w12+1, w12+2,... usw.) abbilden möchtest. Manche Abstammungen beginnen ja schon mit w6 und dürfen auch bis w12+1 gehen. Von einigen Einträgen aus dem Bestiarium ganz abgesehen.
>Werbefläche zu vermieten<

Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Re: Savage Worlds Char Generator
« Antwort #8 am: 12.05.2024 | 08:09 »
Stimmt, da mach ich mir mal Gedanken zu. Danke.

Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Re: Savage Worlds Char Generator
« Antwort #9 am: 13.05.2024 | 21:45 »
- 0_3_1: Attribute d12+1 und d12+2 ergänzt und abgeleitete Werte angepasst
- 0_3_2: Grundfertigkeiten immer aktivieren
- 0_3_2_1: Bugfix Ausgabe Fertigkeiten

Offline Eldrian

  • Survivor
  • **
  • Beiträge: 97
  • Geschlecht: Männlich
  • Username: eldrian
Re: Savage Worlds Char Generator
« Antwort #10 am: 14.05.2024 | 11:46 »
Klingt spannend.
Falls was nutzbares dabei herausfällt: Sehr schön!
Falls nicht, hast du hoffentlich wenigstens Spaß beim basteln gehabt.
Viel Erfolg!

Offline unicum

  • Famous Hero
  • ******
  • Beiträge: 3.188
  • Username: unicum
Re: Savage Worlds Char Generator
« Antwort #11 am: 14.05.2024 | 15:13 »
Kannst du mal beispielsweise einen Prompt dafür geben was du der KI schreibst das sie dir "Code" liefert?


Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Re: Savage Worlds Char Generator
« Antwort #12 am: 14.05.2024 | 15:38 »
Klar doch:

Zitat
Bitte korrigiere diesen Code für die Kosten der Ausrüstung.
Wenn ich eine Ausrüstung aktiviere, dann sollen die Kosten der Ausrüstung vom Vermögen abgezogen werden. Das Eingabefeld für das Vermögen, soll sich dann mit dem neuen Wert aktualisieren.
###
# Funktion zum Hinzufügen der Ausrüstung zum Charakterbogen
def ausruestung_eingabe(tab, kategorie, werte, charakter_daten_values, charakter_daten_selected):
    # Initialisiere das Dictionary für die Ausrüstung, falls es noch nicht existiert
    if charakter_daten_selected is None:
        charakter_daten_selected = {}

    for i, (ausruest, details) in enumerate(werte.items()):
        # Checkbox für jede Ausrüstung
        selected = charakter_daten_selected.get(ausruest, tk.BooleanVar(value=False))
        charakter_daten_selected[ausruest] = selected
        checkbutton = tk.Checkbutton(tab, text=ausruest, variable=selected)
        checkbutton.grid(row=i, column=0)
       
    vermoegen_label = tk.Label(tab, text="Vermögen:")
    vermoegen_label.grid(row=len(werte)+1, column=0, sticky='w')
    vermoegen_entry = tk.Entry(tab, textvariable=vermoegen)
    vermoegen_entry.grid(row=len(werte)+1, column=1, sticky='w')

    # Kosten der aktivierten Ausrüstung vom Vermögen abziehen
    kosten = liste_ausruestung[ausruest]['Kosten']
    if vermoegen.get() >= kosten:
        vermoegen.set(vermoegen.get() - kosten)
        # Hier würde der Code stehen, um die Ausrüstung zum Charakterbogen hinzuzufügen
        messagebox.showinfo("Ausrüstung hinzugefügt", f"{ausruest} wurde zum Charakterbogen hinzugefügt.")
    else:
        messagebox.showwarning("Nicht genug Gold", "Du hast nicht genug Gold, um diese Ausrüstung zu kaufen.")
   
    # Speichern-Button für Ausrüstung und Vermögen
    tk.Button(tab, text="Speichern", command=lambda: speichern(charakter_daten)).grid(row=len(werte)+2, columnspan=2)
###

Zitat
Copilot
Antworten für Sie werden generiert…

Um den Code für die Kosten der Ausrüstung zu korrigieren, sodass die Kosten beim Aktivieren einer Ausrüstung vom Vermögen abgezogen werden und das Eingabefeld für das Vermögen sich mit dem neuen Wert aktualisiert, kannst du folgende Änderungen vornehmen:

Python

import tkinter as tk
from tkinter import messagebox

# Globale Variable für das Kapital in Gold
vermoegen = tk.IntVar(value=1000)  # Startkapital, anpassen nach Bedarf

# Ausrüstung als Dictionary
liste_ausruestung = {
    'Seil': {'Kosten': 300, 'Gewicht': 0},
    'Dietrich': {'Kosten': 300, 'Gewicht': 0},
    'Lederrüstung': {'Kosten': 300, 'Gewicht': 0},
    # ... weitere Ausrüstungsgegenstände
}

# Funktion zum Hinzufügen der Ausrüstung zum Charakterbogen
def ausruestung_eingabe(tab, kategorie, werte, charakter_daten_values, charakter_daten_selected):
    # Initialisiere das Dictionary für die Ausrüstung, falls es noch nicht existiert
    if charakter_daten_selected is None:
        charakter_daten_selected = {}

    for i, (ausruest, details) in enumerate(werte.items()):
        # Checkbox für jede Ausrüstung
        selected = charakter_daten_selected.get(ausruest, tk.BooleanVar(value=False))
        charakter_daten_selected[ausruest] = selected
        checkbutton = tk.Checkbutton(tab, text=ausruest, variable=selected, command=lambda ausruest=ausruest: ausruestung_kaufen(ausruest))
        checkbutton.grid(row=i, column=0)

    # Eingabefeld für das Vermögen
    vermoegen_label = tk.Label(tab, text="Vermögen:")
    vermoegen_label.grid(row=len(werte)+1, column=0, sticky='w')
    vermoegen_entry = tk.Entry(tab, textvariable=vermoegen)
    vermoegen_entry.grid(row=len(werte)+1, column=1, sticky='w')

    # Speichern-Button für Ausrüstung und Vermögen
    tk.Button(tab, text="Speichern", command=lambda: speichern(charakter_daten)).grid(row=len(werte)+2, columnspan=2)

def ausruestung_kaufen(ausruest):
    kosten = liste_ausruestung[ausruest]['Kosten']
    if vermoegen.get() >= kosten and ausruestung_selected[ausruest].get():
        vermoegen.set(vermoegen.get() - kosten)
        messagebox.showinfo("Ausrüstung hinzugefügt", f"{ausruest} wurde zum Charakterbogen hinzugefügt.")
    elif not ausruestung_selected[ausruest].get():
        vermoegen.set(vermoegen.get() + kosten)
    else:
        messagebox.showwarning("Nicht genug Gold", "Du hast nicht genug Gold, um diese Ausrüstung zu kaufen.")
        ausruestung_selected[ausruest].set(False)

def speichern(charakter_daten):
    # ... (Rest des Codes bleibt unverändert)
Zitat
KI-generierter Code. Überprüfen und sorgfältig verwenden. Weitere Informationen zu häufig gestellten Fragen.
In diesem Code wird eine neue Funktion ausruestung_kaufen definiert, die aufgerufen wird, wenn eine Checkbox für einen Ausrüstungsgegenstand aktiviert oder deaktiviert wird. Diese Funktion überprüft, ob genügend Vermögen vorhanden ist, um den Gegenstand zu kaufen, und zieht die Kosten vom Vermögen ab oder fügt sie hinzu, je nachdem, ob die Checkbox aktiviert oder deaktiviert wird. Das Eingabefeld für das Vermögen wird automatisch aktualisiert, da es an die vermoegen-Variable gebunden ist.

Stelle sicher, dass du die messagebox-Bibliothek importierst, um Benachrichtigungen anzeigen zu können. Füge diesen Code in dein Programm ein und passe ihn entsprechend an deine Bedürfnisse an. Wenn du weitere Hilfe benötigst oder spezifische Funktionen hinzufügen möchtest, stehe ich dir gerne zur Verfügung!


0_3_3: Vermögen + Kauf von Ausrüstung implementiert
« Letzte Änderung: 14.05.2024 | 15:55 von Thallion »

Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Re: Savage Worlds Char Generator
« Antwort #13 am: 17.05.2024 | 17:24 »
Bogen aufgehübscht

Offline sma

  • Hero
  • *****
  • a.k.a. eibaan
  • Beiträge: 1.566
  • Geschlecht: Männlich
  • Username: sma
Re: Savage Worlds Char Generator
« Antwort #14 am: 18.05.2024 | 18:13 »
Ich möchte den Thread in hijacken aber kurz off-topic anmerken, dass überrascht bin, wie gut ChatGPT im letzten haben Jahr geworden ist.

Ich hatte das eigentlich für Reddit geschrieben und wollte mit diesem Beispiel ursprünglich "beweisen", dass das alles nicht geht. Doch es ging und ich habe einen rudimentären Charaktergenerator (allerdings nicht -editor) für Mausritter bekommen und bis auf einen Fehler am Anfang war der Quelltext korrekt. Vor wenigen Wochen war das noch undenkbar. Der Code ist schlecht und ich würde niemanden einstellen, der diese Qualität fabriziert, aber wenn man's selbst gar nicht kann, ist das natürlich schon krass hilfreich.

der leider unformatierte Chatverlauf, weil mit OpenAI nicht erlaubt, einen Chat mit Bildern drin zu teilen.

Vielleicht willst du ja statt Python mit dem doch sehr antiquierten Tk noch mal was anderes ausprobieren :)

Ich habe gerade mal ChatGPT gefragt und es hat mir "streamlit" als moderne React-inspirierte webbasierte Lösung empfohlen. Kenn ich überhaupt nicht, aber die AI konnte mir ein Beispiel generieren, um in einem Mausritter-Charaktereditor die Attributwerte mit + und - zu verändern und das Code sieht plausibel aus. Mit einigen Nachfragen, wo ich fand, dass der Code inkonsequent war, hat es dann noch Korrekturen vorgenommen und innerhalb von 5 min hatte ich etwas, das durchaus funktionieren könnte.
(Klicke zum Anzeigen/Verstecken)

Abschließend würde ich dir empfehlen, der AI zu sagen, sie soll nicht Modell und UI vermischen. Das geht auch in Python besser. MVVM wurde für solche Fälle erfunden. Sag, du willst, dass sie DDD befolgt. Das müsste sie verstehen. In dem Beispiel, das du gepostet hat, hielt sie den Zustand deiner Anwendung im UI fest. Das ist eigentlich ein No-go. Außerdem sollte sie einbauen, wie man das Modell speichert und wieder lädt. Finde ich einen wichtigeren Schritt als jetzt etwas anzuzeigen.

Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Re: Savage Worlds Char Generator
« Antwort #15 am: 19.05.2024 | 07:32 »
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.

Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Re: Savage Worlds Char Generator
« Antwort #16 am: 20.05.2024 | 07:58 »
@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()
« Letzte Änderung: 20.05.2024 | 08:23 von Thallion »

Offline sma

  • Hero
  • *****
  • a.k.a. eibaan
  • Beiträge: 1.566
  • Geschlecht: Männlich
  • Username: sma
Re: Savage Worlds Char Generator
« Antwort #17 am: 20.05.2024 | 11:50 »
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.

Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Re: Savage Worlds Char Generator
« Antwort #18 am: 20.05.2024 | 16:26 »
Vielen Dank für die wertvollen Tipps. Werde ich berücksichtigen.

Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Re: Savage Worlds Char Generator
« Antwort #19 am: 30.05.2024 | 12:59 »
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

Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Re: Savage Worlds Char Generator
« Antwort #20 am: 10.06.2024 | 18:39 »
Savage Worlds Charakter Generator Alpha Version 0.4.9.7

https://github.com/Thallion/Savage-Worlds-Charakter-Generator-deutsch

So ihr Würfelschubser. Ich hab eine ZIP Datei mit ausführbarer EXE bei github hochgeladen für einen ersten Alpha Test. Noch sind nicht alle Funktionen implementiert. Welche noch fehlen findet ihr unter issues im github. Bugs und Wünsche bitte dort eintragen oder Nachricht an mich. Ich freue mich über euer Feedback.

Offline Eldrian

  • Survivor
  • **
  • Beiträge: 97
  • Geschlecht: Männlich
  • Username: eldrian
Re: Savage Worlds Char Generator
« Antwort #21 am: 11.06.2024 | 08:24 »
Hi,

Ich hab mich gerade mal durchgeklickt. Schick! Macht einen guten Eindruck.

Offensichtlich fehlt noch zu viel um nutzbar zu sein, aber scheinbar bist du auf einem guten Weg.
Viel Erfolg weiterhin!

Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Re: Savage Worlds Char Generator
« Antwort #22 am: 11.06.2024 | 08:33 »
Vielen Dank! Ja, die Liste der ToDo's ist noch lang, aber der weg zur Beta ist nicht mehr so weit.
Wenn man grundsätzlich alle Standard Charaktere regelgerecht erstellen kann, dann bin ich schon sehr glücklich.
Ich überlege nochmal die GUI auf Kivy zu wechseln, damit ich auch eine Android APK anbieten kann.

Offline sma

  • Hero
  • *****
  • a.k.a. eibaan
  • Beiträge: 1.566
  • Geschlecht: Männlich
  • Username: sma
Re: Savage Worlds Char Generator
« Antwort #23 am: 11.06.2024 | 09:57 »
Du nutzt Github … ungewöhnlich. Frage doch mal ChatGPT "Wie sollte ein typisches Python-Projekt auf Github strukturiert sein?" Da kommt eine gute Antwort.

Kivy mag moderner als Tkinter sein, braucht aber erst noch ein GUI-Rahmenwerk wie KivyMD (so mein kurzes Googlen) und da es meines Wissens stark von Flutter inspiriert ist… wenn du nicht an Python hängst, könntest du deine AI natürlich auch bitten, damit weiter zu machen.

Oder du setzt auf Webtechnologie, wo du natürlich extrem viel Auswahl hast. Mit Tauri oder Capacitor kannst du auch hier deine Web-App in eine mobile App oder eine Desktop-App (im Fall von Tauri, Capacitor sagt, nimm doch Electron) wrappen.

Offline Thallion

  • Rezi-Spezi
  • Famous Hero
  • ******
  • Beiträge: 2.549
  • Geschlecht: Männlich
  • Username: Thallion
    • Rollenspiel-Bewertungen
Re: Savage Worlds Char Generator
« Antwort #24 am: 12.06.2024 | 09:46 »
Danke, schau ich mir an.
Für ein schönes und strukturiertes github bin ich grad zu faul,
aber vielleicht mach ich das irgendwann mal.