Seite 1 von 2

Feld erstellen

Verfasst: Donnerstag 25. September 2008, 10:26
von jonas
Hallo.
Habe nach Betätigen der SuFu nichts für mich passendes gefunden.
Ich möchte mal kurz mein Problem schildern:

Ich möchte gern komplett von eine graphischen Oberfläche getrennt
eine Art festgelegtes Spielfeld erzeugen.
Ich habe aber keine Ahnung für einen Ansatz.
(höchstens etwas in folgender Richtung erstellen:
Feld = ((x0,y0),(x1,y1)...) um die Koordinaten einzeln
festzulegen.)
Das erscheint mir allerdings sehr kompliziert.
Im Bezug hierauf verweise ich auf den AntMe! Klon
(den es zu erstellen ja bereits einen eigenen Thread hat)
Ich hoffe ich habe das Problem einigermaßen plausibel beschrieben.

Mit freundlichen Grüßen
Jonas

Verfasst: Donnerstag 25. September 2008, 11:10
von sma
Ein Spielfeld könnte man so repräsentieren:

Code: Alles auswählen

class Field:
    def __init__(self, x, y):
        self.x, self.y = x, y

class Board:
    def __init__(self, width, height, cls=Field):
        self.width, self.height = width, height
        self.fields = [cls(x, y) for y in range(height) for x in range(width)]
        
    def __getitem__(self, (x, y)):
        return self.fields[x + y * self.width]

    def __iter__(self):
        return iter(self.fields)

b = Board(3, 4)
b[2, 1].name = "Heimat"
for f in b:
    print f.x, f.y
Wenn es nur wenige relevante Felder gibt -- etwa bei einer Weltraumkarte, die im wesentlichen leer ist -- könnte man auch Koordinaten-Tupel als Schlüssel für eine Hashmap benutzen.

Code: Alles auswählen

class Space:
    def __init__(self):
        self.planets = {}
        
    def distribute_planets(self, planets):
        for planet in planets:
            while True:
                xy = random.randrange(100), random.randrange(100)
                if xy not in self.planets: break
            self.planets[xy] = planet
    
    def __getitem__(self, xy):
        return self.planet.get(xy)
Ist die Karte nicht regelmäßig aufgebaut -- man denke die Länder des Brettspiels Risiko -- ist es vielleicht sinnvoll, Objekte mit einer Nachbarschaftsrelation anzulegen:

Code: Alles auswählen

NORTH, WEST, SOUTH, EAST = range(4)

class Field:
    def __init__(self):
        self.neighbors = [None, None, None, None]

    def north_of(self, field):
        self.neighbors[SOUTH] = field
        field.neighbors[NORTH] = self
        
    def south_of(self, field):
        field.north_of(self)
    
    def west_of(self, field):
        self.neighbors[EAST] = field
        field.neighbors[WEST] = self
    
    def east_of(self, field):
        field.west_of(self)

usa, europe, africa, asia = Field(), Field(), Field(), Field()
usa.west_of(europe)
africa.south_of(europe)
asia.east_of(europe)
Stefan

Verfasst: Donnerstag 25. September 2008, 11:58
von wuf
Hallo jonas

Hier noch eine kleine Erweiterung für das erste Code-Snippet von 'sma'. (Eine minimale Zeichen-Grafik-Ausgabe auf die Konsole)

Code: Alles auswählen

class Field:
    def __init__(self, x, y):
        self.x, self.y = x, y

class Board:
    def __init__(self, width, height, cls=Field):
        self.width, self.height = width, height
        self.fields = [cls(x, y) for y in range(height) for x in range(width)]

    def __getitem__(self, (x, y)):
        return self.fields[x + y * self.width]

    def __iter__(self):
        return iter(self.fields)

def output_board(board_obj):
    """Gebe die Belegung der Spielflaeche aus"""

    print
    print "Spielfeld-Belegung:"
    print "(Geometrie: X=%d Y=%d)" % (board_obj.height, board_obj.width)
    for y in xrange(board_obj.height):
        for x in xrange(board_obj.width):
            try:
                name = board_obj[x, y].name
                field_symbol = "X"
            except:
                field_symbol = "."
            print field_symbol,
        print
    print

b = Board(10, 4)
b[2, 1].name = "Heimat"
for f in b:
    print f.x, f.y

output_board(b)
Gruss wuf :wink:

Verfasst: Donnerstag 25. September 2008, 12:05
von jonas
hi sma!
Erstmal vielen Dank für deine schnelle Antwort!
Ich habe ein paar Fragen zu deinem Code:
(habe mich noch nicht mit Klassen beschäftigt :oops:)
was bringt denn in der Klasse

