Dojo Defender

Dojo Defender is onze huisgame: een top-down survival shooter die we samen bouwen over meerdere sessies.

Voor wie?

Dojo Defender is voor ninja’s die regelmatig langskomen. De game groeit over 7 sessies mee met de gewone lessen. Kom je maar af en toe? Geen probleem: doe de mini-game van die sessie. Die is altijd compleet op zich.

Reken niet op een complete Dojo Defender als je veel sessies mist. Maar het sjabloon en alle snapshots staan op deze site, dus thuis verder bouwen kan altijd.

Hoe doe ik mee?

  • Vanaf les 2 krijg je aan het einde van elke les een Dojo Defender snapshot: een ZIP-bestand dat je kan downloaden van de lespagina.
  • Pak die uit, open in Thonny, en volgende les begin je daarmee verder.
  • Mis je een les? Download de snapshot van die les, of spring een les over en pak het verderop op.

Mag ik er mijn eigen ding van maken?

Ja! Verander de naam, de grafische stijl, het verhaal. Dojo Defender is een sjabloon, geen verplichting.

1 juli 2026, 22:43

Subsecties van Dojo Defender

Dojo Defender: Sessie 1

Je ruimteschip is geland in de donkere ruimte. Tijd om het tot leven te brengen. In deze sessie voeg je een bestuurbaar schip toe en laat je asteroïden naar beneden vallen. Ontwijk ze of verlies een leven. Hoe lang overleef jij?

Wat je vandaag leert

  • Lijsten gebruiken om meerdere asteroïden bij te houden
  • Objecten spawnen en verwijderen
  • Botsingen detecteren met colliderect
  • Levens bijhouden en een explosie-effect tonen
  • Game-over afdwingen als je levens op zijn

Stap 0: Installeren

  1. Open Thonny.
  2. Download de Dojo Defender starter, pak de ZIP uit en open main.py.
  3. Klik op Run. Je ziet een donkerblauw scherm met je ruimteschip onderaan. Je kan het al bewegen met de pijltjestoetsen.
  4. Problemen? Kijk bij Thonny instellen.

Stap 1: Laat asteroïden spawnen

Een game zonder gevaar is saai. Voeg een lijst toe waarin je alle asteroïden bijhoudt, en een functie die nieuwe asteroïden aanmaakt:

# bovenaan, na WIDTH en HEIGHT:
asteroids = []

def spawn_asteroid():
    rock = Actor('asteroid_big')
    rock.x = random.randint(30, WIDTH - 30)
    rock.y = -30
    rock.speed = random.uniform(3, 5)
    asteroids.append(rock)

Je hebt ook import random nodig bovenaan het bestand. Voeg die regel toe.

Roep de functie aan in update() met een teller zodat niet élke frame een asteroïde verschijnt:

# bovenaan, buiten de functies:
timer = 0

def update():
    global timer
    timer += 1
    if timer % 60 == 0:   # elke seconde (60 frames)
        spawn_asteroid()

Zet dit vóór de bewegingscode van het schip.

Stap 2: ✅ Basic: asteroïden laten vallen en tekenen

Asteroïden moeten naar beneden vallen en verdwijnen als ze het scherm uit zijn:

def update():
    global timer
    timer += 1
    if timer % 60 == 0:
        spawn_asteroid()

    for rock in asteroids[:]:      # [:] = kopie zodat je veilig kunt verwijderen
        rock.y += rock.speed
        if rock.y > HEIGHT + 30:
            asteroids.remove(rock)

Vergeet niet om de asteroïden te tekenen in draw():

def draw():
    screen.fill((10, 10, 30))
    for rock in asteroids:
        rock.draw()
    ship.draw()

Stap 3: ⭐ Stretch: botsing + levens

Als een asteroïde je schip raakt, verlies je een leven. Voeg lives en game_over toe bovenaan:

lives = 3
game_over = False

Voeg in de for-loop collision-detectie toe:

    for rock in asteroids[:]:
        rock.y += rock.speed

        if rock.y > HEIGHT + 30:
            asteroids.remove(rock)
        elif rock.colliderect(ship):
            asteroids.remove(rock)
            global lives, game_over
            lives -= 1
            if lives <= 0:
                game_over = True

Toon de levens en score in draw():

    screen.draw.text(f"Score: {score}", (10, 10), color="white", fontsize=24)
    screen.draw.text(f"Lives: {lives}", (10, 40), color="red", fontsize=24)

Stop het spel als game_over True is:

def update():
    if game_over:
        return
    ...

Toon een game-over scherm in draw():

    if game_over:
        screen.draw.text(
            "GAME OVER",
            center=(WIDTH / 2, HEIGHT / 2),
            color="red",
            fontsize=60,
        )
        screen.draw.text(
            f"Final Score: {score}",
            center=(WIDTH / 2, HEIGHT / 2 + 60),
            color="white",
            fontsize=30,
        )

Stap 4: 🔥 Expert: explosies + onzichtbaarheid

Als een asteroïde je raakt, wil je een explosie zien in plaats van dat het schip zomaar verdwijnt. Gebruik de vier explosie-frames (explosion_1 t/m explosion_4):

explosion = Actor('explosion_1')
explosion_active = False
explosion_frame = 0
explosion_timer = 0

Bij een botsing: verberg het schip, start de explosie:

        elif rock.colliderect(ship):
            asteroids.remove(rock)
            global lives, game_over, explosion_active, explosion_frame, explosion_timer
            lives -= 1

            explosion.pos = (ship.x, ship.y)
            explosion.image = 'explosion_1'
            explosion_frame = 1
            explosion_timer = 5
            explosion_active = True
            ship.visible = False

            if lives <= 0:
                game_over = True

Laat de explosie afspelen in update():

    if explosion_active:
        explosion_timer -= 1
        if explosion_timer <= 0:
            explosion_frame += 1
            if explosion_frame > 4:
                explosion_active = False
                ship.visible = True
                ship.pos = (WIDTH / 2, HEIGHT - 50)
            else:
                explosion.image = f'explosion_{explosion_frame}'
                explosion_timer = 5

Voeg daarna een onzichtbaarheidsperiode toe zodat je niet meteen opnieuw geraakt wordt:

invincible = False
invincible_timer = 0

In update():

    if invincible:
        invincible_timer -= 1
        if invincible_timer <= 0:
            invincible = False
            ship.visible = True

Pas de collision-check aan:

        elif rock.colliderect(ship) and not invincible and ship.visible:

Teken de explosie in draw():

    if explosion_active:
        explosion.draw()

Showcase

Laat je Dojo Defender zien aan een coach en een buddy. Hoe lang overleef jij? Heb je explosies? Ziet het er vet uit?

Tot de volgende keer!

“Volgende keer: je ruimteschip SCHIET TERUG. Vijanden in formatie, en jij blast ze weg met lasers. Dit wordt episch.”

Neem mee naar huis

