Seite 1 von 1

Textadventure in Python

Verfasst: Mittwoch 22. September 2010, 00:54
von p90
Hi,

hab mich mal dran gesetzt ein altes (sehr einfaches) Textadventure nach Python zu portieren.
Ging wie immer hauptsächlich darum was zu lernen, habe auch bereits eine funktionierende Lösung. Da ich das ganze schön verpackt für den nächsten Hochladen möchte suche ich eine Möglichkeit das ganze etwas zu entschlacken.
Benutze immo wxPython mit einem Richtext um eine Console zu emulieren (das Adventure ist sehr einfach, man hat im Prinzip im jedem Raum verschiedene Antwortmöglichkeiten, die einen dann in den entsprechenden nächsten Raum bringen aber ohne dauerhaft gespeicherte Sachen...)
Dadurch kommt das ganze mit Abhängigkeiten auf Stolze 16MB (das Originalspiel kommt auf 22kb ^^)
Versuche nun den Engine entweder so umzuschreiben das ich ne echte Console verwende und dort dann halt Farben anpassen kann (was wenn ich die Beiträge hier im Forum so sehe nicht gerade schön ist...) oder ne andere Lösung für das Problem finde.
Brauche Eigentlich auch nur 2 Sachen (hab den Rest testweise eingebaut um für den nächsten Engine zu lernen)
Ich muss den ganzen Bildschirm Centern können, den ganzen Bildschirm Linksbündig machen, den Hintergrund auf Blau und die Schrift auf Gelb.
Sowas kompliziertes wie eine Farbe pro Buchstaben usw muss nicht sein, wäre aber super.
Das ganze soll auch Crossplatform werden.
Hier erst mal der Engine (und bitte nicht tot umfallen)

Code: Alles auswählen

class Keys:
    def __init__(self):
        self.list = dict()
        
    def add(self, key):
        self.list[key.char] = key

    def generatetext(self):
        listkeys = self.list.keys()
        listkeys.sort()
        out = ""
        for listkey in listkeys:
            if self.list[listkey].visible:
                out = out + self.list[listkey].char + "." + "\t\t" + self.list[listkey].chartext + "\n"
        return out


class Key:
    def __init__(self, char, chartext, do, visible):
        self.char = char
        self.chartext = chartext
        self.do = do
        self.visible = visible

class Screens:
    def __init__(self):
        self.list = dict()

    def add(self, screen):
        self.list[screen.name] = screen

class Screen:
    def __init__(self, name, text, keys):
        self.name = name
        self.text = text
        self.keys = keys


import wx
import wx.richtext
class Engine(wx.Frame):
    def __init__(self, title = "Test", GameScreens = None, GameStart = None):
        self.app = wx.App()
        wx.Frame.__init__(self, None, -1, title, style= wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX, size=(745,425))
        self.title = title
        self.panel = wx.Panel(self, -1)
        self.output = wx.richtext.RichTextCtrl(self.panel, wx.ID_ANY, value="", size=(740,420), style = wx.TE_READONLY)
        self.output.SetEditable = False
        self.output.BeginTextColour(wx.Colour(255,255,0))
        self.output.BeginFontSize(11)
        self.output.BeginBold()
        self.output.SetBackgroundColour(wx.Colour(0,0,128))
        self.Centre()
        self.output.EnableScrolling(False, False)
        self.Show(True)
        self.Bind(wx.EVT_CHAR, self.onCharEvent)
        self.Screen = None
        self.Screens = Screens()
        self.output.Enabled = False
        if Screen is None:
            #Default Screens
            default = Screens()
            b = Keys()
            b.add(Key("ALL", "No Keys pressent", ("load", "test"), False))
            b.add(Key("1", "Testscreen", ("load", "test"), True))
            a = Screen("default", "<(255,0,0)>Engine loaded </(255,0,0)>\nDefault Screen loaded!", b)
            default.add(a)
                
            #Test Screen
            b = Keys()
            c = Key("B", "Back", ("load", "default"), True)
            b.add(c)
            a = Screen("test", "Test loaded!", b)
            default.add(a)

            self.Screens = default
            self.load("default")
        else:
            self.Screens = GameScreens
            self.load(GameStart)
        self.app.MainLoop()

    def onCharEvent(self, event):
        try:
            a = chr(event.GetKeyCode())
        except ValueError:
            a = ""
        try:
            key = self.Screen.keys.list[a]
        except KeyError:
            try:
                key = self.Screen.keys.list['ALL']
            except KeyError:
                return False
        if key.do[0] == "load":
            self.load(key.do[1])
        if key.do[0] == "quit":
            pass
            # hier quit rein!

    def load(self, screenname):
        self.Screen = self.Screens.list[screenname]
        self.output.Clear()
        #text = self.Screen.text
        text = []
        text.extend(parser(self.Screen.text))
        text.extend("\n\n")
        text.extend(parser(self.Screen.keys.generatetext()))
        for l in text:
            if l == "center":
                self.output.BeginAlignment(2) 
            elif l == "/center":
                self.output.EndAlignment()
            elif l == "!left":
                self.output.BeginAlignment(1) 
            elif l == "!/left":
                self.output.EndAlignment()
            elif l == "!right":
                self.output.BeginAlignment(3) 
            elif l == "!/right":
                self.output.EndAlignment() 
            elif l[:1] == "(" and l[-1:] == ")":
                self.output.BeginTextColour(colourparser(l[1:-1]))
            elif l[:2] == "/(" and l[-1:] == ")":
                self.output.EndTextColour()
            else:
                self.output.WriteText(l)

        #self.output.WriteText(self.Screen.text)
        #self.output.WriteText("\n \n")
        #self.output.WriteText(self.Screen.keys.generatetext())
                
