There is a bug where the game automatically skips the 2nd wave, can you fix it?

// game.js

// Get the canvas and context
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

// Set canvas dimensions to match the window
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

// Create an offscreen canvas for the bloom effect
const offscreenCanvas = document.createElement('canvas');
offscreenCanvas.width = canvas.width;
offscreenCanvas.height = canvas.height;
const offscreenCtx = offscreenCanvas.getContext('2d');

// Player class
class Player {
    constructor() {
        this.width = 40;
        this.height = 60;
        this.x = canvas.width / 2 - this.width / 2;
        this.y = canvas.height - this.height - 20;
        this.isHighlighted = false;  // To indicate incorrect input
    }

    draw(ctx) {
        // Change color based on isHighlighted
        ctx.fillStyle = this.isHighlighted ? '#ff8800' : '#00ff00'; // Orange if highlighted, else green

        // Draw the ship (triangle pointing upwards)
        ctx.beginPath();
        ctx.moveTo(this.x + this.width / 2, this.y);        // Tip of the ship
        ctx.lineTo(this.x, this.y + this.height);           // Bottom left
        ctx.lineTo(this.x + this.width, this.y + this.height); // Bottom right
        ctx.closePath();
        ctx.fill();
    }
}

const player = new Player();

// Projectile class
class Projectile {
    constructor(x, y, targetEnemy) {
        this.x = x + player.width / 2; // Start from the center of the player
        this.y = y;
        this.targetEnemy = targetEnemy;
        this.speed = 8;    // Increased speed to catch up with enemies
        this.radius = 5;
        this.updateVelocity();
    }

    updateVelocity() {
        if (this.targetEnemy && !this.targetEnemy.destroyed) {
            const dx = this.targetEnemy.x - this.x;
            const dy = this.targetEnemy.y - this.y;
            const distance = Math.hypot(dx, dy);
            this.vx = (dx / distance) * this.speed;
            this.vy = (dy / distance) * this.speed;
        }
    }

    update() {
        // Recalculate velocity to home in on target enemy
        this.updateVelocity();

        // Move the projectile
        this.x += this.vx;
        this.y += this.vy;
    }

    draw(ctx) {
        ctx.fillStyle = '#ffff00'; // Yellow color for projectile
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.fill();
    }

    offscreen() {
        return (
            this.x < 0 || this.x > canvas.width ||
            this.y < 0 || this.y > canvas.height
        );
    }
}

// Stars for the background
const stars = [];
for (let i = 0; i < 200; i++) {
    stars.push({
        x: Math.random() * canvas.width,
        y: Math.random() * canvas.height,
        size: Math.random() * 2,
        opacity: Math.random()
    });
}

function drawStars(ctx) {
    ctx.fillStyle = 'white';
    stars.forEach(star => {
        ctx.globalAlpha = star.opacity;
        ctx.fillRect(star.x, star.y, star.size, star.size);
    });
    ctx.globalAlpha = 1;
}

// Nebulas for the background
const nebulas = [];
for (let i = 0; i < 3; i++) {
    nebulas.push({
        x: Math.random() * canvas.width,
        y: Math.random() * canvas.height,
        radius: Math.random() * 200 + 100,
        color: `hsla(${Math.random() * 360}, 60%, 50%, 0.1)`
    });
}

function drawNebulas(ctx) {
    nebulas.forEach(nebula => {
        const gradient = ctx.createRadialGradient(
            nebula.x, nebula.y, 0,
            nebula.x, nebula.y, nebula.radius
        );
        gradient.addColorStop(0, nebula.color);
        gradient.addColorStop(1, 'transparent');
        ctx.fillStyle = gradient;
        ctx.beginPath();
        ctx.arc(nebula.x, nebula.y, nebula.radius, 0, Math.PI * 2);
        ctx.fill();
    });
}

// Enemy class
class Enemy {
    constructor(word, speed) {
        this.word = word;
        this.speed = speed;
        this.x = this.getSpawnX();
        this.y = -50;
        this.destroyed = false;
        this.currentTypingIndex = 0;  // Number of letters typed by the player
        this.isTypingComplete = false; // Indicates if the player has finished typing the word
        this.isTargeted = false;      // To indicate if this enemy is being targeted
        this.vx = 0;
        this.vy = 0;
        this.knockbackVx = 0;     // Knockback velocity in x-direction
        this.knockbackVy = 0;     // Knockback velocity in y-direction
        this.knockbackTime = 0;   // Duration of knockback effect
        this.hitPoints = this.word.length; // Number of hits required to destroy the enemy
    }

