Seite 1 von 1
Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Mittwoch 8. Juni 2016, 14:11
von weitnow
Hallo zusammen
Mein Name ist Christian und ich bin hobbymässig am Python üben. Damit das Ganze auch so richtig Spass macht, habe ich direkt mit einem Game angefangen. Genauer gesagt ich bin dabei einen kleinen ASCII-Dungeoncrawler zu schreiben.
Vor einiger Zeit habe ich bereits einmal in diesem Forum gepostet. Ich habe damals sehr coole Typs (insbesondere von BlackJack) erhalten, wie ich meinen Code (in einem anderen Beispiel) verbessern kann. Ich würde mich auch bei meinem jetzigen kleinen Projekt (ASCII-Dungencrawler) über Feedbacks von euch zwecks Verbesserung meines Codes freuen.
Hier mein Code:
Code: Alles auswählen
class Character:
""" Erstellt ein Character (Monster oder Player) """
def __init__(self, name, symbol, position = (0,0)):
self.max_hitpoints = 50
self.hitpoints = self.max_hitpoints
self.attack_power = 5
self.position = position
self.name = name
self.symbol = symbol
def attack(self, enemy):
pass
class Grid:
"""erstellt ein Grid
es muss Grösse in Form einer Liste (x,y) als Parameter angegeben werden"""
def paint_grid(self):
""" Zeichnet das Grid inkl. Character-Objecte auf den Bildschirm """
# Zeichnet leeres Grid
self.grid = []
for column in range(self.y):
self.grid.append([])
for row in range(self.x):
self.grid[column].append("_")
# Fügt Wände dem Grid hinzu
wall_x = 0
wall_y = -1
for row in self.wallist:
wall_y += 1
wall_x = -1
for wall in row:
wall_x += 1
if wall == 1:
self.grid[wall_y][wall_x] = '#'
# Fügt Character-Objekte dem Grid hinzu
for monster in self.monsterlist:
monster_x, monster_y = monster.position
self.grid[monster_x][monster_y] = monster.symbol
# Print Grid on Screen
for liste in self.grid:
for item in liste:
print(item, end = " ")
print()
def import_monster(self, monster):
""" Importiert Monster in das Grid, es muss als Paramter ein Monsterobject angegeben werden"""
for item in self.monsterlist:
if monster.position == item.position:
raise Exception ("There is already annother Monster on that position")
self.monsterlist.append(monster)
def remove_monster(self, monster):
""" Löscht Monster vom Grid, es muss als Paramter ein Monsterobject angegeben werden"""
for item in self.monsterlist:
monsterlist.pop(monster)
def show_monster(self):
""" Zeigt alle Monster auf dem Grid """
counter = 1
for monster in self.monsterlist:
print("Monster Nr. {}, Name: {}, Position: {}".format(counter, monster.name, monster.position))
counter += 1
self.paint_grid()
def import_walls(self, walls):
""" Importiert eine Walls-Liste """
self.wallist = walls
def __init__(self, grid_size):
self.x, self.y = grid_size
self.monsterlist = []
self.walllist = []
""" Testing """
oggar = Character("Oggar", "O", (2,2))
rudolf = Character("Rudolf", "R", (3,3))
# walls ist eine liste mit weiteren listen. diese listen stellen praktisch das spielfeld
# dar. 1 = solid wall, 0 = keine wall
walls = [
[1, 1, 1, 1, 1],
[1, 0, 0, 0, 1],
[1, 0, 0, 0, 1],
[1, 0, 0, 0, 1],
[1, 1, 1, 1, 1]
]
map = Grid((5,5))
map.import_monster(oggar)
map.import_monster(rudolf)
map.import_walls(walls)
map.paint_grid()
Ich habe eine Klasse Character, mit welcher jeweils Monster erstellt werden. Die Klasse Grid importiert dann die Monster und stellt sie auf dem Grid dar. Die Klasse Grid hat noch eine Methode, welche Wände importiert, welche in einer Variable des Typs Liste gespeichert sind. 1 = Wand, 0 = keine Wand. Ich habe das Gefühl, dass ich den Code in der Klasse Grid unterhalb des Kommentars (Fügt Wände dem Grid hinzu) viel zu kompliziert geschrieben habe. Wie könnte ich das besser lösen?
Ich bin für alles Tipps dankbar. Lieber Gruss, Christian
Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Mittwoch 8. Juni 2016, 21:41
von __deets__
Da laesst sich einiges vereinfachen. Du musst zb nicht alles neu aufbauen, sondern kannst direkt den Dungeon durch Iteration ueber die Zeilen malen. Und dann an den passenden Stellen ein Monster, wenn du eine entsprechende Datenstruktur aufbaust:
Code: Alles auswählen
GRID = [
[1, 1, 1, 1],
[1, 0, 1, 1],
[1, 0, 0, 0],
[1, 1, 1, 1],
]
def draw_playfield(dungeon, monsters):
monster_coords2monster = {(m.y, m.x): m.symbol for m in monsters}
for y, row in enumerate(dungeon):
for x, wall in enumerate(row):
c = "#" if wall else monster_coords2monster.get((y, x), " ")
print c,
print
class Monster(object):
def __init__(self, pos, symbol="@"):
self.x, self.y = pos
self.symbol = symbol
def main():
monsters = [Monster((1, 1)), Monster((2, 2), "X")]
draw_playfield(GRID, monsters)
if __name__ == '__main__':
main()
Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Donnerstag 9. Juni 2016, 09:12
von weitnow
Hallo Deets, hallo BlackJack
Das Wichtigste zuerst: Vielen herzlichen Dank, dass du dir die Zeit genommen hast, meinen Code zu überarbeiten. Es hilft mir sehr, wenn ich zuerst selber den Code schreibe und dann eine optimierte Version bekomme. Insbesondere beeindruckt hat mich, dass dein Code soviel kürzer/effizienter als meiner ist.
Ich habe deinen Code einmal einfach 1 zu 1 in ein File (dungeoncrawler_enhanced.py) kopiert und mit dem Python3-Interpreter ausgeführt. Im Terminal habe ich dann folgendes erhalten:
weitnow: ~ $ python3 -i crawler_enhanced.py
#
#
#
#
#
@
#
#
#
X
#
#
#
#
Ich habe mir dann deinen überarbeiteten Code angesehen und zu verstehen versucht. Ich gehe einmal der Reihe nach durch, wie der Code nach meiner Meinung vom Intepreter ausgeführt wird:
In def main() wird eine Liste mit zwei Monsterobjekten erzeugt. Das erste Monsterobjekt hat Position (1,1) und es wird das Standardsymbol @ verwendet. Das zweite Monster hat Position (2,2) und übergibt der Klasse Monster das Symbol X als Parameter. Dann wird die Methode draw_playfield mit der Liste GRID und der Liste Monsters als Parameter aufgerufen.
In der Methode draw_playfield steht dann:
monster_coords2monster = { (m.y, m.x): m.symbol for m in monsters }
wenn ich mir nun Monster_coords2monster anzeigen lasse, dann sehe ich:
{(1, 1): '@', (2, 2): 'X'}
Es wurde also ein Dict mit diesem Inhalt erzeugt. Finde ich einfach den Hammer, dass du das mit einem so kurzen
Code wie { (m.y, m.x): m.symbol for m in monsters } erstellen kannst. Das muss ich mir merken

