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 - This is how the game knows what keys the player is pressing. When a key goes down, the game marks that direction as active. When the key is released, it turns that direction off. The game keeps checking those values to decide how the player should move. This can be tested by going to console and pressing WASD to see which way the character would be moving in a game
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 object knows how to draw itself. A platform, player, and enemy can all appear differently, but the game can still call draw() on each one. This keeps the drawing code organized and makes it easier to add more objects later.
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 stores the main settings for the game, like screen size, gravity, and difficulty. GameSetup lists what objects should be placed into the level. This makes the game easier to change because important settings are kept in one place instead of being scattered everywhere.
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 - This shows how the game sends a score to the backend. The code first sends the request, then checks if it worked, then reads the response. If something goes wrong, the error handler runs so the game does not crash. This is important for the leaderboard because a failed save should not ruin the player’s game.
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 - This shows that API calls take time. The page can keep running while the request is still waiting for a response. When the response finally comes back, the code handles it. This is why games need careful API code, especially for things like saving scores or loading leaderboard data.
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 - Backend data often comes back as text first. JSON.parse() turns that text into real JavaScript data the game can use. After that, the code picks out the important parts, like user, score, and game name, so the leaderboard can show clean information instead of raw backend data.