    getSpawnX() {
        let x;
        let attempts = 0;
        do {
            x = Math.random() * (canvas.width - 100) + 50;
            attempts++;
        } while (this.isOverlapping(x) && attempts < 50);
        return x;
    }

    isOverlapping(x) {
        return enemies.some(enemy => {
            return Math.abs(enemy.x - x) < 50; // Adjust based on enemy size
        });
    }

    update() {
        if (this.knockbackTime > 0) {
            // Apply knockback movement
            this.x += this.knockbackVx;
            this.y += this.knockbackVy;

            // Reduce knockback over time (simulate friction)
            const friction = 0.9;
            this.knockbackVx *= friction;
            this.knockbackVy *= friction;

            this.knockbackTime--;
        } else {
            // Calculate direction towards player
            const dx = (player.x + player.width / 2) - this.x;
            const dy = (player.y + player.height / 2) - this.y;
            const distance = Math.hypot(dx, dy);
            this.vx = (dx / distance) * this.speed;
            this.vy = (dy / distance) * this.speed;

            // Move towards player
            this.x += this.vx;
            this.y += this.vy;
        }
    }

    draw(ctx) {
        ctx.fillStyle = this.isTargeted ? '#ff8800' : '#ff0000'; // Highlight if targeted
        ctx.fillRect(this.x - 20, this.y - 20, 40, 40);

        // Enemy word
        ctx.font = '20px Arial';
        ctx.textAlign = 'left';

        // Split the word into typed and remaining parts
        const typed = this.word.substring(0, this.currentTypingIndex);
        const remaining = this.word.substring(this.currentTypingIndex);

        // Measure the total width of the word
        const wordWidth = ctx.measureText(this.word).width;

        // Set the starting x position so that the word is centered above the enemy
        const wordX = this.x - wordWidth / 2;
        const wordY = this.y - 30;

        // Draw typed letters in green
        ctx.fillStyle = '#00ff00';
        ctx.fillText(typed, wordX, wordY);

        // Measure the width of the typed text to offset the remaining text
        const typedWidth = ctx.measureText(typed).width;

        // Draw remaining letters in white
        ctx.fillStyle = '#ffffff';
        ctx.fillText(remaining, wordX + typedWidth, wordY);
    }
}

let enemies = [];
let enemySpawnQueue = [];
let wave = 1;
let enemySpeed = 1;
let enemiesPerWave = 5;
let spawnIntervalTime = 2000; // Initial spawn interval in milliseconds
let enemySpawnInterval = null; // Initialize to null
let isWaveTransitioning = false; // Flag to prevent multiple wave transitions
let waveClearMessage = null; // Object to handle wave clear message

// Function to prepare enemies for the wave
async function prepareEnemies() {
    const usedLetters = new Set();

    // Collect first letters of pre-spawned enemies
    enemies.forEach(enemy => {
        usedLetters.add(enemy.word.charAt(0).toLowerCase());
    });
    enemySpawnQueue.forEach(enemy => {
        usedLetters.add(enemy.word.charAt(0).toLowerCase());
    });

    let wordsNeeded = enemiesPerWave;
    let words = [];

    while (words.length < wordsNeeded) {
        // Get more words than needed, to increase chance of getting words with unique starting letters
        const wordsToFetch = (wordsNeeded - words.length) * 2;

        const newWords = await getRandomWords(wordsToFetch);

        for (let word of newWords) {
            // Ignore words that do not start with a letter
            if (!/^[a-zA-Z]/.test(word)) continue;

            const firstLetter = word.charAt(0).toLowerCase();
            if (!usedLetters.has(firstLetter)) {
                words.push(word);
                usedLetters.add(firstLetter);
                if (words.length >= wordsNeeded) break;
            }
        }

        // Break if cannot find enough words
        if (newWords.length === 0) {
            console.log('Not enough unique words could be fetched.');
            break;
        }
    }

    words.forEach(word => {
        enemySpawnQueue.push(new Enemy(word.toLowerCase(), enemySpeed));
    });
}

// Function to spawn an enemy from the queue
function spawnEnemyFromQueue() {
    if (enemySpawnQueue.length > 0) {
        const enemy = enemySpawnQueue.shift();
        enemies.push(enemy);
    }
}

