Input/Output (I/O)

Table of Contents

  1. Mermaid Sequence Diagram
  2. Keyboard Input
  3. Canvas Rendering
  4. GameEnv Configuration
  5. API Integration
  6. Asynchronous I/O
  7. JSON Parsing
  8. I/O in Action

Mermaid Sequence Diagram

This sequence diagram shows what happens when a coin is collected in the Local Storage Demo — from the player triggering an event all the way to the leaderboard updating.

sequenceDiagram
    participant Player
    participant Coin
    participant localStorage
    participant API
    participant Leaderboard

    Player->>Coin: collision detected
    Coin->>localStorage: setItem("coinsCollected", n+1)
    Coin->>Coin: dispatch coinCollected event
    Coin->>API: fetch POST /api/leaderboard
    API-->>Coin: 200 OK { status: "ok" }
    Coin->>Leaderboard: update display
    Leaderboard-->>Player: shows new score

I/O covers how your program interacts with users and other systems, including keyboard input, canvas rendering, and API calls.


Subrequirements

Keyboard Input

What was learned: Keyboard input lets users control games and apps by pressing keys. We use event listeners to detect when keys are pressed and trigger actions like moving paddles or starting a game. Practicing keyboard input in Pong and Snake made it clear how important responsive controls are for a good user experience.
Evidence:
// From hacks/pong.md — Input class captures all key presses with event listeners
class Input {
    constructor() {
        this.keys = {};
        document.addEventListener("keydown", e => {
            const k = e.key;
            // Prevent default scrolling for game keys
            if (k === 'ArrowUp' || k === 'ArrowDown' || k === ' ' || k === 'Spacebar') {
                e.preventDefault();
            }
            this.keys[k] = true;
        });
        document.addEventListener("keyup", e => {
            this.keys[e.key] = false;
        });
    }
    isDown(k) { return !!this.keys[k]; }
}

// From hacks/pong.md — handleInput() uses the Input class to move paddles
handleInput() {
    if (this.gameOver) return;
    if (this.input.isDown(Config.keys.p1Up))   this.paddleLeft.move(-this.paddleLeft.speed);
    if (this.input.isDown(Config.keys.p1Down))  this.paddleLeft.move(this.paddleLeft.speed);
}

Code Runner Challenge

Hit Run to see keyboard event tracking in action. This simulates the same Input class used in Pong — it logs every key state change. Try editing the key list to add your own keys.

Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Quick Quiz: Keyboard Input

  1. What JavaScript event is used to detect when a key is pressed?
    • a) keydown
    • b) keypress
    • c) keyup
  2. Why do we use event listeners for keyboard input in games?
  3. In the Pong Input class, what does the isDown(k) method check?
Show Answers 1. a) keydown 2. To respond to user actions in real time and make controls interactive. 3. Whether a specific key is currently being held down (true/false).

Canvas Rendering

What was learned: Canvas rendering is how we draw graphics, shapes, and text in games using code. By updating the canvas every frame, we can animate objects and create interactive visuals. Working with the canvas in Pong and Snake helped me understand how games display action and feedback to players.
Evidence:
  • Pong Game: Drawing paddles, ball, background, and scores
  • Snake Game: Drawing snake segments and food on a grid canvas
// From hacks/pong.md — Renderer class wraps all canvas drawing operations
class Renderer {
    constructor(ctx) { this.ctx = ctx; }

    clear(w, h) {
        this.ctx.fillStyle = Config.visuals.bg;
        this.ctx.fillRect(0, 0, w, h);
    }
    rect(r, color = Config.visuals.fg) {
        this.ctx.fillStyle = color;
        this.ctx.fillRect(r.x, r.y, r.w, r.h);
    }
    circle(ball, color = Config.visuals.fg) {
        this.ctx.fillStyle = color;
        this.ctx.beginPath();
        this.ctx.arc(ball.position.x, ball.position.y, ball.radius, 0, Math.PI * 2);
        this.ctx.closePath();
        this.ctx.fill();
    }
}

GameEnv Configuration

What was learned: Game environment configuration means setting up things like canvas size, difficulty, and other options before the game starts. This helps make games flexible and customizable for different players. Using config objects in Pong and homeworks showed how to organize and manage game settings.
Evidence:
  • Pong Game: Config object for all game settings (View Pong Game)
  • Local Storage Demo: Game environment passed as parameter to level constructors
