Mein Schulprojekt - Games programmieren mit Python

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
nzaugg
User
Beiträge: 5
Registriert: Dienstag 24. Mai 2011, 21:46

Hallo, Ich besuche momentan die 9. Klasse, und mache mit einem Kollegen eine kleine 'Matur-arbeit.'
Wir haben beschlossen, ein/mehrere Spiel/Spiele mit Python zu programieren. Wir sind jetzt etwa 2 wochen daran, und haben ein Scheren,Stein,Papier Spiel entwickelt, (nur Text) and ich arbeite gerade an einem Spiel wo man in einem 'Dungeon' herumlaufen kann, so a la Rogue oder Nethack, alles ASCII. Ich habe gestern und Heute daran gearbeitet, und bin relativ stolz darauf, doch ich glaube ich könnte es noch 'sauberer' programmieren. Bis jetzt habe ich ein X welches in einem 3x4 spielfeld herumläuft. Ich will dann später auch noch viliecht einen Gegner hinein tun.

Fals ich hier im falschen Forums Abschnitt nach Hilfe frage, oder wenn dass überhaupt nicht eine Seite für Anfängern ist, dann entschuldige ich mich im Voraus. Doch wenn ihr mir helfen konntet, wäre ich sehr dankbar.

Im moment muss ich das ganze Spielfeld mit dem neuplazierten x immer wieder zum Bildschirm 'printen.' Kann Ich das irgendwie vermeiden?
Es ist vilicht schwirig zu verstehen, doch ich wollte eigentlich mein Programm anhängen, begreife jedoch nicht so ganz wie. So bald ich es heraus finde, werde ich das Programm hinaufladen.

gruss,
nzaugg =)
BlackJack

@nzaugg: Plattformunabhängig gibt es wohl keine Lösung für das ``print``\en. Unter Unix/Linux könnte man das `curses`-Modul verwenden.

Längere Quelltexte solltest Du nicht in den Beitrag selbst einfügen, sondern bei einem Paste-Service hinterlegen und dann im Beitrag darauf verweisen. Oben auf der Seite ist ein Verweis zum Pastebin von diesem Forum. Man kann aber auch externe Anbieter wie beispielsweise http://paste.pocoo.org/ verwenden.

Für ganze Projekte solltet ihr überlegen eine Versionsverwaltung wie Git oder Mercurial zu verwenden und das Projekt dann über http://github.org oder http://bitbucket.org anzubieten.
nzaugg
User
Beiträge: 5
Registriert: Dienstag 24. Mai 2011, 21:46

@blackjack

Herzlichen Dank für die schnelle Antwort. Ich habe (glaube ich zumindest) das Pastebin begriffen und.... voila :
http://www.python-forum.de/pastebin.php?mode=view&s=189
sollte gehen, glaube ich.

Also gibt es keine lösung anstaat das ganze Feld immer wieder neue printen, sondern dass man irgendwie ein Feld machen könte, wo das x immer nur an einem neuen ort ist, doch den rest des Feldes bleibt?

gruss,
nzaugg
deets

Schoen, dass du deinen code hochladen konntest.

Dein Problem ist aber nicht so sehr, dass du alles neu printen musst. Der Computer erledigt das so schnell, dass es nicht ins Gewicht faellt - strenggenommen "printed" er jedes Pixel jede Sekunde 60 oder wieviel auch immer mal neu.

Dein wirkliches Problem ist, dass du eine ungeeignete Datenstruktur verwendest. Fuer dein Spiel brachst du zuerstmal zwei Dinge:

- eine Raum/Level-Beschreibung
- einen Spieler, zu Beginn hat der erstmal nichts anderes als eine Position

Diese zwei Dinge kombiniertst du dann zur Ausgabe. Danach kommen dann weitere Aspekte, wie zB Bewegungsteuerung, Kollisionsabfrage mit Waenden, Gegnern und Gegenstaenden, Kampf... usw.

Das hier ist eine ganz simpler Anfang :

