Je bouwt de arcade dodge/shooter uit sprint 6 om: het gedrag dat vroeger in de engine zat — bewegen,
vallen, schieten, schade, score — verhuist nu naar methodes in je eigen classes. De
nieuwe engine (v2) roept die methodes aan. Het speler-team geeft Player en Bullet
hun methodes; het wereld-team doet dat voor Enemy en Game. Nieuw in het spel:
een startscherm met uitleg, een schietpauze, levels met snellere vijanden en een game-over-scherm met
een rapport dat jouw Game maakt.
⏱ ±4 uur samen (Opdracht t/m Opleveren; Extra niet inbegrepen)
Jullie bouwen samen je sprint-6-game om tot een methode-gebaseerde versie. In
sprint 6 deed de engine alle actie op basis van attributen; nu bepalen jouw classes zelf hoe ze
bewegen, schieten, schade oplopen, sneller worden en herspawnen — via methodes. De nieuwe
game_engine.py (v2) roept die methodes aan en laat de uitkomsten zien.
player.x -= player.speed wordt Player.move(...);
enemy.y += enemy.speed wordt Enemy.update(); de kogel-fabriek wordt
Player.shoot(); de score wordt Game.add_score(n). Start je spel met
run_world(game) — de speler maakt zijn kogels nu zelf.
Bij het starten verschijnt eerst een startscherm met deze uitleg. Het spel begint pas als je op SPATIE drukt. Lees het scherm dus eerst, anders ben je zó game over.
| Wat | Hoe |
|---|---|
| Bewegen | Pijltje links en pijltje rechts. Je blijft binnen het scherm. |
| Schieten | Houd de spatiebalk ingedrukt. Na elk schot moet je even wachten: bovenin staat Kanon: laden… of Kanon: klaar. |
| Doel | Ontwijk de rode blokken, of schiet ze neer. |
| Punten | +1 voor elk blok dat je mist (het valt onderaan weg). +5 voor elk blok dat je raakt met een kogel. |
| Levens | Je begint met 5 levens (HP). Raakt een blok jou, dan verlies je er één. Bij 0 is het game over. |
| Levels | Elke 20 punten gaat het level omhoog. Dan verschijnt LEVEL n in beeld en worden alle blokken sneller. |
| Game over | Je ziet een scherm met jouw rapport: score, level, aantal schoten, treffers en nauwkeurigheid. Het scherm blijft staan tot je op SPATIE, ENTER of ESC drukt (of het venster sluit). |
AttributeError? Dan mist er een methode of klopt een naam niet — kijk dan in het
contract hieronder.
| Je ziet in het spel | Dat komt van jouw methode |
|---|---|
| Kanon: klaar / laden…, en je kunt niet sneller schieten dan de pauze | Player.can_shoot(), Player.update(), Player.shoot() |
| LEVEL 2, LEVEL 3… en snellere vijanden | Game.level() en Enemy.speed_up() |
| Het game-over-scherm met score, schoten, treffers en percentage | Game.record_shot(), Game.record_hit(), Game.summary() |
| Het startscherm met uitleg | geen methode: dat doet de engine |
| Rol | Bouwt | Kernvraag |
|---|---|---|
| Speler-team | bullet.py + player.py: Bullet- en Player-methodes |
Hoe beweegt, schiet (met pauze) en incasseert de speler via methodes die de engine aanroept? |
| Wereld-team | enemy.py + game.py: Enemy-methodes en Game met score, level en rapport |
Hoe laat elke vijand zichzelf vallen, melden, herspawnen en versnellen, en houdt Game score, level en statistieken bij? |
| Samen | settings.py, main.py, test_game.py + overstap naar engine v2 |
Passen jullie methodes op het engine-contract? |
Elke class krijgt een eigen bestand. Zo kan ieder team aan eigen bestanden werken zonder dat jullie elkaars
code overschrijven. Alle bestanden staan in dezelfde map, samen met game_engine.py.
| Bestand | Van wie | Bevat |
|---|---|---|
settings.py | Samen | WIDTH, HEIGHT en de WAVE-data |
bullet.py | Speler-team | de class Bullet |
player.py | Speler-team | de class Player |
enemy.py | Wereld-team | de class Enemy |
game.py | Wereld-team | de class Game |
main.py | Samen | start het spel met run_world(game) |
test_game.py | Samen | de assert-tests van beide teams |
Een bestand gebruikt een class uit een ander bestand met een import, bijvoorbeeld
from bullet import Bullet bovenaan player.py. Je kent dit uit sprint 5 en 6 en deed het al in een aantal opdrachten van deze sprint.
Start het spel met python main.py en de tests met python test_game.py.
Voor sprint 7 gebruik je de nieuwe engine die methodes aanroept.
Download game_engine.py (v2) en zet hem in
dezelfde map als main.py (vervang de v1 uit sprint 6). Kun je niet downloaden? Klap de code hieronder
uit en kopieer hem naar een bestand game_engine.py.
"""
game_engine.py - Arcade-engine voor Sprint 7 (v2, METHODE-gebaseerd). NIET AANPASSEN.
In sprint 6 muteerde deze engine ZELF de attributen van je objecten (player.x -= ...).
Nu roept de engine METHODES aan op jouw objecten. Jouw classes bepalen zelf hoe ze
bewegen, schieten, schade oplopen, sneller worden en hun eigen rapport maken. De engine
leest alleen nog attributen om te TEKENEN (x, y, width, height, color) en om botsingen
te bepalen. De engine toont ook een startscherm met uitleg en een game-over-scherm.
--------------------------------------------------------------------------
HET CONTRACT - welke METHODES de engine aanroept:
game.player - naast de teken-attributen x, y, width, height, color, health:
move(direction, width) direction is -1 (links), 0, of +1 (rechts); de speler
verschuift zichzelf EN houdt zich binnen [0, width-breedte]
update() wordt elk frame aangeroepen (bijv. om de schietpauze te laten aflopen)
can_shoot() -> bool True als de speler nu mag schieten
shoot() -> bullet geeft een NIEUW bullet-object terug, boven de speler,
en start de schietpauze
take_damage(n) verlaagt de eigen health met n
is_dead() -> bool True als de speler geen leven meer heeft
game.enemies - een LIJST; elke enemy heeft de teken-attributen EN:
update() laat zichzelf een stap vallen
off_screen(height) -> bool True als de vijand onder het scherm is
respawn(width) zet zichzelf weer bovenaan, op een nieuwe x
speed_up() wordt bij elk nieuw level aangeroepen: de vijand wordt sneller
game.bullets - een LIJST; elke bullet heeft de teken-attributen EN:
update() beweegt zichzelf omhoog
off_screen() -> bool True als de kogel boven het scherm verdwenen is
game - het spelobject met:
player, enemies, bullets, score, width, height (attributen)
add_score(n) telt n bij de score op
level() -> int het huidige level (begint op 1)
record_shot() de engine meldt: er is een kogel afgevuurd
record_hit() de engine meldt: een kogel heeft een vijand geraakt
summary() -> str tekst voor het game-over-scherm (mag uit meerdere regels bestaan)
y telt naar BENEDEN. Kleuren zijn RGB-tuples. De engine tekent, leest toetsen,
detecteert botsingen en beheert de kogellijst; jouw METHODES doen de rest.
--------------------------------------------------------------------------
"""
import pygame
BACKGROUND = (15, 15, 22)
WHITE = (240, 240, 240)
GRAY = (150, 150, 160)
CYAN = (0, 200, 255)
YELLOW = (255, 220, 0)
RED = (255, 80, 80)
GREEN = (80, 220, 120)
def _overlap(a, b):
"""True als twee rechthoeken (objecten met x/y/width/height) elkaar raken."""
return (a.x < b.x + b.width and a.x + a.width > b.x and
a.y < b.y + b.height and a.y + a.height > b.y)
def _text(screen, font, text, color, y):
"""Tekent een regel horizontaal gecentreerd op hoogte y."""
image = font.render(text, True, color)
screen.blit(image, ((screen.get_width() - image.get_width()) // 2, y))
def _wait_screen(screen, clock, fonts, title, title_color, lines, footer, keys, ignore_frames=0):
"""Toont een scherm en wacht op een toets uit keys. Toetsen in de eerste ignore_frames
frames tellen niet mee, zodat een speler die nog toetsen vasthoudt het scherm niet per
ongeluk wegdrukt. Geeft False terug als het venster wordt gesloten."""
big, font, small = fonts
pygame.event.clear()
frames = 0
while True:
frames += 1
for event in pygame.event.get():
if event.type == pygame.QUIT:
return False
if event.type == pygame.KEYDOWN and frames > ignore_frames and event.key in keys:
return True
screen.fill(BACKGROUND)
_text(screen, big, title, title_color, 70)
y = 160
for line in lines:
_text(screen, font, line, WHITE, y)
y += 30
_text(screen, small, footer, YELLOW, screen.get_height() - 70)
pygame.display.flip()
clock.tick(30)
START_LINES = [
"Pijltjes links / rechts: bewegen",
"Spatiebalk (ingedrukt houden): schieten",
"",
"Rode blokken vallen naar beneden.",
"Ontwijk ze: +1 punt voor elk blok dat je mist.",
"Schiet ze neer: +5 punten.",
"Raakt een blok jou? Dan verlies je een leven.",
"",
"Elke 20 punten ga je een level omhoog.",
"Hoger level = snellere vijanden.",
]
def run_world(game):
"""Start het spel. Geef je hele Game-object mee."""
pygame.init()
screen = pygame.display.set_mode((game.width, game.height))
pygame.display.set_caption("Sprint 7 - Arcade Dodge (methode-gebaseerd)")
clock = pygame.time.Clock()
fonts = (pygame.font.SysFont(None, 56), pygame.font.SysFont(None, 26), pygame.font.SysFont(None, 22))
big, font, small = fonts
# Startscherm: uitleg, en wachten tot de speler klaar is.
if not _wait_screen(screen, clock, fonts, "ARCADE DODGE", CYAN, START_LINES,
"Druk op SPATIE om te starten", {pygame.K_SPACE, pygame.K_RETURN}):
pygame.quit()
return
last_level = game.level()
banner = 0 # aantal frames dat de LEVEL-melding nog zichtbaar is
window_closed = False
running = True
while running:
# 1) Vensterknop
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
window_closed = True
keys = pygame.key.get_pressed()
# 2) De speler ververst zichzelf; ingedrukte spatie -> de SPELER maakt zelf een kogel
game.player.update()
if keys[pygame.K_SPACE] and game.player.can_shoot():
game.bullets.append(game.player.shoot())
game.record_shot()
# 3) Toetsen -> direction; de SPELER beweegt en klemt zichzelf
direction = 0
if keys[pygame.K_LEFT]:
direction -= 1
if keys[pygame.K_RIGHT]:
direction += 1
game.player.move(direction, game.width)
# 4) Elke vijand valt zichzelf; onderaan -> zichzelf herspawnen
for enemy in game.enemies:
enemy.update()
if enemy.off_screen(game.height):
enemy.respawn(game.width)
game.add_score(1)
# 5) Elke kogel beweegt zichzelf; buiten beeld -> uit de lijst
for bullet in game.bullets:
bullet.update()
game.bullets[:] = [b for b in game.bullets if not b.off_screen()]
# 6) Botsing kogel x vijand (engine detecteert; objecten reageren)
for bullet in game.bullets:
for enemy in game.enemies:
if _overlap(bullet, enemy):
enemy.respawn(game.width)
bullet.y = -100 # markeer voor opruimen (volgende frame)
game.add_score(5)
game.record_hit()
# 7) Botsing vijand x speler
for enemy in game.enemies:
if _overlap(game.player, enemy):
enemy.respawn(game.width)
game.player.take_damage(1)
if game.player.is_dead():
running = False
# 8) Nieuw level? Dan worden alle vijanden sneller.
level = game.level()
if level > last_level:
last_level = level
banner = 90
for enemy in game.enemies:
enemy.speed_up()
# 9) Tekenen op basis van attributen (net als in sprint 6)
screen.fill(BACKGROUND)
p = game.player
pygame.draw.rect(screen, p.color, (p.x, p.y, p.width, p.height))
for enemy in game.enemies:
pygame.draw.rect(screen, enemy.color,
(enemy.x, enemy.y, enemy.width, enemy.height))
for bullet in game.bullets:
pygame.draw.rect(screen, bullet.color,
(bullet.x, bullet.y, bullet.width, bullet.height))
hud = f"Score: {game.score} Level: {level} HP: {game.player.health}"
screen.blit(font.render(hud, True, WHITE), (10, 10))
ready = game.player.can_shoot()
screen.blit(small.render("Kanon: klaar" if ready else "Kanon: laden...", True,
GREEN if ready else GRAY), (10, 38))
if banner > 0:
_text(screen, big, f"LEVEL {level}", YELLOW, game.height // 2 - 30)
banner -= 1
_text(screen, small, "Pijltjes: bewegen Spatie: schieten", GRAY, game.height - 18)
pygame.display.flip()
clock.tick(60)
# Game-over-scherm met het rapport van jouw Game-object.
if not window_closed:
# SPATIE telt hier niet mee: die is al "schieten" en wordt vaak nog ingedrukt.
_wait_screen(screen, clock, fonts, "GAME OVER", RED, str(game.summary()).splitlines(),
"Druk op ENTER of ESC om af te sluiten",
{pygame.K_RETURN, pygame.K_ESCAPE}, ignore_frames=45)
pygame.quit()
move(direction, width), zodat ik zelf binnen het scherm blijf.shoot(), met een schietpauze die can_shoot() bewaakt, zodat mijn speler zelf beslist wanneer hij mag schieten.take_damage(n) en met is_dead() game-over aangeeft, zodat de engine niet zelf aan mijn health zit.update(), zodat de beweging bij de vijand hoort.off_screen(height) zelf meldt of hij eruit is, zodat de engine die beslissing niet hoeft te kennen.respawn(width) en sneller wordt via speed_up(), zodat dit gedrag van de vijand is.Game-object score, level en statistieken bijhoudt via add_score(n), level(), record_shot() en record_hit(), zodat er geen zwevende variabelen meer zijn.summary(), zodat ik weet hoe ik heb gespeeld.Maak vóór jullie beginnen sámen een bestand project_plan.md. Beschrijf: wie bouwt welke
methodes (en in welk bestand); wanneer leggen jullie het contract v2 vast en wanneer integreren jullie; welke engine-regel
uit sprint 6 wordt welke methode. Dit lever je samen in met je code.