Sprint 6 · De blauwdruk  ·  US3: Van dict naar object — de Enemy

Als developer, wil ik elke vijand als één eigen object bijhouden in plaats van in losse, parallelle lijsten, zodat de x, y en snelheid van één vijand nooit meer uit elkaar kunnen lopen doordat ik een van de lijsten vergeet bij te werken.

Taak 3: Twintig objecten, twintig losse toestanden

Een blauwdruk maakt zoveel objecten als je wilt, en elk houdt zijn eigen status bij. Een hele vijandengolf wordt een lijst van objecten. Vijand 5 als neergeschoten markeren doet niets met vijand 6 — precies wat parallelle lijsten niet konden garanderen. En met de meegeleverde game-engine zie je die golf voor het eerst écht op je scherm vallen.

Een lijst van objecten, elk met zijn eigen status

Je maakt meerdere objecten aan en stopt ze in een lijst. Elk object houdt zijn eigen status bij:

measurements = [
    Measurement("height", 120),
    Measurement("temp", 34),
    Measurement("battery", 87),
]

measurements[0].logged = True      # alleen de eerste meting

print(measurements[0].logged)      # True
print(measurements[1].logged)      # False — ongemoeid

Je verandert de status van precies één object. De rest blijft zoals hij was. Er is geen tweede lijst die je ook moet bijwerken — de status leeft in het object zelf.

Dit is de oplossing voor taak 1.
Vroeger moesten enemy_x, enemy_speed en alive per index synchroon blijven. Nu draagt elk object alles zelf, waardoor verschuiven niet meer mogelijk is. Invoegen, verwijderen of herordenen: de status gaat gewoon mee.

Zie de losse toestanden

Hieronder staat een route van vijf objecten uit dezelfde blauwdruk. Klik op een kaart om die stap als uitgevoerd te markeren. Let op: alleen die ene kaart verandert.

Klik op een kaart om step.executed = True te zetten.
>>> Oefenomgeving: Python REPL Simulator

Een lijst measurements ligt klaar met drie Measurement-objecten (elk met logged = False). Bewijs dat ze onafhankelijk zijn.

  1. measurements[0].logged — startstatus.
  2. measurements[0].logged = True — markeer de eerste.
  3. measurements[0].logged en measurements[1].logged — vergelijk.
  4. measurements[1].kind — welke meting is dat?
  5. len(measurements) — hoeveel objecten zijn er?
Python 3.11 REPL Simulator · measurements = [Measurement("height",120), Measurement("temp",34), Measurement("battery",87)]
>>> 
🧩

Jouw opdracht: een zwerm Enemy-objecten op het scherm

Gebruik je Enemy-class uit taak 2 en bouw er een hele golf mee — en zie hem vallen. Zet game_engine.py naast je script (en installeer eenmalig pygame, zie hieronder).

  1. Maak een lijst met minstens tien Enemy-objecten met een willekeurige x (gebruik random.randint) en verschillende snelheden.
  2. Maak één eenvoudige Player (een object met x, y, width, height, color en speed).
  3. Roep run_game(player, enemies) aan uit game_engine en kijk hoe de zwerm valt en bovenaan herspawnt. Bestuur de speler met de pijltjestoetsen.
  4. Zet vóór de aanroep een paar vijanden op alive = False en tel met een for-loop hoeveel er nog "leven"; print dat aantal.
  5. Toon in die telling dat het aanpassen van één vijand de rest niet raakte.

Let op: y telt naar beneden, dus vijanden vallen met een positieve snelheid. Kleuren zijn RGB-tuples zoals (255, 80, 80). Sluit het venster met de vensterknop.

Nodig: download game_engine.py en zet hem in dezelfde map als je script (installeer eenmalig pygame: pip install pygame). Kun je niet downloaden? Klap de code uit en kopieer hem naar game_engine.py.

Toon de code van 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)

Acceptatiecriteria

  • Er is een lijst met minstens tien onafhankelijke Enemy-objecten.
  • De engine toont de zwerm vallend op het scherm; de speler is bestuurbaar.
  • Een for-loop telt hoeveel vijanden nog alive zijn.
  • Eén vijand op alive = False zetten laat de rest ongemoeid.