🐉 Multiplayer Boss Battle — CPT Reference

Akhil  ·  Scrum Master / Multiplayer Developer

Skill A — 55-Second Video Skill B — Written Response
How bullets connect to the boss battle: Students play the Snakes & Ladders board game, land on lesson squares, learn AP CSP concepts, and answer quiz questions correctly to earn bullets. Those bullets are their ammo when they enter the Boss Battle arena — knowledge literally powers their attacks.

⏱ Skill A — 55-Second Video Script

Silent screen recording · text captions edited in post · no narration needed

What to Record

Open two browser tabs (or have a second player join). Show the full flow: lobby → battle start → movement sync → shooting → victory. Record your screen at normal speed — the demo is self-explanatory with captions.

Shot-by-Shot Breakdown

TimestampWhat You Do On ScreenCaption to Add in EditorCaption Position
0:00 – 0:08 Open boss-battle.html. The lobby screen appears — sidebar shows player list and chat panel. A second player is already visible in the list. Multiplayer Boss Battle · up to 10 players · real-time WebSocket Lower-third banner
0:08 – 0:17 Type a message in the chat box and press Enter. The message appears instantly in both players' chat panels. Input: chat message → Output: broadcast to all players in the room Lower-third banner
0:17 – 0:28 Click Start Battle. Canvas expands. Two player sprites spawn at different positions. Move with WASD — the second player's sprite moves in real time on your screen. Input: keyboard → position sent every 100ms → Output: all players update instantly Upper banner
0:28 – 0:35 Click or press Space to fire at the boss. Show the boss health bar dropping for both players simultaneously. Bullets earned from AP CSP questions become ammo · Boss HP synced to all players Lower-third banner
0:35 – 0:40 Let the boss hit your player — the HUD heart/lives counter visibly drops. A notification pops in the top-right corner of the screen. Output: lives lost → HUD updates instantly for that player Lower-third banner
0:40 – 0:44 A powerup icon appears on the canvas — walk over it. A system notification slides in from the right edge of the screen confirming the pickup. Output: server emits powerup event → notification rendered in real time Top-right corner label (point arrow at the notification)
0:44 – 0:48 Boss health hits 0. Victory overlay appears with per-player stats (damage dealt, bullets used). Boss defeated! · Server aggregates every player's stats Center overlay caption
0:48 – 0:55 Show the victory screen / confetti. Pan to the Hall of Champions leaderboard if possible. Output: victory stored in Hall of Champions · knowledge = power Lower-third banner

Caption style: White bold text on a semi-transparent black bar. Keep each caption on screen for its full timestamp range. No music or narration needed.


📝 Skill B — Written Response

Input · Output · Procedure · Algorithm (Sequencing, Selection, Iteration) · List · CPT Checklist

Program Purpose

The Boss Battle is the payoff moment of the Snakes & Ladders learning game. After students move around the board, land on lesson squares, and answer AP CSP questions correctly, they earn bullets. Those bullets are their ammunition when they enter the Boss Battle — up to 10 players fighting together in real time to defeat a dragon boss. The system is built on WebSockets (Socket.IO) so that every player’s movement, shots, chat messages, and the boss’s falling health bar are immediately visible to the whole team.

Key files:

Backend Logicsocketio_handlers/boss_battle.py
REST APIapi/boss_battle.py
Frontendhacks/snakes/boss-battle.html
DB Modelsmodel/boss_room.py

Input → Output

InputHow It ArrivesOutput
Player clicks "Start Battle" POST /api/boss/join — sends bullets count, character, lives Server creates / joins a BossRoom record, returns room_id to the browser
Browser emits boss_join_room socket event WebSocket · includes player data (username, character, position) Server allocates a safe spawn position, adds player to live boss_battles dict, sends boss_room_state to the joiner and boss_player_joined to everyone else already in the room
WASD / arrow keys held down boss_player_move socket event every 100 ms · carries x, y Server resolves collisions, stores new position, emits boss_player_position to all other players — their canvases redraw the moving sprite
Mouse click / Space bar (shoot) boss_player_shoot event · bulletX, bulletY, dx, dy, character Server forwards boss_player_bullet to all others — they render the projectile flying across their screen
Text typed in chat box, Enter pressed boss_chat_send event · content, room_id Server broadcasts boss_chat_message to all other players in the room — message appears in their sidebar instantly
Boss health reaches 0 Triggered by accumulated damage from all players' shots Server emits boss_defeated with every player's stats (damage dealt, bullets fired, lives lost) — victory screen and confetti render for the whole team