Code: Alles auswählen



import time
from copy import copy

RAUM_A = """
+---+
|...|
|...|
....|
----+
"""

class Raum(object):

    def __init__(self, definition):
        # den definitions-String
        # in eine Liste von listen verwandeln,
        # damit man darauf per Koordinate zugreifen
        # kann.
        data = []
        for line in definition.split("\n"):
            line = line.strip()
            if not line:
                continue
            data.append(list(line))
        self.data = data


    def data_for_rendering(self):
        # Eine Kopie erzeugen, damit
        # wir da den Spieler reinpacken koennen,
        # ohne das Level zu veraendern
        return copy(self.data)


class Spieler(object):

    SYMBOL = "X"
    
    def __init__(self, x, y):
        self.x, self.y = x, y

        
raum = Raum(RAUM_A)
spieler = Spieler(2, 2)

while True:
    spieler.move()
    level_data = raum.data_for_rendering()
    level_data[spieler.y][spieler.x] = spieler.SYMBOL

    for line in level_data:
        print "".join(line)

    time.sleep(1)
    
Ich hoffe, das vermittelt dir die Idee.
BlackJack

@nzaugg: Ui, das geht ja so gar nicht. Stell Dir mal vor Du würdest das auf sagen wir mal ein 80×24 Spielfeld ausdehnen wollen was Du da an Quelltext tippen müsstest. ;-)

Wenn Du Quelltext kopierst und einfügst und dabei immer nur geringfügig änderst, sollte Dir das ein Warnzeichen sein, dass Du etwas falsch machst. Das deutet nämlich fast immer darauf hin, dass Du den gemeinsamen Code der Kopien in einem Schleifenkörper oder einer Funktion unterbringen solltest.

Ebenso ist ein durchnummerieren von Namen in der Regel ein Zeichen, dass Du einen Namen und Liste verwenden solltest.

`getkey()` könnte man kürzer schreiben in dem man überprüft ob die Eingabe in einer Liste mit den erlaubten Eingaben enthalten ist:

Code: Alles auswählen

    while key not in list('wasdx'):
        key = input()
Bei `move()` ist die erste Frage wo zum Henker die Namen `a` und `b` herkommen. Werte sollten Funktionen als Argumente betreten und nicht einfach ”aus dem nichts” kommen. Das macht Programme unübersichtlich und fehleranfällig.

Die neue Position sollte man aus der aktuellen Position und der Höhe und Breite des Feldes *berechnen* statt für jede Möglichkeit einen ``if``-Zweig in das Programm zu schreiben. Wie gesagt: Stell Dir vor das Feld ist deutlich grösser — dann ist das so gar nicht mehr praktikabel.

Statt in einem Kommentar zu erklären was `a`, `b`, und `c` bedeuten, solltest Du die Namen besser gleich so wählen das man nicht erst nach einer Erklärung suchen muss. Ein Name sollte widerspiegeln was die Bedeutung des Wertes der daran gebunden wird, für das Programm ist.

Der Code auf Modulebene sollte in einer Funktion verschwinden. Auf der Modulebene sollten nach Möglichkeit nur Definitionen stehen. Also Importe, Funktions- und Klassendefinitionen, und Konstantendefinitionen. Der Hauptcode steht konventionell in einer `main()`-Funktion, die durch folgendes Idiom gestartet wird:

Code: Alles auswählen

if __name__ = '__main__':
    main()
Dann kann man das Modul auch importieren ohne das gleich das Programm los läuft. Zum Beispiel um einzelne Funktionen zu testen oder in anderen Programmen wieder zu verwenden.

Bei einer Endlosschleife braucht man keine ”komplizierte” Bedingung die immer wahr ist — man kann da einfach ”Wahr” direkt hinschreiben: ``while True:``.

Die Ausgabe des Feldes skaliert so natürlich auch nicht. Auch hier sollte man das Feld *berechnen* statt für jeden möglichen Fall einen ``if``-Zweig zu schreiben.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ohne Dich demotivieren zu wollen, aber das, was Du da zeigst, hat mit (gutem) Programmieren noch nicht viel zu tun ;-)

