Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Code Cascade Citadel</title>
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
- body {
- background: linear-gradient(135deg, #0a0e27 0%, #151932 100%);
- color: #00ffcc;
- font-family: 'Courier New', monospace;
- overflow: hidden;
- position: relative;
- height: 100vh;
- }
- #gameCanvas {
- position: absolute;
- top: 0;
- left: 0;
- background: radial-gradient(ellipse at center, rgba(0,255,204,0.05) 0%, transparent 70%);
- }
- #ui {
- position: absolute;
- top: 10px;
- left: 10px;
- z-index: 100;
- background: rgba(10,14,39,0.9);
- padding: 15px;
- border: 2px solid #00ffcc;
- border-radius: 5px;
- box-shadow: 0 0 20px rgba(0,255,204,0.3);
- }
- #ui h2 {
- margin-bottom: 10px;
- text-shadow: 0 0 10px #00ffcc;
- }
- .stat {
- margin: 5px 0;
- display: flex;
- justify-content: space-between;
- min-width: 200px;
- }
- .stat-value {
- color: #fff;
- font-weight: bold;
- }
- #controls {
- position: absolute;
- bottom: 20px;
- left: 50%;
- transform: translateX(-50%);
- display: flex;
- gap: 10px;
- z-index: 100;
- }
- button {
- background: linear-gradient(135deg, #00ffcc 0%, #00ccff 100%);
- border: none;
- color: #0a0e27;
- padding: 10px 20px;
- font-family: inherit;
- font-weight: bold;
- cursor: pointer;
- border-radius: 5px;
- transition: all 0.3s;
- box-shadow: 0 0 15px rgba(0,255,204,0.5);
- }
- button:hover {
- transform: translateY(-2px);
- box-shadow: 0 5px 20px rgba(0,255,204,0.7);
- }
- button:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
- .code-particle {
- position: absolute;
- color: #00ffcc;
- font-size: 10px;
- pointer-events: none;
- animation: fall 2s linear infinite;
- text-shadow: 0 0 5px currentColor;
- }
- @keyframes fall {
- to {
- transform: translateY(100vh);
- opacity: 0;
- }
- }
- #gameOver {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: rgba(10,14,39,0.95);
- padding: 30px;
- border: 3px solid #ff0066;
- border-radius: 10px;
- text-align: center;
- display: none;
- z-index: 200;
- box-shadow: 0 0 30px rgba(255,0,102,0.5);
- }
- #gameOver h2 {
- color: #ff0066;
- margin-bottom: 15px;
- text-shadow: 0 0 10px #ff0066;
- }
- .tooltip {
- position: absolute;
- background: rgba(0,0,0,0.9);
- padding: 5px 10px;
- border-radius: 3px;
- font-size: 12px;
- pointer-events: none;
- z-index: 150;
- display: none;
- border: 1px solid #00ffcc;
- }
- </style>
- </head>
- <body>
- <canvas id="gameCanvas"></canvas>
- <div id="ui">
- <h2>Code Cascade Citadel</h2>
- <div class="stat">
- <span>Power:</span>
- <span class="stat-value" id="power">100</span>
- </div>
- <div class="stat">
- <span>Bugs:</span>
- <span class="stat-value" id="bugs">0</span>
- </div>
- <div class="stat">
- <span>Wave:</span>
- <span class="stat-value" id="wave">1</span>
- </div>
- <div class="stat">
- <span>Score:</span>
- <span class="stat-value" id="score">0</span>
- </div>
- </div>
- <div id="controls">
- <button onclick="game.buildGate('AND')">AND Gate [10]</button>
- <button onclick="game.buildGate('OR')">OR Gate [15]</button>
- <button onclick="game.buildGate('NOT')">NOT Gate [8]</button>
- <button onclick="game.buildGate('XOR')">XOR Gate [20]</button>
- <button onclick="game.buildBombTower()">Bomb Tower [5]</button>
- </div>
- <div id="gameOver">
- <h2>SYSTEM CORRUPTED</h2>
- <p>Final Score: <span id="finalScore">0</span></p>
- <p>Waves Survived: <span id="finalWave">0</span></p>
- <button onclick="location.reload()">Restart</button>
- </div>
- <div class="tooltip" id="tooltip"></div>
- <script>
- const canvas = document.getElementById('gameCanvas');
- const ctx = canvas.getContext('2d');
- canvas.width = window.innerWidth;
- canvas.height = window.innerHeight;
- class CodeParticle {
- constructor(x, y) {
- this.x = x;
- this.y = y;
- this.text = ['if', 'else', 'for', 'while', 'function', 'var', 'let', 'const', '{}', '[]', '=>', '==='][Math.floor(Math.random() * 12)];
- this.speed = Math.random() * 2 + 1;
- this.opacity = 1;
- this.glow = Math.random() * 0.5 + 0.5;
- }
- update() {
- this.y += this.speed;
- this.opacity -= 0.005;
- this.glow = Math.sin(Date.now() * 0.001 + this.x) * 0.3 + 0.7;
- }
- draw() {
- ctx.save();
- ctx.globalAlpha = this.opacity * this.glow;
- ctx.fillStyle = '#00ffcc';
- ctx.shadowBlur = 10;
- ctx.shadowColor = '#00ffcc';
- ctx.font = '12px Courier New';
- ctx.fillText(this.text, this.x, this.y);
- ctx.restore();
- }
- }
- class LogicGate {
- constructor(x, y, type) {
- this.x = x;
- this.y = y;
- this.type = type;
- this.health = 100;
- this.maxHealth = 100;
- this.active = true;
- this.power = this.getPower();
- this.range = 150;
- this.cooldown = 0;
- this.corrupted = false;
- this.decayRate = this.getDecayRate();
- this.shots = 0;
- this.maxShots = this.getMaxShots();
- }
- getPower() {
- const powers = { AND: 5, OR: 7, NOT: 4, XOR: 10 };
- return powers[this.type] || 5;
- }
- getDecayRate() {
- // Different gates have different durability
- const decay = { AND: 5, OR: 4, NOT: 8, XOR: 3 };
- return decay[this.type] || 5;
- }
- getMaxShots() {
- // Maximum shots before breakdown (increased durability)
- const shots = { AND: 40, OR: 50, NOT: 25, XOR: 60 };
- return shots[this.type] || 40;
- }
- update(bugs) {
- // Natural decay over time (reduced by 50%)
- if (this.health > 0) {
- this.health -= 0.01; // Was 0.02, now 50% slower
- }
- if (this.corrupted) {
- this.health -= 0.25; // Was 0.5, now 50% slower
- if (this.health <= 0) {
- this.active = false;
- // Create disintegration effect
- this.createDisintegrationEffect();
- }
- return;
- }
- if (this.cooldown > 0) {
- this.cooldown--;
- return;
- }
- // Attack nearby bugs
- for (let bug of bugs) {
- if (!bug.active) continue;
- const dist = Math.hypot(bug.x - this.x, bug.y - this.y);
- if (dist < this.range) {
- this.attack(bug);
- this.cooldown = 30;
- // Decay from usage (reduced by 50%)
- this.shots++;
- this.health -= this.decayRate * 0.5; // Reduced decay rate
- // Check if gate breaks down
- if (this.health <= 0 || this.shots >= this.maxShots) {
- this.active = false;
- this.createDisintegrationEffect();
- }
- break;
- }
- }
- }
- createDisintegrationEffect() {
- // Create particle effect for disintegration
- for (let i = 0; i < 10; i++) {
- game.effects.push({
- type: 'particle',
- x: this.x + Math.random() * 40 - 20,
- y: this.y + Math.random() * 40 - 20,
- vx: Math.random() * 4 - 2,
- vy: Math.random() * -4 - 1,
- lifetime: 30,
- color: this.corrupted ? '#ff0066' : '#00ffcc'
- });
- }
- }
- attack(bug) {
- // Draw attack beam
- game.effects.push({
- type: 'beam',
- x1: this.x,
- y1: this.y,
- x2: bug.x,
- y2: bug.y,
- lifetime: 10
- });
- bug.takeDamage(this.power);
- }
- draw() {
- if (!this.active) return;
- ctx.save();
- // Draw range indicator when hovering or dragging
- if (game.hoveredGate === this || draggedObject === this) {
- ctx.beginPath();
- ctx.arc(this.x, this.y, this.range, 0, Math.PI * 2);
- ctx.strokeStyle = 'rgba(0,255,204,0.2)';
- ctx.stroke();
- }
- // Highlight if being dragged
- if (draggedObject === this) {
- ctx.shadowBlur = 25;
- ctx.shadowColor = '#00ffff';
- }
- // Fade based on health
- const healthRatio = this.health / this.maxHealth;
- ctx.globalAlpha = Math.max(0.4, healthRatio);
- // Gate body (show deterioration)
- ctx.fillStyle = this.corrupted ? '#ff0066' : '#00ffcc';
- ctx.strokeStyle = this.corrupted ? '#ff0066' : '#00ffcc';
- // Add deterioration visual effect
- if (healthRatio < 0.5) {
- // Flickering effect when low health
- if (Math.random() < 0.1) {
- ctx.strokeStyle = '#ff6600';
- }
- }
- ctx.lineWidth = 2;
- if (!draggedObject || draggedObject !== this) {
- ctx.shadowBlur = 15 * healthRatio;
- ctx.shadowColor = this.corrupted ? '#ff0066' : '#00ffcc';
- }
- // Different shapes for different gates
- ctx.beginPath();
- switch(this.type) {
- case 'AND':
- ctx.moveTo(this.x - 20, this.y - 20);
- ctx.lineTo(this.x + 10, this.y - 20);
- ctx.arc(this.x + 10, this.y, 20, -Math.PI/2, Math.PI/2);
- ctx.lineTo(this.x - 20, this.y + 20);
- ctx.closePath();
- break;
- case 'OR':
- ctx.moveTo(this.x - 20, this.y - 20);
- ctx.quadraticCurveTo(this.x + 20, this.y - 20, this.x + 25, this.y);
- ctx.quadraticCurveTo(this.x + 20, this.y + 20, this.x - 20, this.y + 20);
- ctx.quadraticCurveTo(this.x - 10, this.y, this.x - 20, this.y - 20);
- break;
- case 'NOT':
- ctx.moveTo(this.x - 20, this.y - 15);
- ctx.lineTo(this.x + 15, this.y);
- ctx.lineTo(this.x - 20, this.y + 15);
- ctx.closePath();
- ctx.stroke();
- ctx.beginPath();
- ctx.arc(this.x + 20, this.y, 5, 0, Math.PI * 2);
- break;
- case 'XOR':
- ctx.moveTo(this.x - 25, this.y - 20);
- ctx.quadraticCurveTo(this.x - 15, this.y, this.x - 25, this.y + 20);
- ctx.moveTo(this.x - 20, this.y - 20);
- ctx.quadraticCurveTo(this.x + 20, this.y - 20, this.x + 25, this.y);
- ctx.quadraticCurveTo(this.x + 20, this.y + 20, this.x - 20, this.y + 20);
- ctx.quadraticCurveTo(this.x - 10, this.y, this.x - 20, this.y - 20);
- break;
- }
- ctx.stroke();
- ctx.globalAlpha = 0.3 * healthRatio;
- ctx.fill();
- // Health bar (always visible now)
- ctx.globalAlpha = 1;
- ctx.fillStyle = '#330000';
- ctx.fillRect(this.x - 20, this.y - 35, 40, 4);
- // Color changes based on health
- if (healthRatio > 0.6) {
- ctx.fillStyle = '#00ff66';
- } else if (healthRatio > 0.3) {
- ctx.fillStyle = '#ffaa00';
- } else {
- ctx.fillStyle = '#ff0066';
- }
- ctx.fillRect(this.x - 20, this.y - 35, 40 * healthRatio, 4);
- // Shot counter indicator
- ctx.font = '8px Courier New';
- ctx.fillStyle = '#00ffcc';
- ctx.textAlign = 'center';
- ctx.globalAlpha = 0.8;
- ctx.fillText(`${this.maxShots - this.shots}`, this.x, this.y + 25);
- ctx.restore();
- }
- }
- class BombTower {
- constructor(x, y) {
- this.x = x;
- this.y = y;
- this.active = true;
- this.armed = false;
- this.armTimer = 60; // Takes 1 second to arm
- this.exploding = false;
- this.explosionRadius = 120;
- this.explosionTimer = 0;
- this.pulsePhase = 0;
- this.triggered = false;
- }
- update(bugs) {
- if (!this.active || this.exploding) return;
- // Arming phase
- if (!this.armed) {
- this.armTimer--;
- if (this.armTimer <= 0) {
- this.armed = true;
- }
- return;
- }
- // Check for nearby bugs to trigger explosion
- for (let bug of bugs) {
- if (!bug.active) continue;
- const dist = Math.hypot(bug.x - this.x, bug.y - this.y);
- if (dist < 40 && !this.triggered) { // Trigger range
- this.triggered = true;
- this.explode(bugs);
- break;
- }
- }
- // Pulsing effect when armed
- this.pulsePhase += 0.1;
- }
- explode(bugs) {
- this.exploding = true;
- // Damage all bugs in explosion radius
- for (let bug of bugs) {
- if (!bug.active) continue;
- const dist = Math.hypot(bug.x - this.x, bug.y - this.y);
- if (dist < this.explosionRadius) {
- // Damage falls off with distance
- const damage = 50 * (1 - dist / this.explosionRadius);
- bug.takeDamage(damage);
- }
- }
- // Create explosion effect
- game.effects.push({
- type: 'explosion',
- x: this.x,
- y: this.y,
- radius: this.explosionRadius,
- lifetime: 30
- });
- // Deactivate after explosion
- setTimeout(() => {
- this.active = false;
- }, 500);
- }
- draw() {
- if (!this.active) return;
- ctx.save();
- if (this.exploding) {
- // Draw explosion
- ctx.globalAlpha = 0.8;
- const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.explosionRadius);
- gradient.addColorStop(0, '#ffffff');
- gradient.addColorStop(0.3, '#00ffcc');
- gradient.addColorStop(0.6, '#0099ff');
- gradient.addColorStop(1, 'transparent');
- ctx.fillStyle = gradient;
- ctx.fillRect(this.x - this.explosionRadius, this.y - this.explosionRadius,
- this.explosionRadius * 2, this.explosionRadius * 2);
- } else {
- // Highlight if being dragged
- if (draggedObject === this) {
- ctx.shadowBlur = 30;
- ctx.shadowColor = '#00ffff';
- } else {
- ctx.shadowBlur = 20;
- ctx.shadowColor = this.armed ? '#00ffcc' : '#ffaa00';
- }
- // Draw trigger range when hovering or dragging
- if (draggedObject === this || this.armed) {
- ctx.globalAlpha = 0.1;
- ctx.beginPath();
- ctx.arc(this.x, this.y, 40, 0, Math.PI * 2);
- ctx.strokeStyle = '#ffaa00';
- ctx.stroke();
- // Draw explosion range
- ctx.globalAlpha = 0.05;
- ctx.beginPath();
- ctx.arc(this.x, this.y, this.explosionRadius, 0, Math.PI * 2);
- ctx.strokeStyle = '#00ffcc';
- ctx.stroke();
- }
- ctx.globalAlpha = 1;
- ctx.fillStyle = this.armed ? '#00ffcc' : '#ffaa00';
- ctx.strokeStyle = this.armed ? '#00ffcc' : '#ffaa00';
- ctx.lineWidth = 2;
- // Bomb body (cylindrical shape)
- ctx.beginPath();
- ctx.ellipse(this.x, this.y, 20, 15, 0, 0, Math.PI * 2);
- ctx.stroke();
- ctx.globalAlpha = 0.3;
- ctx.fill();
- // Top details
- ctx.globalAlpha = 1;
- ctx.beginPath();
- ctx.arc(this.x, this.y - 5, 8, 0, Math.PI * 2);
- ctx.stroke();
- // Blinking light when armed
- if (this.armed) {
- const blink = Math.sin(this.pulsePhase) * 0.5 + 0.5;
- ctx.fillStyle = `rgba(255, 0, 102, ${blink})`;
- ctx.beginPath();
- ctx.arc(this.x, this.y - 5, 4, 0, Math.PI * 2);
- ctx.fill();
- } else {
- // Show arming progress
- ctx.strokeStyle = '#ffaa00';
- ctx.lineWidth = 3;
- ctx.beginPath();
- ctx.arc(this.x, this.y, 25, -Math.PI/2, -Math.PI/2 + (Math.PI * 2 * (1 - this.armTimer / 60)), false);
- ctx.stroke();
- }
- // Warning text if not armed
- if (!this.armed) {
- ctx.font = '10px Courier New';
- ctx.fillStyle = '#ffaa00';
- ctx.textAlign = 'center';
- ctx.fillText('ARMING', this.x, this.y + 30);
- }
- }
- ctx.restore();
- }
- }
- class Bug {
- constructor(x, y, type = null) {
- this.x = x;
- this.y = y;
- this.type = type || this.selectType();
- this.setupStats();
- this.active = true;
- this.glitchOffset = Math.random() * Math.PI * 2;
- this.movePattern = 0;
- this.shieldActive = false;
- this.splitOnDeath = false;
- this.zigzagPhase = Math.random() * Math.PI * 2;
- }
- selectType() {
- const wave = game ? game.wave : 1;
- const rand = Math.random();
- // More enemy types appear in later waves
- if (wave >= 5 && rand < 0.1) return 'boss';
- if (wave >= 4 && rand < 0.15) return 'splitter';
- if (wave >= 3 && rand < 0.2) return 'shield';
- if (wave >= 2 && rand < 0.25) return 'speeder';
- if (rand < 0.3) return 'glitch';
- if (rand < 0.4) return 'zigzag';
- return 'bug';
- }
- setupStats() {
- const stats = {
- bug: { health: 20, speed: 1.5, size: 8, power: 3, color: '#ff0066' },
- glitch: { health: 15, speed: 1.8, size: 8, power: 4, color: '#ff00ff' },
- zigzag: { health: 25, speed: 2, size: 7, power: 4, color: '#ffaa00' },
- speeder: { health: 10, speed: 4, size: 5, power: 5, color: '#00ffff' },
- shield: { health: 40, speed: 1, size: 12, power: 7, color: '#6666ff' },
- splitter: { health: 30, speed: 1.5, size: 10, power: 8, color: '#00ff00' },
- boss: { health: 100, speed: 0.8, size: 20, power: 15, color: '#ff0000' }
- };
- const s = stats[this.type] || stats.bug;
- this.health = s.health;
- this.maxHealth = s.health;
- this.speed = s.speed;
- this.size = s.size;
- this.powerReward = s.power;
- this.color = s.color;
- // Special properties
- if (this.type === 'shield') {
- this.shieldActive = true;
- this.shieldHealth = 20;
- }
- if (this.type === 'splitter') {
- this.splitOnDeath = true;
- }
- this.vx = 0;
- this.vy = this.speed;
- }
- update() {
- if (!this.active) return;
- // Move towards citadel (bottom center)
- const targetX = canvas.width / 2;
- const targetY = canvas.height - 100;
- const angle = Math.atan2(targetY - this.y, targetX - this.x);
- // Different movement patterns for different types
- switch(this.type) {
- case 'glitch':
- this.x += Math.cos(angle) * this.speed + Math.sin(Date.now() * 0.01 + this.glitchOffset) * 2;
- this.y += Math.sin(angle) * this.speed;
- break;
- case 'zigzag':
- this.zigzagPhase += 0.15;
- this.x += Math.cos(angle) * this.speed + Math.sin(this.zigzagPhase) * 3;
- this.y += Math.sin(angle) * this.speed;
- break;
- case 'speeder':
- // Burst movement
- if (Math.random() < 0.05) {
- this.vx = Math.cos(angle) * this.speed * 2;
- this.vy = Math.sin(angle) * this.speed * 2;
- }
- this.x += this.vx;
- this.y += this.vy;
- this.vx *= 0.95;
- this.vy *= 0.95;
- break;
- case 'boss':
- // Steady, menacing approach
- this.x += Math.cos(angle) * this.speed;
- this.y += Math.sin(angle) * this.speed;
- // Spawn minions occasionally
- if (Math.random() < 0.01 && game) {
- game.bugs.push(new Bug(
- this.x + Math.random() * 40 - 20,
- this.y + Math.random() * 40 - 20,
- 'bug'
- ));
- }
- break;
- default:
- this.x += Math.cos(angle) * this.speed;
- this.y += Math.sin(angle) * this.speed;
- }
- // Check if reached citadel
- if (this.y > canvas.height - 150) {
- this.active = false;
- if (game) {
- game.power -= this.type === 'boss' ? 25 : 10;
- // Corrupt nearby gates
- game.gates.forEach(gate => {
- const dist = Math.hypot(gate.x - this.x, gate.y - this.y);
- if (dist < 100) {
- gate.corrupted = true;
- }
- });
- }
- }
- }
- takeDamage(amount) {
- // Shield absorbs damage first
- if (this.shieldActive && this.shieldHealth > 0) {
- this.shieldHealth -= amount;
- if (this.shieldHealth <= 0) {
- this.shieldActive = false;
- }
- return;
- }
- this.health -= amount;
- if (this.health <= 0 && this.active) {
- this.active = false;
- if (game) {
- // Award power and score based on bug type
- game.score += this.powerReward * 2;
- game.power += this.powerReward;
- // Splitter creates smaller bugs
- if (this.splitOnDeath) {
- for (let i = 0; i < 2; i++) {
- const miniGlitch = new Bug(
- this.x + Math.random() * 20 - 10,
- this.y + Math.random() * 20 - 10,
- 'glitch'
- );
- miniGlitch.health = 10;
- miniGlitch.maxHealth = 10;
- miniGlitch.size = 5;
- game.bugs.push(miniGlitch);
- }
- }
- // Death effect
- this.createDeathEffect();
- }
- }
- }
- createDeathEffect() {
- if (!game) return;
- for (let i = 0; i < 5; i++) {
- game.effects.push({
- type: 'particle',
- x: this.x,
- y: this.y,
- vx: Math.random() * 6 - 3,
- vy: Math.random() * -6,
- lifetime: 20,
- color: this.color
- });
- }
- }
- draw() {
- if (!this.active) return;
- ctx.save();
- // Draw different shapes for different enemy types
- ctx.fillStyle = this.color;
- ctx.strokeStyle = this.color;
- ctx.shadowBlur = 10;
- ctx.shadowColor = this.color;
- switch(this.type) {
- case 'glitch':
- // Glitchy squares
- ctx.globalAlpha = 0.8;
- for (let i = 0; i < 3; i++) {
- ctx.fillRect(
- this.x - this.size + Math.random() * 4,
- this.y - this.size + Math.random() * 4,
- this.size * 2, this.size * 2
- );
- }
- break;
- case 'zigzag':
- // Triangle
- ctx.beginPath();
- ctx.moveTo(this.x, this.y - this.size);
- ctx.lineTo(this.x - this.size, this.y + this.size);
- ctx.lineTo(this.x + this.size, this.y + this.size);
- ctx.closePath();
- ctx.fill();
- ctx.stroke();
- break;
- case 'speeder':
- // Diamond with trail
- ctx.globalAlpha = 0.3;
- ctx.beginPath();
- ctx.moveTo(this.x, this.y - this.size * 2);
- ctx.lineTo(this.x - this.size/2, this.y);
- ctx.lineTo(this.x, this.y + this.size);
- ctx.lineTo(this.x + this.size/2, this.y);
- ctx.closePath();
- ctx.fill();
- ctx.globalAlpha = 1;
- ctx.beginPath();
- ctx.moveTo(this.x, this.y - this.size);
- ctx.lineTo(this.x - this.size, this.y);
- ctx.lineTo(this.x, this.y + this.size);
- ctx.lineTo(this.x + this.size, this.y);
- ctx.closePath();
- ctx.fill();
- break;
- case 'shield':
- // Shield bubble
- if (this.shieldActive) {
- ctx.globalAlpha = 0.3;
- ctx.beginPath();
- ctx.arc(this.x, this.y, this.size + 8, 0, Math.PI * 2);
- ctx.fill();
- }
- ctx.globalAlpha = 1;
- ctx.beginPath();
- ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
- ctx.fill();
- ctx.stroke();
- break;
- case 'splitter':
- // Hexagon
- ctx.beginPath();
- for (let i = 0; i < 6; i++) {
- const angle = (Math.PI / 3) * i;
- const x = this.x + this.size * Math.cos(angle);
- const y = this.y + this.size * Math.sin(angle);
- if (i === 0) ctx.moveTo(x, y);
- else ctx.lineTo(x, y);
- }
- ctx.closePath();
- ctx.fill();
- ctx.stroke();
- break;
- case 'boss':
- // Large intimidating circle with spikes
- ctx.globalAlpha = 0.5;
- ctx.beginPath();
- ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
- ctx.fill();
- ctx.globalAlpha = 1;
- for (let i = 0; i < 8; i++) {
- const angle = (Math.PI / 4) * i + Date.now() * 0.001;
- ctx.beginPath();
- ctx.moveTo(this.x, this.y);
- ctx.lineTo(
- this.x + (this.size + 10) * Math.cos(angle),
- this.y + (this.size + 10) * Math.sin(angle)
- );
- ctx.stroke();
- }
- ctx.beginPath();
- ctx.arc(this.x, this.y, this.size * 0.7, 0, Math.PI * 2);
- ctx.fill();
- break;
- default:
- // Regular bug - circle
- ctx.beginPath();
- ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
- ctx.fill();
- ctx.stroke();
- }
- // Health bar
- if (this.health < this.maxHealth) {
- ctx.globalAlpha = 1;
- ctx.fillStyle = '#330000';
- ctx.fillRect(this.x - this.size - 5, this.y - this.size - 10, (this.size + 5) * 2, 3);
- ctx.fillStyle = '#00ff66';
- ctx.fillRect(this.x - this.size - 5, this.y - this.size - 10, (this.size + 5) * 2 * (this.health / this.maxHealth), 3);
- }
- ctx.restore();
- }
- }
- class Game {
- constructor() {
- this.power = 100;
- this.wave = 1;
- this.score = 0;
- this.gates = [];
- this.towers = [];
- this.bugs = [];
- this.particles = [];
- this.effects = [];
- this.debugModeActive = false;
- this.hoveredGate = null;
- this.citadelHealth = 100;
- this.waveTimer = 0;
- this.gameRunning = true;
- this.init();
- }
- init() {
- // Create initial code cascade
- setInterval(() => {
- if (this.particles.length < 50) {
- this.particles.push(new CodeParticle(
- Math.random() * canvas.width,
- -20
- ));
- }
- }, 100);
- // Start game loop
- this.gameLoop();
- // Start wave spawning
- this.spawnWave();
- }
- spawnWave() {
- if (!this.gameRunning) return;
- const bugCount = Math.min(5 + this.wave * 2, 20); // Cap at 20 enemies per wave
- for (let i = 0; i < bugCount; i++) {
- setTimeout(() => {
- if (!this.gameRunning) return;
- this.bugs.push(new Bug(
- Math.random() * canvas.width,
- Math.random() * -200 - 50
- ));
- }, i * 300); // Faster spawning
- }
- // Schedule next wave
- setTimeout(() => {
- this.wave++;
- document.getElementById('wave').textContent = this.wave;
- this.spawnWave();
- }, 12000 + Math.min(this.wave * 500, 5000)); // Shorter waves, caps at 17 seconds
- }
- buildGate(type) {
- const costs = { AND: 10, OR: 15, NOT: 8, XOR: 20 };
- const cost = costs[type];
- if (this.power < cost) {
- this.showTooltip('Insufficient power!', canvas.width / 2, canvas.height / 2);
- return;
- }
- // Prevent spending if it would cause game over
- if (this.power - cost <= 0) {
- this.showTooltip('Cannot spend - would lose the game!', canvas.width / 2, canvas.height / 2);
- return;
- }
- this.power -= cost;
- const x = Math.random() * (canvas.width - 100) + 50;
- const y = Math.random() * (canvas.height - 300) + 150;
- this.gates.push(new LogicGate(x, y, type));
- }
- buildBombTower() {
- const cost = 5;
- if (this.power < cost) {
- this.showTooltip('Insufficient power!', canvas.width / 2, canvas.height / 2);
- return;
- }
- // Prevent spending if it would cause game over
- if (this.power - cost <= 0) {
- this.showTooltip('Cannot spend - would lose the game!', canvas.width / 2, canvas.height / 2);
- return;
- }
- this.power -= cost;
- const x = Math.random() * (canvas.width - 100) + 50;
- const y = Math.random() * (canvas.height - 300) + 150;
- this.towers.push(new BombTower(x, y));
- }
- showTooltip(text, x, y) {
- const tooltip = document.getElementById('tooltip');
- tooltip.textContent = text;
- tooltip.style.left = x + 'px';
- tooltip.style.top = y + 'px';
- tooltip.style.display = 'block';
- setTimeout(() => {
- tooltip.style.display = 'none';
- }, 2000);
- }
- update() {
- if (!this.gameRunning) return;
- // Update particles
- this.particles = this.particles.filter(p => {
- p.update();
- return p.opacity > 0 && p.y < canvas.height;
- });
- // Update gates
- this.gates = this.gates.filter(gate => {
- gate.update(this.bugs);
- return gate.active;
- });
- // Update towers
- this.towers = this.towers.filter(tower => {
- tower.update(this.bugs);
- return tower.active;
- });
- // Update bugs
- this.bugs = this.bugs.filter(bug => {
- bug.update();
- return bug.active;
- });
- // Update effects
- this.effects = this.effects.filter(effect => {
- effect.lifetime--;
- return effect.lifetime > 0;
- });
- // Update UI
- document.getElementById('power').textContent = Math.floor(this.power);
- document.getElementById('bugs').textContent = this.bugs.filter(b => b.active).length;
- document.getElementById('score').textContent = this.score;
- // Check game over
- if (this.power <= 0) {
- this.gameOver();
- }
- }
- draw() {
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- // Draw grid
- ctx.strokeStyle = 'rgba(0,255,204,0.1)';
- ctx.lineWidth = 0.5;
- for (let x = 0; x < canvas.width; x += 50) {
- ctx.beginPath();
- ctx.moveTo(x, 0);
- ctx.lineTo(x, canvas.height);
- ctx.stroke();
- }
- for (let y = 0; y < canvas.height; y += 50) {
- ctx.beginPath();
- ctx.moveTo(0, y);
- ctx.lineTo(canvas.width, y);
- ctx.stroke();
- }
- // Draw citadel base
- ctx.save();
- ctx.fillStyle = '#00ffcc';
- ctx.strokeStyle = '#00ffcc';
- ctx.lineWidth = 3;
- ctx.shadowBlur = 30;
- ctx.shadowColor = '#00ffcc';
- const citadelX = canvas.width / 2;
- const citadelY = canvas.height - 50;
- ctx.beginPath();
- ctx.moveTo(citadelX - 100, citadelY);
- ctx.lineTo(citadelX - 80, citadelY - 50);
- ctx.lineTo(citadelX - 40, citadelY - 40);
- ctx.lineTo(citadelX, citadelY - 60);
- ctx.lineTo(citadelX + 40, citadelY - 40);
- ctx.lineTo(citadelX + 80, citadelY - 50);
- ctx.lineTo(citadelX + 100, citadelY);
- ctx.closePath();
- ctx.stroke();
- ctx.globalAlpha = 0.2;
- ctx.fill();
- ctx.restore();
- // Draw particles
- this.particles.forEach(p => p.draw());
- // Draw towers
- this.towers.forEach(tower => tower.draw());
- // Draw gates
- this.gates.forEach(gate => gate.draw());
- // Draw bugs
- this.bugs.forEach(bug => bug.draw());
- // Draw effects
- this.effects.forEach(effect => {
- if (effect.type === 'beam') {
- ctx.save();
- ctx.strokeStyle = '#00ffcc';
- ctx.lineWidth = 2;
- ctx.globalAlpha = effect.lifetime / 10;
- ctx.shadowBlur = 10;
- ctx.shadowColor = '#00ffcc';
- ctx.beginPath();
- ctx.moveTo(effect.x1, effect.y1);
- ctx.lineTo(effect.x2, effect.y2);
- ctx.stroke();
- ctx.restore();
- } else if (effect.type === 'explosion') {
- ctx.save();
- ctx.globalAlpha = effect.lifetime / 30;
- const gradient = ctx.createRadialGradient(effect.x, effect.y, 0, effect.x, effect.y, effect.radius);
- gradient.addColorStop(0, '#ffffff');
- gradient.addColorStop(0.2, '#00ffcc');
- gradient.addColorStop(0.5, '#0099ff');
- gradient.addColorStop(1, 'transparent');
- ctx.fillStyle = gradient;
- ctx.beginPath();
- ctx.arc(effect.x, effect.y, effect.radius * (1 + (30 - effect.lifetime) / 30), 0, Math.PI * 2);
- ctx.fill();
- ctx.restore();
- } else if (effect.type === 'particle') {
- ctx.save();
- effect.x += effect.vx;
- effect.y += effect.vy;
- effect.vy += 0.3; // Gravity
- ctx.globalAlpha = effect.lifetime / 30;
- ctx.fillStyle = effect.color;
- ctx.shadowBlur = 5;
- ctx.shadowColor = effect.color;
- ctx.fillRect(effect.x - 2, effect.y - 2, 4, 4);
- ctx.restore();
- }
- });
- }
- gameLoop() {
- this.update();
- this.draw();
- if (this.gameRunning) {
- requestAnimationFrame(() => this.gameLoop());
- }
- }
- gameOver() {
- this.gameRunning = false;
- document.getElementById('finalScore').textContent = this.score;
- document.getElementById('finalWave').textContent = this.wave - 1;
- document.getElementById('gameOver').style.display = 'block';
- }
- }
- // Mouse interaction variables
- let isDragging = false;
- let draggedObject = null;
- let dragOffset = { x: 0, y: 0 };
- // Mouse down - start dragging
- canvas.addEventListener('mousedown', (e) => {
- if (!game) return;
- const mouseX = e.clientX;
- const mouseY = e.clientY;
- // Check if clicking on a gate
- for (let gate of game.gates) {
- const dist = Math.hypot(mouseX - gate.x, mouseY - gate.y);
- if (dist < 30) {
- isDragging = true;
- draggedObject = gate;
- dragOffset.x = mouseX - gate.x;
- dragOffset.y = mouseY - gate.y;
- canvas.style.cursor = 'grabbing';
- return;
- }
- }
- // Check if clicking on a tower
- for (let tower of game.towers) {
- const dist = Math.hypot(mouseX - tower.x, mouseY - tower.y);
- if (dist < 30) {
- isDragging = true;
- draggedObject = tower;
- dragOffset.x = mouseX - tower.x;
- dragOffset.y = mouseY - tower.y;
- canvas.style.cursor = 'grabbing';
- return;
- }
- }
- });
- // Mouse move - drag or hover
- canvas.addEventListener('mousemove', (e) => {
- if (!game) return;
- const mouseX = e.clientX;
- const mouseY = e.clientY;
- if (isDragging && draggedObject) {
- // Update position of dragged object
- draggedObject.x = mouseX - dragOffset.x;
- draggedObject.y = mouseY - dragOffset.y;
- // Keep objects within canvas bounds
- draggedObject.x = Math.max(30, Math.min(canvas.width - 30, draggedObject.x));
- draggedObject.y = Math.max(30, Math.min(canvas.height - 100, draggedObject.y));
- } else {
- // Check for hovering over draggable objects
- let hoveringObject = false;
- // Check gates
- game.hoveredGate = null;
- for (let gate of game.gates) {
- const dist = Math.hypot(mouseX - gate.x, mouseY - gate.y);
- if (dist < 30) {
- game.hoveredGate = gate;
- hoveringObject = true;
- break;
- }
- }
- // Check towers
- if (!hoveringObject) {
- for (let tower of game.towers) {
- const dist = Math.hypot(mouseX - tower.x, mouseY - tower.y);
- if (dist < 30) {
- hoveringObject = true;
- break;
- }
- }
- }
- // Update cursor
- canvas.style.cursor = hoveringObject ? 'grab' : 'default';
- }
- });
- // Mouse up - stop dragging
- canvas.addEventListener('mouseup', (e) => {
- if (isDragging) {
- isDragging = false;
- draggedObject = null;
- canvas.style.cursor = 'default';
- }
- });
- // Mouse leave - cancel dragging
- canvas.addEventListener('mouseleave', (e) => {
- if (isDragging) {
- isDragging = false;
- draggedObject = null;
- canvas.style.cursor = 'default';
- }
- });
- // Start game
- const game = new Game();
- // Handle window resize
- window.addEventListener('resize', () => {
- canvas.width = window.innerWidth;
- canvas.height = window.innerHeight;
- });
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment