Etwas kompliziertere Tabelle

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Hallo, ich versuche folgende Zusammenhänge in einer gemeinsamen Tabelle darzustellen. Dabei steh ich nun auf dem Schlauch und finde keinen guten Ansatz.

In einem Spiel gibt es gewisse Rätselsprüche (mehrzeilig). Jeder Spieler kann mehrere Rätsel (ZT) zu lösen erhalten, die zufällig aus einer größeren Menge ausgewählt werden.

Die Tabelle soll nun von oben nach unten alle Rätsel auflisten. Ich habe das in einem Tabellenkalkulationsprogramm gestartet, aber es wurde schnell unübersichtlich und langsam, vor allem wegen der Mehrzeiligkeit. Daher denke ich, es sollte nur die erste Zeile jedes Rätselspruchss angezeigt werden sollen.

Zusätzlich soll zu jedem Rätsel noch ein Hinweis und die Lösung als mehrzeiliger Text gespeichert werden können. Ich kapsle das in eine Klasse. In der Tabelle werde ich nur anzeigen, ob überhaupt Hinweis oder Lösung vorliegen (bool) und nicht den detailierten Inhalt.

Die weiteren Spalten, eine pro Spieler, sollen zeigen, ob dieser Spieler dieses Rätsel derzeit lösen muss (bool).

Ich habe: Eine Klasse ZT(), eine Klasse Spieler(), sowie eine Klasse ZTTableModel(QtCore.QAbstractTableModel) als Datenmodell für das Qt.TableWidget

Ich habe auch schon mal eine Klasse Datenbank() angelegt, ohne mir Qt genauer anzugucken, damit ich die Tabelle als Text ausgeben kann und die Logik ohne Gui testen kann. Das funktioniert (mehr schlecht als recht), aber nun habe ich keine Ahnung, wie ich das mit dem Qt Modell verbinden kann.

Zum Beispiel soll ZTTableModel.removeColumns() ja keine der drei ersten Spalten verschwinden lassen, sondern nur ggf. einen der Spieler aus Spalte 4 oder später. Wenn hingegen ein ZT hinzugefügt werden soll, soll ja erst geprüft werden, ob er vielleicht schon vorhanden ist, dann müsste ggf. die alte Zeile aktualisiert werden, anstatt ihn neu doppelt aufzunehmen. Soll ich vielleicht eine vierte Klasse dazwischenschalten, die nur die Zusammenhänge zwischen Rätselsprüchen und Spieler kennt und dann das Modell fernsteuert? Wald -> Bäume.

Die grundlegenden Klassen waren simpel:

Code: Alles auswählen

class ZT(object):
    def __init__(self, tell, hint="", solve=""):
        self.tell, self.hint, self.solve = tell, hint, solve
        
    def __str__(self):
        return "%s (%s, %s)" % (self.tell_short(), self.hinted(), self.solved())
    
    def hinted(self):
        return len(self.hint) > 0
    
    def solved(self):
        return len(self.solve) > 0
    
    def tell_short(self):
        return self.tell.split("\n")[0] # first line


class Player(object):
    def __init__(self, name = "Jemand"):
        self.name = name
        self.zts = []
        
    def __str__(self):
        return "%s knows %d zts." % (self.name, len(self.zts))
    
    def change_name(self, new_name):
        self.name = new_name

    def knows_zt_short(self, tell_short):
        z = tell_short
        for x in self.zts:
            if x.tell_short() == z:
                return True
        return False
        
    def knows_zt(self, zt):
        z = zt.tell_short()
        return self.knows_zt_short(z)
        
    def add_zt(self, zt):
        if not self.knows_zt(zt):
            self.zts.append(zt)
Der Input erfolgt dann in Form einer Liste von Rätselsprüchen pro Spieler. Falls ein Rätsel unbekannt ist, wird es in die Tabelle ergänzt, ansonsten nur die Spalte des Spielers auf "True" gesetzt, da er dieses Rätsel ja offensichtlich kennt.

Später soll auch noch ein weiteres Fenster dazukommen, um die Rätselsprüche, Hinweise und Lösungen als Langtext editierbar zu machen,. Das würde die Tabelle wohl endgültig überfrachten.

