Class_generator/docs/creating-new-module.md
StillHammer 325b97060c Add LEDU Chinese course content and documentation
Add comprehensive Chinese reading course (乐读) with 4 chapters of vocabulary, texts, and exercises. Include architecture documentation for module development and progress tracking system.

Content:
- LEDU book metadata with 12 chapter outline
- Chapter 1: Food culture (民以食为天) - 45+ vocabulary, etiquette
- Chapter 2: Shopping (货比三家) - comparative shopping vocabulary
- Chapter 3: Sports & fitness (生命在于运动) - exercise habits
- Chapter 4: Additional vocabulary and grammar

Documentation:
- Architecture principles and patterns
- Module creation guide (Game, DRS, Progress)
- Interface system (C++ style contracts)
- Progress tracking and prerequisites

Game Enhancements:
- MarioEducational helper classes (Physics, Renderer, Sound, Enemies)
- VocabularyModule TTS improvements
- Updated CLAUDE.md with project status

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 07:25:53 +08:00

8.4 KiB

Creating New Modules

🎮 Game Module Template

Basic Structure

import Module from '../core/Module.js';

class GameName extends Module {
    constructor(name, dependencies, config) {
        super(name, ['eventBus']); // Declare dependencies

        // Validate dependencies
        if (!dependencies.eventBus) {
            throw new Error('GameName requires EventBus dependency');
        }

        this._eventBus = dependencies.eventBus;
        this._config = config;

        Object.seal(this); // Prevent modification
    }

    async init() {
        this._validateNotDestroyed();

        // Set up event listeners
        this._eventBus.on('game:start', this._handleStart.bind(this), this.name);

        this._setInitialized();
    }

    async destroy() {
        this._validateNotDestroyed();

        // Cleanup: remove event listeners, DOM elements, timers
        this._eventBus.off('game:start', this._handleStart, this.name);

        this._setDestroyed();
    }

    // Private methods
    _handleStart(event) {
        this._validateInitialized();
        // Game logic here
    }
}

export default GameName;

Registration in Application.js

modules: [
    {
        name: 'gameName',
        path: './games/GameName.js',
        dependencies: ['eventBus'],
        config: {
            difficulty: 'medium',
            scoreToWin: 100
        }
    }
]

📋 DRS Exercise Module Template

Using DRSExerciseInterface

import DRSExerciseInterface from '../DRS/interfaces/DRSExerciseInterface.js';

class MyExercise extends DRSExerciseInterface {
    constructor() {
        super('MyExercise');

        // Internal state
        this.score = 0;
        this.attempts = 0;
        this.startTime = null;
        this.container = null;
    }

    // ⚠️ REQUIRED - Initialize exercise
    async init(config, content) {
        this.config = config;
        this.content = content;
        this.startTime = Date.now();

        // Validate content
        if (!content || !content.question) {
            throw new Error('MyExercise requires content with question');
        }
    }

    // ⚠️ REQUIRED - Render UI
    async render(container) {
        this.container = container;

        container.innerHTML = `
            <div class="exercise-container">
                <h2>${this.content.question}</h2>
                <input type="text" id="answer-input" />
                <button id="submit-btn">Submit</button>
            </div>
        `;

        // Event listeners
        container.querySelector('#submit-btn').addEventListener('click', () => {
            const answer = container.querySelector('#answer-input').value;
            this.handleUserInput('submit', { answer });
        });
    }

    // ⚠️ REQUIRED - Clean up
    async destroy() {
        if (this.container) {
            this.container.innerHTML = '';
        }
    }

    // ⚠️ REQUIRED - Validate answer
    async validate(userAnswer) {
        this.attempts++;

        const isCorrect = userAnswer.toLowerCase() === this.content.correctAnswer.toLowerCase();
        const score = isCorrect ? 100 - (this.attempts - 1) * 10 : 0;

        return {
            isCorrect,
            score: Math.max(score, 0),
            feedback: isCorrect ? 'Correct!' : 'Try again',
            explanation: `The correct answer is: ${this.content.correctAnswer}`
        };
    }

    // ⚠️ REQUIRED - Get results
    getResults() {
        return {
            score: this.score,
            attempts: this.attempts,
            timeSpent: Date.now() - this.startTime,
            completed: this.score > 0,
            details: {
                question: this.content.question,
                correctAnswer: this.content.correctAnswer
            }
        };
    }