def parser(string):
    out = []
    stringlist = string.split("<")
    for k in stringlist:
        out.extend(k.split(">"))
    return out

def colourparser(string):
    stringlist = string.split(",")
    return wx.Colour(int(stringlist[0]),int(stringlist[1]),int(stringlist[2]))
Ich weiß, ich weiß, viel zu viel.
Was ich im Prinziep mache ist, ich erstelle Räume, jeder Raum besteht aus Name, Angezeigtem Text und Auswahlmöglichkeiten (Keys)
Keys sind wiederum eine Anzahl von Key die die entsprechende Auswahl (also den Char der erwartet wird), den Text der dabei angezeigt wird und die folgenden Aktion enthalten.
Einfach mal mit einem "Engine()" den Testscreen hochbringen, dann sieht man was ich meine.

Habe schon nach PyGame geschielt, konnte dort aber bisher nur 2D und 3D Tutorials finden obwohl ich weiß das es da auch Textadventures gibt. Vlt kann mir da einer einen Tipp geben?

Re: Textadventure in Python

Verfasst: Mittwoch 22. September 2010, 08:01
von lutz.horn
Grundlegend wäre Curses. Ein Tutorial gibt es als Curses Programming with Python.

Re: Textadventure in Python

Verfasst: Mittwoch 22. September 2010, 08:35
von BlackJack
@p90: Ein Dictionary an den Namen `list` zu binden finde ich sehr verwirrend.

Zeichenketten durch wiederholte "Addition" zusammenzusetzen ist von der Laufzeit her potentiell sehr ungünstig. Der idiomatische weg in Python ist das Sammeln der Teilzeichenketten in einer Liste und am Ende dann ein `str.join()`. Dass Du da wiederholt immer wieder über ``self.list[listkey]`` auf immer das selbe Objekt durchgreifst, statt das einmal zu holen und an einen Namen zu binden, ist IMHO unschön. Die `generatetext()` könnte so aussehen (ungetestet):

Code: Alles auswählen

    def __str__(self):
        result = list()
        for _char, key in sorted(self.char2key.iteritems()):
            if key.visible:
                result.append('%s.\t\t%s' % (key.char, key.chartext))
        return '\n'.join(result)
Hast Du das Format der Auszeichnungssprache für die Daten hierfür selbst entworfen oder ist das eine Vorgabe von dem Spiel, das Du portieren willst? Das Ende für die Färbung erscheint mir unnötig redundant, da die Farbe darin ja gar nicht benötigt wird. Das sieht so ein klein wenig nach Pseudo-XML aus. Vielleicht könnte man da besser echtes XML nehmen statt sich selbst ein Format zusammen zu hacken.