Das kann man leicht überprüfen:
Was machst Du, wenn ich jetzt folgenden Dungeon haben will?

Code: Alles auswählen

'------'
'|.....|'
'|.....|'
'|...x.|'
'|.....|'
'------'
;-)

Du müsstest ja zig if-Konstrukte umschreiben!

Daran erkennst Du, dass Du nicht wirklich flexiblen, vom konkreten Problem unabhängigen Code geschrieben hast. Und genau das ist die wahre Kunst beim Programmieren!

Stelle Dir vor, Du schreibst eine Sortierfunktion, die folgendes Problem lösen kann: [4, 2, 6, 1]. Wäre doch doof, wenn die das Problem [10, 3, 43, 2, 5] nicht auch sortieren könnte, oder?

Dein Problem ist: Du hast keine (gute) Datenstruktur gefunden, dein Dungeon darzustellen. Ich denke da hapert es einfach bei Dir. Du musst Dich mit den grundlegenden Datenstrukturen in Python befassen. Du bindest nur Strings an nummerierte Namen und prüfst dann mit exakt auf die Höhe und Breite des Dungeons angepassten if-Statements und Funktionen, wie der neue Zustand aussehen muss.

Du musst Dir also eine Datenstruktur für Deinen Level ausdenken - diese ist zunächst absolut unabhängig von der späteren Darstellung! Mein Tipp: Schau Dir mal Listen an!

So könnte eine solche Struktur aussehen:

Code: Alles auswählen

In [13]: level = [
   ....: [1, 1, 1, 1, 1],
   ....: [1, 0, 0, 1, 1],
   ....: [1, 0, 1, 0, 1],
   ....: [1, 0, 1, 0, 1],
   ....: [1, 0, 0 ,0, 1],
   ....: [1, 1, 1, 1, 1]
   ....: ]
Ich habe einfach eine Liste mit Listen als Einträgen erstellt. "1" bedeutet, dass ein Feld nicht betreten werden kann, "0" bedeutet, das Feld kann betreten werden. Ok, sehr simpel für den Anfang, aber das reicht ja erst einmal.

Mit Hilfe von zwei Indexwerten kann ich nun abfragen, was sich hinter einem bestimmten Feld befindet:

Code: Alles auswählen

In [18]: level[0][0]
Out[18]: 1

In [19]: level[1][2]
Out[19]: 0
Dabei ist der erste Index mit der Y-Koordinate und der zweite mit der X-Koordinate auf einem Karopapier vergleichbar. Damit wiederum kann das Level nun beliebige Ausmaße haben. Ich muss nur die aktuelle Position meines Helden kennen und wissen, an welche Koordinate er ziehen will.

Letzteres kann ich berechnen: Will er nach "oben" gehen, muss ich vom Y-Wert meiner aktuellen Position einfach den Wert 1 abziehen (bei einem Schritt). Will er nach "rechts" gehen, so muss ich auf den X-Wert 1 drauf addieren.

Ich definiere, dass mein Held den Wert 2 haben soll. Damit kann ich ihn einfach mal an eine Position im Level setzen:

Code: Alles auswählen

In [20]: level[1][1] = 2

In [21]: level
Out[21]: 
[[1, 1, 1, 1, 1],
 [1, 2, 0, 1, 1],
 [1, 0, 1, 0, 1],
 [1, 0, 1, 0, 1],
 [1, 0, 0, 0, 1],
 [1, 1, 1, 1, 1]]
Da ich die Position meines Helden ja kennen muss, merke ich mir das zusammen mit einem Namen mal in einer Struktur:

Code: Alles auswählen

In [22]: hero = ["Hyperion", 1, 1]

In [23]: hero[0]
Out[23]: 'Hyperion'

In [24]: hero[1]
Out[24]: 1

In [25]: hero[2]
Out[25]: 1

In [26]: hero[1] = 2

In [27]: hero
Out[27]: ['Hyperion', 2, 1]
Ich definiere für mich, dass zuerst die X und danach die Y-Koordinate in der `hero`-Liste steht. Will ich auf x, oder y zugreifen, muss ich also den Index 1 bzw. 2 verwenden.

Nun kann ich eine Funktion schreiben, die meinen Helden von seiner aktuellen Position auf eine neue bewegt:

Code: Alles auswählen

In [77]: def move(x, y, hero, level):
   ....:     level[hero[2]][hero[1]] = 0
   ....:     hero[1] += x
   ....:     hero[2] += y
   ....:     level[hero[2]][hero[1]] = 2
   ....:     
   ....:     

In [32]: hero[1] = 1

In [33]: level = [
[1, 1, 1, 1, 1],
[1, 0, 0, 1, 1],
[1, 0, 1, 0, 1],
[1, 0, 1, 0, 1],
[1, 0, 0 ,0, 1],
[1, 1, 1, 1, 1]
]

In [41]: move(0, 0, hero, level)

In [42]: level
Out[42]: 
[[1, 1, 1, 1, 1],
 [1, 2, 0, 1, 1],
 [1, 0, 1, 0, 1],
 [1, 0, 1, 0, 1],
 [1, 0, 0, 0, 1],
 [1, 1, 1, 1, 1]]

In [43]: move(1, 0, hero, level)

In [44]: level
Out[44]: 
[[1, 1, 1, 1, 1],
 [1, 0, 2, 1, 1],
 [1, 0, 1, 0, 1],
 [1, 0, 1, 0, 1],
 [1, 0, 0, 0, 1],
 [1, 1, 1, 1, 1]]
Zuerst habe ich meinen Helden wieder die Koordinaten (1, 1) gegeben, dann den Level "resettet". Wenn ich bei x und y jeweils 0 angebe, so wird der Held an seine aktuellen Koordinaten gesetzt. Gebe ich ein "delta" an, so bewegt sich der Held durch den Level :-)
Ich kann die Bewegung als berechnen und muss nicht endlose, fixe if-Statements schreiben, a la "wenn der held auf x=3 war, dann muss er nun x=4 haben. Wenn er x=4 war, dann muss er nun x=5 haben, ...", wie Du es zur Zeit machst. Das ist eben aufgrund der Indizierbarkeit von Listen möglich.
Wichtig bei der Funktion ist es, die Koordinaten des Helden neu zu berechnen und diese nicht nur im Level zu markieren, sondern sich diese auch in der `hero`-Liste zu setzen. (Hatte ich doch glatt vergessen zunächst ;-) )

Es sind bei Werten größer / kleiner als (-1, 0, 1) auch "Sprünge" drin.

Bevor ich meinen Helden bewegen kann, muss ich ja feststellen, ob ein Feld betreten werden darf. Dazu kann ich eine Funktion schreiben, die für eine gegebene Koordinate in der Levelstruktur prüft, ob das Feld betreten werden darf.

Code: Alles auswählen

In [14]: def is_passable(x, y, level):
   ....:     return level[y][x] == 0
   ....: 

In [16]: is_passable(0, 0, level)
Out[16]: False

In [17]: is_passable(3, 2, level)
Out[17]: True
Bevor ich meine `move`-Funktion nun aufrufe, muss ich erst mal prüfen, ob der gewünschte Zug gültig ist:

Code: Alles auswählen

In [88]: walk_x, walk_y = 1, 0

In [89]: is_passable(hero[1] + walk_x, hero[2] + walk_y, level)
Out[89]: False

In [90]: walk_x, walk_y = -1, 0

In [91]: is_passable(hero[1] + walk_x, hero[2] + walk_y, level)
Out[91]: True
Das ganze verpacke ich nun in eine Funktion `do_move`. Diese prüft also zuerst, ob ein Zug möglich ist und führt danach die Bewegung durch:

Code: Alles auswählen

