28. Array-basierte Gitterraster

28.1. Einführung

Spiele wie Minesweeper, Tic-Tac-Toe und viele Arten von Adventures speichern die Daten für das Spiel in einem Zahlenraster. Zum Beispiel ein Tic-Tac-Toe-Brett:

O

O

X

X

… kann ein Zahlengitter verwenden, um die leeren Punkte, die O und die X wie folgt darzustellen:

0

2

2

0

1

0

1

0

0

Dieses Zahlenraster kann auch als zweidimensionales Array oder Matrix bezeichnet werden. (Endlich lernen wir Die Matrix kennen.) Die Werte der Zahlen im Raster geben an, was an jeder Position des Spielfelds angezeigt werden soll. Im vorherigen Beispiel steht 0 für eine Stelle, an der noch niemand gespielt hat, eine 1 für ein X und eine 2 für ein O.

../../_images/minesweeper.png

Abbildung 16.1: Minesweeper-Spiel mit dem dahinterstehenden Zahlengitter

Die obige Abbildung ist ein Beispiel aus dem klassischen Minesweeper-Spiel. Dieses Beispiel wurde geändert, um sowohl die klassische Anzeige auf der linken Seite als auch das Zahlenraster für die Anzeige des Spielfelds auf der rechten Seite anzuzeigen.

Die Zahl 10 steht für eine Mine, die Zahl 0 für ein nicht angeklicktes Feld und die Zahl 9 für ein gelöschtes Feld. Die Zahlen 1 bis 8 geben an, wie viele Minen sich in den umliegenden acht Feldern befinden, und werden nur dann ausgefüllt, wenn der Benutzer auf das Feld klickt.

Minesweeper kann eigentlich zwei Gitter haben. Eine für die reguläre Anzeige und ein vollständig getrenntes Zahlenraster, das nachverfolgt, ob der Benutzer „Flaggen“ auf die Tafelmarkierung gesetzt hat, an der er denkt, dass sich die Minen befinden.

Klassische Adventure Maps werden mit einem auf Kacheln basierenden Karteneditor erstellt. Dies sind riesige Gitter, in denen jeder Ort einfach eine Zahl ist, die den Geländetyp darstellt, der dorthin gehört. Das Gelände könnte aus Sand, einer Straße, einem Pfad, grünem Gras, braunem Gras und so weiter bestehen. Mit Programmen wie Tiled, das in der folgenden Abbildung gezeigt werden, kann ein Entwickler diese Karten auf einfache Weise erstellen und das Raster auf die Festplatte schreiben.

../../_images/qt_tiled.png

Figure 16.2: Using Tiled Map Editor to create an adventure map

In Adventures werden auch mehrere Zahlenraster verwendet, genau wie bei Minesweeper ein Raster für die Minen und ein separates Raster für die Flaggen. Ein Raster oder „Layer“ im Adventure steht für begehbares Gelände. Ein anderes für Dinge, auf denen man nicht laufen kann wie Mauern und Bäume. Ein Layer für Dinge, die dich sofort töten können, wie Lava oder bodenlose Gruben. Einer für Gegenstände, die aufgenommen und bewegt werden können und noch eine Ebene für die Anfangspositionen von Monstern.

Maps like these can be loaded in a Python program, which is shown in more detail with the Python Arcade Library documentation.

28.2. Anwendung

Genug geredet, schreiben wir etwas Code. In diesem Beispiel wird ein Raster erstellt, das auslösen wird, ob ein weißer oder grüner Block angezeigt wird. Wir können den Rasterwert ändern und ihn grün machen, indem wir darauf klicken. Dies ist ein erster Schritt zu einem rasterbasierten Spiel wie Minesweeper, Schiffe versenken, Vier gewinnt und so weiter. (In einem Schuljahr wurde ich von einer Studierenden an ihren Rechner gerufen. Sie hatte ein Programm wie dieses so modifiziert, dass mein Name in blinkenden Feldern angezeigt wurde. Das war … beunruhigend. Bitte nutze dieses Wissen nur zum Guten!)

Beginnen wir mit dieser Vorlage:

array_backed_grid.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import arcade

SCREEN_WIDTH = 500
SCREEN_HEIGHT = 600


class MyGame(arcade.Window):
    """
    Main application class.
    """

    def __init__(self, width, height):
        super().__init__(width, height)

        arcade.set_background_color(arcade.color.WHITE)

    def on_draw(self):
        """
        Render the screen.
        """

        arcade.start_render()

    def on_mouse_press(self, x, y, button, key_modifiers):
        """
        Called when the user presses a mouse button.
        """
        pass


def main():

    window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT)
    arcade.run()


if __name__ == "__main__":
    main()

Beginne mit der obigen Datei und versuche, dieses Programm neu zu erstellen, indem Sie den Anweisungen hier folgen. Das endgültige Programm befindet sich am Ende dieses Kapitels, aber springe nicht direkt dahin und kopiere es! Wenn du das tust, hast du nichts gelernt. Jeder kann den Code kopieren und einfügen, aber wenn du dieses Programm neu erstellen kannst, verfügst du über Fähigkeiten, für die die Leute bereit sind, zu bezahlen. Wenn du nur kopieren und einfügen kannst, hast du hier deine Zeit verschwendet.