Code: Alles auswählen

 
def __init__(self, x, y): 
        self.x, self.y = x, y 
außerdem was macht cls?
Danke im voraus, für deine Antwort.
@wuf: Sry als ich die Antwort geschrieben habe
war dein Beitrag noch nicht da...

Mit freundlichen Grüßen
Jonas

Verfasst: Donnerstag 25. September 2008, 12:12
von Leonidas
jonas hat geschrieben:Ich habe ein paar Fragen zu deinem Code:
(habe mich noch nicht mit Klassen beschäftigt :oops:)
was bringt denn in der Klasse

Code: Alles auswählen

 
def __init__(self, x, y): 
        self.x, self.y = x, y 
außerdem was macht cls?
Das Codesnippsel speichert die Werte von ``x`` und ``y`` in der Instanz der Klasse, damit sie auch in anderen Methoden über ``self.x`` und ``self.y`` erreichbar sind; ``x`` und ``y`` gehen ja am Ende von ``__init__`` sonst verloren.

``cls`` ist einfach ein Parameter dessen Vorbelegung ``Field`` ist, also wird bei ``cls(x, y)`` in der Regel ``Field(x, y)`` aufgerufen.

Verfasst: Donnerstag 25. September 2008, 13:43
von Hyperion
@wuf: Das Symbol eines Zeichens sollte man sich dann aber besser im Objekt selber merken (ob direkt oder zur absoluten Trennung zwischen Daten und Aussehen in einem verlinkten Objekt). Solche if-else Kaskaden sind da schnell unflexibel und häßlich :-)

Verfasst: Donnerstag 25. September 2008, 13:48
von wuf
Hallo Hyperion

Danke für deinen Tipp. Ich wollte das Kern-Code-Snippet von 'sma' nicht antasten.

Gruss wuf :wink:

Verfasst: Donnerstag 25. September 2008, 14:00
von wuf
Hallo Hyperion

Hast du es in etwa so gemeint?

Code: Alles auswählen

class Field:
    def __init__(self, x, y):
        self.x, self.y = x, y
        self.name = "."

class Board:
    def __init__(self, width, height, cls=Field):
        self.width, self.height = width, height
        self.fields = [cls(x, y) for y in range(height) for x in range(width)]

    def __getitem__(self, (x, y)):
        return self.fields[x + y * self.width]

    def __iter__(self):
        return iter(self.fields)

def output_board(board_obj):
    """Gebe die Belegung der Spielflaeche aus"""

    print
    print "Spielfeld-Belegung:"
    print "(Geometrie: X=%d Y=%d)" % (board_obj.height, board_obj.width)
    for y in xrange(board_obj.height):
        for x in xrange(board_obj.width):
            name = board_obj[x, y].name
            print name,
        print
    print

b = Board(10, 4)
b[2, 1].name = "X"
for f in b:
    print f.x, f.y

output_board(b)
Gruss wuf :wink:

Verfasst: Donnerstag 25. September 2008, 14:09
von Hyperion
Japp so in etwa :-)

Man könnte sich auch noch folgendes ausdenken:

Code: Alles auswählen

ascii_art = {"leer":".", "player":"x"}

class Field:
    def __init__(self, x, y, name="leer"):
        self.x, self.y = x, y
        self.name = name

def output_board(board_obj):
...
            name = board_obj[x, y].name
            print ascii_art[name],
...

b = Board(10, 4)
b[2, 1].name = "player"
for f in b:
    print f.x, f.y
So ließen sich schon sehr einfach eigene "Themes" erstellen ;-)

Noch gekapselter dürfte es sein, eine Repräsentationsklasse zu erstellen, von der jedes field ein object kennt, oder der Zusammenhang wie oben über ein Dictionary vorgenommen wird. In dieser Repräsentationsklasse definiert man sich eine paint()-Methode o.ä. und dort findet dann die Ausgabe statt. Bei simpler Ausgabe im Textmodus eben ASCII Zeichen per print, bei SDL eben auch echte Grafik usw.

Verfasst: Donnerstag 25. September 2008, 16:53
von jonas
hi @ all.
ich liebe dieses forum :D
find ich immer klasse wie schnell man was für
antworten bekommt, und dass sich die antwort-
geber sogar noch versuchen zu "übertrumpfen" :lol:

Mit freundlichen Grüßen
Jonas

EDIT: Noch eine Frage zu sma's erstem Post:

Code: Alles auswählen

b[2, 1].name = "Heimat" ##Das definiert ein Teil des Feldes mit
                                     ## Namen oder?
for f in b: ##Und was macht das (wo kommt das f her?)
    print f.x, f.y

Verfasst: Donnerstag 25. September 2008, 17:04
von wuf
Hallo Hyperion

