Akhil — CPT Skill A & B: Multiplayer Boss Battle
College Board Create Performance Task writeup — Skill A video script and Skill B written response for the real-time multiplayer boss battle system
- 🐉 Multiplayer Boss Battle — CPT Reference
- ⏱ Skill A — 55-Second Video Script
- 📝 Skill B — Written Response
🐉 Multiplayer Boss Battle — CPT Reference
Akhil · Scrum Master / Multiplayer Developer
Skill A — 55-Second Video Skill B — Written Response⏱ 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
| Timestamp | What You Do On Screen | Caption to Add in Editor | Caption 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:
Input → Output
| Input | How It Arrives | Output |
|---|---|---|
| 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 Requirement | How 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)
/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.