Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.
Beide Seiten, vorherige Überarbeitung Vorherige Überarbeitung Nächste Überarbeitung | Vorherige Überarbeitung | ||
talit:asteroids_game [2024-05-27 11:20] – [Version 5] sca | talit:asteroids_game [2024-06-03 11:47] (aktuell) – [Version 5] sca | ||
---|---|---|---|
Zeile 181: | Zeile 181: | ||
Mache eine Kopie von der letzten Version und nenne sie `asteroids_v05.py`. | Mache eine Kopie von der letzten Version und nenne sie `asteroids_v05.py`. | ||
- | 1. **Plan:** Das Ziel ist, dass wir **beliebig viele Asteroiden** haben können. Diese werden zufälligerweise gespawned. Es kann also sein, dass wir einmal $0$ und später $7$ Asteroiden haben. Jeder Asteroid ist *unabhängig* und bewegt sich in seinem eigenen Tempo (per Zufall bestimmen, wenn Asteroid erzeugt wird). Daher können sich Asteroiden auch überholen. Bespreche mit KollegIn: Wie kann man das technisch umsetzen? Welche programmiererischen Mittel kann man da verwenden? Vergleicht eure Antworten mit den Lösungen unten.< | + | 1. **Plan:** Das Ziel ist, dass wir **beliebig viele Asteroiden** haben können. Diese werden zufälligerweise gespawned. Es kann also sein, dass wir einmal $0$ und später $7$ Asteroiden haben. Jeder Asteroid ist *unabhängig* und bewegt sich in seinem eigenen Tempo (per Zufall bestimmen, wenn Asteroid erzeugt wird). Daher können sich Asteroiden auch überholen.\\ Bespreche mit KollegIn: Wie kann man das technisch umsetzen? Welche programmiererischen Mittel kann man da verwenden? Vergleicht eure Antworten mit den Lösungen unten.< |
++++Lösung| | ++++Lösung| | ||
- | Da jeder Asteroid unterschiedliche Werte hat, bietet es sich an, **Dictionaries** zu verwenden: Jeder Asteroid ist ein Dictionary, in dem alle relevanten Werte stehen. Alle Asteroiden-Dictionaries speichert man dann in einer Liste. Erreicht ein Asteroid den unteren Rand, wird er aus der Liste gekickt. Wird ein neuer Asteroid gespawned, wird er hinzugefügt. | + | Da jeder Asteroid unterschiedliche Werte hat, bietet es sich an, **Dictionaries** zu verwenden: Jeder Asteroid ist ein Dictionary, in dem alle relevanten Werte stehen |
+ | |||
+ | Das **Spawnen** kann man wie folgt umsetzen: Alle $200$ms (oder anderer Wert) wird mit einer *gewissen Wahrscheinlichkeit* (z.B. $10$%) ein Asteroid erzeug. Verwende dazu das random-Modul. | ||
Diese Lösung ist sehr elegant, da alle Informationen, | Diese Lösung ist sehr elegant, da alle Informationen, | ||
Verwende auch für den Player ein Dictionary. | Verwende auch für den Player ein Dictionary. | ||
+ | |||
+ | Eine gute Alternative zu Dicts wäre die objektorientierte Programmierung (OOP). Verwende aber Dicts, auch wenn du OOP bereits kennst. | ||
++++ | ++++ | ||
</ | </ | ||
Zeile 199: | Zeile 203: | ||
++++ | ++++ | ||
+ | ===== Version Final ===== | ||
+ | |||
+ | **Slides:** {{ : | ||
+ | |||
+ | Nun geht es in die finale Phase! Ziel ist, zuerst ein komplett abstraktes **Modell** des Games zu erstellen. Dieses soll dann genutzt werden, um verschiedene Versionen des Spiels zu erstellen: | ||
+ | |||
+ | 1. Konsolen-App (im Terminal) | ||
+ | 1. Micro-Bit | ||
+ | 1. Desktop-App mit PyQt5 | ||
+ | |||
+ | Lasse (zumindest für den Anfang) allen Schnick-Schnack weg: kein Score, kein Restart, ... einfach nur 1x das Game spielen bis zu einer Collision | ||
+ | |||
+ | In deinem Repo sollst du dann (zusätzlich zu den bisherigen Files) vier neue Files haben: | ||
+ | |||
+ | * `asteroids_game_model.py`: | ||
+ | * `asteroids_game_microbit.py`: | ||
+ | * `asteroids_game_console.py`: | ||
+ | * `asteroids_game_pyqt.py`: | ||
+ | |||
+ | === Teil I: Modell === | ||
+ | |||
+ | 1. Erstelle ein File `asteroids_model.py`. | ||
+ | 1. Dieses Soll die bisherigen Klassen `Asteroid` und `Player` beinhalten. | ||
+ | 1. Erstelle weiter eine Klasse `Game`, die das gesamte Game beinhaltet. Halte dich dabei an die Vorgaben im **Template** unten. | ||
+ | 1. **Entferne alles**, was **nichts mit dem Modell** zu tun hat. | ||
+ | 1. Das ganze Dokument darf also keine `print()`, `display...` usw. enthalten. | ||
+ | 1. Auch sollen die Klassen keine Attribute wie `self.brightness` haben, da diese zur View gehört: Stellt man Asteroiden in einer Desktop-App dar, haben sie vielleicht eine Farbe oder ein Bild anstelle einer brightness. | ||
+ | 1. Achtung: Zeitabfragen funktionieren auf dem Computer anders als auf dem Mircobit. Verwende deshalb die Funktion `get_time_in_ms()` im Template. | ||
+ | |||
+ | ++++Template| | ||
+ | |||
+ | <code python asteroids_model.py> | ||
+ | # ALL IMPORT STATEMENTS | ||
+ | import sys | ||
+ | import ... | ||
+ | |||
+ | def get_time_in_ms(): | ||
+ | """ | ||
+ | Time is handled differently on microbit than on regular computers. | ||
+ | Call this function to get current (system) time. | ||
+ | """ | ||
+ | if sys.platform == ' | ||
+ | try: | ||
+ | return time.ticks_ms() | ||
+ | except Exception as e: | ||
+ | print(" | ||
+ | else: | ||
+ | try: | ||
+ | return time.time()*1000 | ||
+ | except Exception as e: | ||
+ | print(" | ||
+ | |||
+ | class Player: | ||
+ | def __init__(self, | ||
+ | pass | ||
+ | |||
+ | def update(self, | ||
+ | """ | ||
+ | move = 1 -> move to right 1 step | ||
+ | move = -1 -> move to right 1 step | ||
+ | """ | ||
+ | pass | ||
+ | |||
+ | class Asteroid: | ||
+ | def __init__(self, | ||
+ | pass | ||
+ | |||
+ | def update(self): | ||
+ | """ | ||
+ | if enough time has passed, asteroid' | ||
+ | """ | ||
+ | pass | ||
+ | |||
+ | class Game: | ||
+ | def __init__(self, | ||
+ | .... | ||
+ | self.player = Player(...) # create player and attache to Game-class as attribute | ||
+ | self.asteroids = [] # also create empty list for asteroids | ||
+ | .... | ||
+ | |||
+ | def spawn_asteroids(self): | ||
+ | """ | ||
+ | Checks if enough time has passed s.t. new asteroid is allowed to spawn. | ||
+ | If it is, new asteroid spawns with given probability | ||
+ | """ | ||
+ | pass | ||
+ | |||
+ | def update_asteroids(self): | ||
+ | """ | ||
+ | updates position of all asteroids | ||
+ | """ | ||
+ | pass | ||
+ | | ||
+ | def player_is_colliding(self): | ||
+ | """ | ||
+ | checks if player is colliding with an asteroid | ||
+ | returns False or True | ||
+ | """ | ||
+ | pass | ||
+ | </ | ||
+ | |||
+ | ++++ | ||
+ | |||
+ | === Teil II: Micro-Bit === | ||
+ | |||
+ | 1. Erstelle im Online-Editor ein neues File (heisst `main.py`). | ||
+ | 1. Erstelle darin (" | ||
+ | 1. Importiere im `main.py`-File deine Game-Klasse (siehe Template unten) ... | ||
+ | 1. ... und implementiere das Spiel. Rufe dazu die Methoden deiner Game-Klasse auf. | ||
+ | 1. Wenn du alles richtig machst, solltest du nur um die 20 Zeilen benötigen. | ||
+ | 1. **Speichere** deinen Microbit Code dann auch in deinem Repo in einem File: `asteroids_game_microbit.py` | ||
+ | |||
+ | ++++Template| | ||
+ | |||
+ | <code python> | ||
+ | from microbit import * | ||
+ | from asteroids_game_model import Game | ||
+ | |||
+ | def show(game): | ||
+ | """ | ||
+ | Takes game object (instance of Game class) and visualizes it on microbit' | ||
+ | """ | ||
+ | pass | ||
+ | | ||
+ | g = Game(...) # create Game object | ||
+ | |||
+ | while True: | ||
+ | # call methods of Game object | ||
+ | ... | ||
+ | show(g) | ||
+ | sleep(...) | ||
+ | </ | ||
+ | ++++ | ||
+ | === Teil III: Konsolen-App === | ||
+ | |||
+ | 1. Erstelle ein neues File (im gleichen Ordner) mit Namen `asteroids_game_console.py`. | ||
+ | 1. Importiere in diesem deine Game-Klasse (wie in Mirobit-Version) ... | ||
+ | 1. ... und implementiere das Game | ||
+ | 1. Achtung: Keyevents und damit die Player-Navigation kann in der Konsole problematisch sein (zumindest auf Mac). Es ist deshalb auch i.O., wenn man hier: | ||
+ | 1. den Player nicht anzeigt | ||
+ | 1. Collisions deaktiviert | ||
+ | 1. damit einfach eine Art ' | ||
+ | |||
+ | |||
+ | === Teil IV: Desktop-App === | ||
+ | |||
+ | 1. Erstelle ein neues File (im gleichen Ordner) mit Namen `asteroids_game_pyqt.py`. | ||
+ | 1. Studiere das Template unten, führe es aus und verstehe die wichtigsten Schritte (nicht alle Details) zu verstehen. | ||
+ | 1. Importiere wieder deine Game-Klasse ... | ||
+ | 1. ... und implementiere das Spiel. | ||
+ | |||
+ | ++++PyQt5 Template| | ||
+ | |||
+ | **Wichtig: | ||
+ | |||
+ | <code python> | ||
+ | from asteroids_game_model import Game | ||
+ | import sys | ||
+ | import random | ||
+ | from PyQt5.QtWidgets import QApplication, | ||
+ | from PyQt5.QtGui import QColor | ||
+ | from PyQt5.QtCore import Qt,QTimer | ||
+ | |||
+ | N_COLUMNS = 4 | ||
+ | N_ROWS = 7 | ||
+ | |||
+ | class GridApp(QWidget): | ||
+ | def __init__(self): | ||
+ | super().__init__() | ||
+ | self.COLUMNS = N_COLUMNS | ||
+ | self.ROWS = N_ROWS | ||
+ | self.CELL_WIDTH = 70 | ||
+ | self.SPACING = 5 | ||
+ | self.COL_BACKGROUND = QColor(" | ||
+ | self.COL_CELL_DEFAULT = QColor(" | ||
+ | self.initUI() | ||
+ | self.timer = QTimer() | ||
+ | self.timer.timeout.connect(self.update) | ||
+ | self.timer.start(500) | ||
+ | |||
+ | def initUI(self): | ||
+ | self.setWindowTitle(' | ||
+ | layout = QVBoxLayout() | ||
+ | self.setLayout(layout) | ||
+ | grid = QWidget() | ||
+ | grid.setStyleSheet(" | ||
+ | grid_layout = QVBoxLayout(grid) | ||
+ | grid_layout.setSpacing(self.SPACING) | ||
+ | layout.addWidget(grid) | ||
+ | |||
+ | self.cells = [] # Store references to the cell widgets | ||
+ | | ||
+ | for y in range(self.ROWS): | ||
+ | row_layout = QHBoxLayout() | ||
+ | grid_layout.addLayout(row_layout) | ||
+ | for x in range(self.COLUMNS): | ||
+ | cell = QLabel() | ||
+ | cell.setFixedSize(self.CELL_WIDTH, | ||
+ | cell.setStyleSheet(" | ||
+ | row_layout.addWidget(cell) | ||
+ | self.cells.append(cell) | ||
+ | |||
+ | self.show() | ||
+ | self.setFixedSize(self.size().width(), | ||
+ | |||
+ | def draw_cell_at_position(self, | ||
+ | """ | ||
+ | changes color of cell at given position | ||
+ | """ | ||
+ | cell = self.cells[y*self.COLUMNS + x] | ||
+ | cell.setStyleSheet(" | ||
+ | |||
+ | def draw_all_cells(self): | ||
+ | """ | ||
+ | draw all cells | ||
+ | """ | ||
+ | for y in range(self.ROWS): | ||
+ | for x in range(self.COLUMNS): | ||
+ | rnd_color = QColor(random.randint(0, | ||
+ | self.draw_cell_at_position(x, | ||
+ | | ||
+ | def keyPressEvent(self, | ||
+ | """ | ||
+ | deals with keypressed events | ||
+ | """ | ||
+ | key = event.key() | ||
+ | if key == Qt.Key_Up: | ||
+ | print(' | ||
+ | elif key == Qt.Key_Down: | ||
+ | print(' | ||
+ | self.draw_all_cells() | ||
+ | |||
+ | def update(self): | ||
+ | # Perform your periodic check here | ||
+ | # This function will be called every ... ms (value in self.timer.start(...)) | ||
+ | # Update the necessary data or trigger actions as needed | ||
+ | print(" | ||
+ | |||
+ | if __name__ == ' | ||
+ | app = QApplication(sys.argv) | ||
+ | grid_app = GridApp() | ||
+ | sys.exit(app.exec_()) | ||
+ | </ | ||
+ | |||
+ | ++++ | ||
+ | |||
+ | === Teil V (falls Zeit / für Schnelle): Pimp my Desktop-App === | ||
+ | |||
+ | Wandle deine Desktop-App-Version in ein richtig cooles Game: | ||
+ | |||
+ | * Bilder für Asteroiden und Player (auch versch. Asteroiden-Bilder möglich) | ||
+ | * Explosion-Bild bei Collision. | ||
+ | * Schnick-Schnack wieder einbauen: | ||
+ | * Restart | ||
+ | * Score | ||
+ | * ... | ||
+ | * Asteroiden abschiessen können | ||
+ | * ... | ||
+ | |||
+ | ++++Tipps| | ||
+ | |||
+ | **Bilder Laden:** | ||
+ | Es bietet sich an, Bilder in Unterordnern abzulegen. Lädt man das Bild in Python, muss der relative Pfad (in Bezug auf Location von Python-File) angegeben werden. Da Pfade auf versch. Systemen (z.B. Windows und Mac) anders angegeben werden, muss man die `os.path.join(...)` Funktion verwenden. | ||
+ | |||
+ | Beispiel: Bild `asteroid.png` ist im Unterordner `images`. Gebe Pfad dann wie folgt an: | ||
+ | <code python> | ||
+ | import os | ||
+ | ... | ||
+ | |||
+ | PATH_IMG_ASTEROID = os.path.join(' | ||
+ | </ | ||
+ | |||
+ | **Bild in Cell anzeigen:** | ||
+ | |||
+ | <code python> | ||
+ | ... | ||
+ | pixmap = QPixmap(PATH_IMG_ASTEROID) | ||
+ | cell.setPixmap(pixmap.scaled(cell.size(), | ||
+ | </ | ||
+ | |||
+ | ++++ |