Die ``if``\s in `Engine.onCharEvent()` sind IMHO nicht sehr objektorientiert. Da würde ich die `key`\s eher in sowas wie `command`\s umwandeln, die selbst wissen was sie zu tun haben und denen die Enginge übergeben und dann halt machen lassen. Insgesamt mischt Du auch die GUI und die Programmlogik viel zu stark. Wenn Du das auf ein anderen Toolkit oder PyGame portieren möchtest, musst Du ja die ganze Engine neu schreiben, obwohl es doch einen grundlegenden Teil an Programmlogik gibt, der dabei gleich bleiben sollte.


In `Engine.load()`, das eigentlich besser `display_screen()` oder so hiesse, denn mit `load()` verbindet man eher das laden von Daten aus einer Datei oder ähnliches, sollte man `line` ausschreiben. `l` ist nicht nur kurz, sondern kann je nach Zeichensatz auch leicht mit `1` verwechselt werden. Bei `self.Screens` sollte man nicht soweit in die "Innereien" durchgreifen, sondern besser eine `get()`-Methode oder `__getitem__()` auf der `Screens`-Klasse implementieren.

Die ``if``/``elif``-Kaskade kann man mit einem Dictionary kompakter schreiben.

Warum ist 'center' eigentlich eine Ausnahme was das führende '!' angeht?

Re: Textadventure in Python

Verfasst: Mittwoch 22. September 2010, 08:44
von Rebecca
Curses ist aber nicht wirklich angenehm zu programmieren. Ein Alternative ware Urwid. Das laeuft aber alles erstmal nur auf *nix (oder cygwin). Obwohl, es soll ja Windows-Ports geben...

Re: Textadventure in Python

Verfasst: Mittwoch 22. September 2010, 14:06
von p90
Hi,

erst mal Danke für die Antworten.
Und dann noch ein paar Worte zur Erklärung.
Ich habe von dem Spiel leider nur die kompilierte QBasic exe.
Da es aber sehr linear ist und auch keine Raumübergreifenden Sachen aufweist grase ich einfach per Hand alle Möglichkeiten in allen Räumen ab und stelle sie über meine Screen Klasse da.
Das Spiel das ich habe hat einen blauen Hintergrund und gelbe Schrift, es gibt einen Screen auf dem der gesamte Text gecentert ist, der Rest ist Linksbündig.

Als ich angefangen habe den Engine zu schreiben wollte ich es eigentlich möglich machen jede einzelne Zeile zu centern oder links bzw. rechtsbündig zu machen. Leider scheiterte das an den Möglichkeiten von wx.Richtext, da dies das anscheinend nur global setzten kann.
Um dies einfach zu ermöglichen bin ich durch alle Spiele die ich damit Portieren will gegangen und habe mich dann entschieden meine Kommandos in <> ein zu kesseln da diese beiden Zeichen nirgends verwendet werden. Hatte die Idee meinen HTML Kenntnissen (jaa, ist schon ganz lange her...) entnommen. Das ganze ist zwar sehr eingeschränkt tut aber das was es soll. (nur halt in viel zu viel MB).
Da mein Engine genau zwei Dinge tun muss (einmal einen Screen laden und zweitens sich selbst beenden) habe ich auch da bisher nur diese beiden Kommandos eingebaut. Da ich bei einem Game over nicht unbedingt wieder ganz von vorne anfangen will habe ich festgesetzt, das man bei einem "quit" den letzten Auswahlraum, also dort, wo man gerade die tödliche Wahl getroffen hat, übergibt, um in Zukunft sowas wie "Sie sind tot, wollen sie zu ihrer letzen Wahl zurückkehren?" einzubauen.

@BlackJack
1. (dict = list) Ja da hast du Recht, das sollte ich ändern.
2. Jo, das muss ich auch nochmal überarbeiten, hatte mir bisher noch nicht wirklich Gedanken über die Performance gemacht.
3. Jo, das ist selbst ausgedacht und an HTML angelehnt, mehr dazu steht oben.
Denke aber das XML für 5 oder 6 Sonderbefehle etwas zu groß ist.
4. Das hatte ich schonmal Probiert aber da muss ich doch immer jedem Key eine Referenz zu "Engine" geben oder? Ist es da nicht besser das der Engine, der eh das charKey bekommt dann nach sieht was er tun soll?
(habe immo eine Unterteilung in Script (Inhalt der Räume und logische Verbindung zwischen ihnen) und der Engine der die verschiedenen Räume läd je nachdem was der User eingibt.)
5. Ja, das "l" sollte echt was anderes sein, aber nicht line(dafür steht es auch nicht sondern ist ein Zufall...), sind ja keine lines, eher segmente...
6. __getitem__ ist keine schlechte Idee, baue es gerade ein
7. mit dict kompakter, das musst du mir erklären.
Meinst du etwa so?
commands = dict()
commands["center"] = self.output.BeginAlignment(2)
usw.
und dann eifnach ein
for segments in text:
try:
commands[segment]
except:
self.output.WriteText(segment)

