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>
8.4 KiB
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
Modulebase 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.jsmodules 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
- Forgetting Object.seal() - Module can be modified externally
- Not validating dependencies - Module fails at runtime
- Direct module access - Use EventBus instead
- Missing required methods - Red screen error at startup
- Not cleaning up - Memory leaks on destroy
- Hardcoded paths - Use dynamic content loading
- 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