In [101]: def do_move(x, y, hero, level):
   .....:         if is_passable(hero[1]+x, hero[2]+y, level):
   .....:             print "gehe nach", hero[1] + x, hero[2] + y
   .....:         move(x, y, hero, level)

In [102]: level
Out[102]: 
[[1, 1, 1, 1, 1],
 [1, 2, 0, 1, 1],
 [1, 0, 1, 0, 1],
 [1, 0, 1, 0, 1],
 [1, 0, 0, 0, 1],
 [1, 1, 1, 1, 1]]

In [103]: hero
Out[103]: ['Hyperion', 1, 1]

In [104]: do_move(1, 0, hero, level)
gehe nach 2 1

In [105]: do_move(1, 0, hero, level)

In [106]: do_move(0, 1, hero, level)

In [107]: do_move(-1, 0, hero, level)
gehe nach 1 1

In [108]: do_move(0, 1, hero, level)
gehe nach 1 2

In [109]: do_move(0, 1, hero, level)
gehe nach 1 3

In [110]: level
Out[110]: 
[[1, 1, 1, 1, 1],
 [1, 0, 0, 1, 1],
 [1, 0, 1, 0, 1],
 [1, 2, 1, 0, 1],
 [1, 0, 0, 0, 1],
 [1, 1, 1, 1, 1]]
Man erkennt, dass die Bewegung an "Mauern" scheitert. Sinnvoller Weise hätte man noch eine Warnung ausgeben können, damit man beim Debuggen sieht, dass eine Bewegung nicht klappt und ggf. wieso nicht.

Das mal so auf die Schnelle. Vieles hier ist... naja, suboptimal. Zunächst einmal würde ich ein Objekt für einen Helden eher in einem Dictionary darstellen oder - was noch besser ist - in einer Klasse. Diese könnte dann die `move` und `do_move`-Methoden besitzen.

Integer als Feld-Typen sind ggf. auch eher suboptimal. Dann ist da noch die Frage, ob es verschiedene Ebenen im Level gibt. Also werden sich bewegende Objekte in einer anderen Liste gespeichert als starre. Gibt es für verschiedene Arten von Helden verschiedene Beschränkungen? (Ein Held kann schwimmen und damit wäre Wasser kein Problem mehr für ihn; für einen anderen Helden eben doch)

Du siehst, die zugrunde liegende Struktur ist einfach enorm wichtig.

Mein Ansatz soll nur ein Denkanstoß sein - nicht mehr und nicht weniger. Also sei nicht so töricht und kopiere Dir hier alles zusammen ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

So ein Computer heißt ja Computer = Rechner weil er Dinge berechnen kann. Daher würde ich vorschlagen, Räume und Positionen zu berechnen. Der folgende Code funktioniert auf einem Unix-System mit einem VT100-Terminal. Mit "\033[2J" kann man den Bildschirm löschen. Leider muss man nach jeder Eingabe RETURN drücken. Man bräuchte Curses, um das Terminal in den sogenannten RAW-Modus zu schalten, in dem nicht auf RETURN gewartet wird, sondern jedes Zeichen unmittelbar verarbeitet werden kann. Curses macht aber das Debuggen schwerer.

Code: Alles auswählen

#!/opt/local/bin/python3.2

screen = [[" "] * 60 for _ in range(20)]

def display_screen():
    print("\033[2J" + "\n".join("".join(ch for ch in line) for line in screen))

def get(x, y): return screen[y][x]
def put(x, y, ch): screen[y][x] = ch

class Room:
    def __init__(self, x, y, w, h):
        self.x, self.y, self.w, self.h = x, y, w, h
    
    def draw(self):
        put(self.x, self.y, "+")
        for i in range(1, self.w + 1):
            put(self.x + i, self.y, "-")
        put(self.x + self.w + 1, self.y, "+")
        for j in range(1, self.h + 1):
            put(self.x, self.y + j, "|")
            for i in range(1, self.w + 1):
                put(self.x + i, self.y + j, ".")
            put(self.x + self.w + 1, self.y + j, "|")
        put(self.x, self.y + self.h + 1, "+")
        for i in range(1, self.w + 1):
            put(self.x + i, self.y + self.h + 1, "-")
        put(self.x + self.w + 1, self.y + self.h + 1, "+")