Wertet er das dann etwa aus?
Kann ich mir gerade irgendwie nicht vorstellen aber das heißt ja bekanntlich nichts.

8. Hm, ja das "!" war noch zum Debuggen drin... Damit er die Befehle nicht findet...
Und ich sehen konnte ob es am Parser liegt der die Daten nicht bekommt oder sie falsch bearbeitet (er zeigte halt nie eine Zeile Linksbündig und die andere Rechtsbündig etc sondern immer nur für den ganzen Screen). Es lag dann aber wie gesagt an den beschränkten Fertigkeiten von wx.richtext...

Re: Textadventure in Python

Verfasst: Mittwoch 22. September 2010, 15:27
von BlackJack
@p90: Ad 3) Was meinst Du mit XML wäre zu gross? Ein vernünftiges XML-Modul ist mit `ElementTree` in der Standardbibliothek enthalten. Du brauchst Dir dann kein eigenes Grundformat überlegen und keinen eigenen Parser schreiben. Erweiterbar ist es dann auch, wenn Du neue Befehle/Elemente in die Datenstruktur einbauen möchtest, oder zusätzliche Daten die nur bei bestimmten Engines Sinn machen, hinzufügen möchtest. In das Datenformat kommt ja letztendlich noch ein wenig mehr Information, oder wolltest Du alle Räume/Screens wirklich in Python-Quelltext formulieren? Also ich würde ja ein bereits bekanntes und erprobtes Grundformat wie XML oder JSON verwenden. In XML könnte das in etwa so aussehen:

Code: Alles auswählen

<adventure version="0.1" title="Testadventure / Zweiraumwohnung" author="bj">
  <screens>
    <screen id="Startbildschirm">
      <description>
        <center>Einstiegs-Bildschirm</center>
        Hier geht das Spiel los.  Im Westen befindet sich eine Tür.
        <color value="red">Viel Spass!</color>
        <right>bj, 2010-09-22</right>
      </description>
      <keys>
        <key char="q" text="Beenden" command="quit" />
        <key char="w"
             text="Gehe durch die Tür"
             command="goto"
             target="Leerer Raum" />
      </keys>
    </screen>
    <screen id="Leerer Raum">
      <description>
        Ein fensterloser, leerer Raum mit einer Tür im Osten.
      </description>
      <keys>
        <key char="e"
             text="Gehe durch die Tür"
             command="goto"
             target="Startbildschirm" />
      </keys>
    </screen>
  </screens>
</adventure>
Ad 4) Also der `Key` muss keine permanente Referenz auf die `Engine` haben, es reicht ja wenn die entsprechende Methode eine übergeben bekommt. Wenn die `Engine` anhand einer ``if``/``elif``-Kaskade entscheidet was zu tun ist, dann ist das halt nicht besonders OOP-Style. Die Kommandos sind dann fest in der Engine kodiert.

Ad 7) Bei dem Beispiel führst Du das `BeginAlignment()` ja schon beim befüllen von `commands` aus und steckst dann den *Rückgabewert* der Methode in das Dictionary. An der Stelle musst Du ein "callable" da hineinstecken und dass dann später herausholen und aufrufen. Nur für die Ausrichtung könnte das so aussehen (ungetestet):

Code: Alles auswählen

        commands = {
            'center': partial(self.output.BeginAlignment, 2),
            '/center': self.output.EndAlignement,
            'left': partial(self.output.BeginAlignment, 1),
            '/left': self.output.EndAlignement,
            'right': partial(self.output.BeginAlignment, 3),
            '/right': self.output.EndAlignement,
        }
        
        # ...
        
        for segment in self.screen.text:
            command = segment.split(None, 1)[0]
            try:
                commands[command]()
            except:
                self.output.WriteText(segment)
