image image


image

Mazegame in Pygame Zero – Stage 1

Im Rahmen meines Projekts »Retrogaming und Künstliche Intelligenz« habe ich die Freitag angekündigte erste Lieferung fertiggestellt und so etwas wie eine Blaupause für eine NetHack-inspirierte Spieleumgebung geschaffen, in der ich diverse Algorithmen zur Künstlichen Intelligenz testen und verdeutlichen will. Das Template ist noch nicht komplett fertiggestellt (daher »Stage 1« in der Überschrift), aber schon komplett spielbar und wartet darauf, von mir mit Algorithmen gefüttert zu werden.

Wie angekündigt ist Pygame Zero die Programmierbibliothek, die ich dafür genutzt habe. Denn sie hält das Grundgerüst überschaubar klein, so daß sich die Programmiererin oder der Leser auf die KI-Algorithmen konzentrieren kann:

# Simple Maze Game with Pygame Zero (v 1.2) , Python 3 
# Stage 1 (Initialisierung und Kollisionserkennung)
# Assets: DawnLike-Tileset (CC BY 4.0) by DawnBringer und DragonDePlatino
# (https://opengameart.org/content/dawnlike-16x16-universal-rogue-like-tileset-v181)
# Jörg Kantel 2022 (MIT-Lizenz)

import pgzrun

# WIDTH: 25 Tiles á 16 Pixel + je 20 Pixel Rand
WIDTH = 440
# HEIGHT: 25 Tiles á 16 Pixel + je 20 Pixel Rand
HEIGHT = 440
TITLE = "Mazegame Stage 1"

WALL  = 63
CHEST = 22

margin_x = 20
margin_y = 20
sz = 16  # Step-/Tile-Size

maze_map = [[63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63],
            [63,-1,-1,63,63,63,63,63,63,63,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,63,63,63,63,63],
            [63,-1,-1,63,63,63,63,63,63,63,-1,-1,63,63,63,63,63,63,-1,-1,63,63,63,63,63],
            [63,-1,-1,-1,-1,-1,-1,-1,63,63,-1,-1,63,63,63,63,63,63,-1,-1,63,63,63,63,63],
            [63,-1,-1,-1,-1,-1,-1,-1,63,63,-1,-1,63,63,63,-1,-1,-1,-1,-1,-1,-1,-1,63,63],
            [63,63,63,63,63,63,-1,-1,63,63,-1,-1,63,63,63,-1,-1,-1,-1,-1,-1,-1,-1,63,63],
            [63,63,63,63,63,63,-1,-1,63,63,-1,-1,63,63,63,63,63,63,-1,-1,63,63,63,63,63],
            [63,63,63,63,63,63,-1,-1,63,63,-1,-1,-1,-1,63,63,63,63,-1,-1,63,63,63,63,63],
            [63,-1,-1,63,63,63,-1,-1,-1,-1,-1,-1,-1,-1,63,63,63,63,22,-1,63,63,63,63,63],
            [63,-1,-1,63,63,63,-1,-1,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63],
            [63,-1,-1,-1,-1,-1,-1,-1,-1,-1,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63],
            [63,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,63,63,63,63,63,63,63,63],
            [63,63,63,63,63,63,63,63,63,63,63,63,-1,-1,-1,-1,-1,63,63,63,63,63,-1,22,63],
            [63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,-1,-1,63,63,63,63,63,-1,-1,63],
            [63,-1,-1,63,63,63,63,63,63,63,63,63,63,63,63,-1,-1,-1,-1,-1,-1,-1,-1,-1,63],
            [63,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,63],
            [63,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,63,63,63,63,63,63,63,63,63,63,63,63,63],
            [63,63,63,63,63,63,63,63,63,63,-1,-1,63,63,63,63,63,63,63,63,63,63,63,63,63],
            [63,63,63,63,63,63,63,63,63,63,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,63],
            [63,22,-1,-1,63,63,63,63,63,63,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,63],
            [63,-1,-1,-1,63,63,63,63,63,63,63,63,63,63,63,63,63,63,-1,-1,63,63,63,63,63],
            [63,-1,-1,-1,-1,-1,63,63,63,63,63,63,63,63,63,63,63,63,-1,-1,63,63,63,63,63],
            [63,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,63,63,63,63,-1,-1,-1,-1,-1,-1,-1,-1,63],
            [63,63,63,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,63],
            [63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63,63]]

walls      = []
chests     = []
walls_pos  = []
chests_pos = []
for y in range(25):
    for x in range(25):
        if maze_map[y][x] == WALL:
            wall = Actor("wall16.png")
            wall.topleft = margin_x + x*sz, margin_y + y*sz
            walls.append(wall)
            walls_pos.append((margin_x + x*sz, margin_y + y*sz))
        if maze_map[y][x] == CHEST:
            chest = Actor("chest16.png")
            chest.topleft = margin_x + x*sz, margin_y + y*sz
            chests.append(chest)
            chests_pos.append((margin_x + x*sz, margin_y + y*sz))
                
rogue = Actor("rogue16")
rogue_x = 1
rogue_y = 1
rogue.topleft = margin_x + rogue_x*sz, margin_y + rogue_y*sz

