diff options
Diffstat (limited to 'html/tower/js/gameState.js')
-rw-r--r-- | html/tower/js/gameState.js | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/html/tower/js/gameState.js b/html/tower/js/gameState.js new file mode 100644 index 0000000..ac7a968 --- /dev/null +++ b/html/tower/js/gameState.js @@ -0,0 +1,288 @@ +/** + * Game State Module + * + * This module defines the game state and game phases + * + * @module gameState + */ + + +/** + * Game phases + * + * @enum {string} + * @readonly + */ +const GamePhase = { + PLACEMENT: 'place', + COMBAT: 'run' +}; + +/** + * Tower types + * + * @enum {string} + * @readonly + */ +const TowerTypes = { + BASIC: { + name: 'Basic', + cost: 5, + range: 3, + damage: 1, + attackSpeed: 1, + color: '#3498db', + maxAmmo: 75 + }, + RAPID: { + name: 'Fast', + cost: 10, + range: 2, + damage: 1, + attackSpeed: 3, + color: '#16a085', + maxAmmo: 50 + }, + SNIPER: { + name: 'Distance', + cost: 20, + range: 6, + damage: 2, + attackSpeed: 0.5, + color: '#8e44ad', + maxAmmo: 50 + }, + GOOP: { + name: 'Goop', + cost: 20, + range: 3, + damage: 0, + attackSpeed: 1, + color: '#27ae60', + special: 'slow', + slowAmount: 0.75, + maxAmmo: 25 + }, + AOE: { + name: 'AOE', + cost: 25, + range: 2, + damage: 3, + attackSpeed: 0.25, + color: '#d35400', + special: 'aoe', + aoeRadius: 2, + maxAmmo: 25 + } +}; + +/** + * Particle types + * + * @enum {string} + * @readonly + */ +const ParticleTypes = { + DEATH_PARTICLE: { + lifetime: 1000, // milliseconds + speed: 0.1, + colors: ['#e74c3c', '#c0392b', '#d35400', '#e67e22'] + }, + PROJECTILE: { + lifetime: 300, + speed: 0.3, + color: '#ecf0f1' + }, + AOE_EXPLOSION: { + lifetime: 500, + initialRadius: 10, + finalRadius: 60, + color: '#d35400', + ringWidth: 3 + }, + SLIME_TRAIL: { + lifetime: 800, + color: '#27ae60', // Same as Goop tower + size: 12, + fadeStart: 0.2 // When the fade should begin (percentage of lifetime) + } +}; + +/** + * Enemy types + * + * @enum {string} + * @readonly + */ +const EnemyTypes = { + BASIC: { + color: '#c0392b', + baseHealth: { min: 2, max: 6 }, + speed: { min: 1, max: 1.5 }, + damage: 0, + isRanged: false + }, + RANGED: { + color: '#2c3e50', + baseHealth: { min: 1, max: 4 }, + speed: { min: 0.7, max: 1.2 }, + damage: 0.3, + attackRange: 3, + attackSpeed: 1, // attacks per second + isRanged: true + } +}; + +/** + * Creates a tower + * + * @param {string} type - Tower type + * @param {Object} position - Position of the tower + */ +function createTower(type, position) { + const towerType = TowerTypes[type]; + return { + ...towerType, + type, + position, + lastAttackTime: 0, + currentHealth: 10, + maxHealth: 10, + ammo: towerType.maxAmmo // Initialize ammo + }; +} + +/** + * Creates an enemy + * + * @param {Object} startPosition - Starting position of the enemy + */ +function createEnemy(startPosition) { + // 20% chance for ranged enemy + const type = Math.random() < 0.2 ? 'RANGED' : 'BASIC'; + const enemyType = EnemyTypes[type]; + + // Scale health ranges with level + const levelScaling = 1 + (gameState.level - 1) * 0.25; // increase health by 25% per level + const minHealth = Math.floor(enemyType.baseHealth.min * levelScaling); + const maxHealth = Math.floor(enemyType.baseHealth.max * levelScaling); + + const health = Math.floor(Math.random() * + (maxHealth - minHealth + 1)) + minHealth; + + return { + position: { ...startPosition }, + currentHealth: health, + maxHealth: health, + speed: enemyType.speed.min + Math.random() * (enemyType.speed.max - enemyType.speed.min), + pathIndex: 0, + type, + lastAttackTime: 0, + damage: enemyType.damage + }; +} + +/** + * Creates a particle + * + * @param {string} type - Particle type + * @param {Object} position - Position of the particle + */ +function createParticle(type, position, angle) { + return { + position: { ...position }, + velocity: { + x: Math.cos(angle) * type.speed, + y: Math.sin(angle) * type.speed + }, + color: Array.isArray(type.colors) + ? type.colors[Math.floor(Math.random() * type.colors.length)] + : type.color, + createdAt: performance.now(), + lifetime: type.lifetime, + size: 3 + Math.random() * 2 + }; +} + + +/** + * Game state + * + * @type {Object} + */ +const gameState = { + grid: Array(20).fill().map(() => Array(20).fill('empty')), + path: [], + towers: [], + enemies: [], + currency: 100, + phase: GamePhase.PLACEMENT, + isGameOver: false, + particles: [], + projectiles: [], + enemiesDestroyed: 0, + enemiesEscaped: 0, + level: 1, + + /** + * Resets the game state + */ + resetGame() { + this.grid = Array(20).fill().map(() => Array(20).fill('empty')); + this.path = []; + this.towers = []; + this.enemies = []; + this.currency = 100; + this.phase = GamePhase.PLACEMENT; + this.isGameOver = false; + this.particles = []; + this.projectiles = []; + this.enemiesDestroyed = 0; + this.enemiesEscaped = 0; + this.level = 1; + }, + + + /** + * Awards the enemy destroyed + */ + awardEnemyDestroyed() { + this.enemiesDestroyed++; + // Random reward between 1 and 3 + const reward = Math.floor(Math.random() * 3) + 1; + this.currency += reward; + }, + + + /** + * Checks if the level is complete + * + * @returns {boolean} + */ + checkLevelComplete() { + return this.enemies.length === 0 && + enemiesRemaining === 0 && + this.phase === GamePhase.COMBAT; + }, + + + /** + * Advances to the next level + */ + advanceToNextLevel() { + + let ammoBonus = 0; + this.towers.forEach(tower => { + ammoBonus += tower.ammo * 0.25; + }); + this.currency += Math.floor(ammoBonus); // Round down to nearest whole number + + this.level++; + this.phase = GamePhase.PLACEMENT; + this.towers = []; + this.enemies = []; + this.projectiles = []; + this.particles = []; + this.grid = Array(20).fill().map(() => Array(20).fill('empty')); + } +}; \ No newline at end of file |