Für die Farbe müsste man sich dann noch wieder etwas anderes ausdenken, denn das ist ja eine Anweisung mit einem Argument. Aus OOP-Sicht ist das auch wieder alles viel zu eng verbunden. Das Darstellen eines `Screen` könnte man auch in die `Screen`-Klasse verlagern und unabhängig von einem bestimmten GUI-Toolkit machen. Also zum Beispiel etwas in dieser Richtung:

Code: Alles auswählen

class Screen(object):

    # ...
    
    def display(self, display):
        display.set_title(self.name)
        for segment in self.text:
            parts = segment.split(None, 1)
            command = parts[0]
            arguments = '' if len(parts) == 1 else parts[1]
            if command in self.commands:
                if command.startswith('/'):
                    func_name = 'end_' + command[1:]
                else:
                    func_name = 'start_' + command
                getattr(display, func_name)(arguments)
            else:
                display.write_text(segment)
Das übergebene `display`-Objekt müsste man dann für das entsprechende GUI-Toolkit implementieren und es müsste über entsprechende Methoden wie `set_title()`, `write_text()`, `start_center()`, `end_center()` usw. verfügen.

Ad 8) Das '!' wäre schon nicht schlecht, wenn man dadurch sofort am ersten Zeichen einer Zeile schon entscheiden könnte, ob es sich um einen Befehl oder Text handelt.

Die Ausrichtung bei `wx.RichtextCtrl` kann man pro Absatz unterschiedlich einstellen. Du müsstest halt vor dem Start einer neuen Ausrichtung einen neuen Absatz erstellen.

Re: Textadventure in Python

Verfasst: Mittwoch 22. September 2010, 15:59
von sma
Mit den richtigen Klassendefinitionen kann das Beispiel von Blackjack auch (deutlich kompakter) direkt in Python ausgedrückt werden:

Code: Alles auswählen

screens = {
  "Startbildschirm": Screen(title="Einstiegsbildschirm", description="...", keys=[
    Key(char="q", text="Beenden", command="quit"),
    Key(char="w", text="Gehe durch die Tür", command="go", target="Leerer Raum")
  ]),
  "Leerer Raum": Screen(description="...", keys=[
    Key(char="e", "Gehe durch die Tür", command="go", target="Startbildschirm")
  ])
}
Ansonsten wäre noch mein Tipp, klar die "Engine" und das "GUI" zu trennen. Ich würde empfehlen, zunächst das Spiel mit der denkbar einfachsten Kommandozeilen-Schnittstelle zu implementieren und dann erst das GUI mit wx anzugehen. Das richtige Formatieren der Beschreibungen ist dann ein gesondertes Problem.

Muss es in dem Spiel nicht auch noch so etwas wie einen Zustand geben und müssen dann Aktionen nicht auch unterschiedliche Funktionen je nach Zustand haben? Ansonsten ist das ja einfach nur ein stumpfsinniges Durchklicken. Sollten etwa gewisse "Key"-Objekte nur dann verfügbar sein, wenn man z.B. bestimmte Gegenstände bei sich trägt, müsste man derartige Bedingungen noch mit in die Spielbeschreibung aufnehmen. Entweder eingebettet mit Hilfe von "lambda", oder in dem man spezielle Funktionen angibt oder durch eine eigene kleine domainspezifische Sprache, die das ausgewertet werden muss.

Code: Alles auswählen

Key(char="e", "Gehe durch die Tür", ..., condition=lambda state:state.has("Türschlüssel"))
Key(char="t", "Nimm Schlüssel", ..., condition=lambda state:state.screen.has("Türschlüssel"))
Stefan

PS: Vielleicht kann http://gist.github.com/138713 noch als Inspiration dienen. Eine Fingerübung, die ich letztes Jahr mal in Zusammenhang mit einem Beitrag schrieb, den ich jetzt zu faul bin, herauszusuchen.

Re: Textadventure in Python

Verfasst: Mittwoch 22. September 2010, 17:01
von problembär
Hatte mich vor ein paar Monaten auch mal an dem Thema versucht (in Tkinter, Python 2.x):

http://paste.pocoo.org/show/265799/

Hab' aber gemerkt, daß das doch recht aufwendig wird (wenn man's einigermaßen gut machen will) ;).

