Feld erstellen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
jonas
User
Beiträge: 156
Registriert: Dienstag 9. September 2008, 21:03

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
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

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:
Take it easy Mates!
Benutzeravatar
jonas
User
Beiträge: 156
Registriert: Dienstag 9. September 2008, 21:03

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
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

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.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

@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 :-)
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo Hyperion

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

Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

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:
Take it easy Mates!
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

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.
Benutzeravatar
jonas
User
Beiträge: 156
Registriert: Dienstag 9. September 2008, 21:03

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
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

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:
Take it easy Mates!
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

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.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
jonas
User
Beiträge: 156
Registriert: Dienstag 9. September 2008, 21:03

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:
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.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

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:
Take it easy Mates!
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

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:
Take it easy Mates!
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

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:
Take it easy Mates!
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

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
Antworten