(Python is soooo cool).
Auch die enumerate-Methode ist sehr cool...kannte ich vorher gar nicht.
Ich habe jetzt auch herausgefunden, dass dein Code vermutlich für den Python2 Intepreter geschrieben wurde?
Ich habe einfach print c in print(c, end = "") abgeändert und nun funktioniert alles.
Output:
weitnow: ~ $ python3 -i crawler_enhanced.py
####
#@##
# X
####
Also nochmals vielen lieben Dank...du hast mir sehr weiter geholfen....und der Code ist jetzt viel besser....ich werde einmal weiter dran rumbasteln.
Danke dir und eine schöne Woche, Grüsse, Christian
Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Donnerstag 9. Juni 2016, 15:00
von weitnow
Hallo Deets
Nochmals ich
Ich habe mir heute nochmals deine Code genau angesehen....Dank deinen Verbesserungen und deiner Art um Liste, Dictionaries etc zu erzeugen hat mein Python-Wissen gefühlt wieder einen Sprung nach vorne gemacht. Ich wollte nochmals Danke sagen....ist ja wirklich nicht selbstverständlich, dass sich jemand in seiner Freizeit die Mühe macht, anderen zu helfen.
Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Donnerstag 9. Juni 2016, 22:32
von __deets__
@weitnow: schoen, dass es geklappt hat. Und genau, ich hab' Python2 verwandt.
Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Freitag 10. Juni 2016, 09:12
von weitnow
Hallo zusammen
Ich habe nochmals eine allgemeine Frage. Ich habe immer grosse Probleme zu verstehen, in welche Klasse ich jeweils eine Methode anhängen soll.
Um besser zu illustrieren was ich meine nehme ich gleich mein kleinen Dungeon Crawler als Beispiel:
Ich habe eine Klasse Grid oder Map. Diese Klasse übernimmt die Darstellung des Spielfelds/Dungeon und zeichnet auf der Konsole ein Grid mit Wänden und Monstern, sowie den Spieler.
Dann habe ich eine Klasse Monster, welche Attribute hat wie Hitpoints, Angriffstärke und Methoden wie Angriff etc.
Nun möchte ich natürlich, dass sich die Monster auf dem Grid/Map bewegen können. Also schreibe ich eine Methode Bewegen. Spontan würde ich sagen, dass diese Methode natürlich zu der Klasse Monster gehört, da Bewegen ja eine Fähigkeit des Monsters ist.
Allerdings verwaltet in meinem Beispiel die Klasse Grid/Map die Position der Wände. Damit ein Monster überhaupt weiss, wohin es gehen kann, muss es auf Variabeln/Informationen der Klasse Grid/Map zugreifen.
Frage: Schreibe ich nun die Methode Bewegen in der Klasse Grid/Map, weil dort die Informationen über die Position der Wände und der Monster lagern oder schreibe ich die Methode Bewegen in die Klasse Monster, weil Bewegen ja eine Fähigkeit des Monsters ist. Falls ich die in die Klasse Monster schreibe, muss diese Methode also auf Informationen/Variabeln der Klasse Grid/Map zugreifen?
Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Freitag 10. Juni 2016, 09:39
von BlackJack
@weitnow: Das kommt darauf an wie Du das genau lösen möchtest und was „bewegen“ für Spielregeln hat, und ob die alleine vom übergeordneten Spiel abhängen oder ob individuelle Monster da auch einen Teil der Regeln kennen die nur für sie gelten. Also soll sich beispielsweise ein Monster von sich aus bewegen können, mit irgendwelchen Entscheidungen die das Monster selber trifft, oder ist das letztendlich komplett ”fremdbestimmt”?
Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Freitag 10. Juni 2016, 09:51
von weitnow
Hi BlackJack
Also ich habe mir das so vorgestellt:
Klasse Monster:
Hat u.a. die eigene Position als Variable
Klasse Grid/Map:
Hat eine Liste mit allen Instanzen der Klasse Monster. Die Klasse Grid/Map benötigt diese Liste, um alle Monster auf dem Grid/Map darzustellen.
Die Klasse Grid wird einmal pro Runde aufgerufen bzw. die Methode print_map der Klasse Grid, welche die aktuelle Position aller Gegestände auf dem Grid auf dem Bildschirm zeigt.
Die Klasse Monster weiss somit eigentlich nur seine eigene Position. Nun möchte ich, dass das Monster eine Variable Bewegungsgeschwindigkeit hat. Das Monster kann sich also pro Runde auf dem Grid ein oder zwei Felder bewegen. Die Methode Bewegen verschiebt in der Folge die Position des Monsters ein oder zwei Felder in eine beliebige Richtung. Allerdings muss ja geprüft werden, ob das Feld, wo es sich hin bewegen möchte, frei ist bzw. keine Wand im Wege steht oder bereits ein anderes Monster auf diesem Feld ist. Die Methode Bewegen soll also Bewegungen gemäss der Bewegungsgeschwindigkeit des Monsters ermöglichen. Das Monster wird einfach per Zufallgenerator in irgend eine Richtung bewegt. Falles es sich beim Monster um den Spieler handelt, dann kann dieser natürlich bestimmen, wohin er sich bewegen möchte. Wenn ich nun die Methode Bewegen der Klasse Monster zuordne, wie kann ich dann sicherstellen, dass sich das Monster nicht in eine Wand bewegt? Denn die Klasse Monster weiss ja nur seine jetzige Position. Nur die Klasse Grid kennt die Position aller Monster und aller Wände. Oder muss sogar eine Funktion integriert werden, mit welcher die Klasse Monster bei der Klasse Grid nachfragen kann, ob dies ein valider Zug/Bewegung ist?
Ach ja, natürlich möchte ich mit der Zeit die "Intelligenz" des Monsters verbessern. Später soll es sich nicht nur zufällig bewegen sondern zum Beispiel, wenn es Hunger hat, nach Essen auf dem Grid suchen oder den Spieler in Sichtweite angreifen etc.
Dann hätte ich gerne Monster, welche sich nur in einem gewissen Abschnitt der Map bewegen können und solche, welche über die ganze Karten wandern können. Dies würde ich dann auch gerne in der Methode Bewegen definieren. Aber wie gesagt, ich weiss nicht, ob ich diese Methode lieber der Klasse Grid oder Monster anhängen soll....ich denke Monster wäre besser, aber wie prüfe ich dann, ob es ein valider Zug ist oder sich das Monster in eine Wand bewegt?
Wie würdest du das lösen BlackJack?
PS: Erneut besten Dank, dass du dir immer Zeit nimmst. Ich schätze alles Tipps die ich jeweils hier im Forum erhalte sehr

Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Freitag 10. Juni 2016, 14:29
von weitnow
Hm, ich habe es jetzt so gelöst:
Der Klasse Grid/Map habe ich eine Methode spendiert:
Code: Alles auswählen
def check_if_position_is_free(self, position):
""" Prueft ob sich an der Position keine Wand befindet und die Position auf dem Grid ist"""
x, y = position
try:
if self.wallist[y][x] == 1: # 1 bedeutet es ist eine Wand
return False
else:
return True
except IndexError:
# IndexError bedeutet, Position nicht mehr auf dem Grid bzw. ausserhalb des Grids
return False
Der Klasse Monster habe ich eine Methode "move" gegeben.
Die Methode "move" von Monster ruft die Methode "check_if_position_is_free" der Klasse Grid/Map auf. Falls True, dann verschiebt die Methode
move das Monster auf die neue Position.
Keine Ahnung, ob das schlau ist es so zu lösen....Feedback ist sehr willkommen

Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Freitag 10. Juni 2016, 17:35
von __deets__
Konkret ist mir die Implementierung zu langatmig - ein einfaches "return walllist[x][y]" statt der kompletten inneren If-Abfrage reicht. Drumrum bleibt alles gleich.
Und architektonisch finde ich den Ansatz, dem Monster Zugriff auf die Karte zu geben durchaus sinnvoll. Ich habe ja auch Zugriff auf die Welt um mich herum, und kann die Frage danach, ob ich vor einer Wand stehe, beantworten.
Im allgemeinen baut man Game-Engines auch so auf, dass die Spielobjekte wie Monster, Spieler etc. in einer Schleife vor der Darstellung der Spielwelt die Gelegenheit bekommen, sich aktualisieren. Als zB eine Methode update() aufzurufen. Ob sich darin bewegt, angegriffen oder sonstewas wird, ist dann von der konkreten Klasse abhaengig. Diese update-Methode kann zB als Argument die Welt/Karte bekommen, so dass eben zB die Waende oder die Spielerposition abgefragt werden koennen.
Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Samstag 11. Juni 2016, 08:11
von bwbg
Ein anderer Ansatz wäre, dem Actor (Spieler/Monster) eine Art Intention (move south) zu geben, welche durch die Benutzereingabe oder der KI gesetzt würde.
Die übergeordnete "Physik" prüft, ob diese möglich ist und führt diese aus oder eben nicht ("Ye cannot pass.").
DIE Lösung gibt es hier ohnehin nicht.
Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Samstag 11. Juni 2016, 08:38
von weitnow
Hey Deets, hallo bwbg
Herzlichen Dank für euer Feedback
@bwbg:
Ich glaube deine Idee habe ich im Moment umgesetzt. Natürlich seeeeeeehr vereinfacht. Die Klasse Monster hat eine move Methode, welche aufgerufen wird um das Monster auf der Spielwelt zu bewegen. Diese Methode ruft dann eine Methode der Klasse Grid/Map (Spielfeld) auf und prüft ob dies überhaupt ein valider Zug ist (You shall not pass

)).
@Deets:
Oh ja, das hört sich nach einem guten Konzept an. Verstehe ich das richtig, dass die Methode update() in meinem Beispiel also in der Klasse Grid/Map (Spielwelt) angesiedelt wäre. Die Monsterklasse (also ich rede natürlich hier immer von einer Instanz der Klasse) würde dann die externe update() Methode aufrufen und bekommt darin alle Informationen der Spielwelt zurückgeliefert?
Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Samstag 11. Juni 2016, 09:09
von snafu
weitnow hat geschrieben:Code: Alles auswählen
def check_if_position_is_free(self, position):
""" Prueft ob sich an der Position keine Wand befindet und die Position auf dem Grid ist"""
x, y = position
try:
if self.wallist[y][x] == 1: # 1 bedeutet es ist eine Wand
return False
else:
return True
except IndexError:
# IndexError bedeutet, Position nicht mehr auf dem Grid bzw. ausserhalb des Grids
return False
Geht auch kürzer:
Code: Alles auswählen
def is_free(self, position):
x, y = position
try:
return not self.walls[x][y]
except IndexError
# Position out of grid
return False
Anstatt Nullen und Einsen zum Markieren der Wände würde ich übrigens auch besser True und False verwenden. Wobei das zumindest beim händischen Erstellen von Karten natürlich etwas aufwändiger ist.
Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Samstag 11. Juni 2016, 10:51
von BlackJack
@weitnow: ”Die” `update()`-Methode wäre bei Monster *und* Game. `Game.update()` bedeutet alle enthaltenen Monster zu aktualisieren und ein `Monster.update()` macht dann was immer es so machen will.
Code: Alles auswählen
class Dungeon(object):
def update(self):
for monster in self.monsters:
monster.update(self)
class Monster(object):
def update(self, dungeon):
possible_positions = dungeon.get_free_positions_around(self.position)
dungeon.move_object(self, random.choice(possible_positions))
Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Samstag 11. Juni 2016, 11:14
von weitnow
Alles klar, ich glaube nun verstehe ich es. Ich werde es gleich versuchen in meinem kleinen Spiel umzusetzen.
Nochmals vielen Dank an euch alle für die lehrreichen Antworten.
Geniesst das Wochenende

Re: Beginner braucht Tipps um Dungeoncrawler auf einen mehr "Pythonic-Way" zu optimieren :)
Verfasst: Dienstag 14. Juni 2016, 06:24
von weitnow
Hallo zusammen
Ich wollte euch noch eine Rückmeldung geben. Besten Dank für den Tipp und die Erklärung die update()-Methode in beide Klassen zu implementieren. Ich habe das jetzt so gemacht und bin mit dem Resultat sehr zufrieden. Mit dieser Lösung konnte ich die logisch der Monsterklasse zugehörigen Methoden (bewege dich, prüfe ob Feind in der Nähe ist, prüfe ob du gegen Wand läufst etc) auch in der Monsterklasse deponieren und via Update jeweils die Spielfeldinfos abgleichen. Coole Sache....