Eine ganz gute Alternative scheint mir, die Sprache Inform zu lernen. Dann kann man sich nur auf die Adventure-Daten konzentrieren. Steuerung, Eingabe usw. wird dann von einem speziellen Z5-Code-Interpreter wie z.B. Frotz übernommen.
Das geht soweit ganz gut, aber alle Adventures, von denen es schon eine große Menge gibt, haben dann dieselbe Oberfläche (die man vielleicht gar nicht so mag).

Gruß

Re: Textadventure in Python

Verfasst: Mittwoch 22. September 2010, 19:38
von p90
Hi,

also States gibts halt keine (wollte ich aber in Zukunft eh einbauen, kann man sich ja dann schon mal Gedanken machen ^^), das Spiel ist recht einfach gehalten.

Nach etwas lesen denke ich, das XML tatsächlich ne schöne Lösung für da ganze ist (auch wenn es jetzt für das eigentliche Spiel etwas über das Ziel hinaus ist ^^).
Werde jetzt mal überlegen wie ich meine script.py in eine XML-Datei umwandle.
(obwohl ich noch nicht ganz verstanden habe wie ich eine XML-Datei wieder einlese... Naja wird schon irgendwie ^^)

[EDIT]
Oh man. Gerade wenn man denkt man hat etwas verstanden geht schon wieder alles den Bach runter.
Versuche gerade mein Script in XML umkodieren zu lassen.

Dazu mal ein Auszug aus meiner script.py

Code: Alles auswählen

#ending

keys = Keys()
keys.add(Key("1", "Ende", ("load", "quit"), True))
text='''Unten angekommen siehst du wie der Fön in einer unglaublichen Geschwindigkeit
davon fliegt... Erleichtert siehst du hinterher, bis der Fön die Atmosphäre
verlassen hat... Du wirst mit einer riesen Party als Welt-retter empfangen
und gefeiert !
DU HAST ES GESCHAFFT !
CONGRATULATIONS !'''
keyscreen = Screen("ending", text, keys)
S.add(keyscreen)
#letzter Raum in Spiel, nur um zu sehen wie das ganze aussieht
#S is übrigens von der Klasse Screens

#hier der Versuch des Exports
from xml.etree import ElementTree as ET
root_element = ET.Element("Quest - 1")
#erzeuge Root Element
Rooms = ET.SubElement(root_element, "Rooms")
#Erzeuge Subelemente (nenne Screens in Rooms um btw)
for screen in S.list:
    screen = S[screen]
    #iteriere über die Keys im dict, brauche aber die dazugehörigen Werte
    Room = ET.Element("Room", name=screen.name, text=screen.text)
    #Erzeuge ein Raumelemente das alles wichtige über einen screen enthalten soll.
    Options = ET.SubElement(Room, "Options")
    #keys werden zu optins umbenannt
    for lkey in screen.keys.list:
        lkey = screen.keys.list[lkey]
        #selbes spiel wie oben
        Option = ET.Element("Option", key=lkey.char, text=lkey.chartext, command=lkey.do[0], room=lkey.do[1], visible=lkey.visible)
        Options.append(Option)
    Rooms.append(Room)
#bis hier bekomme ich auch noch keinen Fehler
print ET.tostring(root_element)
#macht dann alles kaputt.
#Dachte da würde so was rauskommen wie bei BlackJack aber ich bekomme nur Seitenweise #Fehler angezeigt.
Hab mich übrigens an diesem Howto Entland gehangelt:
http://www.learningpython.com/2008/05/0 ... ingXMLdata

[EDIT2]
Oh man, ich sollte schlafen gehen.
Ich habe da einen Boolischen Wert mit dem XML nicht zu recht kamm.
Nachdem ich mit ner kleinen Funktion das ganze in nen String verwandelt hatte gehts jetzt.

Aber mal ne andere Frage, wie geht das den mit Vererbung bei XML?
Also wenn ich z.B. in Rooms eine Farbe setze so das sie sich auf alle enthaltenen Räume auswirkt? Oder ist das ne Schnapsidee?

[EDIT3]
Und noch was, wie bekomme ich es hin das mir Python menschenlesbares XML schreibt?
Also immo ist das ein langer String.
Würde gerne Zeilenumbrücke nach jedem element einfügen, also nicht so:

Code: Alles auswählen

<root><key>Hallo</key></root>
sondern so:

Code: Alles auswählen