Kommentare sind gern gesehen. Bei Interesse werde ich meinen Codeweg weiter dokumentieren.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Kebap:
Aus Deinen Ausführungen/Code ist mir nicht klar geworden, ob Du für alles Qts View-Klassen (MVC) nutzen willst oder nicht. Das wäre insofern wichtig, da das Vorgehen recht anders ist zu den "Widget"-Gui-Klassen. Generell ist ersteres robuster bzw. besser trennbar in Logik/Datenhaltung/Präsentation. Dafür gibts auch eine schöne Einführung in der Qt-Doku: http://doc.qt.io/qt-5/model-view-programming.html
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Hallo jerch, danke für den Link. Ich hatte die Seite noch etwas zurückgestellt wegen der Länge und ehrlich hat mich der C Code auch abgeschreckt.

Jetzt bin ich wegen deiner Rückfrage aber etwas verwirrt, weil ich folgendes Tutorial bearbeitet habe: PyQt4 Model View Tutorial Dort haben wir auch ein Modell erstellt und dadurch die Views gesteuert, aber - korrigiere, nein, das waren auch QtGui.QTableView, also vermutlich genau dein Vorschlag.

Grundsätzlich bin ich bei Qt sehr neu, dies ist mein erstes Projekt, und ich habe mich mal an den Rat gehalten und die Logik erstmal halbwegs ohne Gui vorbereitet. Zur Datenhaltung benutze ich erstmal pickle. Hier mal der komplette letzte Code für die Tabelle ohne GUI:

Code: Alles auswählen

# -*- coding: utf-8 -*-
import pickle

class ZT(object):
    def __init__(self, tell, hint="", solve=""):
        self.tell, self.hint, self.solve = tell, hint, solve
        
    def __str__(self):
        return "%s (%s, %s)" % (self.tell_short(), self.hinted(), self.solved())
    
    def hinted(self):
        return len(self.hint) > 0
    
    def solved(self):
        return len(self.solve) > 0
    
    def tell_short(self):
        return self.tell.split("\n")[0] # first line


class Player(object):
    def __init__(self, name = "Jemand"):
        self.name = name
        self.zts = []
        
    def __str__(self):
        return "%s knows %d zts." % (self.name, len(self.zts))
    
    def change_name(self, new_name):
        self.name = new_name

    def knows_zt_short(self, ts):
        z = ts
        for x in self.zts:
            if x.tell_short() == z:
                return True
        return False
        
    def knows_zt(self, zt):
        z = zt.tell_short()
        return self.knows_zt_short(z)
        
    def add_zt(self, zt):
        if not self.knows_zt(zt):
            self.zts.append(zt)


class Database(object):
    def __init__(self):
        self.zts = []
        self.players = []
        self.table = [] # zts * players
        self.__header = ["#", "zt info: total text of oracle tell", "hint?", "solve?"]
        
    def __str__(self):
        return "%d zts and %d players known." % (len(self.zts), len(self.players))
    
    def knows_zt_short(self, tell_short):
        z = tell_short
        for x in self.zts:
            if x.tell_short == z:
                return True
        return False

    def knows_zt(self, zt):
        return self.knows_zt_short(zt.tell_short)

    def add_player(self, name = "Jemand"):
        player = Player(name)
        self.players.append(player)
        return player

    def add_zt(self, zt_text):
        zt = ZT(zt_text)
        if not self.knows_zt(zt):
            self.zts.append(zt)
        return zt
            
    def add_zts(self, player, text_of_zts):
        zt_texte = []
        for line in text_of_zts.split("\n"):
            if line.startswith("---"):
                if len(zt_texte) > 0:
                    zt_text = "\n".join(zt_texte)
                    zt = self.add_zt(zt_text)
                    player.add_zt(zt)
                zt_texte = []
            else:
                if len(line.strip()) > 0:
                    zt_texte.append(line)
                    
    def update_table(self):
        """
        # zt tell, hint, solve | char 1, x |
        1 bla      bla   bla          x  x
        2 foo      foo   foo          x  x  
        """
        zt_table = [[i + 1, z.tell_short(), z.hinted(), z.solved()] 
                    for i, z in enumerate(self.zts)]
        header = self.__header[:]
        for p in self.players:
            header.append(p.name)
            for row in zt_table:
                row.append(p.knows_zt_short(row[1]))
        table = [self.__header] + zt_table 
        self.table = table # TO DO: necessary?
        return table
        
    def show_table(self):
        table = self.update_table()
        header = self.__header[:]
        header.extend(p.name for p in self.players)
        mask1 = "%3s | %-70s  %s\t%s\t|" + "%10s   " * len(self.players)
        mask2 = "%3s | %70s  %s\t%s\t|" + "%10s   " * len(self.players)
        print mask1 % tuple(header)
        print "-" * 100
        for row in table[1:]:
            print mask2 % tuple(row)
        print "-" * 100 + "\n"

    def search_zt(self, text):
        for z in self.zts:
            if text in z.tell: # or text in z.hint or text in z.solve:
                return z
        return None
    
    def save(self):
        datafilename = "zt_data.pkl"
        savedata = (self.zts, self.players)
        with open(datafilename, "wb") as outfile:
            pickle.dump(savedata, outfile)
        print "Data saved."
        
    def load(self):
        datafilename = "zt_data.pkl"
        with open(datafilename, "rb") as infile:
            self.zts, self.players = pickle.load(infile)
        print "Data loaded."
        self.update_table()
        print "Table updated."