Probeer thuis één van deze uitbreidingen:

  1. Geluid — Voeg sounds/explosion.wav toe als een asteroïde ontploft (sounds.explosion.play()).
  2. Moeilijker — Laat asteroïden sneller vallen naarmate de score stijgt: rock.speed = random.uniform(3 + score // 10, 5 + score // 10).
  3. Power-up — Voeg een zeldzame groene power-up toe die een extra leven geeft.
  4. Kleiner — Gebruik asteroid_med of asteroid_small voor variatie in grootte en snelheid.

Download Dojo Defender

1 juli 2026, 22:43

Dojo Defender: Sessie 2

Je schip heeft nu een wapen. Vijanden komen eraan. Schiet ze neer voordat ze de bodem bereiken. Elke 10 vijanden betekent een nieuwe wave — en het wordt steeds moeilijker.

Wat je vandaag leert

  • Schieten met de spatiebalk en een cooldown
  • Lijsten voor kogels en vijanden
  • Collision tussen kogels en vijanden
  • Golven (waves) die steeds moeilijker worden
  • Vijanden die sinus-bewegingen maken

Stap 0: Open de starter

  1. Open Thonny.
  2. Download de Dojo Defender starter voor vandaag, pak de ZIP uit en open main.py.
  3. Was je er vorige keer niet bij? Download de oplossing van sessie 1 en begin daarmee.
  4. Klik op Run. Je ziet hetzelfde spel als vorige keer: schip, asteroïden, explosies.
  5. Problemen? Kijk bij Thonny instellen.

Stap 1: Schieten met SPATIE

Je schip heeft een laserkanon. Voeg een lijst voor kogels toe bovenaan:

bullets = []

Gebruik on_key_down — die wordt maar één keer aangeroepen als je een toets indrukt (in tegenstelling tot update die blijft herhalen):

def on_key_down(key):
    if key == keys.SPACE:
        bullet = Actor('bullet')
        bullet.x = ship.x
        bullet.y = ship.y - 20
        bullets.append(bullet)

Laat de kogels omhoog bewegen in update() en verwijder ze als ze uit beeld zijn:

for bullet in bullets[:]:
    bullet.y -= 8
    if bullet.y < -10:
        bullets.remove(bullet)

Vergeet niet de kogels te tekenen in draw():

for bullet in bullets:
    bullet.draw()

Stap 2: ✅ Basic: schiet-cooldown

Zonder cooldown vuur je tientallen kogels per seconde. Voeg een timer toe:

shoot_cooldown = 0

In update():

if shoot_cooldown > 0:
    shoot_cooldown -= 1

Pas on_key_down aan zodat je alleen kunt schieten als de cooldown 0 is:

def on_key_down(key):
    global shoot_cooldown
    if key == keys.SPACE and shoot_cooldown == 0:
        bullet = Actor('bullet')
        bullet.x = ship.x
        bullet.y = ship.y - 20
        bullets.append(bullet)
        shoot_cooldown = 15

Stap 3: ⭐ Stretch: vijanden toevoegen

Vijanden spawnen bovenaan en vallen naar beneden. Voeg een lijst toe:

enemies = []

Een spawn-functie:

def spawn_enemy():
    enemy = Actor('enemy_drone')
    enemy.x = random.randint(30, WIDTH - 30)
    enemy.y = -30
    enemy.speed = random.uniform(1, 3)
    enemies.append(enemy)

Roep spawn_enemy() aan in update() met een spawn-interval:

# bovenaan:
spawn_counter = 0
spawn_interval = 120

# in update():
spawn_counter += 1
if spawn_counter >= spawn_interval:
    spawn_counter = 0
    spawn_enemy()

Teken vijanden in draw():

for enemy in enemies:
    enemy.draw()

Stap 4: 🔥 Expert: collision + waves

Kogel raakt vijand: verwijder beide, +10 punten:

for bullet in bullets[:]:
    bullet.y -= 8
    if bullet.y < -10:
        bullets.remove(bullet)
        continue
    for enemy in enemies[:]:
        if bullet.colliderect(enemy):
            bullets.remove(bullet)
            enemies.remove(enemy)
            global score
            score += 10
            break

Vijand raakt bodem of schip: verlies een leven (zelfde als asteroïde).

Waves: elke 10 vijanden een nieuwe wave. Vijanden spawnen sneller:

# bovenaan:
wave = 1
enemies_destroyed = 0

# bij destroy:
enemies_destroyed += 1
if enemies_destroyed % 10 == 0:
    wave += 1
    spawn_interval = max(60, spawn_interval - 10)

Toon de huidige wave in draw():

screen.draw.text(f"Wave: {wave}", (10, 70), color="yellow", fontsize=24)

Toon “WAVE N!” in het groot bij een nieuwe wave (gebruik een timer).

Sinus-beweging: laat vijanden links-rechts zwenken:

import math  # bovenaan

enemy.x = enemy.start_x + math.sin(timer * 0.03 + enemy.strafe_offset) * 50

Showcase

Laat je Dojo Defender zien aan een coach. Hoeveel waves haal jij? Heb je de sinus-beweging werkend? Vergelijk scores met een buddy.

Tot de volgende keer!

“Volgende keer: POWER-UPS. Schilden, speed-boosts en spread-shots. Je schip wordt steeds sterker naarmate je langer overleeft.”

Neem mee naar huis

Probeer thuis één van deze uitbreidingen:

  1. Vijandvariatie — Gebruik enemy_charger.png voor een snellere vijand die geen sinus maakt, maar recht op je af komt.
  2. Geluid — Voeg sounds/laser.wav en sounds/explosion.wav toe.
  3. Harde modus — Vijanden spawnen steeds sneller, tot maximaal elke 30 frames.
  4. Score multiplier — Hoe hoger de wave, hoe meer punten per kill: score += 10 * wave.

Download Dojo Defender

1 juli 2026, 22:43

Dojo Defender: Sessie 3

Vandaag verhuist Dojo Defender van pgzero naar echte pygame. Geen pgzrun.go() meer — jij schrijft de game loop zelf. Het spel blijft hetzelfde, maar de motor eronder is anders. En aan het einde voeg je GELUID toe.

Wat je vandaag leert

  • Het verschil tussen pgzero en raw pygame
  • De game loop zelf programmeren: while running: clock.tick(60)
  • Actors omzetten naar Surface + Rect
  • Geluid laden en afspelen met pygame.mixer
  • Achtergrondmuziek laten loepen

Stap 0: Download de starter

De starter voor vandaag is een volledige port van Dojo Defender Sessie 2 naar raw pygame. Geen pgzero meer, maar het speelt hetzelfde.

  1. Download de Dojo Defender starter voor vandaag, pak uit en open main.py.
  2. Was je er vorige keer niet bij? Download de oplossing van sessie 2 en probeer die te porten.
  3. Klik op Run. Het spel werkt zoals je gewend bent: schip, asteroïden, kogels, vijanden, waves, explosies.

Wat is er veranderd?

In pgzero schreef je draw() en update() en pgzrun deed de rest. In raw pygame doe je alles zelf:

pgzero (vroeger):

import pgzrun

def draw():
    screen.fill((10, 10, 30))
    ship.draw()

def update():
    if keyboard.left:
        ship.x -= 5

pgzrun.go()

raw pygame (nu):

import pygame, sys
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

running = True
while running:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        ship.rect.centerx -= 5

    screen.fill((10, 10, 30))
    ship.draw(screen)
    pygame.display.flip()

De belangrijkste verschillen:

pgzeroraw pygame
Actor('plaatje')GameObject('plaatje.png', x, y)
keyboard.leftkeys[pygame.K_LEFT]
on_key_down(key)event.type == pygame.KEYDOWN
screen.draw.text(...)font.render() + screen.blit()
rock.colliderect(ship)rock.rect.colliderect(ship.rect)
pgzrun.go()while running: ... clock.tick(60)

Stap 1: Verken de code

Open main.py en vergelijk met de oude pgzero-code van sessie 2. Zoek deze onderdelen:

  1. Game loop — Zoek de while running:-lus. Zie je clock.tick(60), pygame.display.flip()?
  2. GameObject — Zoek de class GameObject. Hoe wordt een afbeelding geladen? Wat is rect?
  3. Input — Zoek pygame.key.get_pressed() en pygame.KEYDOWN. Hoe werkt de spatiebalk?

Verander de WIDTH naar 1024. Wat gebeurt er? (Alles wordt breder — de game werkt nog steeds.)

Stap 2: ✅ Basic — Voeg restart toe met de R-toets

In de pgzero-versie kon je niet herstarten na game over. Voeg dat nu toe:

# in de event loop, bij KEYDOWN:
if event.key == pygame.K_r and game_over:
    # reset alles
    ship.rect.center = (WIDTH / 2, HEIGHT - 50)
    ship.visible = True
    asteroids.clear()
    bullets.clear()
    enemies.clear()
    lives = 3
    score = 0
    wave = 1
    enemies_destroyed = 0
    spawn_counter = 0
    spawn_interval = 120
    wave_display_timer = 0
    game_over = False
    invincible = False
    ship.visible = True

Toon ook een hint op het game-over scherm: druk op R om opnieuw te spelen.

Stap 3: ⭐ Stretch — Voeg geluid toe

De starter heeft nog geen geluid. Voeg sounds toe!

Maak een sounds/ map naast images/ en kopieer de geluidsbestanden uit dojo-defender-assets/sounds/ (vraag de coach). Of download de oplossing die ze al bevat.

Voeg bovenaan toe:

pygame.mixer.init()  # na pygame.init()

shoot_sound = pygame.mixer.Sound('sounds/shoot.wav')
explode_sound = pygame.mixer.Sound('sounds/explode.wav')
hit_sound = pygame.mixer.Sound('sounds/hit.wav')
pygame.mixer.music.load('sounds/bg_music.wav')
pygame.mixer.music.play(-1)  # -1 = blijven herhalen

Let op: het pad moet kloppen. Gebruik os.path.join(_DIR, 'sounds/shoot.wav') zoals de starter ook met afbeeldingen doet.

Speel de geluiden af op de juiste momenten:

  • shoot_sound.play() — als je schiet
  • explode_sound.play() — als een vijand of asteroïde ontploft
  • hit_sound.play() — als je schip geraakt wordt

Stap 4: 🔥 Expert — Mute-knop en volume

Voeg een M-toets toe die het geluid aan/uit zet:

muted = False

# in KEYDOWN:
if event.key == pygame.K_m:
    muted = not muted
    pygame.mixer.music.set_volume(0 if muted else 1)

Elk geluid moet dan gecheckt worden:

if not muted:
    shoot_sound.play()

Volume per geluid: Laat het schietgeluid zachter dan de explosie:

shoot_sound.set_volume(0.3)
explode_sound.set_volume(0.7)

Showcase

Laat aan een coach zien dat:

  • Je spel werkt in echte pygame zonder pgzero
  • Je geluid hebt toegevoegd (shoot, explode, hit, bg music)
  • Bonus: restart met R of mute met M

Tot de volgende keer!

“Volgende keer: POWER-UPS. Schilden, speed-boosts en spread-shots. En je schip wordt sterker naarmate je langer overleeft.”

Neem mee naar huis

Probeer thuis één van deze uitbreidingen:

  1. Health bar — Vervang de tekst “Lives: 3” door een visuele balk: teken drie rechthoekjes bovenaan.
  2. Pauze — Voeg een P-toets toe die het spel pauzeert. Zet running = False is niet genoeg — gebruik een aparte paused variabele.
  3. Particle-effect — Laat kleine witte stipjes rondspatten bij een explosie (gebruik een lijst met deeltjes die steeds kleiner worden).
  4. Eigen skin — Vervang player_ship.png door een eigen ontwerp.

Download Dojo Defender

1 juli 2026, 22:43

Dojo Defender: Sessie 4

Vandaag wordt Dojo Defender een echt spel met een menu. Geen zwarte start meer — je krijgt een titelscherm, een game-over scherm, en drie verschillende vijandtypes die elk hun eigen beweging hebben.

Wat je vandaag leert

  • State machines: je spel heeft toestanden (MENU, PLAYING, GAME OVER)
  • Vijand-AI: elke vijand beweegt anders (recht, zigzag, charger)
  • Wave-systeem: steeds moeilijkere golven met mixen van vijanden
  • Een reset-functie: alles netjes terugzetten zonder herstart

Stap 0: Download de starter

De starter is de oplossing van sessie 3 — Dojo Defender in raw pygame met geluid.

  1. Download de starter voor vandaag, pak uit en open main.py.
  2. Was je er niet bij? Download de oplossing van sessie 3.
  3. Klik op Run. Het spel werkt zoals voorheen: schip, asteroïden, kogels, drones, geluid.

Stap 1: Voeg een state machine toe

Een state machine houdt bij in welke “toestand” het spel zich bevindt:

state = "MENU"  # kan "MENU", "PLAYING", "GAME_OVER" zijn

Plaats deze variabele bovenaan, bij de andere globale variabelen.

Menu-scherm tekenen: Vervang de screen.fill en al het tekenen door een check op state:

if state == "MENU":
    # Teken titel en instructies
    screen.fill((10, 10, 30))
    titel = title_font.render("DOJO DEFENDER", True, (0, 200, 255))
    screen.blit(titel, (WIDTH // 2 - titel.get_width() // 2, HEIGHT // 2 - 60))
    sub = big_font.render("Press SPACE to start", True, (200, 200, 200))
    screen.blit(sub, (WIDTH // 2 - sub.get_width() // 2, HEIGHT // 2))

elif state == "PLAYING":
    # = al jouw bestaande game code hier

elif state == "GAME_OVER":
    # Teken "GAME OVER" + score

Maak title_font en big_font bovenaan:

title_font = pygame.font.SysFont(None, 80)
big_font = pygame.font.SysFont(None, 60)

Nu moet SPACE het volgende doen:

  • Als state == "MENU": reset het spel en ga naar PLAYING
  • Als state == "GAME_OVER": reset het spel en ga naar PLAYING
  • Als state == "PLAYING" en game_over == False: schiet zoals normaal

Pas de KEYDOWN-logic aan:

if event.key == pygame.K_SPACE:
    if state == "MENU":
        reset_game()
        state = "PLAYING"
    elif state == "GAME_OVER":
        reset_game()
        state = "PLAYING"
    elif state == "PLAYING" and shoot_cooldown == 0 and not game_over:
        # schiet (bestaande code)

Stap 2: ✅ Basic — Schrijf de reset-functie

Maak een functie die alles terugzet naar de beginwaarden:

def reset_game():
    global ship, asteroids, bullets, enemies
    global lives, score, game_over, timer
    global explosion_active, explosion_frame, explosion_timer
    global invincible, invincible_timer, shoot_cooldown
    global wave, wave_display_timer, enemies_destroyed
    global spawn_counter, spawn_interval

    ship.rect.center = (WIDTH / 2, HEIGHT - 50)
    ship.visible = True
    asteroids.clear()
    bullets.clear()
    enemies.clear()
    lives = 3
    score = 0
    game_over = False
    timer = 0
    explosion_active = False
    explosion_frame = 0
    explosion_timer = 0
    invincible = False
    invincible_timer = 0
    shoot_cooldown = 0
    wave = 1
    wave_display_timer = 0
    enemies_destroyed = 0
    spawn_counter = 0
    spawn_interval = 120

Zet state = "GAME_OVER" als de speler 0 levens heeft (bij lives <= 0):

if lives <= 0:
    game_over = True
    state = "GAME_OVER"

Check: Het spel start nu met een blauw titelscherm. SPACE start het spel. Bij game over zie je GAME OVER en kun je met SPACE herstarten.

Stap 3: ⭐ Stretch — Drie vijandtypes

Vervang de ene spawn_enemy() door drie functies:

def spawn_enemy_drone():
    enemy = GameObject('enemy_drone.png', random.randint(30, WIDTH - 30), -30)
    enemy.type = 'drone'
    enemy.speed = random.uniform(1, 3)
    enemies.append(enemy)


def spawn_enemy_zigzag():
    enemy = GameObject('enemy_zigzag.png', random.randint(30, WIDTH - 30), -30)
    enemy.type = 'zigzag'
    enemy.speed = random.uniform(2, 4)
    enemy.dx = random.choice([-2, 2])
    enemies.append(enemy)


def spawn_enemy_charger():
    enemy = GameObject('enemy_charger.png', random.randint(30, WIDTH - 30), -30)
    enemy.type = 'charger'
    enemy.speed = random.uniform(3, 5)
    enemies.append(enemy)

Beweging per type

In de game loop, waar je enemy.rect.y += enemy.speed deed, moet je nu per type andere beweging toevoegen:

for enemy in enemies[:]:
    if enemy.type == 'drone':
        enemy.rect.y += enemy.speed

    elif enemy.type == 'zigzag':
        enemy.rect.x += enemy.dx
        enemy.rect.y += enemy.speed
        if enemy.rect.left <= 0 or enemy.rect.right >= WIDTH:
            enemy.dx = -enemy.dx

    elif enemy.type == 'charger':
        # Stuur naar de x-positie van de speler
        dx = ship.rect.centerx - enemy.rect.centerx
        if dx > 0:
            enemy.rect.centerx += min(enemy.speed, abs(dx))
        elif dx < 0:
            enemy.rect.centerx -= min(enemy.speed, abs(dx))
        enemy.rect.y += enemy.speed * 0.8

    # Daarna: off-screen check, collision met schip etc.

Belangrijk: De charger moet langzamer dalen (* 0.8) zodat de speler tijd heeft om te reageren.

Stap 4: 🔥 Expert — Enhanced waves

Maak een spawn_enemy() die beslist welk type er spawnt op basis van de huidige wave:

def spawn_enemy():
    if wave == 1:
        spawn_enemy_drone()
    elif wave <= 3:
        if random.random() < 0.5:
            spawn_enemy_drone()
        else:
            spawn_enemy_zigzag()
    else:
        r = random.random()
        if r < 0.3:
            spawn_enemy_drone()
        elif r < 0.65:
            spawn_enemy_zigzag()
        else:
            spawn_enemy_charger()

Zo wordt het steeds moeilijker:

  • Wave 1: alleen drones (warmlopen)
  • Wave 2-3: drones + zigzag (50/50)
  • Wave 4+: alle drie, met meer chargers (30% drone, 35% zigzag, 35% charger)

Showcase

Laat aan een coach zien dat:

  • Je een menu-scherm hebt met titel en “Press SPACE to start”
  • Je drie vijandtypes hebt: drone (↓), zigzag (↘↗), charger (→ jouw schip)
  • De waves steeds moeilijker worden (meer zigzags, dan chargers)
  • Game over werkt met scoreweergave en restart

Tot de volgende keer!

“Volgende keer: POWER-UPS. Schilden, speed-boosts, spread-shots. En een échte eindbaas.”

Neem mee naar huis

Probeer thuis één van deze uitbreidingen:

  1. High score — Bewaar de hoogste score in een bestand en toon hem op het menu.
  2. Moeilijkheidsgraad — Laat de speler kiezen tussen “Makkelijk” en “Moeilijk” op het menu.
  3. Boss wave — Elke 5 waves verschijnt een grote baas met veel hits.
  4. Charger Warning — Teken een rode lijn onder een charger voordat hij verschijnt.

Download Dojo Defender

1 juli 2026, 22:43

Dojo Defender: Sessie 5

Vandaag geef je Dojo Defender een diepte-effect met een parallax sterrenachtergrond en explosie-deeltjes. Asteroïden worden ook vernietigbaar: grote asteroïden splitsen in kleinere.

Wat je vandaag leert

  • Parallax rekenen: meerdere lagen sterren met eigen snelheid
  • Deeltjeslijsten: een lijst van dictionaries voor particles
  • Positie-gebaseerde effecten: particles spawnen op de juiste plek
  • Recursief splitsen: grote asteroïde → 2 medium → 2 klein → weg

Stap 0: Download de starter

De starter is de oplossing van sessie 4 — Dojo Defender met menu, drie vijandtypes en waves.

  1. Download de starter voor vandaag, pak uit en open main.py.
  2. Was je er niet bij? Download de oplossing van sessie 4.
  3. Klik op Run. Het spel werkt zoals voorheen: schip, asteroïden, kogels, drones, zigzags, chargers.

Stap 1: 🌟 Parallax sterrenachtergrond

Een parallax achtergrond heeft meerdere lagen die met verschillende snelheden bewegen. Ver weg = langzaam, dichtbij = snel. Dat geeft diepte.

Voeg een lijst stars = [] toe bovenaan het bestand, bij de andere globale variabelen:

stars = []

Maak een init_stars() functie die drie lagen sterren aanmaakt:

def init_stars():
    global stars
    stars = []
    layers = [
        (40, 0.3, 2, 120),   # ver weg: 40 sterren, speed 0.3, grootte 2, donkerder
        (40, 0.7, 3, 180),   # middel:  40 sterren, speed 0.7, grootte 3
        (30, 1.5, 4, 255),   # dichtbij: 30 sterren, speed 1.5, grootte 4, fel
    ]
    for count, speed, size, brightness in layers:
        for _ in range(count):
            stars.append({
                'x': random.randint(0, WIDTH),
                'y': random.randint(0, HEIGHT),
                'speed': speed,
                'size': size,
                'brightness': brightness,
            })

Wiskunde erachter: elke ster krijgt een y-snelheid. Verre sterren (0.3 px/frame) bewegen bijna niet, nabije sterren (1.5 px/frame) bewegen sneller. Je brein interpreteert dat als diepte, precies zoals in een auto: bomen naast de weg vliegen voorbij, bergen ver weg blijven stil.

Voeg ook de update-functie toe. Die laat sterren zakken en zet ze terug naar boven als ze onder het scherm zijn:

def update_stars():
    for star in stars:
        star['y'] += star['speed']
        if star['y'] > HEIGHT + star['size']:
            star['y'] = -star['size']
            star['x'] = random.randint(0, WIDTH)

En een teken-functie. Kleine sterren teken je met pygame.draw.rect (2×2), grotere met pygame.draw.circle:

def draw_stars(surface):
    for star in stars:
        c = (star['brightness'], star['brightness'], star['brightness'])
        if star['size'] <= 2:
            pygame.draw.rect(surface, c, (star['x'], star['y'], star['size'], star['size']))
        else:
            pygame.draw.circle(surface, c, (int(star['x']), int(star['y'])), star['size'] // 2)

Aanroepen: Roep init_stars() één keer aan (bij het opstarten, ná alle functies), update_stars() elke frame (zelfs in het menu!), en draw_stars(screen) na screen.fill() in ALLE states.

Check

Zie je drie lagen sterren naar beneden scrollen, ook in het menu? De verre sterren zijn kleiner en bewegen langzamer.

Stap 2: ✅ Basic — Particle systeem

Een particle is een klein deeltje dat beweegt, vervaagt en verdwijnt. We gebruiken een lijst van dictionaries:

particles = []  # {x, y, vx, vy, life, max_life, color, size}

Maak een functie om particles te spawnen:

def spawn_particles(x, y, count, colors, speed_range, size_range, life_range):
    for _ in range(count):
        angle = random.uniform(0, 2 * math.pi)
        speed = random.uniform(speed_range[0], speed_range[1])
        particles.append({
            'x': x,
            'y': y,
            'vx': math.cos(angle) * speed,
            'vy': math.sin(angle) * speed,
            'life': random.randint(life_range[0], life_range[1]),
            'max_life': life_range[1],
            'color': random.choice(colors),
            'size': random.uniform(size_range[0], size_range[1]),
        })

Wat gebeurt er? Elke particle krijgt:

  • Een positie (x, y)
  • Een snelheid in x en y (vx, vy), berekend uit een hoek en snelheid
  • Een levensduur (life wordt elke frame met 1 verlaagd)
  • Een kleur en grootte

Update-functie (elke frame elke particle 1 stap verplaatsen en de levensduur verkorten):

def update_particles():
    for p in particles[:]:
        p['x'] += p['vx']
        p['y'] += p['vy']
        p['life'] -= 1
        if p['life'] <= 0:
            particles.remove(p)

Waarom [:]? We passen de lijst aan tijdens het loopen (we verwijderen particles). Zonder [:] krijg je een runtime error. De slice [:] maakt een kopie van de lijst om veilig over te loopen.

Teken-functie met vervaging (alpha-fade): hoe minder levensduur, hoe kleiner en donkerder:

def draw_particles(surface):
    for p in particles:
        ratio = p['life'] / p['max_life']
        color = tuple(int(c * ratio) for c in p['color'])
        pygame.draw.circle(surface, color, (int(p['x']), int(p['y'])), int(p['size'] * ratio))

De ratio is een getal tussen 1 (net geboren) en 0 (bijna dood). We vermenigvuldigen de kleur en grootte ermee, zodat de particle langzaam verdwijnt.

Particle-lijst wissen in reset

Voeg particles.clear() toe aan de reset_game() functie, anders blijven dode particles na een restart zweven.

Roep update_particles() aan in de PLAYING state en draw_particles(screen) na draw_stars(screen) in alle states.

Check

Start het spel en druk op SPACE. Er gebeurt nog niets zichtbaars — maar het systeem staat klaar voor de volgende stappen.

Stap 3: ⭐ Stretch — Positie-gebaseerde effecten

Nu gaan we particles spawnen op de juiste momenten en posities.

Uitlaatgassen

Als het schip beweegt, spawn kleine rode/oranje particles onder het schip. Voeg dit toe in de if not game_over sectie, waar je de pijltjestoetsen checked:

keys = pygame.key.get_pressed()
moving = False
if keys[pygame.K_LEFT] and ship.rect.centerx > 30:
    ship.rect.centerx -= 5
    moving = True
if keys[pygame.K_RIGHT] and ship.rect.centerx < WIDTH - 30:
    ship.rect.centerx += 5
    moving = True

if moving and ship.visible:
    spawn_particles(
        ship.rect.centerx, ship.rect.bottom,
        1,  # of random.randint(1, 2)
        [(255, 100, 0), (255, 60, 0), (200, 50, 0)],
        (0.5, 1.5), (2, 4), (10, 20),
    )

De particles spawnen onder het schip (ship.rect.bottom) en krijgen een kleine snelheid omhoog (negatieve vy door de hoek).

Explosie puin

Als een vijand wordt geraakt door een kogel, spawn 8-12 deeltjes in alle richtingen. Voeg dit toe waar je een enemy uit de lijst haalt na een bullet-collision:

spawn_particles(
    enemy.rect.centerx, enemy.rect.centery,
    random.randint(8, 12),
    [(255, 140, 0), (255, 0, 0), (255, 255, 0), (255, 255, 255)],
    (1, 4), (2, 5), (20, 40),
)

Vijand-sterfsparkels

Als een vijand de onderkant van het scherm bereikt, spawn 3-5 witte vonkjes:

spawn_particles(
    enemy.rect.centerx, HEIGHT - 30,
    random.randint(3, 5),
    [(255, 255, 255), (200, 200, 255)],
    (1, 3), (1, 3), (15, 30),
)

Check

Beweeg het schip — zie je oranje uitlaatgassen? Schiet een vijand — zie je 8-12 rondvliegende deeltjes? Laat een vijand ontsnappen — zie je witte vonkjes?

Stap 4: 🔥 Expert — Vernietigbare asteroïden

Asteroïden zijn nu recursief splitsbaar:

  • asteroid_big.png → wordt geraakt → 2× asteroid_med.png
  • asteroid_med.png → wordt geraakt → 2× asteroid_small.png
  • asteroid_small.png → wordt geraakt → weg + particles

Pas spawn_asteroid() aan zodat elke asteroïde een asteroid_size krijgt:

def spawn_asteroid(size='big'):
    rock = GameObject(f'asteroid_{size}.png', random.randint(30, WIDTH - 30), -30)
    rock.speed = random.uniform(3, 5)
    rock.asteroid_size = size
    asteroids.append(rock)
    return rock

Maak een split_asteroid() functie die een asteroïde in twee kleinere splitst:

def split_asteroid(rock):
    # Eerst: exploderende particles
    spawn_particles(
        rock.rect.centerx, rock.rect.centery,
        random.randint(8, 12),
        [(255, 140, 0), (255, 0, 0), (255, 255, 0), (255, 255, 255)],
        (1, 4), (2, 5), (20, 40),
    )
    if rock.asteroid_size == 'big':
        for _ in range(2):
            r = spawn_asteroid('med')
            r.rect.center = rock.rect.center
            r.rect.x += random.randint(-20, 20)
            r.speed = random.uniform(2, 4)
    elif rock.asteroid_size == 'med':
        for _ in range(2):
            r = spawn_asteroid('small')
            r.rect.center = rock.rect.center
            r.rect.x += random.randint(-15, 15)
            r.speed = random.uniform(1, 3)
    asteroids.remove(rock)

Wat gebeurt hier?

  1. We spawnen explosie-particles op de positie van de asteroïde
  2. We checken de grootte: ‘big’ → 2 ‘med’, ‘med’ → 2 ‘small’, ‘small’ → geen verdere splitsing
  3. Nieuwe asteroïden spawnen vlakbij de oude positie met een lichte offset
  4. De kleinere asteroïden zijn langzamer dan de grotere

Pas de collision-logic aan: waar je astersoids.remove(rock) deed bij een kogelraak, roep nu split_asteroid(rock) aan:

for rock in asteroids[:]:
    if bullet.rect.colliderect(rock.rect):
        bullets.remove(bullet)
        explode_sound.play()
        split_asteroid(rock)
        score += 5
        break

Belangrijk: Het geluid (explode_sound.play()) speel je af vóór split_asteroid() zodat het niet per ongeluk twee keer afspeelt.

Check

Schiet een grote asteroïde. Zie je hem splitsen in twee medium asteroïden? Schiet die — ze splitsen in twee kleine. Schiet de kleine — die verdwijnen met een mooie particle-explosie.

Showcase

Laat aan een coach zien dat:

  • Parallax sterrenachtergrond met drie snelheden
  • Particle systeem met vervaging (alpha-fade)
  • Uitlaatgassen onder het bewegende schip
  • Explosie-puin bij vernietigde vijanden en asteroïden
  • Asteroïden splitsen: big → med → small → weg

Tot de volgende keer!

“Volgende keer: POWER-UPS. Schilden, speed-boosts, spread-shots. En een échte eindbaas.”

Neem mee naar huis

Probeer thuis één van deze uitbreidingen:

  1. Makkelijk: verander de kleur van de sterren per laag (bijv. verre sterren blauwachtig, nabije geel).
  2. Middel: voeg een vierde laag sterren toe, nog sneller (speed 3.0), met grote heldere sterren.
  3. Lastig: laat particle-kleur veranderen tijdens de levensduur (van geel naar rood naar grijs).
  4. Erg lastig: voeg een “comet” particle type toe dat een klein staartje heeft.

Download Dojo Defender

1 juli 2026, 22:43

Dojo Defender: Sessie 6

Vandaag voeg je een eindbaas (boss) toe aan Dojo Defender. De boss verschijnt om de 5 waves en heeft 3 fases met eigen gedrag. Je leert werken met een eigen state machine, HP-balken en schermeffecten.

Wat je vandaag leert

  • State machine voor een baas: PHASE_1, PHASE_2, PHASE_3 en EXPLODING
  • HP-balk tekenen met kleurverloop (groen → geel → rood)
  • Doelzoekende kogels: een kogel die naar de speler beweegt
  • Screen shake: willekeurige offset op teken-coördinaten
  • Fase-overgangen met visuele effecten

Stap 0: Download de starter

De starter is de oplossing van sessie 5 — Dojo Defender met parallax sterren, particles en splitsende asteroïden.

  1. Download de starter voor vandaag, pak uit en open main.py.
  2. Was je er niet bij? Download de oplossing van sessie 5.
  3. Klik op Run. Het spel werkt zoals voorheen. Dood 10 vijanden om wave 5 te halen — dan verschijnt de boss!

Stap 1: ✅ Basic — Boss-variabelen

Een boss heeft eigen variabelen. Voeg deze toe bij de andere globale variabelen:

boss = None
boss_active = False
boss_hp = 0
boss_hp_max = 50
boss_phase = "PHASE_1"
boss_timer = 0
boss_phase_transition_timer = 0
boss_defeated_timer = 0
boss_shoot_timer = 0
boss_minion_spawn_timer = 0
boss_bullets = []
boss_minions = []
screen_shake_x = 0
screen_shake_y = 0
boss_direction = 1

Voeg ook leegmaken van boss_bullets en boss_minions toe in reset_game().

Waarom zoveel variabelen? Een baas is complexer dan gewone vijanden. Hij moet bewegen, schieten, minions spawnen, fases hebben en een dood-animatie. Elke variabele is één aspect van dat gedrag.

Check

Start het spel. Er verandert nog niets — maar de variabelen staan klaar.

Stap 2: ⭐ Stretch — Boss spawnen en tekenen

Spawn-functie

Schrijf een functie die de boss activeert:

def start_boss():
    global boss_active, boss, boss_hp, boss_phase, boss_timer
    global boss_bullets, boss_minions, boss_shoot_timer
    global boss_minion_spawn_timer, boss_direction, screen_shake_x, screen_shake_y
    boss_active = True
    boss = GameObject('boss.png', WIDTH / 2, -80)
    boss_hp = boss_hp_max
    boss_phase = "PHASE_1"
    boss_timer = 0
    boss_shoot_timer = 0
    boss_minion_spawn_timer = 0
    boss_direction = 1
    screen_shake_x = 0
    screen_shake_y = 0
    boss_bullets.clear()
    boss_minions.clear()
    roar_path = os.path.join(_DIR, 'sounds/boss_roar.wav')
    if os.path.exists(roar_path):
        pygame.mixer.Sound(roar_path).play()

Boss-wave detectie

Elke 5e wave (vanaf wave 5) verschijnt een boss. Voeg dit toe waar de wave omhoog gaat:

def check_boss_wave():
    if wave >= 5 and wave % 5 == 0 and not boss_active:
        start_boss()

Roep check_boss_wave() aan na wave += 1.

Vijanden stoppen tijdens boss

Zorg dat gewone vijanden niet spawnen tijdens een boss fight. Omcirkel de spawn-logic:

if not boss_active:
    spawn_counter += 1
    if spawn_counter >= spawn_interval:
        spawn_counter = 0
        spawn_enemy()

Boss tekenen

Teken de boss als hij actief is, en teken ook de HP-balk bovenaan het scherm:

if boss_active and boss is not None:
    boss.draw(screen)
    # HP-balk
    bar_width = 300
    bar_height = 20
    bar_x = (WIDTH - bar_width) // 2
    bar_y = 10
    bar_bg = pygame.Rect(bar_x, bar_y, bar_width, bar_height)
    pygame.draw.rect(screen, (100, 20, 20), bar_bg)
    hp_ratio = max(0, boss_hp / boss_hp_max)
    fill_width = int(bar_width * hp_ratio)
    if hp_ratio > 0.5:
        color = (0, 255, 0)
    elif hp_ratio > 0.25:
        color = (255, 255, 0)
    else:
        color = (255, 0, 0)
    fill_rect = pygame.Rect(bar_x, bar_y, fill_width, bar_height)
    pygame.draw.rect(screen, color, fill_rect)
    pygame.draw.rect(screen, (255, 255, 255), bar_bg, 2)
    label = font.render(f"BOSS  {boss_hp}/{boss_hp_max}", True, (255, 255, 255))
    screen.blit(label, (bar_x + bar_width // 2 - label.get_width() // 2, bar_y - 5))

Check

Speel tot wave 5. Zie je de boss binnenkomen met een HP-balk? Gewone vijanden spawnen niet meer.

Stap 3: 🔥 Expert — Boss AI (3 fases)

De boss heeft een state machine met 3 fases. Elke fase is anders gedrag.

Fase 1 (HP 34-50, 100-66%)

De boss drijft langzaam naar links en rechts en vuurt af en toe een doelzoekende kogel.

def fire_boss_bullet():
    dx = ship.rect.centerx - boss.rect.centerx
    dy = ship.rect.centery - boss.rect.centery
    dist = math.sqrt(dx * dx + dy * dy)
    if dist == 0:
        dist = 1
    speed = 3
    bullet = {
        'x': boss.rect.centerx,
        'y': boss.rect.centery + 30,
        'vx': dx / dist * speed,
        'vy': dy / dist * speed,
    }
    boss_bullets.append(bullet)

Wiskunde: Door dx en dy te delen door de afstand (dist) krijg je een eenheidsvector — een richting met lengte 1. Vermenigvuldig met speed voor de gewenste snelheid. Dit werkt in élke richting, niet alleen horizontaal/verticaal.

Voeg de AI-update toe in de game loop (if boss_active and boss is not None):

if boss.rect.centery < 80:
    boss.rect.centery += 2  # komt binnen
else:
    if boss_phase == "PHASE_1":
        boss.rect.centerx += boss_direction * 1.5
        if boss.rect.centerx > WIDTH - 60:
            boss_direction = -1
        if boss.rect.centerx < 60:
            boss_direction = 1
        boss_shoot_timer += 1
        if boss_shoot_timer >= 60:
            boss_shoot_timer = 0
            fire_boss_bullet()

Fase-overgang (HP ≤ 66%)

Check of de baas van fase moet wisselen. Roep dit aan na de boss update:

hp_ratio = boss_hp / boss_hp_max
if hp_ratio <= 0.66 and boss_phase == "PHASE_1":
    boss_phase = "PHASE_2"
    boss_phase_transition_timer = 20

Tijdens een fase-overgang laat je de boss knipperen:

if boss_phase_transition_timer > 0 and boss_phase_transition_timer % 4 < 2:
    flash_surf = pygame.Surface(boss.image.get_size(), pygame.SRCALPHA)
    flash_surf.fill((255, 255, 255, 180))
    screen.blit(flash_surf, (boss.rect.x, boss.rect.y))

Fase 2 (HP 17-33, 66-33%)

De boss beweegt sneller, vuurt vaker en spawnt minions:

elif boss_phase == "PHASE_2":
    boss.rect.centerx += boss_direction * 3
    ...
    boss_shoot_timer += 1
    if boss_shoot_timer >= 40:
        boss_shoot_timer = 0
        fire_boss_bullet()
    boss_minion_spawn_timer += 1
    if boss_minion_spawn_timer >= 180:
        boss_minion_spawn_timer = 0
        spawn_boss_minion()

Een minion spawnen:

def spawn_boss_minion():
    minion = GameObject('boss_minion.png', random.randint(30, WIDTH - 30), -30)
    minion.speed = random.uniform(1, 2)
    boss_minions.append(minion)

Fase 3 (HP 0-16, 33-0%)

De boss is enraged (woedend): vuurt twee kogels tegelijk, spawn sneller minions, en het scherm schudt:

elif boss_phase == "PHASE_3":
    boss.rect.centerx += boss_direction * 4
    ...
    boss_shoot_timer += 1
    if boss_shoot_timer >= 20:
        boss_shoot_timer = 0
        fire_boss_bullet()
        if boss_hp < boss_hp_max * 0.15:
            fire_boss_bullet()
    ...

Screen shake: voeg een willekeurige offset toe aan ALLE teken-coördinaten:

if boss_phase == "PHASE_3":
    screen_shake_x = random.randint(-3, 3)
    screen_shake_y = random.randint(-3, 3)
else:
    screen_shake_x = 0
    screen_shake_y = 0

Pas alle draw functies aan om offset_x en offset_y parameters te accepteren. Bijvoorbeeld draw_stars(screen, screen_shake_x, screen_shake_y).

Check

Speel tot wave 5. Zie je de boss door 3 fases gaan? Fase 1: langzaam, af en toe een kogel. Fase 2: sneller + minions. Fase 3: scherm schudt, regen van kogels.

Stap 4: 💀 Expert — Boss verslaan

Als de boss 0 HP bereikt, start de exploding sequence:

if boss_hp <= 0:
    boss_phase = "EXPLODING"
    boss_defeated_timer = 90
    boss_phase_transition_timer = 60
    score += 500
    explode_sound.play()

Tijdens EXPLODING spawn je elke 10 frames een particle-burst:

if boss_phase == "EXPLODING":
    if boss_phase_transition_timer > 0 and boss_phase_transition_timer % 10 == 0:
        spawn_particles(
            boss.rect.centerx + random.randint(-40, 40),
            boss.rect.centery + random.randint(-40, 40),
            random.randint(10, 20),
            [(255, 255, 0), (255, 100, 0), (255, 0, 0), (255, 255, 255)],
            (2, 6), (3, 7), (20, 40),
        )

Toon “BOSS DEFEATED!” bovenaan het scherm:

if boss_defeated_timer > 0:
    defeat_msg = big_font.render("BOSS DEFEATED!", True, (255, 255, 0))
    screen.blit(defeat_msg, (WIDTH // 2 - defeat_msg.get_width() // 2, HEIGHT // 2 - 100))

Let op: zet na de baas de gewone vijand-spawning weer aan. De wave gaat door naar de volgende.

Check

Versla de boss. Zie je de explosie-animatie, de “BOSS DEFEATED!” tekst en +500 score? Gaan de gewone waves daarna verder?

Showcase

Laat aan een coach zien dat:

  • Boss verschijnt om de 5 waves met HP-balk
  • 3 fases met eigen gedrag (langzaam → minions → enraged)
  • Doelzoekende kogels die naar de speler vliegen
  • Screen shake in fase 3
  • Minions spawnen in fase 2 en 3
  • Boss defeated: explosion sequence, “BOSS DEFEATED!”, +500 score

Tot de volgende keer!

“Volgende keer: de gepolijste versie. Geluid, menu-scherm, high scores — en jouw eigen draai aan de game.”

Neem mee naar huis

Probeer thuis één van deze uitbreidingen:

  1. Makkelijk: verander de boss-kleur of -grootte via de image.
  2. Middel: voeg een vierde fase toe (HP < 10%) waarin de boss teleporteert.
  3. Lastig: laat de boss delay-bullets schieten die een spoor achterlaten.
  4. Erg lastig: voeg een tweede boss-type toe die in een andere wave verschijnt.

Download Dojo Defender

1 juli 2026, 22:43

Dojo Defender: Sessie 7

Vandaag bepaal jij wat er in Dojo Defender gebeurt. Geen stap-voor-stap instructies — kies een feature, plan hem, en bouw hem.

Je start van de complete Dojo Defender met alles erop en eraan: schip, asteroïden, 3 vijandtypes, waves, particles, parallax sterren, en een eindbaas met 3 fases.

Dit is jouw game. Maak er iets van.

Kies wat jij wil bouwen

Hieronder staan ideeën. Kies er één — of bedenk je eigen feature.

Makkelijk

  • Power-ups: vijanden laten soms (20%) een power-up vallen. Rood = spread shot (3 kogels), groen = schild, geel = speed boost. Power-ups werken 5 seconden.
  • Moeilijkheidsgraad: maak het spel makkelijker (meer levens, langzamere vijanden) of moeilijker (minder levens, snellere spawns).
  • Geluid: voeg een power-up geluid toe (powerup.wav), een game-over geluid, of achtergrondmuziek die versnelt bij hogere waves.

Gemiddeld

  • High score: bewaar de hoogste score in een bestand (highscore.txt). Toon hem op het menu en game-over scherm. Alleen overschrijven als de nieuwe score hoger is.
  • Moeilijkheidsschalen: maak het spel elke wave iets moeilijker — snellere vijanden, kleinere asteroïden, vaker spawnen.
  • Gifte vijanden: een nieuwe vijandsoort die een spoor van giftige deeltjes achterlaat.
  • Ship-animatie: het schip kan maar 3 kogels tegelijk schieten. Laad een “heat” indicator of maak een cooldown.

Moeilijk

  • Tweede boss: maak een nieuwe boss die op een andere wave verschijnt (bijv. wave 7). Ander uiterlijk, ander gedrag.
  • Power-up systeem uitbreiden: voeg een 4e power-up toe (bijv. “time freeze” — vijanden bevriezen voor 3 seconden).
  • Combo-systeem: hoe sneller je vijanden doodt, hoe meer punten. Een “combo multiplier” die oploopt.
  • Baas heeft eigen power-up drops: de baas laat een zeldzame power-up vallen als hij verslagen wordt.

Eigen idee

Heb je een eigen idee? Top. Bespreek het kort met een coach en begin met bouwen.

Tips voor het plannen van je feature

Voor je begint met code schrijven, beantwoord deze vragen:

  1. Wat moet de speler zien? (een nieuwe power-up, een tekst, een icoon?)
  2. Wanneer gebeurt het? (als je SPACE indrukt, als een vijand sterft, als wave 10 begint?)
  3. Hoe lang duurt het? (5 seconden? Permanent? Tot de volgende wave?)
  4. Wat moet er veranderen aan de code? (nieuwe variabelen? een nieuwe lijst? een timer?)
  5. Hoe weet ik dat het werkt? (wat is de “check” — wat moet ik zien om te weten dat het af is?)

Schrijf je antwoorden op papier of in commentaar bovenaan het bestand.

Cheatsheets van vorige sessies

Kun je iets niet meer herinneren? Gebruik de cheatsheets van vorige keren:

Coach-geleide planning

Dit deel vul je samen met een coach in voordat je begint met coderen.

Mijn feature: _________________________________________________

Ik kies:   Makkelijk  /  Gemiddeld  /  Moeilijk  /  Eigen idee

Nieuwe variabelen die ik nodig heb:
- __________________________________
- __________________________________
- __________________________________

Nieuwe afbeeldingen of geluiden:
- __________________________________
- __________________________________

Check: hoe weet ik dat het werkt?
- __________________________________

Showcase

Aan het einde van de sessie laat je aan een coach en je buddy zien wat je hebt gebouwd. Vertel in 30 seconden:

  • Wat je wilde maken
  • Of het gelukt is
  • Wat het moeilijkst was

Stappenplan (voor als je er zelf niet uitkomt)

  1. Open main.py in Thonny en klik Run — werkt het nog?
  2. Maak een kopie van de game-map voor het geval je vastloopt.
  3. Begin met de kleinste mogelijke verandering (bijv. een variabele toevoegen).
  4. Test. Werkt het? → Volgende stap. Werkt het niet? → Vraag een coach.

Tot de volgende keer!

“Volgende keer: een compleet nieuw spel. Wat wordt het?”

1 juli 2026, 22:43

Voortgang per sessie

DD SessieNa curriculumWat is er nieuw?Snapshot
1S2Ruimteschip + asteroïden + leven + explosies(volgt)
2S3Schieten + vijanden + score + waves(volgt)
3S4 (Pygame)Herbouwd in Pygame met sprites + geluid(volgt)
4S5Menu + 3 vijandtypes(volgt)
5S6Parallax sterren + particles + splitsende asteroïden(volgt)
6S7Boss fight met 3 fases + HP-balk(volgt)
7S8Jouw eigen versie (power-ups, high score, …)(volgt)