Je bouwt dit spel met zijn tweeën, elk aan een eigen kant met een eigen contract. Het
speler-team bouwt Player en Bullet; het
wereld-team bouwt Enemy en Game. Jullie werken grotendeels
onafhankelijk — en komen samen bij de integratiestap, waar de twee kanten via de meegeleverde
game-engine voor het eerst echt op elkaar moeten passen. Aan het eind zie je je eigen arcade-spel
draaien: een speler die vijanden ontwijkt en neerschiet.
⏱ ±4 uur samen (Opdracht t/m Opleveren, niet per persoon; Extra niet inbegrepen)
Jullie bouwen samen een arcade dodge/shooter met drie classes, verdeeld over twee kanten. Een
Player beweegt onderin en schiet; Enemy-objecten vallen van boven;
Bullet-objecten vliegen omhoog; en één Game-object bundelt de hele
spelstaat. De meegeleverde game_engine.py tekent alles en draait de game-loop — jullie
schrijven alleen de classes.
| Rol | Bouwt | Kernvraag |
|---|---|---|
| Speler-team | Player + Bullet + spawn_bullet() |
Hoe lever je precies de attributen die de engine tekent, en hoe maakt de factory een kogel op de juiste plek? |
| Wereld-team | Enemy + Game |
Hoe bundel je speler, vijanden en score in één object dat je aan de engine geeft? |
| Samen | het __main__-integratieblok |
Passen jullie twee kanten via het engine-contract op elkaar? |
Verdeel deze twee rollen onderling. Installeer eenmalig pygame in je venv uit sprint 1
(pip install pygame) en zet game_engine.py naast je script.
De engine krijg je kant-en-klaar mee — die pas je niet aan. Download game_engine.py en zet hem in dezelfde map als je script. Kun je niet downloaden? Klap dan de code hieronder uit en kopieer hem naar een bestand game_engine.py.
"""
game_engine.py - Arcade-engine voor Sprint 6 (v1, ATTRIBUUT-gebaseerd). NIET AANPASSEN.
Jij schrijft de classes; deze engine opent het venster, draait de game-loop,
leest de ATTRIBUTEN van jouw objecten en tekent ze als rechthoeken. Jouw classes
zijn dus pure data; de engine verzorgt alle beweging en het tekenwerk.
--------------------------------------------------------------------------
HET CONTRACT - wat de engine van jouw objecten verwacht:
player - een object met:
x, y (int) positie van de linkerbovenhoek
width (int) breedte in pixels
height (int) hoogte in pixels
color (tuple) RGB, bijvoorbeeld (0, 200, 255)
speed (int) pixels die de speler per toetsstap opschuift
health (int) OPTIONEEL - levens; bij 0 stopt het spel
enemies - een LIJST objecten, elk met:
x, y, width, height, color (zoals hierboven)
speed (int) pixels die deze enemy per frame ZAKT
world - OPTIONEEL (US4 + project) - een object met:
player, enemies, bullets, score, width, height
bullet_factory(player) -> bullet - OPTIONEEL, een gewone functie die een
nieuw bullet-object teruggeeft met: x, y, width, height, color, speed
(speed = pixels die de kogel per frame OMHOOG gaat)
y telt naar BENEDEN: y groter = lager op het scherm. Vijanden vallen dus met
een POSITIEVE speed; kogels gaan omhoog. Kleuren zijn RGB-tuples, geen strings.
--------------------------------------------------------------------------
"""
import random
import pygame
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 run_game(player, enemies, width=480, height=640, fps=60,
world=None, bullet_factory=None):
pygame.init()
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Sprint 6 - Arcade Dodge")
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 28)
bullets = getattr(world, "bullets", []) if world is not None else []
running = True
while running:
# 1) Vensterknop / afsluiten en schieten
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
if bullet_factory is not None:
bullets.append(bullet_factory(player))
# 2) Toetsen lezen -> speler-attribuut aanpassen
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
player.x -= player.speed
if keys[pygame.K_RIGHT]:
player.x += player.speed
player.x = max(0, min(player.x, width - player.width))
# 3) Vijanden laten vallen; onderaan -> herspawnen bovenaan
for enemy in enemies:
enemy.y += enemy.speed
if enemy.y > height:
enemy.y = -enemy.height
enemy.x = random.randint(0, width - enemy.width)
if world is not None:
world.score += 1
# 4) Kogels omhoog; buiten beeld -> weg
for bullet in bullets:
bullet.y -= bullet.speed
bullets[:] = [b for b in bullets if b.y + b.height > 0]
# 5) Botsing - kogel raakt vijand
for bullet in bullets:
for enemy in enemies:
if _overlap(bullet, enemy):
enemy.y = -enemy.height
enemy.x = random.randint(0, width - enemy.width)
bullet.y = -100
if world is not None:
world.score += 5
# 6) Botsing - vijand raakt speler
for enemy in enemies:
if _overlap(player, enemy):
enemy.y = -enemy.height
enemy.x = random.randint(0, width - enemy.width)
if hasattr(player, "health"):
player.health -= 1
if player.health <= 0:
running = False
# 7) Tekenen (alles als rechthoek, op basis van attributen)
screen.fill((15, 15, 22))
pygame.draw.rect(screen, player.color,
(player.x, player.y, player.width, player.height))
for enemy in enemies:
pygame.draw.rect(screen, enemy.color,
(enemy.x, enemy.y, enemy.width, enemy.height))
for bullet in bullets:
pygame.draw.rect(screen, bullet.color,
(bullet.x, bullet.y, bullet.width, bullet.height))
score = getattr(world, "score", 0) if world is not None else 0
hp = getattr(player, "health", None)
text = f"Score: {score}" + (f" HP: {hp}" if hp is not None else "")
screen.blit(font.render(text, True, (240, 240, 240)), (10, 10))
pygame.display.flip()
clock.tick(fps)
pygame.quit()
def run_world(world, bullet_factory=None):
"""Voor US4 + project: geef je hele Game/World-object mee."""
run_game(world.player, world.enemies,
width=world.width, height=world.height,
world=world, bullet_factory=bullet_factory)
for doorheen kan.game.bullets), zodat we parallel kunnen bouwen en de code aan het
eind gewoon samenpast.Player te lezen — en hoeft het speler-team Game niet te
kennen. Alleen de namen en retourwaarden uit die tabel tellen.
spawn_bullet is een gewone functie — precies wat je uit sprint 5 kent.
Naast de attributen die de engine tekent, laat je de constructor ook attributen zélf
berekenen uit de andere waarden. En de Game-constructor doet echt werk:
hij bouwt de vijandengolf op uit een data-lijst WAVE — een lijst met
(x, speed) per vijand — en berekent daaruit overzichtswaarden.
| Class | Eigenaar | Attributen uit parameters / vast | Berekend in de constructor |
|---|---|---|---|
Player |
Speler-team | x, y, width, height, color, speed, health |
center_x, start_x/start_y (beginpositie onthouden) |
Bullet |
Speler-team | x, y, width, height, color, speed |
(+ losse functie spawn_bullet(player) die een Bullet teruggeeft) |
Enemy |
Wereld-team | x, y, width, height, color, speed |
points (uit speed), valid (snelheid speelbaar?) |
Game |
Wereld-team | player, bullets, score, width, height |
enemies (uit WAVE met een for-lus, gestaffeld in y), total_enemies, hardest_speed |
Maak vóór jullie beginnen sámen een bestand project_plan.md. Beschrijf daarin:
Dit bestand lever je samen in met je code.