if __name__ == "__main__":
    zt_texts = """
-----
Horch, was kommt von draussen rein?
hollahi hollaho
-----
Was habe ich hier in meiner Hand?
fragte Bilbo laut. 
-----
"""
    d = Database()
    d.add_zts(d.add_player("Kebap"), zt_texts)
    d.add_player("jerch")
    d.show_table()
Ergebnis:

Code: Alles auswählen

  # | zt info: total text of oracle tell                                      hint?     solve?  |     Kebap        jerch   
----------------------------------------------------------------------------------------------------
  1 |                                    Horch, was kommt von draussen rein?  False     False   |      True        False   
  2 |                                      Was habe ich hier in meiner Hand?  False     False   |      True        False   
----------------------------------------------------------------------------------------------------
Bedeutung: Es gibt 2 bekannte ZT Rätselsprüche (Zeilen) und 2 Spieler (die letzten beiden Spalten), wobei ein Spieler beide Sprüche kennt, der andere kennt noch keinen.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Das Ausbleiben von Antworten könnte mehrere Ursachen haben, zum Beispiel

a) Die Lösung ist so offensichtlich, dass niemand nötig findet zu kommentieren
b) Die Frage ist zu kompliziert formuliert und niemand blickt durch die Anforderungen
c) Die Lösung ist zu kompliziert, so dass der Aufwand der Antwort zu hoch erscheint
d) Niemand liest das Qt Unterforum
e) ...
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Kebap:
Irgendwie sehe ich keine konkrete Frage, daher hab ich nicht geantwortet.
Kebap hat geschrieben:Jetzt bin ich wegen deiner Rückfrage aber etwas verwirrt, weil ich folgendes Tutorial bearbeitet habe: PyQt4 Model View Tutorial Dort haben wir auch ein Modell erstellt und dadurch die Views gesteuert, aber - korrigiere, nein, das waren auch QtGui.QTableView, also vermutlich genau dein Vorschlag.
Ja genau das meinte ich - das verlinkte Videotutorial zeigt auch ganz gut, wie Du die Datenhaltung vorbereitet werden muss, damit die Qt-Model-Klassen damit arbeiten können. Das sehe ich noch nicht in Deinem Codeausschnitt umgesetzt, müsstest Du halt machen. Das MVC-Pattern bei Qt ist anfangs etwas gewöhnungsbedürftig, zum Üben eignet da vllt. eine ganz simple Datenhaltung mittels eines Standardcontainers besser. Daran kann man schneller lernen/abschätzen, wie die eigene Modelklasse auszusehen hat.
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Die Datenhaltung der Rätsel / Hinweise / Lösungen soll in menschenlesbare Textdateien erfolgen.
Die "Beschreibung eines Rätsels" von der "Lösung durch Spieler X" getrennt werden.
Das liegt auch daran, dass es beliebig mehr / andere Spieler werden könnten.

Ich kenne nicht so viele Textformate, also habe ich mich für Markdown entschieden. Ob das gut ist?

So könnte eine Liste der Rätsel aussehen. Unklar ist, wie ich die Eigenschaft der fortlaufenden Nummer am besten hinterlege:

Code: Alles auswählen

---
# 1
Text
Text
--
Hint
Hint
--
Solution
Solution
---
# 2
Text
Text
--
Hint
Hint
--
Solution
Solution
---
So könnte davon abgeleitet eine Datei für Spieler X aussehen, jeweils mit dessen Status pro Rätsel:

Code: Alles auswählen

# Name
1: unknown
2: unknown
3: received
4: solved
5: unknown
Der Spieler heißt "Name" und hat Rätsel 3 erhalten, 4 schon gelöst, den Rest kennt er nicht. Es ist also mehr als nur Bool geworden.

Dann muss ich das Format (oder vielleicht ein anderes / ähnliches?) nur noch in Python lesen / schreiben lernen...

Wobei die Spieler-Datei nicht unbedingt in Markdown sein muss, da würde sich vielleicht auch YAML oder sowas anbieten.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Antworten