Mein Schulprojekt - Games programmieren mit Python
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 =)
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 =)
@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.
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.
@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
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
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 :
Ich hoffe, das vermittelt dir die Idee.
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)
@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:
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:
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.

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()
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()
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.
- 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?

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:
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:
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:
Da ich die Position meines Helden ja kennen muss, merke ich mir das zusammen mit einem Namen mal in einer Struktur:
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:
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.
Bevor ich meine `move`-Funktion nun aufrufe, muss ich erst mal prüfen, ob der gewünschte Zug gültig ist:
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:
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

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]
....: ]
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
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]]
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]
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]]

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
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
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]]
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
assert encoding_kapiert
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.
Stefan
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()
@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.
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.

@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.
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.
- Hyperion
- Moderator
- Beiträge: 7478
- Registriert: Freitag 4. August 2006, 14:56
- Wohnort: Hamburg
- Kontaktdaten:
Na, das offizielle, welches in der Python-Doku dabei ist z.B.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)
http://docs.python.org/py3k/tutorial/index.html
Ansonsten gibts hier Links für Anfänger:
http://wiki.python-forum.de/Tutorial
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.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.
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
assert encoding_kapiert
- 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 
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

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

encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
assert encoding_kapiert
@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
Stefan
- Hyperion
- Moderator
- Beiträge: 7478
- Registriert: Freitag 4. August 2006, 14:56
- Wohnort: Hamburg
- Kontaktdaten:
Dankesma 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

encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
assert encoding_kapiert
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.Hyperion hat geschrieben:leider sind die ja nicht änderbar, ansonsten würde mir das sehr gefallen.
Stefan
- Hyperion
- Moderator
- Beiträge: 7478
- Registriert: Freitag 4. August 2006, 14:56
- Wohnort: Hamburg
- Kontaktdaten:
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 - bishersma 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.

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:

encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
assert encoding_kapiert
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.
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.

Zuletzt geändert von nzaugg am Donnerstag 9. Juni 2011, 15:48, insgesamt 1-mal geändert.
Du benutzt Python 3.x, Hyperions Code ist aber in Python 2.x geschrieben
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

Edit: Der Fehler auf dem Bild das hier mal kurzzeitig war

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().
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?
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().


existiert raw_input in python 3 nicht?