// Start enemy spawning with intervals
function startEnemySpawning() {
    spawnEnemyFromQueue(); // Spawn one enemy immediately
    enemySpawnInterval = setInterval(() => {
        spawnEnemyFromQueue();
        if (enemySpawnQueue.length === 0) {
            clearInterval(enemySpawnInterval);
            enemySpawnInterval = null; // Set to null after clearing the interval
        }
    }, spawnIntervalTime);
}

// Fetch random words from external API
async function getRandomWords(count) {
    try {
        const response = await fetch(`https://random-word-api.herokuapp.com/word?number=${count}`);
        const words = await response.json();
        return words.filter(word => /^[a-zA-Z]/.test(word));
    } catch (error) {
        console.error('Error fetching words:', error);
        return [
            'fallback', 'words', 'typing', 'game', 'space', 'enemy', 'ship',
            'laser', 'attack', 'defend', 'asteroid', 'galaxy', 'meteor',
            'planet', 'comet', 'star', 'universe', 'rocket', 'alien'
        ];
    }
}

let currentInput = '';
let score = 0;
let isGameOver = false;
let keystrokes = 0;          // Total number of keystrokes (both correct and incorrect)
let correctKeystrokes = 0;   // Total number of correct keystrokes
let streak = 0;
let longestStreak = 0;
let projectiles = [];
let targetEnemy = null;

// Handle user input
document.addEventListener('keydown', function (event) {
    if (isGameOver) return;
    const key = event.key.toLowerCase();

    // Accept only alphabetic keys
    if (/^[a-z]$/.test(key)) {
        keystrokes++; // Increment total keystrokes for every alphabetic key pressed

        const potentialInput = currentInput + key;

        // Find matching enemies among those not yet typing complete
        let matchingEnemies = enemies.filter(enemy => !enemy.isTypingComplete && enemy.word.startsWith(potentialInput));

        if (matchingEnemies.length > 0) {
            correctKeystrokes++; // Increment correct keystrokes only when the key press is correct
            currentInput += key;
            targetEnemy = matchingEnemies[0]; // Prioritize the first matching enemy

            enemies.forEach(enemy => {
                enemy.isTargeted = (enemy === targetEnemy);
            });

            // Update enemy's typing index
            targetEnemy.currentTypingIndex = currentInput.length;

            // Create a new projectile towards the target enemy
            projectiles.push(new Projectile(player.x, player.y, targetEnemy));

            checkInput();
        } else {
            // Incorrect input
            // Highlight the player's ship
            player.isHighlighted = true;
            setTimeout(() => {
                player.isHighlighted = false;
            }, 300); // Highlight for 300ms

            // Do not increment correctKeystrokes or currentInput
            // Keep currentInput as it is
            // Do not reset streak
        }
    }
});

// Check input against enemy words
function checkInput() {
    enemies.forEach(enemy => {
        if (!enemy.isTypingComplete && enemy.word.startsWith(currentInput)) {
            enemy.currentTypingIndex = currentInput.length;

            // If the full word matches
            if (enemy.word === currentInput) {
                // Mark enemy as typing complete
                enemy.isTypingComplete = true;

                // Clear current input to prevent further typing
                currentInput = '';

                // Reset target enemy
                targetEnemy = null;
            }
        } else {
            enemy.currentTypingIndex = 0;
        }
    });
}

// Display end-of-game stats
function displayStats() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    ctx.fillStyle = '#ffffff';
    ctx.font = '30px Arial';
    ctx.textAlign = 'center';
    ctx.fillText('Game Over', canvas.width / 2, canvas.height / 2 - 80);

    ctx.font = '24px Arial';
    ctx.fillText(`Score: ${score}`, canvas.width / 2, canvas.height / 2 - 40);
    ctx.fillText(`Waves Survived: ${wave - 1}`, canvas.width / 2, canvas.height / 2);

    const accuracy = ((keystrokes > 0) ? (correctKeystrokes / keystrokes) * 100 : 0).toFixed(2);
    ctx.fillText(`Typing Accuracy: ${accuracy}%`, canvas.width / 2, canvas.height / 2 + 40);
    ctx.fillText(`Longest Streak: ${longestStreak}`, canvas.width / 2, canvas.height / 2 + 80);

    ctx.font = '20px Arial';
    ctx.fillText('Press F5 to Restart', canvas.width / 2, canvas.height / 2 + 120);
}