Procedure with Parameters

resolve_player_collision is the core server-authoritative function that prevents players from overlapping each other or walking through the boss. It is called inside the movement handler for every other player in the room on every position update.

# socketio_handlers/boss_battle.py

def resolve_player_collision(desired_x, desired_y, other_x, other_y, min_dist):
    """
    Pushes a player out of overlap with another player or the boss.

    Parameters:
      desired_x, desired_y  — where the player wants to move
      other_x, other_y      — position of the object they might overlap
      min_dist              — minimum allowed distance (sum of both radii)

    Returns: (adjusted_x, adjusted_y, did_collide)
    """
    dx   = desired_x - other_x
    dy   = desired_y - other_y
    dist = math.sqrt(dx * dx + dy * dy)   # distance between the two centers

    if dist < 0.001:                       # edge case: exact overlap
        return other_x + min_dist, desired_y, True

    if dist >= min_dist:                   # no collision — keep desired position
        return desired_x, desired_y, False

    overlap  = min_dist - dist            # how far they're overlapping
    nx, ny   = dx / dist, dy / dist       # unit vector pointing away
    return desired_x + nx * overlap, desired_y + ny * overlap, True

This function has 5 parameters and a return value — meeting the College Board requirement for a student-developed procedure. It is called from the movement handler and also from the spawn allocator.


Algorithm — Sequencing, Selection, and Iteration

All three appear together in handle_player_move, the handler that runs every time any player sends a position update:

# socketio_handlers/boss_battle.py

@socketio.on('boss_player_move')
def handle_player_move(data):
    room_id = data.get('room_id')
    x, y    = data.get('x'), data.get('y')
    sid     = request.sid

    # ── SELECTION ─────────────────────────────────────────────
    # Guard: only proceed if the room exists and the player is in it
    if not room_id or room_id not in boss_battles:
        return
    if sid not in boss_battles[room_id]['players']:
        return
    if x is None or y is None:
        return

    # ── SEQUENCING ────────────────────────────────────────────
    # Step 1: clamp position to arena boundaries
    room_bounds = boss_battles[room_id].get('bounds') or normalize_boss_bounds({})
    desired_x = max(BOSS_PLAYER_RADIUS,
                    min(x, room_bounds['width'] - BOSS_PLAYER_RADIUS))
    desired_y = max(room_bounds['top'] + BOSS_PLAYER_RADIUS,
                    min(y, room_bounds['height'] - BOSS_PLAYER_RADIUS))

    # ── ITERATION ─────────────────────────────────────────────
    # Step 2: resolve collisions against every other player in the room
    for other_sid, other in boss_battles[room_id]['players'].items():
        if other_sid == sid:               # Selection: skip self
            continue
        desired_x, desired_y, _ = resolve_player_collision(
            desired_x, desired_y,
            other.get('x', desired_x), other.get('y', desired_y),
            BOSS_PLAYER_RADIUS * 2
        )

    # ── SEQUENCING (continued) ────────────────────────────────
    # Step 3: store the resolved position
    boss_battles[room_id]['players'][sid]['x'] = desired_x
    boss_battles[room_id]['players'][sid]['y'] = desired_y

    # Step 4: broadcast to everyone else — they redraw the sprite
    emit('boss_player_position',
         {'sid': sid, 'x': desired_x, 'y': desired_y},
         room=room_id, include_self=False)

Sequencing — the steps must happen in order: validate → clamp → resolve collisions → store → broadcast. Swapping any two steps would produce wrong results (e.g. broadcasting before resolving collisions would let players walk through each other).

Selection — the three guard if statements at the top stop the function early if the data is invalid, preventing crashes. The if other_sid == sid: continue inside the loop skips self-comparison.

Iteration — the for loop walks through every player currently in the room and calls resolve_player_collision for each one. As more players join, the loop naturally handles them all without any code changes.


