Table of Contents
Input is how the game receives information — from the keyboard, from an API, from a config file. Output is how the game sends information back — drawing to the canvas, posting a score, displaying text.
The game listens for key presses using event listeners. When a key is pressed or released, a function runs that updates the player’s movement flags.
Assessment evidence: Key event handlers respond correctly to arrow keys, space, and WASD controls.
Explanation — addEventListener watches for keyboard events on the whole page. When a key is pressed, keydown fires and sets the matching flag to true. When the key is released, keyup fires and sets it back to false. The game loop reads these flags every frame to decide which direction to move the player.
Canvas Rendering
The Canvas API lets JavaScript draw images, shapes, and text directly onto an HTML canvas element. Every game object has a draw() method that is called every frame to paint it onto the screen.
Explanation — Each class has its own draw() method that describes what gets painted for that object. The game loop calls draw() on every object in gameObjects every frame using forEach
GameEnv Configuration
GameEnv is a central configuration object that stores the canvas size, difficulty settings, and environment state. Everything in the game reads from it instead of using hardcoded values.
// Run this to see GameEnv being created and read by game objects
const GameEnv = {
canvas: null,
width: 800,
height: 450,
difficulty: "normal",
gravity: 0.4,
isPaused: false,
create(canvasId) {
console.log(`GameEnv created — canvas: ${canvasId}, size: ${this.width}x${this.height}`);
console.log(`Difficulty: ${this.difficulty}, Gravity: ${this.gravity}`);
}
};
// GameSetup tells the level which objects to build
const GameSetup = {
player: {
data: { x: 100, y: 300, width: 48, height: 48, speed: 4 },
class: "Player",
},
enemies: [
{ data: { x: 400, y: 300, speed: 2 }, class: "Enemy" },
{ data: { x: 600, y: 300, speed: 3 }, class: "Enemy" },
],
};
GameEnv.create("gameCanvas");
console.log("\n--- Reading GameSetup ---");
console.log("Player start position:", GameSetup.player.data.x, GameSetup.player.data.y);
console.log("Number of enemies:", GameSetup.enemies.length);
GameSetup.enemies.forEach((e, i) => {
console.log(`Enemy ${i + 1} — x: ${e.data.x}, speed: ${e.data.speed}`);
});
Explanation — GameEnv holds all the values the game needs to know about the environment — canvas size, gravity, difficulty. GameEnv.create() initializes it. GameSetup is a separate config object that lists every game object the level should instantiate and what data to pass in. Instead of hardcoding values everywhere, everything reads from these two objects, so changing the difficulty or canvas size in one place updates the whole game.
API Integration
The leaderboard uses fetch to POST a new score and GET scores from a backend server. This comes directly from Leaderboard.js in the project. Every fetch is wrapped in a .then()/.catch() chain so errors are handled cleanly without crashing the game.
// This is the real submitScore method from Leaderboard.js
// It POSTs a score to the backend SCORE_COUNTER endpoint
const javaURI = "https://spring.opencodingsociety.com";
function submitScore(username, score, gameName) {
const url = `${javaURI}/api/events/SCORE_COUNTER`;
const requestBody = {
payload: {
user: username,
score: score,
gameName: gameName
}
};
console.log("Posting score to:", url);
console.log("Payload:", JSON.stringify(requestBody));
// POST to backend using .then() API chaining
fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(requestBody)
})
.then(res => {
if (!res.ok) {
throw new Error(`POST failed: ${res.status}`);
}
return res.json();
})
.then(savedEntry => {
console.log("Score saved successfully:", savedEntry);
})
.catch(error => {
// If backend is down, fall back to localStorage
console.log("Backend unavailable — saving locally:", error.message);
const stored = JSON.parse(localStorage.getItem("scores") || "[]");
stored.push({ username, score, gameName });
localStorage.setItem("scores", JSON.stringify(stored));
console.log("Score saved to localStorage as fallback");
});
}
submitScore("mario", 4500, "MarioGame");
Explanation — fetch sends an HTTP POST request to the backend. The .then() chain handles the response step by step — first checking if the response was OK, then parsing the JSON. If anything fails, .catch() runs instead of crashing the game. This is the same pattern used in the real Leaderboard.js — if the backend is unavailable, the score is saved to localStorage as a fallback so the player never loses their data.
Asynchronous I/O
async/await and .then() chains let the game wait for an API response without freezing. Leaderboard.js uses .then() chaining throughout. The key idea is that fetch runs in the background while the rest of the game keeps going.
Explanation — The key thing to notice is that line “2. This prints immediately” appears in the console before “3. Response received”. That proves fetch runs in the background — the rest of the code does not wait for it. Leaderboard.js uses .then() chaining for this reason, which lets it do sequential steps — fetch → transform → display — in a clean readable chain. async/await does the exact same thing but reads more like normal top-to-bottom code.
JSON Parsing
When the API sends back data, it arrives as a raw JSON string. JSON.parse() converts it into a real JavaScript object. In Leaderboard.js, the backend returns an array of score events that need to be transformed before display.
Explanation — Before JSON.parse(), rawResponse is just a string — you cannot read .score from it. After parsing it becomes a real JavaScript array that you can loop through and read properties from. The .map() then transforms each raw backend event into a cleaner object with just the fields the display needs. Object destructuring (const { user, score, gameName } = entry) pulls three values out in one line instead of writing three separate assignments — the same pattern used in Leaderboard.js when rendering the score table.