====== Micro:bit Asteroids Game 2023 ======
===== Programm 1M FS2023 Q2 =====
===== Version 1: Challenge =====
Programmiere das Game **Asteroids** in einer einfachen Version in **45 Minuten**:
zu
* Spieler:in steuert Player (heller Pixel in unterster Reihe) mit Knöpfen
* Jeweils ein Asteroid (etw. weniger heller Pixel) fliegt von oben nach unten
* Player muss Asteroid ausweichen. Kollidiert dieser, ist Game Over!
* Erreicht Asteroid unteres Ende, wir ein neuer Asteroid in oberster Reihe an zufälliger $x-$Position erzeugt
* Speichere regelmässig!
* **Hauptziele:**
* Das Spiel **muss funktionieren** (auch wenn es noch nicht perfekt ist)
* Du hast es ganz **alleine programmiert** (siehe Spielregeln unten)
Spielregeln:
* Erlaubt:
* Internet-Suche zu allg. Fragen bzgl. Programmierung & Micro:bit
* Lehrperson fragen zu allg. Fragen
* nicht erlaubt:
* Austausch mit echten und künstlichen Intelligenzen (also kein ChatGPT, Ausnahme: Lehrperson)
* Internet-Suche zu Spiel-spezifischen Fragen (z.B. auf diesem Wiki)
Nach Challenge:
* Erstelle **neues Repo** auf GitHub mit Name `asteroids`.
* Gebe der LP (anschae) frei
* Füge dein Game mit Name `asteroids_v01.py` hinzu: add / commit / push
===== Version 2 =====
Mache Kopie von Game und speichere unter Namen `asteroids_v02.py`. Verbessere nun dieses mithilfe der Tipps unten.
++++Tipps|
* **Display:**
* `display.set_pixel(3,2,9)` um einzelne Pixel anzusteuern anstelle `Image("00000:00.....")`
* Arbeite mit **Koordinaten**, z.B. `ast_x = 2` `ast_y = 1`
* **Bewegung Player:**
* `button_a.get_presses()` anstelle `button_a.is_pressed()`
* **Update Spiel:**
* Vermeide lange Delays wie `sleep(1000)`, da diese auch die Bewegung des Players stören.
* Arbeite stattdessen mit `time.ticks_ms()` (siehe unten).
* Speichere den Zeitpunkt zu dem der Asteroid zuletzt bewegt wurde in einer Variablen.
* Überprüfe in jedem Durchlauf, ob seit der letzten Bewegung genügend Zeit (z.B. 500ms) vergangen sind. Falls ja: Bewege Asteroid.
* kurzes Delay wie `sleep(20)` verhindert nervöses Flackern
* **Programmierstil:**
* Verwende sinnvolle, aussagekräftige Variablennamen, z.B. `asteroid_x` (für $x-$Koordinate von Asteroid) anstelle `a`.
* Halte dich an Python-Konventionen: `asteroid_x` anstelle `AsteroidX`
import time
...
time.ticks_ms() # gibt an, seit wie vielen Millisekunden Micro:bit schon läuft.
...
++++
===== Version 3 =====
Mache Kopie von der letzten Version des Games und speichere es unter dem Namen `asteroids_v03.py`. Verbessere das Game nun dieses mithilfe der Tipps unten. Falls du unzufrieden mit deine alten Version bist, kannst du auch mit einem leeren File beginnen und alles neu programmieren.
* Code sauber strukturieren wie in Template unten
* Kommentare hinzufügen: für Überschriften (wie in Vorgabe) und Erklärungen (nicht zu viel, nicht zu wenig) auf *Englisch* (höherer Lässigkeitsfaktor, ähm coolness factor!)
* Alle Wert, die sich nicht verändern (quasi die Game-Settings) werden in KONSTANTEN am Anfang des Spiels gespeichert.
++++ Template|
from microbit import *
# CONSTANT
""" all constants: 'variables' that don't change, use CAPITAL letters only """
# VARIABLES
""" variables (that do change) that need to be declared before the while-loop """
while True:
# get current time
# update asteroid position
# update player (button presses)
# collision check
# update display
""" ONLY place in code where we have display.... First clear, then show """
# short sleep
++++
===== Version 4 =====
Mache eine Kopie von der letzten Version v03 und nenne sie `asteroids_v04.py`. WICHTIG: Bespreche deine Lösung von v03 mit der Lehrperson, bevor du weiter arbeitest.
Implementiere nun folgende Features:
* Game wird schneller mit der Zeit. Probiere verschiedene Einstellungen aus, Stichwort: Game-Balancing
* Game Over:
* zeigt z.B. SAD oder ANGRY-Smiley an
* zeigt Score an: definiere dazu eine Art Levels (z.B. für jede weiteren 10s, die man überlebt, steigt Level um 1
* kann dann Taste drücken, um Game neu zu starten
* Tipp: Mache zuerst einen Plan dazu, wie du das umsetzen kannst. Beginne erst nachher mit Programmieren.
===== Version 5 =====
Mache eine Kopie von der letzten Version und nenne sie `asteroids_v05.py`. Implementiere dann folgende Features:
* Allfällige Verbesserungen von v04 (direkt in v05 hinein)
* **Asteroiden:**
* **Mehrere** Asteroiden gleichzeitig
* Asteroiden mit **OOP**, siehe Tipps
* Asteroiden werden **zufällig erzeugt**
* jeder Asteroid bewegt sich **unabhängig** (unterschiedliche Geschwindigkeiten, also auch Positions-Update zu unterschiedlichen Zeiten)
* Asteroiden sind komplett unabhängig voneinander. Verschwindet einer unten aus Bild, so ist er weg und spawned nicht gleich wieder am oberen Rand.
* **Player:**
* Player mit **OOP**
* die beiden Klassen sollten sich möglichst ähnlich sein, z.B. gleiche Methoden-Namen
++++Tipps|
* Tipp: Kommentiere Collision temporär aus, damit du dich auf die Asteroiden konzentrieren kannst.
* Jeder Asteroid ist ein Objekt einer Klasse `Asteroid`
* Speichere die Asteroiden-Objekte in einer Liste
* Update jeden Asteroiden, indem du dessen `update()`-Methode aufrufst.
Asteroiden-Klasse muss folgende Attribute und Methoden beinhalten. Natürlich kannst du andere (aber sinnvolle) Namen verwenden und auch weitere (sinnvolle) Attribute und Methoden definieren.
# Class
class Asteroid():
def __init__(self,...):
self.x = ...
self.y = ...
self.brightness = ...
self.update_time = ... # time between next move
self.time_last_update = ...
self.is_active = True # optional, see info for update-Method
def update(self):
"""
Method is called each time in game loop.
In here, check if enough time has passed s.t. asteroid moves to next position.
If yes, move asteroid.
Somewhere need to check if asteroid is out of screen. Can be done here or outside in game loop.
If done here, class need attribute self.is_active = False/True
"""
pass
def display(self):
"""
displays asteroid
"""
pass
# Create object of class asteroid
a = Asteroid(...)
++++
===== Version Final =====
**Slides:** {{ :talit:microbit_model_view.pdf |Model vs. View}}
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`: Beinhaltet das **Modell** und damit die ganze Logik des Spiels. Wird von den anderen Files importiert.
* `asteroids_game_microbit.py`: Kümmert sich *ausschliesslich* um die **View auf dem Microbit**
* `asteroids_game_console.py`: Kümmert sich *ausschliesslich* um die **View in der Konsole**
* `asteroids_game_pyqt.py`: Kümmert sich *ausschliesslich* um die **View auf der Desktop App**
=== 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|
# 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 == 'microbit':
try:
return time.ticks_ms()
except Exception as e:
print("An error occurred on microbit:", str(e))
else:
try:
return time.time()*1000
except Exception as e:
print("An error occurred on Windows/Mac/Linux/... (not microbit):", str(e))
class Player:
def __init__(self,...):
pass
def update(self,move):
"""
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's y position increases by 1
"""
pass
class Game:
def __init__(self,...): # pass game settings as parameters
....
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 ("Create file") ein neues File mit Namen `asteroids_game_model.py` und füge dort deinen Code vom Teil I ein. Wenn du dort alles richtig gemacht hast, musst du in diesem File gar nichts mehr anpassen. Falls du dies musst, übernehme die Änderungen auch im File auf dem Computer. Es ist wichtig, dass diese beiden immer identisch sind.
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|
from microbit import *
from asteroids_game_model import Game
def show(game):
"""
Takes game object (instance of Game class) and visualizes it on microbit's led-matrix.
"""
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 'Asteroiden-Regen' generiert
=== 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:** Importiere PyQt5 mit **pip** (siehe [[talit:python_setup#installation_von_python_modulen|Tutorial "Installation von Python Modulen"]])
from asteroids_game_model import Game
import sys
import random
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel
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("black")
self.COL_CELL_DEFAULT = QColor("white")
self.initUI()
self.timer = QTimer()
self.timer.timeout.connect(self.update)
self.timer.start(500) # Trigger the checkSomething function every ... milliseconds
def initUI(self):
self.setWindowTitle('Grid App')
layout = QVBoxLayout()
self.setLayout(layout)
grid = QWidget()
grid.setStyleSheet("QWidget {{ background-color: {0}; }}".format(self.COL_BACKGROUND.name()))
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,self.CELL_WIDTH)
cell.setStyleSheet("background-color: {}".format(self.COL_CELL_DEFAULT.name()))
row_layout.addWidget(cell)
self.cells.append(cell)
self.show()
self.setFixedSize(self.size().width(),self.size().height()) # -> window non-resizable
def draw_cell_at_position(self,x,y,color):
"""
changes color of cell at given position
"""
cell = self.cells[y*self.COLUMNS + x]
cell.setStyleSheet("background-color: {}".format(color.name()))
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, 255), random.randint(0, 255), random.randint(0, 255))
self.draw_cell_at_position(x,y,rnd_color)
def keyPressEvent(self, event):
"""
deals with keypressed events
"""
key = event.key()
if key == Qt.Key_Up:
print('key up pressed')
elif key == Qt.Key_Down:
print('key down pressed')
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("Do something!")
if __name__ == '__main__':
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:
import os
...
PATH_IMG_ASTEROID = os.path.join('images','asteroid.png')
**Bild in Cell anzeigen:**
...
pixmap = QPixmap(PATH_IMG_ASTEROID)
cell.setPixmap(pixmap.scaled(cell.size(), aspectRatioMode=Qt.AspectRatioMode.KeepAspectRatio))
++++
===== Lösungen =====
++++Lösung v01/v02|
from microbit import *
import random
import time
def rand_x():
return random.randint(0,4)
AST_DELAY = 500
player_x = 2
ast_x = rand_x()
ast_y = 0
t0 = time.ticks_ms()
while True:
t1 = time.ticks_ms()
print(t1)
# update asteroid
if t1 - t0 > AST_DELAY:
if ast_y < 4:
ast_y += 1
else:
ast_x = rand_x()
ast_y = 0
t0 = t1
# update player
if button_a.get_presses() and player_x > 0:
player_x -= 1
elif button_b.get_presses() and player_x < 4:
player_x += 1
# collision check
if ast_y == 4 and ast_x == player_x:
display.show(Image.ANGRY)
break
# update display
display.clear()
display.set_pixel(player_x,4,9)
display.set_pixel(ast_x,ast_y,5)
sleep(10)
++++
++++Lösung v03|
from microbit import *
import random
import time
# CONSTANT
N_X = 5 # nr pixels in x direction
N_Y = 5 # nr pixels in x direction
ASTEROID_DELAY = 300
ASTEROID_BRIGHTNESS = 5
PLAYER_BRIGHTNESS = 9
SLEEP_TIME_MS = 20
# VARIABLES
player_x = 2
asteroid_x = random.randint(0,N_X-1)
asteroid_y = 0
game_over = False
time_last_update = time.ticks_ms()
while True:
# get current time
time_current = time.ticks_ms()
# update asteroid position
if time_current - time_last_update > ASTEROID_DELAY:
asteroid_y += 1
if asteroid_y >= N_Y:
asteroid_x, asteroid_y = random.randint(0,N_X-1), 0
time_last_update = time_current
# update player (button presses)
if button_a.get_presses() and player_x > 0:
player_x -= 1
elif button_b.get_presses() and player_x < N_X-1:
player_x += 1
# collision check
if asteroid_y == N_Y-1 and asteroid_x == player_x:
game_over = True
# update display
display.clear()
if game_over:
display.show(Image.ANGRY)
break
display.set_pixel(player_x,4,PLAYER_BRIGHTNESS)
display.set_pixel(asteroid_x,asteroid_y,ASTEROID_BRIGHTNESS)
# short sleep
sleep(SLEEP_TIME_MS)
++++
++++Lösung v04|
from microbit import *
import random
import time
# CONSTANTS
N_X = 5 # nr pixels in x direction
N_Y = 5 # nr pixels in x direction
ASTEROID_DELAY_MAX = 500
ASTEROID_DELAY_MIN = 100
ASTEROID_DELAY_DELTA = 50
TIME_LEVEL_UPDATE = 3000
ASTEROID_BRIGHTNESS = 5
PLAYER_BRIGHTNESS = 9
SLEEP_TIME_MS = 20
GAME_OVER_TIME = 1000
IMAGE_GAME_OVER = Image.ANGRY
# VARIABLES
player_x = N_X // 2
asteroid_x = random.randint(0,N_X-1)
asteroid_y = 0
asteroid_delay = ASTEROID_DELAY_MAX
game_over = False
level = 1
time_last_asteroid_update = time.ticks_ms()
time_last_level_update = time.ticks_ms()
# GAME LOOP
while True:
# get current time
t1 = time.ticks_ms()
# update level
if t1 - time_last_level_update > TIME_LEVEL_UPDATE:
if asteroid_delay > ASTEROID_DELAY_MIN: # speed up game
asteroid_delay -= ASTEROID_DELAY_DELTA
else: # avoid negative times
asteroid_delay = ASTEROID_DELAY_MIN
level += 1
time_last_level_update = t1
# update asteroid position
if t1 - time_last_asteroid_update > asteroid_delay:
if asteroid_y < N_Y-1:
asteroid_y += 1
else:
asteroid_x = random.randint(0,N_X-1)
asteroid_y = 0
time_last_asteroid_update = t1
# update player (button presses)
if button_a.get_presses() and player_x > 0:
player_x -= 1
elif button_b.get_presses() and player_x < N_X-1:
player_x += 1
# collision check
if asteroid_y == N_Y-1 and asteroid_x == player_x:
game_over = True
# update display
display.clear()
if game_over:
display.show(IMAGE_GAME_OVER)
sleep(GAME_OVER_TIME) # wait before can reset
display.scroll("Level: " + str(level))
display.show(Image.ANGRY)
while True:
# reset game (not great because is copy paste from above)
if button_a.get_presses() or button_b.get_presses():
player_x = N_X // 2
asteroid_x = random.randint(0,N_X-1)
asteroid_y = 0
asteroid_delay = ASTEROID_DELAY_MAX
game_over = False
time_last_asteroid_update = time.ticks_ms()
time_last_level_update = time.ticks_ms()
break
else:
display.set_pixel(player_x,N_Y-1,PLAYER_BRIGHTNESS)
display.set_pixel(asteroid_x,asteroid_y,ASTEROID_BRIGHTNESS)
# short sleep
sleep(SLEEP_TIME_MS)
An der Version oben ist unschön, dass die Anfangsbedingungen des Spiels beim Game Over copy-pasted werden müssen. Die Version unten umgeht dieses Problem.
from microbit import *
import random
import time
# CONSTANT
N_X = 5 # nr pixels in x direction
N_Y = 5 # nr pixels in x direction
ASTEROID_DELAY_MAX = 500
ASTEROID_DELAY_MIN = 100
ASTEROID_DELAY_DELTA = 50
TIME_LEVEL_UPDATE = 2000
GAME_OVER_SLEEP = 2000
IMAGE_GAME_OVER = Image.ANGRY
ASTEROID_BRIGHTNESS = 5
PLAYER_BRIGHTNESS = 9
SLEEP_TIME_MS = 20
def game():
# VARIABLES
player_x = N_X // 2
asteroid_x = random.randint(0,N_X-1)
asteroid_y = 0
asteroid_delay = ASTEROID_DELAY_MAX
t0 = time.ticks_ms()
time_last_asteroid_update = t0
time_last_level_update = t0
level = 1
# GAME LOOP
while True:
# get current time
t1 = time.ticks_ms()
# update level
if t1 - time_last_level_update > TIME_LEVEL_UPDATE:
if asteroid_delay > ASTEROID_DELAY_MIN:
asteroid_delay -= ASTEROID_DELAY_DELTA
else:
asteroid_delay = ASTEROID_DELAY_MIN
level += 1
time_last_level_update = t1
# update asteroid position
if t1 - time_last_asteroid_update > asteroid_delay:
if asteroid_y < N_Y-1:
asteroid_y += 1
else:
asteroid_x = random.randint(0,N_X-1)
asteroid_y = 0
time_last_asteroid_update = t1
# update player (button presses)
if button_a.get_presses() and player_x > 0:
player_x -= 1
elif button_b.get_presses() and player_x < N_X-1:
player_x += 1
# collision check
if asteroid_y == N_Y-1 and asteroid_x == player_x:
return level
# update display
display.clear()
display.set_pixel(player_x,N_Y-1,PLAYER_BRIGHTNESS)
display.set_pixel(asteroid_x,asteroid_y,ASTEROID_BRIGHTNESS)
# short sleep
sleep(SLEEP_TIME_MS)
def game_over(level):
display.show(IMAGE_GAME_OVER)
sleep(GAME_OVER_SLEEP)
display.scroll("Level: " + str(level))
display.show(IMAGE_GAME_OVER)
while True: # restart
if button_a.get_presses() or button_b.get_presses():
return
while True:
level = game()
game_over(level)
++++
++++Lösung v05|
from microbit import *
import random
import time
# CONSTANT
N_X = 5 # nr pixels in x direction
N_Y = 5 # nr pixels in x direction
#ASTEROID_DELAY_MAX = 500
#ASTEROID_DELAY_MIN = 100
#ASTEROID_DELAY_DELTA = 50
#TIME_LEVEL_UPDATE = 2000
TIME_ASTEROID_UPDATE = 500
TIME_ASTEROID_SPAWN = 300
ASTEROID_SPAWN_PROBABILITY = 30 # in percent
ASTEROID_BRIGHTNESS = 5
PLAYER_BRIGHTNESS = 9
SLEEP_TIME_MS = 20
class Asteroid:
def __init__(self):
self.x = random.randint(0,N_X-1)
self.y = 0
self.delay = random.randint(300,1000)
self.brightness = ASTEROID_BRIGHTNESS
self.time_update = TIME_ASTEROID_UPDATE
self.time_last_update = time.ticks_ms()
def __del__(self):
print("asteroid destroyed")
def update(self):
t1 = time.ticks_ms()
if t1 - self.time_last_update > self.time_update:
self.y += 1
self.time_last_update = t1
def game():
# VARIABLES
player_x = N_X // 2
t0 = time.ticks_ms()
time_last_asteroid_update = t0
time_last_asteroid_spawn_slot = t0
level = 1
asteroids = [Asteroid()]
while True:
# get current time
t1 = time.ticks_ms()
# create new asteroids
if t1 - time_last_asteroid_spawn_slot > TIME_ASTEROID_SPAWN:
if random.randint(1,100) <= ASTEROID_SPAWN_PROBABILITY:
asteroids.append(Asteroid())
time_last_asteroid_spawn_slot = t1
# update asteroid positions
for asteroid in asteroids:
asteroid.update()
if asteroid.y >= N_Y:
asteroids.remove(asteroid)
# update player (button presses)
if button_a.get_presses() and player_x > 0:
player_x -= 1
elif button_b.get_presses() and player_x < N_X-1:
player_x += 1
# collision check
for asteroid in asteroids:
if asteroid.y == N_Y-1 and asteroid.x == player_x:
return level
# update display
display.clear()
display.set_pixel(player_x,N_Y-1,PLAYER_BRIGHTNESS)
for asteroid in asteroids:
display.set_pixel(asteroid.x,asteroid.y,asteroid.brightness)
# short sleep
sleep(SLEEP_TIME_MS)
def game_over(level):
display.show(Image.ANGRY)
sleep(2000)
display.scroll("Level: " + str(level))
display.show(Image.ANGRY)
while True:
if button_a.get_presses() or button_b.get_presses():
return
while True:
level = game()
game_over(level)
++++
++++Lösung v06|
from microbit import *
import random
import time
# CONSTANT
N_X = 5 # nr pixels in x direction
N_Y = 5 # nr pixels in x direction
TIME_ASTEROID_UPDATE_MIN = 300
TIME_ASTEROID_UPDATE_MAX = 2000
TIME_ASTEROID_SPAWN = 300
TIME_LEVEL = 1000
ASTEROID_SPAWN_PROBABILITY = 30 # in percent
ASTEROID_BRIGHTNESS = 5
PLAYER_BRIGHTNESS = 9
SLEEP_TIME_MS = 11
class Player:
def __init__(self):
self.x = N_X // 2
self.y = N_Y - 1
self.brightness = PLAYER_BRIGHTNESS
def update(self,move):
self.x += move
if self.x < 0:
self.x = 0
elif self.x >= N_X - 1:
self.x = N_X - 1
def display(self):
display.set_pixel(self.x,self.y,self.brightness)
class Asteroid:
def __init__(self):
self.x = random.randint(0,N_X-1)
self.y = 0
self.update_time = random.randint(TIME_ASTEROID_UPDATE_MIN,TIME_ASTEROID_UPDATE_MAX)
self.brightness = ASTEROID_BRIGHTNESS
self.time_last_update = time.ticks_ms()
def update(self):
t1 = time.ticks_ms()
if t1 - self.time_last_update > self.update_time:
self.y += 1
self.time_last_update = t1
def display(self):
display.set_pixel(self.x,self.y,self.brightness)
def game():
# VARIABLES
t0 = time.ticks_ms()
time_last_asteroid_spawn_slot = t0
time_level_last_update = t0
level = 1
player = Player()
asteroids = [Asteroid()]
while True:
# get current time
t1 = time.ticks_ms()
# level
if t1 - time_level_last_update > TIME_LEVEL:
level += 1
time_level_last_update = t1
# create new asteroids
if t1 - time_last_asteroid_spawn_slot > TIME_ASTEROID_SPAWN:
if random.randint(1,100) <= ASTEROID_SPAWN_PROBABILITY:
asteroids.append(Asteroid())
time_last_asteroid_spawn_slot = t1
# update asteroid positions
for asteroid in asteroids:
asteroid.update()
if asteroid.y >= N_Y:
asteroids.remove(asteroid)
# update player (button presses)
player_move = 0
if button_a.get_presses(): player_move = -1
elif button_b.get_presses(): player_move = 1
player.update(player_move)
# collision check
for asteroid in asteroids:
if asteroid.y == N_Y-1 and asteroid.x == player.x:
return level
# update display
display.clear()
for asteroid in asteroids: asteroid.display()
player.display()
# short sleep
sleep(SLEEP_TIME_MS)
def game_over(level):
display.show(Image.ANGRY)
sleep(2000)
display.scroll("Level: " + str(level))
display.show(Image.ANGRY)
while True:
if button_a.get_presses() or button_b.get_presses():
return
while True:
level = game()
game_over(level)
++++
++++Lösung v07|
from microbit import *
import random
import time
import os
PLATFORM = print(os.uname()[4]) #e.g. micro:bit with nRF52833, x86_64
# CONSTANTS
N_X = 5 # nr pixels in x direction
N_Y = 5 # nr pixels in x direction
TIME_ASTEROID_UPDATE_MIN = 300
TIME_ASTEROID_UPDATE_MAX = 2000
TIME_ASTEROID_SPAWN = 300
TIME_LEVEL = 1000
ASTEROID_SPAWN_PROBABILITY = 30 # in percent
GAME_OVER_TIME_DELAY = 2000
ASTEROID_BRIGHTNESS = 5
PLAYER_BRIGHTNESS = 9
SLEEP_TIME_MS = 11
# CLASSES
class Sprite:
def __init__(self,_x,_y,_brightness):
self.x = _x
self.y = _y
self.brightness = _brightness
def display(self):
display.set_pixel(self.x,self.y,self.brightness)
class Player(Sprite):
def __init__(self,_x,_y,_brightness):
super().__init__(_x,_y,_brightness)
def update(self,move):
self.x += move
if self.x < 0:
self.x = 0
elif self.x >= N_X - 1:
self.x = N_X - 1
class Asteroid(Sprite):
def __init__(self,_x,_y,_brightness,_update_time):
super().__init__(_x,_y,_brightness)
self.update_time = _update_time
self.time_last_update = time.ticks_ms()
def update(self):
t1 = time.ticks_ms()
if t1 - self.time_last_update > self.update_time:
self.y += 1
self.time_last_update = t1
# FUNCTIONS
def game():
# VARIABLES
t0 = time.ticks_ms()
time_last_asteroid_spawn_slot = t0
time_level_last_update = t0
level = 1
player = Player(N_X//2,N_X-1,PLAYER_BRIGHTNESS)
asteroids = []
while True:
# get current time
t1 = time.ticks_ms()
# level
if t1 - time_level_last_update > TIME_LEVEL:
level += 1
time_level_last_update = t1
# create new asteroids
if t1 - time_last_asteroid_spawn_slot > TIME_ASTEROID_SPAWN:
if random.randint(1,100) <= ASTEROID_SPAWN_PROBABILITY:
rand_ast_time = random.randint(TIME_ASTEROID_UPDATE_MIN,TIME_ASTEROID_UPDATE_MAX)
asteroids.append(Asteroid(random.randint(0,N_X-1),0,ASTEROID_BRIGHTNESS,rand_ast_time))
time_last_asteroid_spawn_slot = t1
# update asteroid positions
for asteroid in asteroids:
asteroid.update()
if asteroid.y >= N_Y: asteroids.remove(asteroid)
# update player (button presses)
player_move = 0
if button_a.get_presses(): player_move = -1
elif button_b.get_presses(): player_move = 1
player.update(player_move)
# collision check
for asteroid in asteroids:
if asteroid.y == N_Y-1 and asteroid.x == player.x: return level
# update display
display.clear()
for asteroid in asteroids: asteroid.display()
player.display()
# short sleep
sleep(SLEEP_TIME_MS)
def game_over(level):
display.show(Image.ANGRY)
sleep(GAME_OVER_TIME_DELAY)
display.scroll("Level: " + str(level))
display.show(Image.ANGRY)
while True:
if button_a.get_presses() or button_b.get_presses():
return
# MAIN LOOP
while True:
level = game()
game_over(level)
++++
++++Lösung v07b|
from microbit import *
import random
import time
import os
PLATFORM = print(os.uname()[4]) #e.g. micro:bit with nRF52833, x86_64
# CONSTANTS
N_X = 5 # nr pixels in x direction
N_Y = 5 # nr pixels in x direction
TIME_ASTEROID_UPDATE_MIN = 300
TIME_ASTEROID_UPDATE_MAX = 2000
TIME_ASTEROID_SPAWN = 300
TIME_LEVEL = 1000
ASTEROID_SPAWN_PROBABILITY = 30 # in percent
GAME_OVER_TIME_DELAY = 2000
IMAGE_GAME_OVER = Image.ANGRY
ASTEROID_BRIGHTNESS = 5
PLAYER_BRIGHTNESS = 9
SLEEP_TIME_MS = 11
# CLASSES
class Sprite:
def __init__(self,_x,_y,_brightness):
self.x = _x
self.y = _y
self.brightness = _brightness
def display(self):
display.set_pixel(self.x,self.y,self.brightness)
class Player(Sprite):
def __init__(self,_x,_y,_brightness):
super().__init__(_x,_y,_brightness)
def update(self,move):
self.x += move
if self.x < 0:
self.x = 0
elif self.x >= N_X - 1:
self.x = N_X - 1
class Asteroid(Sprite):
def __init__(self,_x,_y,_brightness,_update_time):
super().__init__(_x,_y,_brightness)
self.update_time = _update_time
self.time_last_update = time.ticks_ms()
def update(self):
t1 = time.ticks_ms()
if t1 - self.time_last_update > self.update_time:
self.y += 1
self.time_last_update = t1
class Game:
def __init__(self):
self.new_game()
def new_game(self):
self.asteroids = []
self.player = Player(N_X//2,N_X-1,PLAYER_BRIGHTNESS)
self.t0 = time.ticks_ms()
self.time_last_asteroid_spawn_slot = self.t0
self.time_level_last_update = self.t0
self.level = 1
def add_asteroid(self):
rand_ast_time = random.randint(TIME_ASTEROID_UPDATE_MIN,TIME_ASTEROID_UPDATE_MAX)
self.asteroids.append(Asteroid(random.randint(0,N_X-1),0,ASTEROID_BRIGHTNESS,rand_ast_time))
def game_over(self):
display.show(IMAGE_GAME_OVER)
sleep(GAME_OVER_TIME_DELAY)
display.scroll("Level: " + str(self.level))
display.show(IMAGE_GAME_OVER)
while True:
if button_a.get_presses() or button_b.get_presses():
return
def run(self):
self.new_game()
print(self.asteroids)
while True:
# get current time
t1 = time.ticks_ms()
# level
if t1 - self.time_level_last_update > TIME_LEVEL:
self.level += 1
self.time_level_last_update = t1
# create new asteroids
if t1 - self.time_last_asteroid_spawn_slot > TIME_ASTEROID_SPAWN:
if random.randint(1,100) <= ASTEROID_SPAWN_PROBABILITY:
self.add_asteroid()
self.time_last_asteroid_spawn_slot = t1
# update asteroid positions
for asteroid in self.asteroids:
asteroid.update()
if asteroid.y >= N_Y: self.asteroids.remove(asteroid)
# update player (button presses)
player_move = 0
if button_a.get_presses(): player_move = -1
elif button_b.get_presses(): player_move = 1
self.player.update(player_move)
# collision check
for asteroid in self.asteroids:
if asteroid.y == N_Y-1 and asteroid.x == self.player.x: return
# update display
display.clear()
for asteroid in self.asteroids: asteroid.display()
self.player.display()
# short sleep
sleep(SLEEP_TIME_MS)
# MAIN LOOP
game = Game()
while True:
game.run()
game.game_over()
++++
++++Demo für OOP|
from microbit import *
import random
import time
import os
class Asteroid:
def __init__(self,_x,_y,_delay):
self.x = _x
self.y = _y
self.delay = _delay
self.active = True
self.time_last_update = time.ticks_ms()
def update(self):
t = time.ticks_ms()
if t - self.time_last_update > self.delay:
self.y += 1
self.time_last_update = t
if self.y > 4:
self.active = False
def display(self):
display.set_pixel(self.x,self.y,9)
asteroids = []
TIME_SPAWN = 400
SPAWN_PROBABILITY = 60
t_last_spawn = time.ticks_ms()
while True:
t = time.ticks_ms()
# update asteroids
for asteroid in asteroids:
asteroid.update()
for asteroid in asteroids:
if not asteroid.active:
asteroids.remove(asteroid)
# spawn
if t - t_last_spawn > TIME_SPAWN:
r = random.randint(1,100)
if r <= SPAWN_PROBABILITY:
asteroids.append(Asteroid(random.randint(0,4),0,random.randint(300,1000)))
t_last_spawn = t
# show
display.clear()
for asteroid in asteroids:
asteroid.display()
sleep(20)
++++