<root>
<key>Hallo</key>
</root>
Am besten noch mit Einrückungen, konnte da bisher noch nichts finden aber auch ka nach was ich da suchen soll.

Re: Textadventure in Python

Verfasst: Donnerstag 23. September 2010, 20:21
von problembär
Oh, den XML-Ansatz finde ich ja mal spannend!

In Inform sieht das Beispiel aus diesem Tutorial so aus:

Code: Alles auswählen

Constant Story "INSIDE HACK";
Constant Headline "^An Inform sample adventure.^";

Include "Parser";
Include "VerbLib";

[ Initialise;
  location = Router;
];

Include "Grammar";

Object Router "Router NP-462",
  has light,
  with
    description
        "This little data interchange is run-down, shabby, and
        rather sad. You can't see any traffic -- besides yourself --
        and cobwebs of noisy static hang in the dusty corners. The
        meat-side that this router serves must be a bare hallway,
        almost no hardware.^^
        A broad network connection leads east, and a serial line runs
        south. The network connection is barred by a fierce-eyed access
        controller.",
    s_to Scanner;

Object Scanner "Scanner South-9",
  has light,
  with
    description
        "The firmware here is tight and clean; this device, at
        least, is well-maintained. You can see image-analysis nets
        strung in their neat rows around the sensor fountain which
        dominates this space. The only exit is the serial line to the
        north.",
    n_to Router;
Das könnte man tatsächlich auch in einer XML-Struktur schreiben (und umgekehrt). Faszinierend!

Gruß

Re: Textadventure in Python

Verfasst: Freitag 24. September 2010, 03:56
von p90
@problembär
Wenn ich das Richtig gelesen habe verwaltet Python XML-Trees eh als Listen.
Ka wie das mit dem von dir gepostetem aussieht, aber das was ich mir da selber geschrieben hatte ist genau das ^^.
Habe jetzt eine überarbeitete, auf XML basierende Version.
Fürs erste habe ich es ind er Konsole am laufen, aber da ist wiedermal der übliche Quark (kein getChar, cls nur unter Windows, also alles was einem so richtig schön den Schlaf raubt ^^)

Code: Alles auswählen

from xml.etree import ElementTree as ET
from functools import partial
import sys

class Engine:
    def __init__(self, gamefile = "./page.xhtml"):
        temp = ET.parse(gamefile)
        
        self.gamedata = temp.getroot()
        self.Rooms = self.gamedata.find("Rooms")
        startroom = self.gamedata.find("Settings").attrib["startroom"]
        self.title = self.gamedata.find("Settings").attrib["titel"]
        self.actualroom = self.name2room(startroom)
        self.lastRoom = None
        self.commands = dict()
        self.commands["load"] = partial(self.load)
        self.commands["quit"] = partial(self.quit)
        self.commands["exit"] = partial(self.exit)
        self.commands["lastRoom"] = partial(self.load, self.lastRoom)

    def option(self, key):
        options = self.actualroom.find("Options")
        temp = None
        for option in options:
            if option.attrib["key"] == key:
                temp = option
                break
            if option.attrib["key"] == "ALL" and not key == "\n":
                print "Mu", key, "ha"
                temp = option

        if temp is None:
            return None
        command = temp.attrib["command"]
        arg = []
        i = 0
        try:
            while 1:
                i+=1
                arg.append(temp.attrib["arg" + str(i)])
        except:
            pass
        i = len(arg)
        #this is stupid but cant figure out how to do it!
        if i == 0:
            return self.commands[command]()
        if i == 1:
            return self.commands[command](arg[0])
        if i == 2:
            return self.commands[command](arg[1], arg[2])

        
        
        
    def exit(self):
        self.destroy()
        return None

    def quit(self, lastRoom = None):
        temp = self.name2room("quit")
        if temp is None:
            self.exit()
        self.load("quit")
        self.lastRoom = lastRoom

    def load(self, room):
        self.actualroom = self.name2room(room)
        return self.name2room(room)
        
        
        

    def name2room(self, name):
        return self.Rooms.find(name)
    

        


class Screen:
    def __init__(self, engine):
        self.engine = engine
        self.load(self.engine.actualroom)
        while 1:
            try:
                temp = sys.stdin.read(1)
            except IOError: temp = None
            k = self.engine.option(temp)
            self.load(self.engine.actualroom)
            temp = None



    def load(self, Room):
        if Room is None:
            return None
        print Room.find("Text").text
        print "\n"
        Options = Room.find("Options")
        for option in Options:
            if option.attrib["visible"] == "True":
                print option.attrib["key"], "\t", option.attrib["text"]
    