List / Data Structure

The live game state lives in a Python dictionary called boss_battles. It acts as the single source of truth for every room:

# socketio_handlers/boss_battle.py  (in-memory, module level)

boss_battles = {
    "boss_battle_room": {
        "boss_health": 2000,
        "max_health":  2000,
        "players": {
            "socket-sid-abc": {
                "username":      "Akhil",
                "character":     "knight",
                "x": 342.5,  "y": 480.1,   # live position — updated every 100ms
                "lives":         5,
                "bullets":       30,
                "damage_dealt":  120,
                "bullets_fired": 12,
                "status":        "alive"
            },
            "socket-sid-xyz": { ... }       # up to 10 players per room
        },
        "powerups": [
            {"id": "pw1", "type": "damage", "x": 500, "y": 300}
        ],
        "bounds": {"width": 1100, "height": 600, "top": 200}
    }
}

This list (dict of dicts) is:

  • Iterated every movement event to check collisions between all players
  • Read every second to send a full player-list sync to each client
  • Mutated when players join, move, shoot, or die
  • Aggregated when the boss is defeated to produce the per-player stats screen

A parallel SQL table (BossRoom + BossPlayer in model/boss_room.py) persists join/leave records so the REST API can gate entry (you need at least 10 bullets, earned from correct answers, to fight the boss).


College Board CPT Checklist

CB RequirementHow the Boss Battle Meets It
Program purpose Students answer AP CSP questions on the board to earn bullets, then spend those bullets cooperatively in the Boss Battle — knowledge directly fuels gameplay
Input Keyboard (WASD/arrows) → movement; mouse click / Space → shoot; text box → chat; POST request → room join with bullet count
Output Other players' sprites reposition in real time; boss HP bar drops for the whole team; chat messages appear in sidebar; victory screen shows per-player damage stats
Procedure with parameter + return resolve_player_collision(desired_x, desired_y, other_x, other_y, min_dist) — 5 parameters, returns (adjusted_x, adjusted_y, did_collide)
List used meaningfully boss_battles[room_id]['players'] iterated for collision checks, queried for sync, aggregated for victory stats; not hard-coded — grows/shrinks as players join and leave
Sequencing Movement handler: validate → clamp bounds → resolve collisions → store position → broadcast — strict order, cannot be rearranged
Selection Guards in movement handler stop processing invalid data; if dist >= min_dist: return in collision function skips no-overlap case; if other_sid == sid: continue skips self-check
Iteration for other_sid, other in players.items() collision loop; 80-attempt random spawn loop; setInterval broadcasts position every 100ms on the client; room-sync fires every 1000ms
The Internet (Big Idea 4) WebSocket (Socket.IO) for real-time events; HTTP REST API (Flask) for join/leave/stats; JWT cookie authentication; CORS headers for cross-origin browser access; deployed at snakes.opencodingsociety.com

The Full Story (in one paragraph)

A student plays Snakes & Ladders, lands on a lesson square, learns a Computer Science Principles concept, and answers a multiple-choice question correctly — they earn bullets, the game's currency. Once they've collected enough bullets and reached square 25, the Boss Battle unlocks. They click Start Battle: the browser sends a POST to /api/boss/join (input), the server creates a BossRoom record, and returns a room_id. The browser then emits boss_join_room over WebSocket — the server runs allocate_boss_spawn() (an iterative random-sampling algorithm) to find a clear starting position, adds the player to the live boss_battles dict, and broadcasts their arrival to the room. From that point, every 100 ms the player's keyboard state becomes a boss_player_move event. The server runs the collision loop (iterating every other player, calling resolve_player_collision with 5 parameters), stores the resolved position, and emits boss_player_position — every other player's canvas redraws the sprite in its new location (output). When a bullet is fired, the same broadcast pattern delivers it to all screens. As the team chips away at the boss's 2000 HP, the health bar drops in real time for everyone. When it hits zero, the server emits boss_defeated with the aggregated per-player stats — bullets spent, damage dealt, lives lost — all pulled from that same players dict. The victory screen renders, the BossRoom is marked complete in the database, and the Hall of Champions leaderboard records the team's win forever. A quiz answer became a bullet; a bullet became a victory.