// Variables for fixed timestep game loop
let delta = 0;
let lastFrameTimeMs = 0;
const timestep = 1000 / 60; // 60 FPS

// Start the game loop
function gameLoop(timestamp) {
    if (isGameOver) {
        displayStats();
        return;
    }

    // Throttle the frame rate
    if (timestamp < lastFrameTimeMs + (1000 / 60)) {
        requestAnimationFrame(gameLoop);
        return;
    }

    delta += timestamp - lastFrameTimeMs;
    lastFrameTimeMs = timestamp;

    while (delta >= timestep) {
        updateGame();
        delta -= timestep;
    }

    renderGame();

    // Continue the game loop
    requestAnimationFrame(gameLoop);
}

// Function to update game logic
function updateGame() {
    // Update enemies
    if (!isWaveTransitioning || waveClearMessage) {
        enemies.forEach(enemy => {
            enemy.update();

            // Check collision with player
            const dx = enemy.x - (player.x + player.width / 2);
            const dy = enemy.y - (player.y + player.height / 2);
            const distance = Math.hypot(dx, dy);
            if (distance < 30 && !enemy.destroyed) { // Adjust collision distance as needed
                isGameOver = true;
            }
        });

        // Update projectiles
        projectiles = projectiles.filter((projectile) => {
            projectile.update();

            // Check for collision with target enemy
            if (projectile.targetEnemy && !projectile.targetEnemy.destroyed) {
                const dx = projectile.x - projectile.targetEnemy.x;
                const dy = projectile.y - projectile.targetEnemy.y;
                const distance = Math.hypot(dx, dy);
                if (distance < projectile.radius + 20) { // Adjust collision radius as needed
                    score += 10;

                    // Apply slight knockback to the enemy
                    const knockbackMagnitude = 2; // Adjust as needed
                    const dxPlayer = (player.x + player.width / 2) - projectile.targetEnemy.x;
                    const dyPlayer = (player.y + player.height / 2) - projectile.targetEnemy.y;
                    const angleTowardsPlayer = Math.atan2(dyPlayer, dxPlayer);
                    const angleAwayFromPlayer = angleTowardsPlayer + Math.PI; // Reverse direction

                    projectile.targetEnemy.knockbackVx = Math.cos(angleAwayFromPlayer) * knockbackMagnitude;
                    projectile.targetEnemy.knockbackVy = Math.sin(angleAwayFromPlayer) * knockbackMagnitude;
                    projectile.targetEnemy.knockbackTime = 5; // Number of frames to apply knockback

                    // Decrement enemy hit points
                    projectile.targetEnemy.hitPoints--;

                    if (projectile.targetEnemy.hitPoints <= 0) {
                        projectile.targetEnemy.destroyed = true;
                        streak++;
                        if (streak > longestStreak) {
                            longestStreak = streak;
                        }

                        // No need to reset currentInput here as it's handled when typing is complete
                    }

                    // Remove projectile after hit
                    return false; // Remove projectile from array
                }
            }

            // Remove projectile if offscreen
            if (projectile.offscreen()) {
                return false; // Remove projectile
            }

            return true; // Keep projectile
        });

        // Remove destroyed enemies
        enemies = enemies.filter(enemy => !enemy.destroyed);
    }

    // Update wave clear message
    if (waveClearMessage) {
        waveClearMessage.elapsedTime += timestep;

        const t = waveClearMessage.elapsedTime;
        const { fadeInDuration, holdDuration, fadeOutDuration, yStart, yEnd } = waveClearMessage;
        const totalDuration = fadeInDuration + holdDuration + fadeOutDuration;

        if (t >= totalDuration) {
            // Message is done
            waveClearMessage = null;
            isWaveTransitioning = false; // Allow progression to next wave

            // Proceed to next wave
            // Increase difficulty
            wave++; // Increment wave number for the next wave
            enemySpeed += 0.2;
            enemiesPerWave += 1;
            spawnIntervalTime = Math.max(1000, spawnIntervalTime - 100); // Decrease spawn interval, minimum 1000ms

            prepareEnemies().then(() => {
                startEnemySpawning();
            });
        } else {
            // Update message properties: opacity and position
            let opacity = 0;
            let y = 0;
            if (t <= fadeInDuration) {
                // Fade in
                opacity = t / fadeInDuration;
                y = waveClearMessage.yStart + ((t / fadeInDuration) * (canvas.height / 2 - waveClearMessage.yStart));
            } else if (t <= fadeInDuration + holdDuration) {
                // Hold
                opacity = 1;
                y = canvas.height / 2;
            } else {
                // Fade out
                const tFadeOut = t - fadeInDuration - holdDuration;
                opacity = 1 - (tFadeOut / fadeOutDuration);
                y = canvas.height / 2 + ((tFadeOut / fadeOutDuration) * (waveClearMessage.yEnd - canvas.height / 2));
            }
            waveClearMessage.opacity = opacity;
            waveClearMessage.y = y;
        }
    }

    // Check if wave is over
    // Moved wave increment to after the message animation is complete
    if (!isWaveTransitioning && enemies.length === 0 && enemySpawnQueue.length === 0 && !enemySpawnInterval) {
        isWaveTransitioning = true; // Prevent multiple wave increments
        // Set up wave clear message
        waveClearMessage = {
            text: `WAVE ${wave} CLEAR`,
            elapsedTime: 0,
            duration: 4000,       // Total duration in milliseconds
            fadeInDuration: 1000, // Fade in over 1 second
            holdDuration: 2000,   // Hold for 2 seconds
            fadeOutDuration: 1000,// Fade out over 1 second
            yStart: canvas.height / 2 - 50, // Start a bit above center
            yEnd: canvas.height / 2 + 50,   // End a bit below center
            x: 20,                 // Horizontally on the left
            opacity: 0,
            y: canvas.height / 2 - 50,
        };
    }
}