def update():
    global dir, rogue_x, rogue_y
    if dir == "left":
        move_to_x = margin_x + (rogue_x*sz) - sz
        move_to_y = margin_y + rogue_y*sz
        dir = None
        if (move_to_x, move_to_y) not in walls_pos:   # Kollisionserkennung
            rogue.topleft = move_to_x, move_to_y
            rogue_x -= 1
    elif dir == "right":
        move_to_x = margin_x + (rogue_x*sz) + sz
        move_to_y = margin_y + rogue_y*sz
        dir = None
        if (move_to_x, move_to_y) not in walls_pos:   # Kollisionserkennung
            rogue.topleft = move_to_x, move_to_y
            rogue_x += 1
    elif dir == "up":
        move_to_x = margin_x + rogue_x*sz
        move_to_y = margin_y + (rogue_y*sz) - sz
        dir = None
        if (move_to_x, move_to_y) not in walls_pos:   # Kollisionserkennung
            rogue.topleft = move_to_x, move_to_y
            rogue_y -= 1
    elif dir == "down":
        move_to_x = margin_x + rogue_x*sz
        move_to_y = margin_y + (rogue_y*sz) + sz
        dir = None
        if (move_to_x, move_to_y) not in walls_pos:   # Kollisionserkennung
            rogue.topleft = move_to_x, move_to_y
            rogue_y += 1
    
def draw():
    screen.fill((90, 90, 90))
    
    for wall in walls:
        wall.draw()
    for chest in chests:
        chest.draw()
    rogue.draw()

def on_key_down(key):
    global dir
    if key == keys.LEFT:
        dir = "left"
    elif key == keys.RIGHT:
        dir = "right"
    elif key == keys.UP:
        dir = "up"
    elif key == keys.DOWN:
        dir = "down"
    if key == keys.ESCAPE:              # ESCAPE beendet das Spiel
        print("Bye, bye, Baby!")
        quit()
        
pgzrun.go()

Das Labyrinth (eigentlich ein Irrgarten) habe ich in Tiled zusammengebastelt und von dort als CSV-Datei exportiert und nahezu unverändert als Liste in den Quellcode eingefügt.

Momentan bewegen sich die Sprites (bisher gibt es nur die Figur des Players) von Kachel zu Kachel, also in diesem Fall immer um 16 Pixel vorwärts (in einer späteren Version plane ich auch eine mehr kontinuierliche Bewegung, aber momentan und bei diesem kleinen Maßstab reicht mir das).

Einzige Herausforderung war die Kollisionskerkennung: Wegen der Differenz der Bildschirm-Koordinaten (440x440 Pixel) zu den diskreten Koordinaten des Spielfeldes (25x25 Felder) war eine ständige Umrechnnung erforderleich, in der ich mich während der Entwicklung immer mal wieder verheddert hatte. Aber im Grunde funktioniert sie ganz einfach: Abhängig von der Bewegungsrichtung (up, down, right, left) werden die gewünschten Zielkoordinate (move_to_x, move_to_y) ermittelt. Dann wird geprüft. ob diese Zielkoordinaten frei oder blockert sind. Und nur im ersten Fall werden die Koordinaten des Spielers auf die Zielkoordinaten gesetzt:

rogue.topleft = move_to_x, move_to_y

Und auch der Standort des Spielers im Labyrinth wird den neuen Koordinaten angepaßt: rogue_x respektive rogue_y. Eigentlich ganz einfach, oder?

Was ich als nächstes implementieren möchte: Das Template schreit geradezu nach einer objektorientierten Implementierung, die auch die Anzahl der global zu vereinbarenden Variablen deutlich reduziert. Spätestens wenn auch noch Gegner (NPCs) das Spielfeld bevölkern, komme ich sowieso nicht darum herum. Die Objekte sollen (müssen?) alle von Actor() erben und bekommen zusätzlich eigene Eigenschaften (zum Beispiel den Punktestand des Spielers) und Methoden (zum Beispiel Kollisionserkennung als Methode) verpaßt. Still digging!

Die Sprites und Tiles habe ich dem DawnLike-Tileset auf OpenGameArt.org entnommen. Die Lizenz (CC BY 4.0) verlangt zwingend die Namensnennung der Schöpfer DawnBringer und DragonDePlatino. Dem bin ich hiermit pflichtbewußt nachgekommen.

Wie – fast immer – in meinen Tutorials findet Ihr den Quellcode und die Assets auf GitHub zur freien Verwendung. Habt Spaß damit!


(Kommentieren) 

image image



Über …

Der Schockwellenreiter ist seit dem 24. April 2000 das Weblog digitale Kritzelheft von Jörg Kantel (Neuköllner, EDV-Leiter Rentner, Autor, Netzaktivist und Hundesportler — Reihenfolge rein zufällig). Hier steht, was mir gefällt. Wem es nicht gefällt, der braucht ja nicht mitzulesen. Wer aber mitliest, ist herzlich willkommen und eingeladen, mitzudiskutieren!

Alle eigenen Inhalte des Schockwellenreiters stehen unter einer Creative-Commons-Lizenz, jedoch können fremde Inhalte (speziell Videos, Photos und sonstige Bilder) unter einer anderen Lizenz stehen.

Der Besuch dieser Webseite wird aktuell von der Piwik Webanalyse erfaßt. Hier können Sie der Erfassung widersprechen.

Diese Seite verwendet keine Cookies. Warum auch? Was allerdings die iframes von Amazon, YouTube und Co. machen, entzieht sich meiner Kenntnis.


Werbung

Diese Spalte wurde absichtlich leergelassen!


Werbung


image  image  image
image  image  image


image