28.2.1. Raster zeichnen

  1. Erstelle Variablen mit den Namen WIDTH, HEIGHT und MARGIN. Stelle die Breite und Höhe auf 20 ein. Dies gibt an, wie groß die einzelnen Rasterpositionen sind. Setze den MARGIN auf 5. Dies stellt den Rand zwischen den einzelnen Rasterpositionen und den Rändern des Bildschirms dar. Erstelle diese Variablen oben im Programm nach den import-Anweisungen. Erstelle auch die Variablen ROW_COUNT und COLUMN_COUNT. Stelle sie auf 10 ein. Dadurch wird gesteuert, wie viele Zeilen und Spalten wir haben werden.

  2. Berechne SCREEN_WIDTH und SCREEN_HEIGHT basierend auf den oben erstellten Variablen. Wenn wir 10 Zeilen haben und jede Zeile 20 hoch ist, sind das 200 Pixel. Wenn wir 10 Zeilen haben, sind das auch 11 Ränder. (Neun zwischen den Zellen und zwei an jeder Kante.) Das sind 55 weitere Pixel für insgesamt 255. Schreibe die Gleichung so, dass sie mit allen in Schritt 1 erstellten Konstanten funktioniert.

  3. Ändere den Hintergrund in Schwarz. Zeichne ein weißes Kästchen in die linke untere Ecke. Zeichne das gezeichnete Feld mit den zuvor erstellten Höhen- und Breitenvariablen. (Wenn du magst kannst du die Farben ändern.) Verwende die Funktion draw_rectangle_filled. Du solltest das Rechteck nicht auf (0, 0) zentrieren, sondern auf eine Koordinate, die die Höhe und Breite des Rechtecks ​​berücksichtigt, zum Beispiel \(\frac{width}{2}\). Wenn du fertig bist, sollte das Fenster deines Programms so aussehen:

../../_images/step_03.png

Abbildung 16.3: Schritt 3

  1. Verwende eine for-Schleife, um COLUMN_COUNT Kästchen in einer Reihe zu zeichnen. Verwende column für den Variablennamen in der for-Schleife. Die Ausgabe sieht aus wie eine lange Box, bis der Rand zwischen den Boxen hinzugefügt wird. Siehe Abbildung 16.4.

../../_images/step_04.png

Abbildung 16.4: Schritt 4

  1. Passe die Zeichnung des Rechtecks ​​an, um die Variable MARGIN hinzuzufügen. Jetzt sollte es Lücken zwischen den Rechtecken geben. Siehe Abbildung 16.5.

../../_images/step_05.png

Abbildung 16.5: Schritt 5

  1. Füge den Rand hinzu, bevor du die Rechtecke zeichnest, zusätzlich zu jedem Rechteck. Dies sollte verhindern, dass das Kästchen direkt neben der Fensterkante angezeigt wird. Siehe Abbildung 16.6. Am Ende erhälst du eine Gleichung wie: \((Rand + Breite)\cdot Spalte + Rand + \frac{Rand}{2}\)

../../_images/step_06.png

Abbildung 16.6: Schritt 6

  1. Füge eine weitere for-Schleife, die durch die Zeilen iteriert, hinzu. Nenne die Variable in dieser for-Schleife row. Jetzt sollten wir ein vollständiges Raster von Kisten haben. Siehe Abbildung 16.7.

../../_images/step_07.png

Abbildung 16.7: Schritt 7

28.2.2. Auffüllen des Rasters

  1. Jetzt müssen wir ein zweidimensionales Array erstellen. Wir müssen dies einmal beim Programmstart erstellen. Das geht also in die __init__-Methode des Programms. Das Erstellen eines zweidimensionalen Arrays in Python ist leider nicht so einfach wie in einigen anderen Computersprachen. Es gibt einige Bibliotheken, die für Python heruntergeladen werden können, um das zu vereinfachen (zum Beispiel numpy), aber für dieses Beispiel werden sie nicht verwendet. Verwende den folgenden Code, um ein zweidimensionales Array zu erstellen als ein Beispiel:

Erstelle ein 10x10-Array von Zahlen
ROW_COUNT = 10
COLUMN_COUNT = 10

# --- Create grid of numbers
# Create an empty list
self.grid = []
# Loop for each row
for row in range(ROW_COUNT):
    # For each row, create a list that will
    # represent an entire row
    self.grid.append([])
    # Loop for each column
    for column in range(COLUMN_COUNT):
        # Add a the number zero to the current row
        self.grid[row].append(0)

Ein viel kürzeres Beispiel findest du weiter unten, aber in diesem Beispiel werden einige merkwürdige Teile von Python verwendet, die ich in diesem Buch nicht näher erläutere:

Erstelle ein 10x10-Array von Zahlen
self.grid = [[0 for x in range(10)] for y in range(10)]

Verwende eines dieser beiden Beispiele und platziere den Code vor deiner Hauptprogrammschleife, um unser Array zu erstellen.

  1. Setze einen beispielhaften Ort im Array auf 1.