    // ⚠️ REQUIRED - Handle user input
    handleUserInput(event, data) {
        if (event === 'submit') {
            this.validate(data.answer).then(result => {
                this.score = result.score;
                this.displayFeedback(result);
            });
        }
    }

    // ⚠️ REQUIRED - Mark as completed
    async markCompleted(results) {
        // Save to progress system
        await window.app.getCore().progressTracker.markExerciseCompleted(
            'my-exercise',
            this.content.id,
            results
        );
    }

    // ⚠️ REQUIRED - Get progress
    getProgress() {
        return {
            percentage: this.score > 0 ? 100 : 0,
            currentStep: 1,
            totalSteps: 1,
            itemsCompleted: this.score > 0 ? 1 : 0,
            itemsTotal: 1
        };
    }

    // ⚠️ REQUIRED - Get exercise type
    getExerciseType() {
        return 'my-exercise';
    }

    // ⚠️ REQUIRED - Get exercise config
    getExerciseConfig() {
        return {
            type: 'my-exercise',
            difficulty: this.config?.difficulty || 'medium',
            estimatedTime: 120, // seconds
            prerequisites: [],
            metadata: {
                hasAI: false,
                requiresInternet: false
            }
        };
    }

    // Helper methods
    displayFeedback(result) {
        const feedbackDiv = this.container.querySelector('.feedback') ||
                           document.createElement('div');
        feedbackDiv.className = 'feedback';
        feedbackDiv.textContent = result.feedback;

        if (!this.container.querySelector('.feedback')) {
            this.container.appendChild(feedbackDiv);
        }
    }
}

export default MyExercise;

📊 Progress Item Template

Using ProgressItemInterface

import ProgressItemInterface from '../DRS/interfaces/ProgressItemInterface.js';

class MyCustomItem extends ProgressItemInterface {
    constructor(id, metadata) {
        super('my-custom-item', id, metadata);
    }

    // ⚠️ REQUIRED - Validate item data
    validate() {
        if (!this.metadata.requiredField) {
            throw new Error('MyCustomItem requires requiredField in metadata');
        }
        return true;
    }

    // ⚠️ REQUIRED - Convert to JSON
    serialize() {
        return {
            ...this._getBaseSerialization(),
            customData: this.metadata.custom,
            timestamp: Date.now()
        };
    }

    // ⚠️ REQUIRED - Return item weight
    getWeight() {
        return ProgressItemInterface.WEIGHTS['my-custom-item'] || 5;
    }

    // ⚠️ REQUIRED - Check prerequisites
    canComplete(userProgress) {
        // Check if user has completed prerequisites
        const prerequisite = this.metadata.prerequisite;
        if (prerequisite) {
            return userProgress.hasCompleted(prerequisite);
        }
        return true;
    }
}

export default MyCustomItem;

Checklist for New Modules

For Game Modules

  • Extends Module base class
  • Validates dependencies in constructor
  • Uses Object.seal(this) at end of constructor
  • Implements init() and calls _setInitialized()
  • Implements destroy() and calls _setDestroyed()
  • Uses EventBus for all communication
  • No direct access to other modules
  • Registered in Application.js modules array

For DRS Exercise Modules

  • Extends DRSExerciseInterface
  • Implements all 10 required methods
  • Validates content in init()
  • Cleans up in destroy()
  • Returns correct format from validate()
  • Integrates with progress system
  • Tested with ImplementationValidator

For Progress Items

  • Extends ProgressItemInterface
  • Implements all 4 required methods
  • Validates data correctly
  • Returns proper weight
  • Checks prerequisites properly
  • Added to ImplementationValidator

🚨 Common Mistakes to Avoid

  1. Forgetting Object.seal() - Module can be modified externally
  2. Not validating dependencies - Module fails at runtime
  3. Direct module access - Use EventBus instead
  4. Missing required methods - Red screen error at startup
  5. Not cleaning up - Memory leaks on destroy
  6. Hardcoded paths - Use dynamic content loading
  7. Skipping ImplementationValidator - Interface violations not caught

📚 Examples in Codebase

  • Game Module: src/games/FlashcardLearning.js
  • DRS Exercise: src/DRS/exercise-modules/VocabularyModule.js
  • Progress Item: src/DRS/services/ProgressItemInterface.js
  • Validation: src/DRS/services/ImplementationValidator.js