Ich finde deinen Vorschlag gut! Besten Dank!

@jonas: Ich habe den Vorschlag von 'Hyperion' in ein ausführbares Code-Snippet einfliessen lassen.

Code: Alles auswählen

class Field:
    def __init__(self, x, y, name="leer"):
        self.x, self.y = x, y
        self.name = name

class Board:
    def __init__(self, width, height, cls=Field):
        self.width, self.height = width, height
        self.fields = [cls(x, y) for y in range(height) for x in range(width)]

    def __getitem__(self, (x, y)):
        return self.fields[x + y * self.width]

    def __iter__(self):
        return iter(self.fields)

def output_board(board_obj):
    """Gebe die Belegung der Spielflaeche aus"""

    print
    print "Spielfeld-Belegung:"
    print "(Geometrie: X=%d Y=%d)" % (board_obj.height, board_obj.width)
    for y in xrange(board_obj.height):
        for x in xrange(board_obj.width):
            name = board_obj[x, y].name
            print ascii_art[name],
        print
    print

ascii_art = {"leer":".", "ant-1":"1", "ant-2":"2", "ant-3":"3", "ant-4":"4"}

b = Board(10, 4)
b[2, 1].name = "ant-1"
b[9, 3].name = "ant-2"
b[5, 2].name = "ant-3"
b[1, 0].name = "ant-4"

for f in b:
    print f.x, f.y 

#~~ Test Spielflaeche-Belegung
output_board(b)
Gruss wuf :wink:

Verfasst: Donnerstag 25. September 2008, 17:14
von Leonidas
jonas hat geschrieben:EDIT: Noch eine Frage zu sma's erstem Post:

Code: Alles auswählen

b[2, 1].name = "Heimat" ##Das definiert ein Teil des Feldes mit
                                     ## Namen oder?
for f in b: ##Und was macht das (wo kommt das f her?)
    print f.x, f.y
Ersteres setzt das Attribut ``name`` auf 'Heimat' auf dem Objekt, dass unter dem Index ``2, 1`` am Objekt ``b`` erreichbar ist.
Zweiteres: das ``f`` wird einfach gesetzt, es ist eine Schleife und ``f`` nimmt in jedem Durchlauf den Wert von ``b[0]``, ``b[1]``, ... ``b[n-1]`` ein (``n`` ist die Länge von ``b``). Also es iteriert über jeden Indexwert.

Verfasst: Samstag 27. September 2008, 00:31
von jonas
nabend.
Diese Klassen machen mich noch völlig fertig...
Ich versteh ein Teil des Codes immer noch nicht...

Was bringen die Funktionen :

Code: Alles auswählen


 def __getitem__(self, (x, y)):
        return self.fields[x + y * self.width]

    def __iter__(self):
        return iter(self.fields)

und warum müssen bei der Klasse Field für x und y nichts angegeben
werden? Werden da "automatisch" Werte eingesetzt (0,1,2...) ?

Mit freundlichen Grüßen
Jonas
PS: Kann mir gut vorstellen, dass ich euch langsam, aber sicher sehr
auf die Nerven gehe, würde mir auch so gehen. (Sorry)
Bitte trotzdem um gescheite Antworten, sonst muss ich noch öfters
fragen :oops:

Verfasst: Samstag 27. September 2008, 08:03
von BlackJack
Vielleicht solltest Du Dich dann erst einmal mit Klassen und wie die funktionieren im Allgemeinen beschäftigen und nicht gleich so eine relativ grosse Klasse am Stück verstehen wollen.

Verfasst: Samstag 27. September 2008, 09:44
von wuf
Hallo

Ehrlich gesagt ist bei mir der Groschen betreffs der Methode:

Code: Alles auswählen

  def __iter__(self):
        return iter(self.fields)
trotzt Docs auch noch nicht gefallen.

Wie würde diese in Low-Level Python-Code geschrieben aussehen?

Gruss wuf :wink:

Verfasst: Samstag 27. September 2008, 10:09
von sma
Hätte nicht gedacht, das mein bisschen Code solche Wellen schlägt :)

Jonas, Field-Exemplare werden in Zeile 8 mit x und y initialisiert. `cls(x, y)` erzeugt, da `cls==Field`, entsprechende Exemplare, was dazu führt, dass `Field.__init__` aufgerufen wird, was dann die beiden Koordinaten setzt. Die "list comprehension" (der Ausdruck in eckigen Klammern mit den beiden for-in-Ausdrücken) sorgt dafür, dass für alle x von 0 bis `width-1` und für alle y von 0 bis `height-1` jeweils ein Field-Exemplar erzeugt und initialisiert wird, die dann alle zusammen in Form einer Liste der Exemplarvariablen `fields` zugewiesen werden.

