diff options
Diffstat (limited to 'html/tower/docs/game.js.html')
-rw-r--r-- | html/tower/docs/game.js.html | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/html/tower/docs/game.js.html b/html/tower/docs/game.js.html new file mode 100644 index 0000000..b34bcf8 --- /dev/null +++ b/html/tower/docs/game.js.html @@ -0,0 +1,537 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>JSDoc: Source: game.js</title> + + <script src="scripts/prettify/prettify.js"> </script> + <script src="scripts/prettify/lang-css.js"> </script> + <!--[if lt IE 9]> + <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> + <![endif]--> + <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> + <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> +</head> + +<body> + +<div id="main"> + + <h1 class="page-title">Source: game.js</h1> + + + + + + + <section> + <article> + <pre class="prettyprint source linenums"><code>// generate updated docs +// jsdoc js -d docs + +/** + * Main game entry point + * Initializes the game state and starts the game loop + * + * @module game + */ + +/** Canvas elements for rendering the game */ +const canvas = document.getElementById('gameCanvas'); +const ctx = canvas.getContext('2d'); + +/** Game timing variables */ +let lastTimestamp = 0; +const ENEMY_SPAWN_INTERVAL = 1000; // 1 second between enemy spawns +let lastEnemySpawn = 0; +let enemiesRemaining = 0; + +/** Drag and drop state tracking */ +let draggedTowerType = null; +let hoverCell = null; + +/** + * Main game loop using requestAnimationFrame + * This is the heart of the game, running approximately 60 times per second + * + * @param {number} timestamp - Current time in milliseconds, provided by requestAnimationFrame + * + * Key concepts: + * - RequestAnimationFrame for smooth animation + * - Delta time for consistent motion regardless of frame rate + * - Game state management + */ +function gameLoop(timestamp) { + const deltaTime = timestamp - lastTimestamp; + lastTimestamp = timestamp; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + if (!gameState.isGameOver) { + if (gameState.phase === GamePhase.COMBAT) { + handleCombatPhase(timestamp, deltaTime); + + // Check for level completion + if (gameState.checkLevelComplete()) { + handleLevelComplete(); + } + } + } + + renderGame(); + requestAnimationFrame(gameLoop); +} + +/** + * Handles all combat phase updates including enemy movement, attacks, and collisions + * + * @param {number} timestamp - Current game time in milliseconds + * @param {number} deltaTime - Time elapsed since last frame + * + * Key concepts: + * - Game state updates + * - Entity management (enemies, towers, projectiles) + * - Particle effects + * - Combat mechanics + */ +function handleCombatPhase(timestamp, deltaTime) { + spawnEnemies(timestamp); + updateEnemies(); + // Update particle effects with time-based animation + gameState.particles = updateParticles(gameState.particles, timestamp, deltaTime); + // Remove expired projectiles + gameState.projectiles = gameState.projectiles.filter(p => timestamp - p.createdAt < p.lifetime); + + const cellSize = canvas.width / 20; + + // Process combat interactions + processTowerAttacks( + gameState.towers, + gameState.enemies, + gameState.projectiles, + gameState.particles, + timestamp, + cellSize + ); + + processEnemyAttacks( + gameState.enemies, + gameState.towers, + gameState.particles, + timestamp, + cellSize + ); + + // Remove defeated enemies and destroyed towers + // Uses array filter with a callback that has side effects (awarding currency) + gameState.enemies = gameState.enemies.filter(enemy => { + if (enemy.currentHealth <= 0) { + gameState.awardEnemyDestroyed(); + return false; + } + return true; + }); + gameState.towers = gameState.towers.filter(tower => tower.currentHealth > 0); +} + +/** + * Spawns new enemies at regular intervals during combat + * + * @param {number} timestamp - Current game time in milliseconds + * + * Key concepts: + * - Time-based game events + * - Enemy creation and management + * - Game balance through spawn timing + */ +function spawnEnemies(timestamp) { + if (enemiesRemaining > 0 && timestamp - lastEnemySpawn > ENEMY_SPAWN_INTERVAL) { + gameState.enemies.push(createEnemy({ x: 0, y: gameState.path[0].y })); + lastEnemySpawn = timestamp; + enemiesRemaining--; + } +} + +/** + * Renders all game elements to the canvas using a layered approach. + * This function demonstrates several key game development patterns: + * + * 1. Canvas State Management: + * - Uses save()/restore() to isolate rendering contexts + * - Resets transform matrix to prevent state leaks + * - Maintains clean state between rendering phases + * + * 2. Layered Rendering Pattern: + * - Renders in specific order (background → entities → UI) + * - Each layer builds on top of previous layers + * - Separates rendering concerns for easier maintenance + * + * 3. Separation of Concerns: + * - Each render function handles one specific type of game element + * - UI rendering is isolated from game element rendering + * - Clear boundaries between different rendering responsibilities + * + * The rendering order is important: + * 1. Grid (background) + * 2. Particles (effects under entities) + * 3. Projectiles (dynamic game elements) + * 4. Towers (static game entities) + * 5. Enemies (moving game entities) + * 6. UI (top layer) + */ +function renderGame() { + // Reset the canvas transform matrix to identity + // This prevents any previous transformations from affecting new renders + ctx.setTransform(1, 0, 0, 1, 0, 0); + + // Clear the entire canvas to prevent ghosting + // This is crucial for animation smoothness + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // Save the initial clean state + // This is part of the state stack pattern used in canvas rendering + ctx.save(); + + // Render game world elements in specific order + // This creates the layered effect common in 2D games + renderGrid(ctx, gameState.grid); // Background layer + renderParticles(ctx, gameState.particles); // Effect layer + renderProjectiles(ctx, gameState.projectiles); // Dynamic elements + renderTowers(ctx, gameState.towers); // Static entities + renderEnemies(ctx, gameState.enemies); // Moving entities + + // Restore to clean state before UI rendering + // This ensures UI rendering isn't affected by game world rendering + ctx.restore(); + ctx.save(); + + // Render UI elements last so they appear on top + // UI is rendered with its own clean state to prevent interference + renderUI(ctx, gameState); + + // Final state restoration + // Ensures clean state for next frame + ctx.restore(); +} + +/** + * Initializes the game by: + * 1. Generating the path for enemies to follow + * 2. Setting up initial enemy count + * 3. Binding event listeners + * 4. Starting the game loop + * + * Uses Promise-based path generation to handle async initialization + */ +generatePath(gameState.grid).then(path => { + gameState.path = path; + // Random enemy count between 5-30 for variety + enemiesRemaining = Math.floor(Math.random() * 26) + 5; + initializeEventListeners(); + // Start the game loop using requestAnimationFrame for smooth animation + requestAnimationFrame(gameLoop); +}); + +/** + * Transitions the game from placement to combat phase. + * Demonstrates state machine pattern commonly used in games. + * + * Side effects: + * - Updates game phase + * - Disables UI elements + * - Updates visual feedback + */ +function startCombat() { + if (gameState.phase === GamePhase.PLACEMENT && gameState.towers.length > 0) { + // State transition + gameState.phase = GamePhase.COMBAT; + + // UI updates + document.getElementById('startCombat').disabled = true; + + // Visual feedback for disabled state + document.querySelectorAll('.tower-option').forEach(option => { + option.draggable = false; + option.style.cursor = 'not-allowed'; + option.style.opacity = '0.5'; + }); + } +} + +/** + * Sets up all event listeners for user interaction + * + * Key concepts: + * - Event-driven programming + * - HTML5 Drag and Drop API + * - DOM manipulation + * - Method decoration (towers.push) + */ +function initializeEventListeners() { + // Add this at the beginning of the function + populateTowerPalette(); + + // Set up tower palette drag events + document.querySelectorAll('.tower-option').forEach(option => { + option.addEventListener('dragstart', (e) => { + draggedTowerType = e.target.dataset.towerType; + // Required for Firefox - must set data for drag operation + e.dataTransfer.setData('text/plain', ''); + }); + + option.addEventListener('dragend', () => { + draggedTowerType = null; + hoverCell = null; + }); + }); + + // Set up canvas drag and drop handling + canvas.addEventListener('dragover', (e) => { + e.preventDefault(); // Required for drop to work + const rect = canvas.getBoundingClientRect(); + // Convert mouse coordinates to grid coordinates + const x = Math.floor((e.clientX - rect.left) / (canvas.width / 20)); + const y = Math.floor((e.clientY - rect.top) / (canvas.height / 20)); + + // Validate grid boundaries + if (x >= 0 && x < 20 && y >= 0 && y < 20) { + hoverCell = { x, y }; + } else { + hoverCell = null; + } + }); + + canvas.addEventListener('dragleave', () => { + hoverCell = null; + }); + + // Handle tower placement on drop + canvas.addEventListener('drop', (e) => { + e.preventDefault(); + if (!draggedTowerType || !hoverCell) return; + + const tower = TowerTypes[draggedTowerType]; + // Validate placement and currency + if ( + gameState.grid[hoverCell.y][hoverCell.x] === 'empty' && + gameState.currency >= tower.cost + ) { + gameState.grid[hoverCell.y][hoverCell.x] = 'tower'; + gameState.towers.push(createTower(draggedTowerType, { ...hoverCell })); + gameState.currency -= tower.cost; + } + + // Reset drag state + draggedTowerType = null; + hoverCell = null; + }); + + // Combat phase transition + document.getElementById('startCombat').addEventListener('click', startCombat); + + // Dynamic button state management + const updateStartButton = () => { + const button = document.getElementById('startCombat'); + button.disabled = gameState.towers.length === 0; + }; + + // Decorator pattern: Enhance towers.push to update UI + const originalPush = gameState.towers.push; + gameState.towers.push = function(...args) { + const result = originalPush.apply(this, args); + updateStartButton(); + return result; + }; + + updateStartButton(); +} + +/** + * Handles the transition between levels + * Shows completion message and sets up next level + */ +function handleLevelComplete() { + // Pause the game briefly + gameState.phase = GamePhase.TRANSITION; + + // Calculate ammo bonus + let ammoBonus = 0; + gameState.towers.forEach(tower => { + ammoBonus += tower.ammo * 0.25; + }); + ammoBonus = Math.floor(ammoBonus); + + // Show level complete message with modal + const message = ` + Level ${gameState.level} Complete! + + Stats: + - Enemies Destroyed: ${gameState.enemiesDestroyed} + - Enemies Escaped: ${gameState.enemiesEscaped} + + Bonuses: + - Current Money: $${gameState.currency} + - Remaining Ammo Bonus: +$${ammoBonus} + + Total After Bonuses: $${gameState.currency + ammoBonus + 10} + + Ready for Level ${gameState.level + 1}? + `; + + // Use setTimeout to allow the final frame to render + setTimeout(() => { + if (confirm(message)) { + startNextLevel(); + } + }, 100); +} + +/** + * Sets up the next level + * Increases difficulty and resets the game state while preserving currency + */ +function startNextLevel() { + gameState.advanceToNextLevel(); + + // Generate new path + generatePath(gameState.grid).then(path => { + gameState.path = path; + + // Exponential enemy scaling + const baseEnemies = 5; + const scalingFactor = 1.5; // Each level increases by 50% + enemiesRemaining = Math.floor(baseEnemies * Math.pow(scalingFactor, gameState.level - 1)); + + // Re-enable tower palette + document.querySelectorAll('.tower-option').forEach(option => { + option.draggable = true; + option.style.cursor = 'grab'; + option.style.opacity = '1'; + }); + + // Reset start button + const startButton = document.getElementById('startCombat'); + startButton.disabled = false; + startButton.textContent = `Start Level ${gameState.level}`; + }); +} + +// Update the renderUI function to show current level +function renderUI(ctx, gameState) { + ctx.fillStyle = 'black'; + ctx.font = '20px Arial'; + ctx.fillText(`Level: ${gameState.level}`, 10, 30); + ctx.fillText(`Currency: $${gameState.currency}`, 10, 60); + ctx.fillText(`Phase: ${gameState.phase}`, 10, 90); + ctx.fillText(`Destroyed: ${gameState.enemiesDestroyed}`, 10, 120); + ctx.fillText(`Escaped: ${gameState.enemiesEscaped}`, 10, 150); +} + +/** + * Dynamically populates the tower palette based on TowerTypes + */ +function populateTowerPalette() { + const palette = document.querySelector('.tower-palette'); + // Clear existing tower options + palette.innerHTML = ''; + + // Create tower options dynamically + Object.entries(TowerTypes).forEach(([type, tower]) => { + const towerOption = document.createElement('div'); + towerOption.className = 'tower-option'; + towerOption.draggable = true; + towerOption.dataset.towerType = type; + + towerOption.innerHTML = ` + <div class="tower-preview" style="background: ${tower.color};"></div> + <div class="tower-info"> + <div class="tower-name">${tower.name}</div> + <div class="tower-cost">Cost: $${tower.cost}</div> + <div class="tower-ammo">Ammo: ${tower.maxAmmo}</div> + </div> + `; + + palette.appendChild(towerOption); + }); + + // Add start combat button + const startButton = document.createElement('button'); + startButton.id = 'startCombat'; + startButton.className = 'start-button'; + startButton.textContent = 'Start Run'; + palette.appendChild(startButton); +} + +/** + * Handles game over state and prompts for restart + */ +function handleGameOver() { + gameState.phase = GamePhase.TRANSITION; + gameState.isGameOver = true; + + const message = ` + Game Over! + + Final Stats: + Level Reached: ${gameState.level} + Enemies Destroyed: ${gameState.enemiesDestroyed} + Enemies Escaped: ${gameState.enemiesEscaped} + + Would you like to restart from Level 1? + `; + + setTimeout(() => { + if (confirm(message)) { + restartGame(); + } + }, 100); +} + +/** + * Restarts the game from level 1 with fresh state + */ +function restartGame() { + gameState.resetGame(); + + // Generate new path + generatePath(gameState.grid).then(path => { + gameState.path = path; + + // Reset enemy count to level 1 + enemiesRemaining = 5; + + // Re-enable tower palette + document.querySelectorAll('.tower-option').forEach(option => { + option.draggable = true; + option.style.cursor = 'grab'; + option.style.opacity = '1'; + }); + + // Reset start button + const startButton = document.getElementById('startCombat'); + startButton.disabled = false; + startButton.textContent = 'Start Level 1'; + }); +} </code></pre> + </article> + </section> + + + + +</div> + +<nav> + <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-game.html">game</a></li><li><a href="module-gameState.html">gameState</a></li><li><a href="module-mechanics.html">mechanics</a></li><li><a href="module-path.html">path</a></li><li><a href="module-renderer.html">renderer</a></li><li><a href="module-uiHandlers.html">uiHandlers</a></li></ul> +</nav> + +<br class="clear"> + +<footer> + Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.3</a> on Mon Feb 17 2025 09:19:19 GMT-0500 (Eastern Standard Time) +</footer> + +<script> prettyPrint(); </script> +<script src="scripts/linenumber.js"> </script> +</body> +</html> |