class Hero:
    def __init__(self, x, y):
        self.x, self.y = x, y
    
    def draw(self):
        self.ch = get(self.x, self.y)
        put(self.x, self.y, "@")
    
    def undraw(self):
        put(self.x, self.y, self.ch)
    
    def move_up(self): self.y -= 1
    def move_down(self): self.y += 1
    def move_left(self): self.x -= 1
    def move_right(self): self.x += 1

def main_loop():
    Room(2, 3, 5, 4).draw()
    Room(10, 2, 4, 3).draw()
    Room(18, 1, 5, 6).draw()

    hero = Hero(4, 4)
    hero.draw()
    
    while True:
        display_screen()
        d = input("WSAD?").upper()
        hero.undraw()
        if d == "W": hero.move_up()
        if d == "S": hero.move_down()
        if d == "A": hero.move_left()
        if d == "D": hero.move_right()
        hero.draw()

main_loop()
Stefan
nzaugg
User
Beiträge: 5
Registriert: Dienstag 24. Mai 2011, 21:46

@alle
Dankevielmals für eure gute antworten, sie haben mich alle sehr zum nachdenken gebracht. ;-)
I wusste das ich 'ein wenig' zu viele if's brauchte, doch die einzigen paar funktions die ich kenne im moment sind if, while, def(), return, int(), und str(). und input(). Mehr nicht. und ich dachte: "so jetzt genug gelernt, ich versuchs mal!" Dann habe ich versucht mein erstes Game zu programmieren, mit was ich bis jetzt wusste. Es war hauptsächlich
ausversuchen und herumbasteln. Anscheinendend vergeblich. ;-)
Aber class, lists usw. kenne ich noch nicht, denn ich habe noch nicht ein sehr guttes tut gefunden welches mir etwas brachte.
Für einen Link oder so etwas wäre ich dankbar, und wenn möglich auf english. (Deutsch ist eigentlich meine 2. Sprache)
Ich versuche dann eure Beiträge zu verstehen, und komm (hoffentlich) damit klar.
Doch nochmals vielen Dank, dieses Forum ist echt hilfreich.

gruss,
nzaugg

PS: @hyperion:
deinen Beitrag fand ich super, du hast dir wirklich etwas ausgedacht und mir viel erklärt. Es war auch sehr Witzig ;-)
jedoch versteh ich eins nich: du hast vor alle Zeilen ein In und OUT + Zahlen. Das war alles Bahnhof für mich, doch ich würde es gern begreifen. :D
BlackJack

@nzaugg: Das Tutorial in der Python-Dokumentation ist empfehlenswert um die Sprache kennen zu lernen. A Byte Of Python (ABOP) wird oft empfohlen und Zed Shaw's ”Learning Python The Hard Way”. Einen Link dafür habe ich gerade nicht zur Hand, aber eine Suchmaschine sollte weiterhelfen.

Die Sachen mit ``In [#]`` und ``Out [#]`` ist Teil einer Sitzung mit IPython. Das ist eine Alternative zur normalen Python-Shell mit ein paar Zusatzfunktionen. Unter anderem kann man auf die Werte von den Ausgaben über die Nummer später immer noch zugreifen.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

nzaugg hat geschrieben:@alle
Für einen Link oder so etwas wäre ich dankbar, und wenn möglich auf english. (Deutsch ist eigentlich meine 2. Sprache)
Na, das offizielle, welches in der Python-Doku dabei ist z.B.
http://docs.python.org/py3k/tutorial/index.html

Ansonsten gibts hier Links für Anfänger:
http://wiki.python-forum.de/Tutorial
nzaugg hat geschrieben: PS: @hyperion:
deinen Beitrag fand ich super, du hast dir wirklich etwas ausgedacht und mir viel erklärt. Es war auch sehr Witzig ;-)
jedoch versteh ich eins nich: du hast vor alle Zeilen ein In und OUT + Zahlen. Das war alles Bahnhof für mich, doch ich würde es gern begreifen. :D
Das sind einfach nur Ausgaben der Python-Shell IPpython. Die Zahlen haben also für den Code keine Bedeutung, den kannst Du auch in der "normalen" Python-Shell ausführen.