import aes
if __name__ == '__main__':
    Screen(Engine())
Geht soweit, bis halt auf diesen Kram mit dem getChar.

Habe übrigens jetzt raus gefunden das Firefox meinen XML Code vernünftig darstellt.

[EDIT]
Vlt noch ein Tipp wie man das Vernünftig parsen soll.
Habe ja sowas:

Code: Alles auswählen

<Room>
<Text>
<center> Hallo</center><rot>, hier ist der Mond!</rot>
</Text>
</Room>
Das mit dem Rot ist jetzt nur ein Bsp, aber wie bearbeite ich das am besten?
Den Text hole ich mir ja bisher mit Room.find("Text").text
Wenn das aber wieder Elemente enthält, was mache ich dann?

Re: Textadventure in Python

Verfasst: Freitag 24. September 2010, 12:08
von BlackJack
@p90: Vielleicht habe ich Dich mit dem XML auch ein wenig auf den falschen Weg geschickt. Ich hatte dabei keine Engine im Sinn, die XML interpretiert. Ich würde weiterhin Klassen schreiben die keine Ahnung von XML haben. Das ist ja im Grunde das gleiche wie bei der Kopplung von GUI und Logik -- die Logik sollte auch unabhängig von der Datenspeicherung sein. Jetzt hast Du sowohl die Benutzerinteraktion als auch das Datenformat mit der Programmlogik vermischt.

Der Hinweis von sma, dass man mit geeigneten Klassen das Adventure auch recht kompakt in Python selbst ausdrücken kann, ist gar nicht so schlecht gewesen. Mein Vorschlag die Daten in XML oder JSON auszulagern fusste hauptsächlich darauf, dass Du ja anscheinend mehrere Adventures portieren wolltest. So hatte ich das zumindest verstanden. Und da würde es halt Sinn machen eine allgemeine Engine zu schreiben und die Daten für das Adventure davon strikt zu trennen um die Engine wiederverwenden zu können.

"Actual" heisst auf deutsch übrigens nicht "aktuell" sondern "tatsächlich". Weiss nicht, ob das so gewollt war!?

`partial()` nur mit einer Funktion aufzurufen macht nicht viel Sinn, da kann man auch gleich einfach nur die Funktion direkt verwenden. Und `partial()` bindet Werte. Die werden zu dem Zeitpunkt ausgewertet wenn `partial()` aufgerufen wird, nicht wenn das Ergebnis der Funktion aufgerufen wird. In der letzten Zeile von `__init__()` bindest Du also `None` als erstes Argument von `load()` und nicht den Wert von `self.lastRoom` wenn die resultierende Funktion aufgerufen wird.

Gibt's einen Grund, warum Du da kein literales Dictionary hingeschrieben hast? Ist weniger Tipparbeit.

`Engine.option()` scheint mir zu viel zu tun oder unpassend benannt zu sein. Holt das nun eine Option oder führt es ein Kommando aus? Das Ende kann man durch ``return self.commands[command](*arg)`` ersetzen.

Re: Textadventure in Python

Verfasst: Montag 27. September 2010, 18:15
von p90
Hi,

sry das ich mich so lange nicht gemeldet habe.
Erstmal, XML ist für das was ich mache wirklich etwas groß (ich bekomme 1MB Extra Dependencies) aber eigentlich ist es die richtige Wahl.

Wie es immo ist, ist es schon gut.
XML als Kommunikation zwischen Engine und Screen zu verwenden ist denke ich der richtige Weg. Auf diese Weise kann ich in Screen entscheiden wie ich es anzeigen will.

So oder so habe ich ne Menge über XML gelernt ^^
Wird in Zukunft bestimmt noch mal Praktisch sein.

Re: Textadventure in Python

Verfasst: Montag 27. September 2010, 18:28
von BlackJack
@p90: `ElementTree` ist doch Bestandteil der Standardbibliothek!? Davon abgesehen ist 1 MiB heutzutage wirklich nicht mehr viel. Ich denke man darf nicht die Grösse von den alten Textadventuren mit heutigen Programmen vergleichen, auch wenn die nur das gleiche tun.