Die spezielle Methode `__iter__` sorgt dafür, dass ein Objekt aufzählbar wird und damit in einer for-Schleife benutzt werden kann (siehe Zeile 18 in meinem Code). Das Objekt aufzuzählen interpretiere ich als auf Aufzählen aller Felder und daher gebe ich einen mit `iter()` erzeugten Iterator für die Liste aller Felder zurück. Man hätte auch `for f in b.fields` schreiben können.

Die spezielle Methode `__getitem__` sorgt dafür, dass man mit `[]` auf ein Board-Exemplar zugreifen darf. Ein `b[2,1]` ist die Kurzform für `b.__getitem__((2, 1))`. Beachte, dass `(2, 1)` ein Tupel ist, welches ist dann in meiner `__getitem__`-Implementierung durch die Extraklammern in der Parameterliste sofort auf zwei Variablen aufspalte.

Wuf, ein `output_board` passt IMHO besser als Methode. Auch finde ich nicht so gut, fehlende `name`-Attribute über eine Exception -- zumal noch eine "catch all"-Exception -- aufzufangen. Wenn man viele unterschiedliche Attribute hat, die häufig fehlen, würde ich's eher so machen:

Code: Alles auswählen

class Field...
    def __getattr__(self, name):
        return getattr(self, name, None)
In der Regel ist aber wohl der richtige Weg, das Attribut in `__init__` zu initialisieren.

Hyperion, wenn schon "themes", dann bitte nicht als globale Variable. Besser wäre es, so ein Dictionary als weiteres Argument der Methode bzw. Funktion `output_board` zu übergeben. Andererseits: Solange man sich nicht sicher ist, dass man verschiedene Varianten benötigt, sollte man YAGNI beachten.

Stefan

Verfasst: Samstag 27. September 2008, 10:20
von wuf
Hallo sma

Besten Dank für deinen Feedback. Ich sehe jetzt klarer. Ich muss nur feststellen, dass Tag für Tag in der Python-Sprache neu Elemente auftauchen die intensives Doc-Lesen fordern, dass nicht mehr von den geschriebenen Worte abgelesen werden kann was abläuft.

Gruss wuf :wink:

Verfasst: Samstag 27. September 2008, 10:46
von sma
Eigentlich kann man's schon ablesen (es geht gar nicht anders, sonst wäre es keine für einen Computer eindeutig interpretierbare Sprache), nur ist der Sprachumfang größer als du zur Zeit denkst.

Ich bin mir sicher, dass dir auch irgendwann "Pattern matching, "Sequence comprehensions" oder Funktionen höherer Ordnung als ganz normale Sprachkonstrukte vorkommen werden.

Ich gestehe, bei Dingen wie dem Y-Kombinator komme ich auch nicht mehr mit:

Code: Alles auswählen

def Y(f):
    l = lambda cc: f(lambda x: cc(cc)(x))
    return l(l)

fac = Y(lambda f: lambda n: 1 if n == 0 else n * f(n - 1))
Stefan

Verfasst: Samstag 27. September 2008, 11:31
von wuf
Hallo sma

Danke für deine guten Erklärungen. Muss zugeben bei dem von dir vorgestellten Y-Kombinator gerät meine Sprache ins stottern. :lol:

Gruss wuf :wink:

Verfasst: Samstag 27. September 2008, 12:51
von sma
Och, vorlesen kann ich's schon:

Das innere lambda in Zeile 5 ist die Fakultätsfunktion, die aber statt sich selbst rekursiv aufzurufen, das in dieser Funktion freie "f" als Funktion benutzt. Das äußere Lambda stellt diese Funktion zur Verfügung und ist damit eine Funktion, die eine Funktion liefert, die ein "n" für die Faktultätsfunktion erwartet. Der Y-Kombinator nimmt nun diese Funktion, die eine Funktion liefert, die das "n" erwartet, und macht sie zu einer vollständigen Fakulitätsfunktion, die sich rekursiv aufruft und ein "n" erwartet.

Der Y-Kombinator macht offenbar aus einer dergestalten Funktion eine rekursive Funktion.

In Zeile 3 liefert er das Ergebnis von "l" auf sich selbst angewandt zurück. "l" ist eine Funktion, die eine Funktion übergeben bekommen muss, die sagt, wie es weiter geht, eine sogenannte "continuation" (auch current continuation bzw. cc genannt). Diese ist sie selbst. Damit ist "l(l)" eine Funktion, die an "f" übergeben, "f" wieder aufruft (bis das `if n==0` in Zeile 5 wahr ist). Arg... Hut ab vor den verdrehten Gedanken von Haskell Curry, der das Ding erfunden hat.

Stefan