Edit: Ok, BlackJack war schneller ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

So, irgend wie hat es mich gereizt, mein Beispiel mal aufzubohren :-D

Hier das Ergebnis.

Man könnte noch smas Screen-Lösch-Lösung einbauen; ich fand es ganz reizvoll die vorherigen Abläufe zu sehen :-)

Einige Namen würde ich nachträglich ändern (Level -> Game / Engine). Aber was solls. "Stolz" bin ich auf die Idee, einer Kreatur ein "Movement"-Objekt zu verpassen. Früher habe ich von einer Basis-Kreatur-Klasse immer abgeleitet, wenn ich unterschiedliche Bewegungen haben wollte. So finde ich es eigentlich schöner, da die Bewegung ja nur ein Teil des ganzen ist und man dieser vor allem zur Laufzeit ändern könnte.

Was fehlt ist ein Kampfsystem. Nuja, ist halt ein pazifistischer Held, der nicht kämpft und daher zu Grunde geht gegen die fiesen Monster :twisted:
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

@Hyperion Nett. Strategie-Pattern für die Bewegung ist eine gute Idee, aber warum Strings, wenn man da auch die Klassen selbst eintragen könnte. Du müsstest nur die Reihenfolge der Definitionen umstellen. Die 5 Objekte in den Tupeln für die Kreaturen könnte man durch Exemplare einer passenden Klasse oder wenigstens einer Konstruktorfunktion noch besser erklären. Wenn ich noch raten kann, dass das erste Objekt wohl für die Kreaturenklasse steht und die nächsten beiden für die X- und Y-Koordinate auf der Karte, bin ich bei der Liste der Geländeformen schon ratlos und müsste erst den Quelltext studieren. Auch hier würden sich aber Objekte statt Strings anbieten.

Stefan
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

sma hat geschrieben:@Hyperion Nett. Strategie-Pattern für die Bewegung ist eine gute Idee, aber warum Strings, wenn man da auch die Klassen selbst eintragen könnte. Du müsstest nur die Reihenfolge der Definitionen umstellen. Die 5 Objekte in den Tupeln für die Kreaturen könnte man durch Exemplare einer passenden Klasse oder wenigstens einer Konstruktorfunktion noch besser erklären. Wenn ich noch raten kann, dass das erste Objekt wohl für die Kreaturenklasse steht und die nächsten beiden für die X- und Y-Koordinate auf der Karte, bin ich bei der Liste der Geländeformen schon ratlos und müsste erst den Quelltext studieren. Auch hier würden sich aber Objekte statt Strings anbieten.

Stefan
Danke :-) Ja, da hast Du recht! Ich hatte bei der obigen Definition bereits ein wenig Persistenz im Hinterkopf. Dabei kann man das Mapping von String auf Klassenname ja auch beim Parsen erledigen. Für die x und y Koordinaten hatte ich mir nachträglich überlegt, einen eigenen Datentypen zu schreiben, mit dem Basisoperationen bereits implementiert sind (Addition, Tauschen / Invertieren der Werte). Damit wären die Bewegungsfunktionen sicherlich deutlich kompakter formulierbar. Ich habe in der Doku das Beispiel bei den `namedtuples` gesehen; leider sind die ja nicht änderbar, ansonsten würde mir das sehr gefallen.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Hyperion hat geschrieben:leider sind die ja nicht änderbar, ansonsten würde mir das sehr gefallen.
Das ist ein schönes Stichwort für die folgende Herausforderung: Mache das ganze Spiel "funktional" und verzichte bis auf das "print" auf Seiteneffekte. Alles, was du brauchst ist eine Funktion, die aus einem Spielstand und einer Eingabe einen neuen Spielstand erzeugt. Wenn du random brauchst, reiche stattdessen außerdem in die Funktion einen Strom von Zahlen hinein.