// From hacks/pong.md — full Config object controls every aspect of the game
const Config = {
    canvas: { width: 800, height: 500 },
    paddle: { width: 10, height: 100, speed: 10.5 },
    ball: { radius: 10, baseSpeedX: 5, maxRandomY: 2, spinFactor: 0.3, maxSpeed: 15 },
    bumper: { enabledAtScore: 9, radius: 40, color: "#ed1111ff" },
    rules: { winningScore: 11 },
    visuals: { bg: "#000", fg: "#fff", text: "#fff", gameOver: "red", win: "yellow" }
};

// From _projects/local-storage/levels/localStorageDemo.js — gameEnv config
class localStorageDemo {
    constructor(gameEnv) {
        let width  = gameEnv.innerWidth;
        let height = gameEnv.innerHeight;
        let path   = gameEnv.path;
        // environment dimensions drive all sprite and background sizing
    }
}

Code Runner Challenge

Hit Run to see a GameEnv-style config object being built and used to create a game setup. This is the same pattern used in Pong and the Local Storage Demo — one config object drives the whole game.

Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

API Integration

What was learned: APIs let our programs communicate with external servers using fetch. In the Local Storage Demo, we POST scores to a leaderboard API and GET them back to display rankings. Implementing real API calls showed how games connect to the web and share data between players.
Evidence:
// From _projects/local-storage/levels/localStorageDemo.js
// submitScore sends a POST request with the player name and score
leaderboard.submitScore(playerName, total, 'CoinsCollected').catch(err => {
    console.warn('[Leaderboard] submitScore failed:', err);
});

// Leaderboard.submitScore internally uses fetch — POST/GET pattern:
async submitScore(name, score, gameName) {
    const response = await fetch('/api/leaderboard', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name, score, gameName })
    });
    if (!response.ok) throw new Error('HTTP ' + response.status);
    return response.json();
}

Code Runner Challenge

Hit Run to see a simulated API integration flow — building a request body, sending it with fetch to a public test API, and handling the response. This mirrors how the Leaderboard in the Local Storage Demo submits scores.

Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Asynchronous I/O

What was learned: Asynchronous I/O means running tasks (like network calls) without freezing the program. We use async/await and Promise chains so the game keeps running while waiting for API responses. Practicing async patterns in the leaderboard and homeworks showed how to build responsive, non-blocking programs.
Evidence:
// From _projects/local-storage/levels/localStorageDemo.js — Promise chain
leaderboard.submitScore(playerName, total, 'CoinsCollected').catch(err => {
    console.warn('[Leaderboard] submitScore failed:', err);
});

// async/await equivalent — both handle the same asynchronous fetch
async function getScores(gameName) {
    try {
        const res  = await fetch('/api/leaderboard?game=' + gameName);
        const data = await res.json();
        return data;
    } catch (err) {
        console.warn('getScores failed:', err);
    }
}

Code Runner Challenge

Hit Run to see async/await and Promise chains running side by side. Both patterns handle the same async operation — this is exactly what the leaderboard uses to submit scores without blocking the game loop.

Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

JSON Parsing

What was learned: JSON parsing converts raw API response strings into usable JavaScript objects. We use JSON.parse() to read incoming data and JSON.stringify() to send it. In the leaderboard and homeworks, parsing JSON responses let us read player scores and display them in the game.
Evidence:
// From _notebooks/JavaScriptLessons/JSON — JSON.parse and JSON.stringify
const jsonString = '{"name":"Alex","score":42,"game":"CoinsCollected"}';
const obj = JSON.parse(jsonString);
console.log(obj.name);   // "Alex"
console.log(obj.score);  // 42

// Object destructuring — cleaner way to pull out API response fields
const { name, score, game } = JSON.parse(jsonString);
console.log(name + " scored " + score + " in " + game);

// JSON.stringify to build request body (used in fetch POST calls)
const payload = JSON.stringify({ name: "Player1", score: 99 });
// → '{"name":"Player1","score":99}'

Code Runner Challenge

Hit Run to see JSON.parse() and JSON.stringify() working together — the exact pattern used when the leaderboard API response arrives and gets turned into a usable JavaScript object. Try changing the score value and run again.

Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Gamerunner — I/O in Action

This mini Snake game demonstrates all I/O concepts together: keyboard input (arrow keys), canvas rendering (drawing each frame), and game config (grid size, speed, colors). Use arrow keys to steer the snake.

Challenge

Snake game showing keyboard input, canvas rendering, and game configuration

Lines: 1 Characters: 0
Game Status: Not Started