// Function to render the game
function renderGame() {
    // Clear both canvases
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    offscreenCtx.clearRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);

    // Draw scene onto the offscreen canvas
    drawScene(offscreenCtx);

    // Apply bloom effect
    applyBloom();

    // Draw HUD elements (e.g., input text) onto the main canvas
    // Display current input
    ctx.fillStyle = '#ffffff';
    ctx.font = '20px Arial';
    ctx.textAlign = 'left';
    ctx.fillText(`Input: ${currentInput}`, 20, canvas.height - 20);

    // Update stats display
    document.getElementById('stats').innerHTML = `Score: ${score} | Wave: ${wave} | Streak: ${streak}`;

    // Draw wave clear message
    if (waveClearMessage) {
        ctx.save();

        ctx.globalAlpha = waveClearMessage.opacity;
        ctx.fillStyle = '#00ffff'; // A great color

        ctx.font = '48px Arial';
        ctx.textAlign = 'left';
        ctx.textBaseline = 'middle';

        ctx.fillText(waveClearMessage.text, waveClearMessage.x, waveClearMessage.y);

        ctx.restore();
    }
}

// Function to draw the entire scene onto a given context
function drawScene(context) {
    // Draw background
    drawStars(context);
    drawNebulas(context);

    // Draw player
    player.draw(context);

    // Draw enemies
    enemies.forEach(enemy => {
        enemy.draw(context);
    });

    // Draw projectiles
    projectiles.forEach(projectile => {
        projectile.draw(context);
    });
}

// Function to apply the bloom effect
function applyBloom() {
    // Save the current canvas state
    ctx.save();

    // Draw the offscreen canvas onto the main canvas
    ctx.drawImage(offscreenCanvas, 0, 0);

    // Apply blur filter
    ctx.filter = 'blur(8px)';

    // Set the global composite operation to lighter to create the bloom effect
    ctx.globalCompositeOperation = 'lighter';

    // Draw the offscreen canvas again with the blur and blending mode
    ctx.drawImage(offscreenCanvas, 0, 0);

    // Restore the canvas state
    ctx.restore();

    // Reset filters and composite operation
    ctx.filter = 'none';
    ctx.globalCompositeOperation = 'source-over';
}

// Start the game
prepareEnemies().then(() => {
    startEnemySpawning();
    requestAnimationFrame(gameLoop);
});

// Adjust canvas size on window resize
window.addEventListener('resize', function () {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    offscreenCanvas.width = canvas.width;
    offscreenCanvas.height = canvas.height;
    player.x = canvas.width / 2 - player.width / 2;
    player.y = canvas.height - player.height - 20;
});

---

Write the whole modified code