Keyboard Input · Canvas Rendering · GameEnv Configuration · API Integration · Asynchronous I/O · JSON Parsing
Keyboard events are captured with addEventListener on the document. The SPACE key triggers laser attacks; WASD is handled by the engine's Player class.
// Keyboard event listener registered in initialize()
this.boundKeyDown = this.handleKeyDown.bind(this);
document.addEventListener('keydown', this.boundKeyDown);
handleKeyDown(event) {
if (event.code === 'Space') {
this.attackRequested = true; // Boolean flag set on keypress
event.preventDefault(); // stop page from scrolling
}
}
// Listener is cleaned up in destroy() to prevent memory leaks
document.removeEventListener('keydown', this.boundKeyDown);
Simulate a key event system and nested condition logic like your play attack cooldown.
All characters are drawn to the HTML5 Canvas each frame. The draw() method is inherited from GameObject and called inside the overridden update(). The hitbox overlay uses the Canvas 2D context directly.
// draw() is inherited — called every frame inside update()
update() {
this.draw(); // Canvas API — renders sprite to canvas element
// ...
}
// Direct Canvas API usage in drawHitbox()
const ctx = this.gameEnv?.ctx;
ctx.strokeRect(this.position.x, this.position.y, this.width, this.height);
Canvas API methods can't run in a code runner (no DOM), but this simulates the draw call sequence so you can see what happens each frame.
The game environment is initialized in the main entry file. Canvas size, level classes, and server URIs are all passed in via the environment object.
// 03-v1-1-PeppaPig.md — game entry point
const environment = {
path: "{{site.baseurl}}",
pythonURI: pythonURI, // String — backend server
javaURI: javaURI, // String — backend server
fetchOptions: fetchOptions, // Object — shared fetch config
gameContainer: document.getElementById("gameContainer"),
gameCanvas: document.getElementById("gameCanvas"),
gameLevelClasses: [PeppaLevel1, PeppaLevel2, PeppaLevel3] // Array
};
Game.main(environment); // launches the game loop
Run this to see how the config object drives the game. Try reordering levelClasses to change the level sequence.
An API (Application Programming Interface) is a way for your code to talk to an external service over the internet. You send a request, the service sends back data — usually as JSON. In the game, this same pattern is used to save scores and fetch leaderboard data. To show how APIs work in a fun way, I used the public iTunes Search API to search for songs and play previews directly in the browser.
The iTunes Search API is free and requires no API key. You send a GET request with a search term, and it returns a JSON object with an array of matching tracks — each one includes the song name, artist, album art, and a 30-second previewUrl you can feed directly to an Audio object.
// Step 1 — build the request URL with a search term
const url = 'https://itunes.apple.com/search?term=every+breath+you+take+police&entity=song&limit=1';
// Step 2 — fetch() sends the HTTP GET request (async — we must await it)
const response = await fetch(url);
// Step 3 — parse the JSON body (also async)
const data = await response.json();
// Step 4 — pull the first result out of the results array
const track = data.results[0];
// Step 5 — use the previewUrl to play the song
const audio = new Audio(track.previewUrl);
audio.play();
console.log(`Now playing: ${track.trackName} by ${track.artistName}`);
This simulates the iTunes API response so you can see how the data looks and how to read it. The real version uses fetch().
/**
* Searches the iTunes API for a song and plays the preview.
* @param {string} searchTerm - e.g. "every breath you take police"
* @returns {Promise<void>}
*/
async fetchAndPlay(searchTerm) {
try {
const url = `https://itunes.apple.com/search?term=${encodeURIComponent(searchTerm)}&entity=song&limit=1`;
const response = await fetch(url); // async HTTP GET
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json(); // parse JSON response
const { trackName, artistName, previewUrl } = data.results[0]; // destructuring
if (!previewUrl) throw new Error('No preview available');
new Audio(previewUrl).play();
console.log(`Playing: ${trackName} — ${artistName}`);
} catch (error) { // API error handling
console.error(`[iTunes] Fetch failed: ${error.message}`);
}
}
fetchAndPlay('every breath you take police');
await pauses until the response arrives. Destructuring { trackName, artistName, previewUrl } pulls only what's needed.
Run this to see async/await with a simulated API call. The await pauses execution until the Promise resolves.
The game's PeppaMusic class uses this exact same pattern — it calls the iTunes API to fetch background music previews and plays them during gameplay. The same fetch() → await response.json() → extract field → play flow powers both the leaderboard and the in-game music.
// Parsing the iTunes API JSON response
const data = await response.json(); // JSON.parse() equivalent
// Object destructuring — pull only the fields we need
const { trackName, artistName, previewUrl } = data.results[0];
// Leaderboard response parsing
const scores = await response.json(); // Array of score objects
scores.forEach(({ player, score, date }) => {
console.log(`${player}: ${score} on ${date}`); // destructuring + template literal
});
response.json() calls JSON.parse() automatically — it turns the raw text the API sends into a JavaScript object you can read. Destructuring pulls out only the fields you need.
Run this to see JSON.parse() and object destructuring. Try adding a new field to the JSON string and reading it.
Click the button to make a real iTunes API call and play the 30-second preview in your browser.
JSON Homework - Demonstrates JSON parsing and serialization, directly applicable to API integration where data is sent and received as JSON objects.