Zweidimensionale Arrays werden normalerweise dargestellt, indem zuerst ihre Zeile und dann die Spalte adressiert werden. Dies wird als zeilendominiert bezeichnet. Die meisten Sprachen, mit Ausnahme von Fortran und MATLAB, verwenden zeilendominierte Speicherung. Fortran und MATLAB verwenden Spaltendominanz.

# Set row 1, column 5 to one
self.grid[1][5] = 1

Platziere diesen Code irgendwo vor der Hauptprogrammschleife.

  1. Wähle die Farbe des Rechtecks ​​basierend auf dem Wert einer Variablen namens color. Suche dazu zuerst die Codezeile, in der das Rechteck gezeichnet wird. Erstelle davor eine Variable mit dem Namen color und setze sie auf white. Ersetze dann die weiße Farbe in der Rechteckdeklaration durch die Variable color.

  2. Wähle die Farbe basierend auf dem Wert im Raster. Nachdem du die Farbe auf Weiß gesetzt haben, platziere eine if-Anweisung, die den Wert in grid[row][column] betrachtet und die Farbe in grün ändert, wenn der Rasterwert gleich 1 ist. Es sollte jetzt ein grünes Quadrat geben. Siehe Abbildung 16.8.

../../_images/step_11.png

Abbildung 16.8: Schritt 11

  1. Gebe „klick“ auf dem Bildschirm aus, wenn der Benutzer mit der Maustaste klickt. Siehe Mausklicks, wenn du vergessen hast, wie das geht.

  2. Gib die Mauskoordinaten aus, wenn der Benutzer mit der Maus klickt.

  3. Konvertiere die Mauskoordinaten in Rasterkoordinaten. Gebe diese stattdessen aus. Denke daran, die Breite und Höhe jeder Rasterposition in Kombination mit dem Rand zu verwenden. Es ist notwendig, den Endwert in eine Ganzzahl umzuwandeln. Dies kann durch Verwendung von int oder durch Verwendung des Integer-Divisionsoperators // anstelle des normalen Divisionsoperators / erfolgen. Siehe Abbildung 16.9.

../../_images/step_14.png

Abbildung 16.9: Schritt 14

  1. Setze die Rasterposition, auf die geklickt wurde, auf 1. Siehe Abbildung 16.10.

../../_images/step_15.png

Abbildung 16.10: Schritt 15

28.2.3. Resulting Program

array_backed_grid.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
"""
Array Backed Grid

Show how to use a two-dimensional list/array to back the display of a
grid on-screen.
"""
import arcade

# Set how many rows and columns we will have
ROW_COUNT = 10
COLUMN_COUNT = 10

# This sets the WIDTH and HEIGHT of each grid location
WIDTH = 20
HEIGHT = 20

# This sets the margin between each cell
# and on the edges of the screen.
MARGIN = 5

# Do the math to figure out our screen dimensions
SCREEN_WIDTH = (WIDTH + MARGIN) * COLUMN_COUNT + MARGIN
SCREEN_HEIGHT = (HEIGHT + MARGIN) * ROW_COUNT + MARGIN


class MyGame(arcade.Window):
    """
    Main application class.
    """

    def __init__(self, width, height):
        """
        Set up the application.
        """
        super().__init__(width, height)
        # Create a 2 dimensional array. A two dimensional
        # array is simply a list of lists.
        self.grid = []
        for row in range(ROW_COUNT):
            # Add an empty array that will hold each cell
            # in this row
            self.grid.append([])
            for column in range(COLUMN_COUNT):
                self.grid[row].append(0)  # Append a cell

        arcade.set_background_color(arcade.color.BLACK)

    def on_draw(self):
        """
        Render the screen.
        """

        # This command has to happen before we start drawing
        arcade.start_render()

        # Draw the grid
        for row in range(ROW_COUNT):
            for column in range(COLUMN_COUNT):
                # Figure out what color to draw the box
                if self.grid[row][column] == 1:
                    color = arcade.color.GREEN
                else:
                    color = arcade.color.WHITE

                # Do the math to figure out where the box is
                x = (MARGIN + WIDTH) * column + MARGIN + WIDTH // 2
                y = (MARGIN + HEIGHT) * row + MARGIN + HEIGHT // 2

                # Draw the box
                arcade.draw_rectangle_filled(x, y, WIDTH, HEIGHT, color)

    def on_mouse_press(self, x, y, button, modifiers):
        """
        Called when the user presses a mouse button.
        """

        # Change the x/y screen coordinates to grid coordinates
        column = x // (WIDTH + MARGIN)
        row = y // (HEIGHT + MARGIN)

        print(f"Click coordinates: ({x}, {y}). Grid coordinates: ({row}, {column})")

        # Make sure we are on-grid. It is possible to click in the upper right
        # corner in the margin and go to a grid location that doesn't exist
        if row < ROW_COUNT and column < COLUMN_COUNT:

            # Flip the location between 1 and 0.
            if self.grid[row][column] == 0:
                self.grid[row][column] = 1
            else:
                self.grid[row][column] = 0


def main():

    window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT)
    arcade.run()


if __name__ == "__main__":
    main()