Stefan
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

sma hat geschrieben: Das ist ein schönes Stichwort für die folgende Herausforderung: Mache das ganze Spiel "funktional" und verzichte bis auf das "print" auf Seiteneffekte. Alles, was du brauchst ist eine Funktion, die aus einem Spielstand und einer Eingabe einen neuen Spielstand erzeugt. Wenn du random brauchst, reiche stattdessen außerdem in die Funktion einen Strom von Zahlen hinein.
Hui... klingt schwierig. Immerhin brauchen meine Bewegungsalgorithmen im Moment ja Zustände. Da habe ich noch keinen Plan, wie ich das umsetzen kann. Bin halt kein Lisper - bisher ;-)

Nuja, hab vor lauter Muße mal eine simple GUI mit pygame drum herum gestrickt, die Levels in eine JSON-Datei ausgelagert und das ganze ohne Spielereingabe lauffähig gemacht, damit man quasi auch eine Art Simulation laufen lassen kann. Allerdings ist der Code teilweise doch noch ein wenig wirr. Da muss ich dringend aufräumen und einiges an Refactoring betreiben, bevor ich den hier verlinke ;-)

Anbei jedoch schon mal nen Screenshot:
Bild
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
nzaugg
User
Beiträge: 5
Registriert: Dienstag 24. Mai 2011, 21:46

Sorry, dass ich gerade nicht so aktiv war, ich hatte exrtrem viel zu tun mit der Schule.
Nochmals vielen dank für eure weitere Beiträge.
Hyperion, ich habe deinen Code mir angesehen, ihn etwa eine Stunde lang studiert, und verstehe jetzt etwa 3/4.
Vileicht auch nur 1/2. ;-)
auf jeden fall, ich bringe es nicht zum starten, es hat anscheinend einen Syntax Fehler, welcher ich nicht heilen kann.
Screenshot:[img]C:\Users\Nicolas\Desktop[/img]

Und diesen Screenshot von dem Spiel noch mit GUI von Pygame.. WOW!
Ich bin beeindruckt, too say the least. ;-)
Naja, studiere noch ein bisschen weiter, probiere den Code zu verstehen, hoffentlich klappts.

/nzaugg

edit: Da ich ein Forum NEWBIE bin, bringe ich das mit dem screen shot nicht hin. :mrgreen:
Zuletzt geändert von nzaugg am Donnerstag 9. Juni 2011, 15:48, insgesamt 1-mal geändert.
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Du benutzt Python 3.x, Hyperions Code ist aber in Python 2.x geschrieben :arrow: Du wirst ihn also noch anpassen müssen. Bei dem aufgezeigten Fehler fehlen die Klammern, da "print" ab Python 3.x eine Funktion und kein Statement mehr ist.

Edit: Der Fehler auf dem Bild das hier mal kurzzeitig war :P
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
nzaugg
User
Beiträge: 5
Registriert: Dienstag 24. Mai 2011, 21:46

Hoppla, habe ich es geuploadet, aber wieder gelöscht? ^^
Einen moment lang war ich super stolz auf mich, weil ich alle print's mit klammern geschreiben habe und rawinput mit input ausgetauscht habe, und danach str(). :D Das programme startete zwar, sturzte aber nach einer eingabe von mir wieder ab. Doch nicht so einfach python 2 in python 3 umzuwandeln. ;-)

existiert raw_input in python 3 nicht?
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Jein, "raw_input" ist zu "input" geworden, das "input" aus Python 2.x existiert nicht mehr.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

nzaugg: Es gibt die Programme 2to3 und 3to2, die *.py Dateien für dich unwandeln können. Das ist deutlich sicherer als es selber zu tun.
Antworten