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>
255 lines
8.3 KiB
JavaScript
255 lines
8.3 KiB
JavaScript
/**
|
|
* Boss.js
|
|
* Colossal Boss enemy for level 6
|
|
* Large immobile boss with turrets and special attacks
|
|
*/
|
|
|
|
export class Boss {
|
|
/**
|
|
* Generate the colossal boss for level 6
|
|
* @param {Object} level - Level data
|
|
* @param {number} levelWidth - Width of the level
|
|
* @param {number} canvasHeight - Canvas height
|
|
* @returns {Object} - Boss data with turrets
|
|
*/
|
|
static generate(level, levelWidth, canvasHeight) {
|
|
console.log(`👹 Generating Colossal Boss for level 6!`);
|
|
|
|
// Boss positioned in center-right of level to block the path
|
|
const bossX = levelWidth * 0.6; // 60% through the level
|
|
const bossY = canvasHeight - 250; // Standing on ground
|
|
const bossWidth = 150;
|
|
const bossHeight = 200;
|
|
|
|
const boss = {
|
|
x: bossX,
|
|
y: bossY,
|
|
width: bossWidth,
|
|
height: bossHeight,
|
|
health: 5, // Takes 5 hits
|
|
maxHealth: 5,
|
|
color: '#2F4F4F', // Dark slate gray
|
|
type: 'colossus',
|
|
active: true,
|
|
// Collision boxes (knees for damage)
|
|
leftKnee: {
|
|
x: bossX + 20,
|
|
y: bossY + bossHeight - 60,
|
|
width: 40,
|
|
height: 40
|
|
},
|
|
rightKnee: {
|
|
x: bossX + bossWidth - 60,
|
|
y: bossY + bossHeight - 60,
|
|
width: 40,
|
|
height: 40
|
|
},
|
|
// Boss behavior
|
|
lastTurretShot: Date.now(),
|
|
turretCooldown: 2000, // Turrets fire every 2 seconds
|
|
lastMinionLaunch: Date.now(),
|
|
minionCooldown: 4000, // Launch minions every 4 seconds
|
|
// Visual
|
|
eyeColor: '#FF0000', // Red glowing eyes
|
|
isDamaged: false,
|
|
damageFlashTimer: 0,
|
|
enraged: false // Becomes enraged at low health
|
|
};
|
|
|
|
// Generate turrets on the boss (2 turrets)
|
|
const turrets = [
|
|
{
|
|
x: bossX + 30,
|
|
y: bossY + 50,
|
|
width: 25,
|
|
height: 25,
|
|
color: '#8B4513',
|
|
type: 'turret',
|
|
lastShot: Date.now(),
|
|
shootCooldown: 2500 // Individual cooldown
|
|
},
|
|
{
|
|
x: bossX + bossWidth - 55,
|
|
y: bossY + 50,
|
|
width: 25,
|
|
height: 25,
|
|
color: '#8B4513',
|
|
type: 'turret',
|
|
lastShot: Date.now(),
|
|
shootCooldown: 3000 // Slightly different timing
|
|
}
|
|
];
|
|
|
|
console.log(`👹 Colossal Boss spawned at x=${bossX.toFixed(0)}, health=${boss.health}`);
|
|
console.log(`🔫 ${turrets.length} turrets mounted on boss`);
|
|
|
|
return { boss, turrets };
|
|
}
|
|
|
|
/**
|
|
* Update boss behavior
|
|
* @param {Object} boss - Boss object
|
|
* @param {Array} turrets - Boss turrets
|
|
* @param {Object} mario - Mario object
|
|
* @param {Array} projectiles - Projectiles array
|
|
* @param {Array} flyingEyes - Flying eyes array (for spawning minions)
|
|
* @param {Function} playSound - Sound callback
|
|
*/
|
|
static update(boss, turrets, mario, projectiles, flyingEyes, playSound) {
|
|
if (!boss || !boss.active) return;
|
|
|
|
const currentTime = Date.now();
|
|
|
|
// Update boss state
|
|
if (boss.health < boss.maxHealth / 2) {
|
|
boss.enraged = true;
|
|
}
|
|
|
|
// Damage flash animation
|
|
if (boss.isDamaged) {
|
|
boss.damageFlashTimer--;
|
|
if (boss.damageFlashTimer <= 0) {
|
|
boss.isDamaged = false;
|
|
}
|
|
}
|
|
|
|
// Update turrets - they shoot projectiles at Mario
|
|
turrets.forEach(turret => {
|
|
if (currentTime - turret.lastShot > turret.shootCooldown) {
|
|
// Calculate trajectory to Mario
|
|
const dx = mario.x - turret.x;
|
|
const dy = mario.y - turret.y;
|
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
const speed = boss.enraged ? 6 : 4; // Faster when enraged
|
|
const velocityX = (dx / distance) * speed;
|
|
const velocityY = (dy / distance) * speed;
|
|
|
|
projectiles.push({
|
|
x: turret.x + turret.width / 2,
|
|
y: turret.y + turret.height / 2,
|
|
velocityX: velocityX,
|
|
velocityY: velocityY,
|
|
radius: 10,
|
|
color: boss.enraged ? '#FF0000' : '#FF8C00', // Red when enraged, orange normally
|
|
type: 'boss_projectile',
|
|
life: 300
|
|
});
|
|
|
|
turret.lastShot = currentTime;
|
|
if (playSound) playSound('enemy_defeat');
|
|
console.log(`🔫 Boss turret fired at Mario!`);
|
|
}
|
|
});
|
|
|
|
// Spawn flying eye minions periodically
|
|
if (boss.enraged && currentTime - boss.lastMinionLaunch > boss.minionCooldown) {
|
|
// Spawn a flying eye minion
|
|
const minionX = boss.x + boss.width / 2;
|
|
const minionY = boss.y + 50;
|
|
|
|
flyingEyes.push({
|
|
x: minionX,
|
|
y: minionY,
|
|
width: 25,
|
|
height: 25,
|
|
velocityX: (Math.random() - 0.5) * 2,
|
|
velocityY: -3, // Fly upward initially
|
|
color: '#8B0000', // Dark red for minions
|
|
pupilColor: '#000000',
|
|
type: 'flying_eye_minion',
|
|
health: 1,
|
|
maxHealth: 1,
|
|
chaseDistance: 300,
|
|
chaseSpeed: 3,
|
|
idleSpeed: 1,
|
|
lastDirectionChange: Date.now(),
|
|
directionChangeInterval: 2000,
|
|
isChasing: false,
|
|
dashCooldown: 0,
|
|
dashDuration: 0,
|
|
isDashing: false,
|
|
dashSpeed: 6,
|
|
lastDashTime: Date.now(),
|
|
dashInterval: 4000,
|
|
blinkTimer: 0,
|
|
isBlinking: false
|
|
});
|
|
|
|
boss.lastMinionLaunch = currentTime;
|
|
if (playSound) playSound('powerup');
|
|
console.log(`👁️ Boss spawned a flying eye minion!`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Damage the boss
|
|
* @param {Object} boss - Boss object
|
|
* @param {Function} playSound - Sound callback
|
|
* @returns {boolean} - True if boss was defeated
|
|
*/
|
|
static damage(boss, playSound) {
|
|
if (!boss || !boss.active) return false;
|
|
|
|
boss.health--;
|
|
boss.isDamaged = true;
|
|
boss.damageFlashTimer = 15; // Flash for 15 frames
|
|
|
|
if (playSound) playSound('enemy_defeat');
|
|
console.log(`👹 Boss damaged! Health: ${boss.health}/${boss.maxHealth}`);
|
|
|
|
if (boss.health <= 0) {
|
|
boss.active = false;
|
|
console.log(`👹 BOSS DEFEATED!`);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check collision between Mario and boss knees (weak points)
|
|
* @param {Object} mario - Mario object
|
|
* @param {Object} boss - Boss object
|
|
* @returns {boolean} - True if Mario hit a knee from above
|
|
*/
|
|
static checkKneeCollision(mario, boss) {
|
|
if (!boss || !boss.active) return false;
|
|
|
|
// Check if Mario is jumping down onto knees
|
|
const isFalling = mario.velocityY > 0;
|
|
|
|
// Check left knee
|
|
const hitLeftKnee = this._isCollidingRectRect(mario, boss.leftKnee) && isFalling;
|
|
|
|
// Check right knee
|
|
const hitRightKnee = this._isCollidingRectRect(mario, boss.rightKnee) && isFalling;
|
|
|
|
return hitLeftKnee || hitRightKnee;
|
|
}
|
|
|
|
/**
|
|
* Check collision between Mario and boss body (damage to Mario)
|
|
* @param {Object} mario - Mario object
|
|
* @param {Object} boss - Boss object
|
|
* @returns {boolean} - True if Mario touched boss body
|
|
*/
|
|
static checkBodyCollision(mario, boss) {
|
|
if (!boss || !boss.active) return false;
|
|
|
|
return this._isCollidingRectRect(mario, boss);
|
|
}
|
|
|
|
/**
|
|
* Rectangle-Rectangle collision detection
|
|
*/
|
|
static _isCollidingRectRect(rect1, rect2) {
|
|
return rect1.x < rect2.x + rect2.width &&
|
|
rect1.x + rect1.width > rect2.x &&
|
|
rect1.y < rect2.y + rect2.height &&
|
|
rect1.y + rect1.height > rect2.y;
|
|
}
|
|
}
|
|
|
|
export default Boss;
|