User prompt
Reduce buying cost increase amount.
User prompt
Change the grids to be 3x5, increase the size of the cells and orbs and use more of the space between lanes. Use from one lane to the other with a slight buffer between.
User prompt
Reduce the pause for the blobs jumping ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Increase the amount of gold gained per enemy by 25%
User prompt
Widen lanes so that slimes can travel two wide and spawn them one two different planes
User prompt
Fix the slime enemy animation so that they jump in the following stages: getting ready = vertical squish, jumping = smooth arc in the direction they are traveling, combined with a horizontal stretch, landing = vertical squish again, then return to normal, brief pause and back to ready again. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Expand enemy path equally so that the bottom path is just above the wave manager UI and the top path remains where it is. Move unit grids to follow.
User prompt
Reduce life of all enemies by 50%
User prompt
Reduce life of all enemies by 20%
User prompt
Orbs should only combine if there are two of the same kind and level. Right now orbs are leveling up if tapped. Analyze and fix.
User prompt
Update as needed with: /**** * Game Constants - ACTUALLY USE THE FULL SCREEN ****/ var CELL_SIZE = 120; // Bigger cells for better visibility var GRID_COLS = 5; var GRID_ROWS = 4; var SCREEN_WIDTH = 2048; var SCREEN_HEIGHT = 2732; // Actually use the full screen dimensions var BUILDING_AREA_WIDTH = GRID_COLS * CELL_SIZE; // 600px wide var BUILDING_AREA_HEIGHT = GRID_ROWS * CELL_SIZE; // 480px tall // Center the building areas in the screen with proper spacing var TOP_AREA_X = (SCREEN_WIDTH - BUILDING_AREA_WIDTH) / 2; // ~724px from left var TOP_AREA_Y = 500; // Start well below UI var BOTTOM_AREA_X = TOP_AREA_X; // Same X position var BOTTOM_AREA_Y = TOP_AREA_Y + BUILDING_AREA_HEIGHT + 300; // 300px gap between areas // Path uses much more of the screen var PATH_MARGIN = 200; // Bigger margins for wider paths ``` ```javascript /**** * Path System - USING FULL SCREEN DIMENSIONS ****/ var PathSystem = { pathPoints: [], init: function() { // Use much more of the screen for the path var pathLeft = 150; // Start near left edge of screen var pathRight = SCREEN_WIDTH - 150; // Go to near right edge of screen var pathTop = 400; // Start below UI var pathMiddle = TOP_AREA_Y + BUILDING_AREA_HEIGHT + 150; // Between building areas var pathBottom = BOTTOM_AREA_Y + BUILDING_AREA_HEIGHT + 150; // Below bottom area this.pathPoints = [ // Start from top-left of screen {x: pathLeft, y: pathTop}, // Across top of screen {x: pathRight, y: pathTop}, // Down right side to middle {x: pathRight, y: pathMiddle}, // Across middle (between building areas) to left {x: pathLeft, y: pathMiddle}, // Down left side to bottom {x: pathLeft, y: pathBottom}, // Across bottom to exit at right {x: pathRight, y: pathBottom} ]; }, // ... rest of PathSystem methods stay the same }; ``` ```javascript /**** * Generate Buttons - PROPERLY POSITIONED ****/ var topGenerateButton = new GenerateButton(0); topGenerateButton.x = TOP_AREA_X + BUILDING_AREA_WIDTH + 100; topGenerateButton.y = TOP_AREA_Y + BUILDING_AREA_HEIGHT / 2; game.addChild(topGenerateButton); var bottomGenerateButton = new GenerateButton(1); bottomGenerateButton.x = BOTTOM_AREA_X + BUILDING_AREA_WIDTH + 100; bottomGenerateButton.y = BOTTOM_AREA_Y + BUILDING_AREA_HEIGHT / 2; game.addChild(bottomGenerateButton); ``` ```javascript /**** * Wave Manager - BOTTOM OF SCREEN ****/ var waveManager = new WaveManager(); waveManager.x = SCREEN_WIDTH / 2; waveManager.y = SCREEN_HEIGHT - 200; // Actually near bottom of screen game.addChild(waveManager);
User prompt
Update as needed with: /**** * Game Constants - ACTUALLY USE THE FULL SCREEN ****/ var CELL_SIZE = 120; // Bigger cells for better visibility var GRID_COLS = 5; var GRID_ROWS = 4; var SCREEN_WIDTH = 2048; var SCREEN_HEIGHT = 2732; // Actually use the full screen dimensions var BUILDING_AREA_WIDTH = GRID_COLS * CELL_SIZE; // 600px wide var BUILDING_AREA_HEIGHT = GRID_ROWS * CELL_SIZE; // 480px tall // Center the building areas in the screen with proper spacing var TOP_AREA_X = (SCREEN_WIDTH - BUILDING_AREA_WIDTH) / 2; // ~724px from left var TOP_AREA_Y = 500; // Start well below UI var BOTTOM_AREA_X = TOP_AREA_X; // Same X position var BOTTOM_AREA_Y = TOP_AREA_Y + BUILDING_AREA_HEIGHT + 300; // 300px gap between areas // Path uses much more of the screen var PATH_MARGIN = 200; // Bigger margins for wider paths ``` ```javascript /**** * Path System - USING FULL SCREEN DIMENSIONS ****/ var PathSystem = { pathPoints: [], init: function() { // Use much more of the screen for the path var pathLeft = 150; // Start near left edge of screen var pathRight = SCREEN_WIDTH - 150; // Go to near right edge of screen var pathTop = 400; // Start below UI var pathMiddle = TOP_AREA_Y + BUILDING_AREA_HEIGHT + 150; // Between building areas var pathBottom = BOTTOM_AREA_Y + BUILDING_AREA_HEIGHT + 150; // Below bottom area this.pathPoints = [ // Start from top-left of screen {x: pathLeft, y: pathTop}, // Across top of screen {x: pathRight, y: pathTop}, // Down right side to middle {x: pathRight, y: pathMiddle}, // Across middle (between building areas) to left {x: pathLeft, y: pathMiddle}, // Down left side to bottom {x: pathLeft, y: pathBottom}, // Across bottom to exit at right {x: pathRight, y: pathBottom} ]; }, // ... rest of PathSystem methods stay the same }; ``` ```javascript /**** * Generate Buttons - PROPERLY POSITIONED ****/ var topGenerateButton = new GenerateButton(0); topGenerateButton.x = TOP_AREA_X + BUILDING_AREA_WIDTH + 100; topGenerateButton.y = TOP_AREA_Y + BUILDING_AREA_HEIGHT / 2; game.addChild(topGenerateButton); var bottomGenerateButton = new GenerateButton(1); bottomGenerateButton.x = BOTTOM_AREA_X + BUILDING_AREA_WIDTH + 100; bottomGenerateButton.y = BOTTOM_AREA_Y + BUILDING_AREA_HEIGHT / 2; game.addChild(bottomGenerateButton); ``` ```javascript /**** * Wave Manager - BOTTOM OF SCREEN ****/ var waveManager = new WaveManager(); waveManager.x = SCREEN_WIDTH / 2; waveManager.y = SCREEN_HEIGHT - 200; // Actually near bottom of screen game.addChild(waveManager);
User prompt
start with 100 gold
User prompt
update as needed with: /**** * Generate Buttons - POSITIONED BESIDE BUILDING AREAS ****/ var topGenerateButton = new GenerateButton(0); topGenerateButton.x = TOP_AREA_X + BUILDING_AREA_WIDTH + 50; topGenerateButton.y = TOP_AREA_Y + BUILDING_AREA_HEIGHT / 2; game.addChild(topGenerateButton); var bottomGenerateButton = new GenerateButton(1); bottomGenerateButton.x = BOTTOM_AREA_X + BUILDING_AREA_WIDTH + 50; bottomGenerateButton.y = BOTTOM_AREA_Y + BUILDING_AREA_HEIGHT / 2; game.addChild(bottomGenerateButton);
User prompt
update as needed with: /**** * Path System - WRAPPED AROUND BUILDING AREAS ****/ var PathSystem = { pathPoints: [], init: function() { // Path coordinates that wrap around the building areas var pathLeft = TOP_AREA_X - PATH_MARGIN; var pathRight = TOP_AREA_X + BUILDING_AREA_WIDTH + PATH_MARGIN; var pathTop = TOP_AREA_Y - PATH_MARGIN; var pathMiddle = TOP_AREA_Y + BUILDING_AREA_HEIGHT + 100; // Between the two areas var pathBottom = BOTTOM_AREA_Y + BUILDING_AREA_HEIGHT + PATH_MARGIN; this.pathPoints = [ // Start from top-left {x: pathLeft, y: pathTop}, // Across top {x: pathRight, y: pathTop}, // Down right side to middle {x: pathRight, y: pathMiddle}, // Across middle (between building areas) to left {x: pathLeft, y: pathMiddle}, // Down left side to bottom {x: pathLeft, y: pathBottom}, // Across bottom to exit {x: pathRight, y: pathBottom} ]; }, getPositionAlongPath: function(progress) { if (progress <= 0) return {x: this.pathPoints[0].x, y: this.pathPoints[0].y}; if (progress >= 1) return {x: this.pathPoints[this.pathPoints.length - 1].x, y: this.pathPoints[this.pathPoints.length - 1].y}; var totalSegments = this.pathPoints.length - 1; var segmentProgress = progress * totalSegments; var currentSegment = Math.floor(segmentProgress); var segmentRatio = segmentProgress - currentSegment; if (currentSegment >= totalSegments) { return {x: this.pathPoints[this.pathPoints.length - 1].x, y: this.pathPoints[this.pathPoints.length - 1].y}; } var start = this.pathPoints[currentSegment]; var end = this.pathPoints[currentSegment + 1]; return { x: start.x + (end.x - start.x) * segmentRatio, y: start.y + (end.y - start.y) * segmentRatio }; }, getPathLength: function() { var totalLength = 0; for (var i = 0; i < this.pathPoints.length - 1; i++) { var dx = this.pathPoints[i + 1].x - this.pathPoints[i].x; var dy = this.pathPoints[i + 1].y - this.pathPoints[i].y; totalLength += Math.sqrt(dx * dx + dy * dy); } return totalLength; } };
User prompt
update with: /**** * Game Constants - CORRECTED LAYOUT ****/ var CELL_SIZE = 100; // Good size for visibility var GRID_COLS = 5; var GRID_ROWS = 4; // Shorter grids to fit the layout better var SCREEN_WIDTH = 2048; var SCREEN_HEIGHT = 2732; // Use most of the screen for the play area var PLAY_AREA_WIDTH = SCREEN_WIDTH * 0.9; // 90% of screen width var PLAY_AREA_HEIGHT = SCREEN_HEIGHT * 0.7; // 70% of screen height (accounting for UI) var PLAY_AREA_X = SCREEN_WIDTH * 0.05; // Center horizontally var PLAY_AREA_Y = 300; // Start below UI // Building areas centered in the play area var BUILDING_AREA_WIDTH = GRID_COLS * CELL_SIZE; var BUILDING_AREA_HEIGHT = GRID_ROWS * CELL_SIZE; var TOP_AREA_X = (SCREEN_WIDTH - BUILDING_AREA_WIDTH) / 2; var TOP_AREA_Y = PLAY_AREA_Y + 100; // Some margin from top var BOTTOM_AREA_X = TOP_AREA_X; var BOTTOM_AREA_Y = TOP_AREA_Y + BUILDING_AREA_HEIGHT + 200; // Gap between areas // Path margins around the building areas var PATH_MARGIN = 150;
User prompt
update with: /**** * Path System - UPDATED FOR BETTER LAYOUT ****/ var PathSystem = { pathPoints: [], init: function() { // Create backwards "5" path between the two unit areas with wider lanes var topAreaBottom = TOP_AREA_Y + GRID_ROWS * CELL_SIZE; var bottomAreaTop = BOTTOM_AREA_Y; var centerY = (topAreaBottom + bottomAreaTop) / 2; // Use more of the screen width for the path var leftX = SCREEN_WIDTH * 0.1; // Start at 10% of screen width var rightX = SCREEN_WIDTH * 0.9; // End at 90% of screen width var midLeftX = SCREEN_WIDTH * 0.3; var midRightX = SCREEN_WIDTH * 0.7; this.pathPoints = [ // Start from far left {x: leftX, y: centerY - CELL_SIZE}, // Across to the far right (top horizontal) {x: rightX, y: centerY - CELL_SIZE}, // Down to center {x: rightX, y: centerY}, // Back across to mid-left (center horizontal) {x: midLeftX, y: centerY}, // Down to lower section {x: midLeftX, y: centerY + CELL_SIZE}, // Across to far right (bottom horizontal) {x: rightX, y: centerY + CELL_SIZE} ]; }, getPositionAlongPath: function(progress) { if (progress <= 0) return {x: this.pathPoints[0].x, y: this.pathPoints[0].y}; if (progress >= 1) return {x: this.pathPoints[this.pathPoints.length - 1].x, y: this.pathPoints[this.pathPoints.length - 1].y}; var totalSegments = this.pathPoints.length - 1; var segmentProgress = progress * totalSegments; var currentSegment = Math.floor(segmentProgress); var segmentRatio = segmentProgress - currentSegment; if (currentSegment >= totalSegments) { return {x: this.pathPoints[this.pathPoints.length - 1].x, y: this.pathPoints[this.pathPoints.length - 1].y}; } var start = this.pathPoints[currentSegment]; var end = this.pathPoints[currentSegment + 1]; return { x: start.x + (end.x - start.x) * segmentRatio, y: start.y + (end.y - start.y) * segmentRatio }; }, getPathLength: function() { var totalLength = 0; for (var i = 0; i < this.pathPoints.length - 1; i++) { var dx = this.pathPoints[i + 1].x - this.pathPoints[i].x; var dy = this.pathPoints[i + 1].y - this.pathPoints[i].y; totalLength += Math.sqrt(dx * dx + dy * dy); } return totalLength; } };
User prompt
update with: /**** * Game Constants - UPDATED FOR BETTER LAYOUT ****/ var CELL_SIZE = 120; // Increased from 76 for better visibility var GRID_COLS = 5; // Increased from 3 for more strategic gameplay var GRID_ROWS = 6; // Increased from 5 for more strategic gameplay var AREA_SPACING = CELL_SIZE * 2.5; // Space for the path var SCREEN_WIDTH = 2048; var SCREEN_HEIGHT = 2732; // Center the grids horizontally and use more vertical space var GRID_START_X = (SCREEN_WIDTH - GRID_COLS * CELL_SIZE) / 2; var GRID_START_Y = 200; // Start lower to account for UI var TOP_AREA_Y = GRID_START_Y; var BOTTOM_AREA_Y = TOP_AREA_Y + GRID_ROWS * CELL_SIZE + AREA_SPACING; var TOP_AREA_X = GRID_START_X; var BOTTOM_AREA_X = GRID_START_X;
User prompt
Please fix the bug: 'TypeError: allEffects[i].update is not a function' in or related to this line: 'allEffects[i].update();' Line Number: 1736
User prompt
Please fix the bug: 'GRID_START_Y is not defined' in or related to this line: 'var topAreaBottom = GRID_START_Y + GRID_ROWS * CELL_SIZE + AREA_SPACING / 2;' Line Number: 1268
Code edit (2 edits merged)
Please save this source code
User prompt
Update with: // Replace the getArcPosition function with this version that ensures the arc always goes up: self.getArcPosition = function (progress) { // Linear interpolation for horizontal movement var x = self.startPosition.x + (self.targetPosition.x - self.startPosition.x) * progress; // Calculate the base line between start and end points var startY = self.startPosition.y; var endY = self.targetPosition.y; var baseY = startY + (endY - startY) * progress; // Create an upward arc that peaks at the midpoint // This arc always goes UP from the baseline, regardless of direction var arcHeight = self.jumpHeight * 4 * progress * (1 - progress); // Final Y position is the baseline MINUS the arc height (negative = up) var y = baseY - arcHeight; return { x: x, y: y }; };
User prompt
// Replace the existing getArcPosition function with this simpler, more natural version: self.getArcPosition = function (progress) { // Linear interpolation for horizontal movement var x = self.startPosition.x + (self.targetPosition.x - self.startPosition.x) * progress; // Simple parabolic arc for vertical movement // The arc peaks at the midpoint (progress = 0.5) var startY = self.startPosition.y; var endY = self.targetPosition.y; var arcOffset = -self.jumpHeight * 4 * progress * (progress - 1); // Linear interpolation for base movement plus arc offset var y = startY + (endY - startY) * progress + arcOffset; return { x: x, y: y }; };
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BlobEnemy = Container.expand(function () {
var self = Container.call(this);
self.active = false;
self.material = 'slime';
self.health = 100;
self.maxHealth = 100;
self.speed = 1.2;
self.pathProgress = 0;
self.waveNumber = 1;
// Jump states
self.jumpState = 'paused';
self.jumpTimer = 0;
self.pauseDuration = 60;
self.jumpDuration = 30;
self.startPosition = {
x: 0,
y: 0
};
self.targetPosition = {
x: 0,
y: 0
};
self.jumpDistance = 80;
self.jumpHeight = 40;
self.baseY = 0;
// Status effects
self.slowed = false;
self.poisoned = false;
self.slowDuration = 0;
self.poisonDuration = 0;
self.poisonDamage = 0;
self.originalSpeed = self.speed;
var blobGraphics = self.attachAsset('blob', {
anchorX: 0.5,
anchorY: 0.5
});
var healthBarOutline = self.attachAsset('healthBarOutline', {
anchorX: 0,
anchorY: 0.5
});
var healthBar = self.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0.5
});
// Position health bars
healthBarOutline.y = healthBar.y = -30;
healthBarOutline.x = -36;
healthBar.x = -35;
healthBar.tint = 0x00ff00;
self.activate = function (material, waveNumber) {
self.active = true;
self.visible = true;
self.material = material || 'slime';
self.waveNumber = waveNumber || 1;
self.pathProgress = 0;
self.jumpState = 'paused';
self.jumpTimer = self.pauseDuration;
// Reset status effects
self.slowed = false;
self.poisoned = false;
self.slowDuration = 0;
self.poisonDuration = 0;
// Set material properties
self.setMaterial(self.material);
// Scale health with wave number
var healthMultiplier = Math.pow(1.15, self.waveNumber);
self.maxHealth = Math.round(self.maxHealth * healthMultiplier);
self.health = self.maxHealth;
// Start at beginning of path
var startPos = PathSystem.getPositionAlongPath(0);
self.x = startPos.x;
self.y = startPos.y;
self.baseY = self.y;
// Reset blob scale
blobGraphics.scaleX = 1;
blobGraphics.scaleY = 1;
};
self.setMaterial = function (material) {
switch (material) {
case 'slime':
blobGraphics.tint = 0x00FF00;
self.speed = 0.8;
self.pauseDuration = 60;
self.jumpDistance = 80;
self.jumpHeight = 40;
self.maxHealth = 100;
break;
case 'metal':
blobGraphics.tint = 0xC0C0C0;
self.speed = 0.6;
self.pauseDuration = 80;
self.jumpDistance = 60;
self.jumpHeight = 25;
self.maxHealth = 150;
break;
case 'lava':
blobGraphics.tint = 0xFF4500;
self.speed = 1.0;
self.pauseDuration = 45;
self.jumpDistance = 90;
self.jumpHeight = 50;
self.maxHealth = 120;
break;
case 'ice':
blobGraphics.tint = 0x87CEEB;
self.speed = 0.5;
self.pauseDuration = 90;
self.jumpDistance = 50;
self.jumpHeight = 30;
self.maxHealth = 80;
break;
case 'shadow':
blobGraphics.tint = 0x4B0082;
self.speed = 1.2;
self.pauseDuration = 30;
self.jumpDistance = 100;
self.jumpHeight = 60;
self.maxHealth = 200;
break;
}
self.originalSpeed = self.speed;
};
self.takeDamage = function (damage, damageType, towerLevel) {
self.health -= damage;
healthBar.width = Math.max(0, self.health / self.maxHealth * 70);
// Apply special effects
if (damageType === 'splash') {
var effect = PoolManager.getEffect();
if (effect) {
effect.activate(self.x, self.y, 'splash');
gameLayer.addChild(effect);
}
} else if (damageType === 'slow' && self.material !== 'ice') {
// Ice is immune to slow
if (!self.slowed) {
var slowPct = 0.5 + (towerLevel - 1) * 0.05; // 50% to 75% slow
self.speed = self.originalSpeed * (1 - slowPct);
self.slowed = true;
self.slowDuration = 180;
var effect = PoolManager.getEffect();
if (effect) {
effect.activate(self.x, self.y, 'slow');
gameLayer.addChild(effect);
}
} else {
self.slowDuration = 180; // Reset duration
}
} else if (damageType === 'poison' && self.material !== 'metal') {
// Metal is immune to poison
if (!self.poisoned) {
self.poisoned = true;
self.poisonDamage = damage * 0.2;
self.poisonDuration = 300;
var effect = PoolManager.getEffect();
if (effect) {
effect.activate(self.x, self.y, 'poison');
gameLayer.addChild(effect);
}
}
} else if (damageType === 'sniper') {
var effect = PoolManager.getEffect();
if (effect) {
effect.activate(self.x, self.y, 'sniper');
gameLayer.addChild(effect);
}
}
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Create death particles
for (var i = 0; i < 8; i++) {
var particle = PoolManager.getParticle();
if (particle) {
particle.activate(self.x, self.y, self.material);
gameLayer.addChild(particle);
}
}
// Give gold based on enemy type and wave
var goldEarned = Math.floor(5 + self.waveNumber * 0.5);
if (self.material === 'shadow') goldEarned *= 2; // Shadow enemies give double gold
setGold(gold + goldEarned);
score += 10 + self.waveNumber;
PoolManager.returnBlob(self);
};
self.startJumpPreparation = function () {
self.jumpState = 'preparing';
// Calculate next position along path
var nextProgress = Math.min(1, self.pathProgress + self.jumpDistance / PathSystem.getPathLength());
var nextPos = PathSystem.getPositionAlongPath(nextProgress);
self.startPosition.x = self.x;
self.startPosition.y = self.baseY;
self.targetPosition.x = nextPos.x;
self.targetPosition.y = nextPos.y;
// Pre-jump compression
tween.stop(blobGraphics, {
scaleX: true,
scaleY: true
});
tween(blobGraphics, {
scaleX: 1.5,
scaleY: 0.5
}, {
duration: 400,
easing: tween.quadIn,
onFinish: function onFinish() {
self.executeJump();
}
});
};
self.executeJump = function () {
self.jumpState = 'jumping';
self.jumpTimer = self.jumpDuration;
// Stretch animation during jump
tween.stop(blobGraphics, {
scaleX: true,
scaleY: true
});
tween(blobGraphics, {
scaleX: 0.8,
scaleY: 1.8
}, {
duration: self.jumpDuration * 16.67,
easing: tween.sineInOut
});
};
self.landJump = function () {
self.jumpState = 'landing';
self.baseY = self.targetPosition.y;
// Update path progress
var pathLength = PathSystem.getPathLength();
self.pathProgress = Math.min(1, self.pathProgress + self.jumpDistance / pathLength);
// Landing compression
tween.stop(blobGraphics, {
scaleX: true,
scaleY: true
});
tween(blobGraphics, {
scaleX: 1.4,
scaleY: 0.6
}, {
duration: 200,
easing: tween.quadOut,
onFinish: function onFinish() {
tween(blobGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.elasticOut,
onFinish: function onFinish() {
self.jumpState = 'paused';
self.jumpTimer = self.pauseDuration;
}
});
}
});
};
self.getArcPosition = function (progress) {
var x = self.startPosition.x + (self.targetPosition.x - self.startPosition.x) * progress;
var startHeight = self.startPosition.y;
var endHeight = self.targetPosition.y;
var peakHeight = Math.min(startHeight, endHeight) - self.jumpHeight;
var arcHeight = peakHeight + 4 * self.jumpHeight * progress * (1 - progress);
var y = startHeight + (endHeight - startHeight) * progress + (arcHeight - startHeight - (endHeight - startHeight) * progress);
return {
x: x,
y: y
};
};
self.update = function () {
if (!self.active) return;
// Handle status effects
if (self.slowed) {
self.slowDuration--;
if (self.slowDuration <= 0) {
self.speed = self.originalSpeed;
self.slowed = false;
}
}
if (self.poisoned) {
if (LK.ticks % 30 === 0) {
// Poison damage every 30 frames
self.health -= self.poisonDamage;
healthBar.width = Math.max(0, self.health / self.maxHealth * 70);
if (self.health <= 0) {
self.die();
return;
}
}
self.poisonDuration--;
if (self.poisonDuration <= 0) {
self.poisoned = false;
}
}
// Check if reached end of path
if (self.pathProgress >= 1) {
lives--;
updateUI();
PoolManager.returnBlob(self);
if (lives <= 0) {
LK.showGameOver();
}
return;
}
// Jump state machine
switch (self.jumpState) {
case 'paused':
self.jumpTimer--;
if (self.jumpTimer <= 0) {
self.startJumpPreparation();
}
break;
case 'preparing':
// Animation handles this
break;
case 'jumping':
self.jumpTimer--;
var progress = (self.jumpDuration - self.jumpTimer) / self.jumpDuration;
var arcPos = self.getArcPosition(progress);
self.x = arcPos.x;
self.y = arcPos.y;
if (self.jumpTimer <= 0) {
self.x = self.targetPosition.x;
self.y = self.targetPosition.y;
self.landJump();
}
break;
case 'landing':
// Animation handles this
break;
}
};
self.reset = function () {
self.material = 'slime';
self.health = 100;
self.maxHealth = 100;
self.speed = 1.2;
self.pathProgress = 0;
self.waveNumber = 1;
self.jumpState = 'paused';
self.jumpTimer = 0;
self.slowed = false;
self.poisoned = false;
self.slowDuration = 0;
self.poisonDuration = 0;
tween.stop(blobGraphics);
blobGraphics.scaleX = 1;
blobGraphics.scaleY = 1;
blobGraphics.tint = 0x00FF00;
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
self.active = false;
self.targetEnemy = null;
self.damage = 10;
self.speed = 5;
self.type = 'default';
self.sourceTowerLevel = 1;
var bulletGraphics = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
bulletGraphics.width = 15;
bulletGraphics.height = 15;
self.activate = function (startX, startY, targetEnemy, damage, speed, type, sourceTowerLevel) {
self.active = true;
self.visible = true;
self.x = startX;
self.y = startY;
self.targetEnemy = targetEnemy;
self.damage = damage || 10;
self.speed = speed || 5;
self.type = type || 'default';
self.sourceTowerLevel = sourceTowerLevel || 1;
// Customize appearance based on type
switch (self.type) {
case 'rapid':
bulletGraphics.tint = 0x00AAFF;
bulletGraphics.width = bulletGraphics.height = 12;
break;
case 'sniper':
bulletGraphics.tint = 0xFF5500;
bulletGraphics.width = bulletGraphics.height = 8;
break;
case 'splash':
bulletGraphics.tint = 0x33CC00;
bulletGraphics.width = bulletGraphics.height = 20;
break;
case 'slow':
bulletGraphics.tint = 0x9900FF;
bulletGraphics.width = bulletGraphics.height = 18;
break;
case 'poison':
bulletGraphics.tint = 0x00FFAA;
bulletGraphics.width = bulletGraphics.height = 18;
break;
default:
bulletGraphics.tint = 0xFFFFFF;
bulletGraphics.width = bulletGraphics.height = 15;
}
};
self.update = function () {
if (!self.active || !self.targetEnemy || !self.targetEnemy.active) {
PoolManager.returnBullet(self);
return;
}
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.speed) {
// Hit target
self.targetEnemy.takeDamage(self.damage, self.type, self.sourceTowerLevel);
PoolManager.returnBullet(self);
} else {
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
}
};
self.reset = function () {
self.targetEnemy = null;
self.damage = 10;
self.speed = 5;
self.type = 'default';
self.sourceTowerLevel = 1;
};
return self;
});
var DeathParticle = Container.expand(function () {
var self = Container.call(this);
self.active = false;
self.velocity = {
x: 0,
y: 0
};
self.life = 0;
self.maxLife = 60;
var particleGraphics = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
self.activate = function (x, y, material) {
self.active = true;
self.visible = true;
self.x = x;
self.y = y;
self.life = self.maxLife;
// Random velocity
var angle = Math.random() * Math.PI * 2;
var speed = 2 + Math.random() * 4;
self.velocity.x = Math.cos(angle) * speed;
self.velocity.y = Math.sin(angle) * speed;
// Set color based on material
switch (material) {
case 'slime':
particleGraphics.tint = 0x00FF00;
break;
case 'metal':
particleGraphics.tint = 0xC0C0C0;
break;
case 'lava':
particleGraphics.tint = 0xFF4500;
break;
case 'ice':
particleGraphics.tint = 0x87CEEB;
break;
case 'shadow':
particleGraphics.tint = 0x4B0082;
break;
default:
particleGraphics.tint = 0xFFFFFF;
}
particleGraphics.width = particleGraphics.height = 4 + Math.random() * 8;
self.alpha = 1;
};
self.update = function () {
if (!self.active) return;
self.x += self.velocity.x;
self.y += self.velocity.y;
self.velocity.y += 0.2; // Gravity
self.life--;
self.alpha = self.life / self.maxLife;
if (self.life <= 0) {
PoolManager.returnParticle(self);
}
};
self.reset = function () {
self.velocity.x = 0;
self.velocity.y = 0;
self.life = 0;
};
return self;
});
var EffectIndicator = Container.expand(function () {
var self = Container.call(this);
self.active = false;
self.effectType = 'splash';
var effectGraphics = self.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
effectGraphics.blendMode = 1;
self.activate = function (x, y, type) {
self.active = true;
self.visible = true;
self.x = x;
self.y = y;
self.effectType = type;
switch (type) {
case 'splash':
effectGraphics.tint = 0x33CC00;
effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5;
break;
case 'slow':
effectGraphics.tint = 0x9900FF;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
break;
case 'poison':
effectGraphics.tint = 0x00FFAA;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
break;
case 'sniper':
effectGraphics.tint = 0xFF5500;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
break;
}
effectGraphics.alpha = 0.7;
self.alpha = 0;
self.scaleX = 0.5;
self.scaleY = 0.5;
// Animate effect
tween(self, {
alpha: 0.8,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
PoolManager.returnEffect(self);
}
});
}
});
};
self.reset = function () {
self.effectType = 'splash';
tween.stop(self);
};
return self;
});
var EnergyOrb = Container.expand(function () {
var self = Container.call(this);
self.active = false;
self.orbType = 'default';
self.level = 1;
self.maxLevel = 6;
self.gridX = 0;
self.gridY = 0;
self.area = 0; // 0 for top area, 1 for bottom area
self.range = 3 * CELL_SIZE;
self.damage = 10;
self.fireRate = 60;
self.bulletSpeed = 5;
self.lastFired = 0;
self.targetEnemy = null;
self.isDragging = false;
self.magneticTarget = null;
var orbGraphics = self.attachAsset('energyOrb', {
anchorX: 0.5,
anchorY: 0.5
});
var coreGraphics = self.attachAsset('energyOrb', {
anchorX: 0.5,
anchorY: 0.5
});
coreGraphics.width = 40;
coreGraphics.height = 40;
// Level indicators
var levelIndicators = [];
for (var i = 0; i < 6; i++) {
var dot = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
dot.width = 6;
dot.height = 6;
dot.x = (i - 2.5) * 12;
dot.y = CELL_SIZE * 0.6;
dot.tint = 0x444444;
levelIndicators.push(dot);
}
self.activate = function (orbType, gridX, gridY, area) {
self.active = true;
self.visible = true;
self.orbType = orbType || 'default';
self.level = 1;
self.gridX = gridX;
self.gridY = gridY;
self.area = area;
self.lastFired = 0;
self.targetEnemy = null;
self.setOrbType(self.orbType);
self.updateVisuals();
self.updatePosition();
};
self.setOrbType = function (type) {
self.orbType = type;
switch (type) {
case 'rapid':
self.fireRate = 30;
self.damage = 5;
self.range = 2.5 * CELL_SIZE;
self.bulletSpeed = 7;
orbGraphics.tint = coreGraphics.tint = 0x00AAFF;
break;
case 'sniper':
self.fireRate = 90;
self.damage = 25;
self.range = 5 * CELL_SIZE;
self.bulletSpeed = 25;
orbGraphics.tint = coreGraphics.tint = 0xFF5500;
break;
case 'splash':
self.fireRate = 75;
self.damage = 15;
self.range = 2 * CELL_SIZE;
self.bulletSpeed = 4;
orbGraphics.tint = coreGraphics.tint = 0x33CC00;
break;
case 'slow':
self.fireRate = 50;
self.damage = 8;
self.range = 3.5 * CELL_SIZE;
self.bulletSpeed = 5;
orbGraphics.tint = coreGraphics.tint = 0x9900FF;
break;
case 'poison':
self.fireRate = 70;
self.damage = 12;
self.range = 3.2 * CELL_SIZE;
self.bulletSpeed = 5;
orbGraphics.tint = coreGraphics.tint = 0x00FFAA;
break;
default:
self.fireRate = 60;
self.damage = 10;
self.range = 3 * CELL_SIZE;
self.bulletSpeed = 5;
orbGraphics.tint = coreGraphics.tint = 0xAAAAAA;
}
};
self.updateVisuals = function () {
var baseSize = 60 + self.level * 8;
orbGraphics.width = orbGraphics.height = baseSize;
var coreSize = 30 + self.level * 4;
coreGraphics.width = coreGraphics.height = coreSize;
// Update level indicators
for (var i = 0; i < levelIndicators.length; i++) {
if (i < self.level) {
levelIndicators[i].tint = 0xFFFFFF;
levelIndicators[i].alpha = 1;
} else {
levelIndicators[i].tint = 0x444444;
levelIndicators[i].alpha = 0.5;
}
}
// Pulsing animation based on level
var pulseScale = 1 + self.level * 0.02;
tween.stop(orbGraphics, {
scaleX: true,
scaleY: true
});
tween(orbGraphics, {
scaleX: pulseScale,
scaleY: pulseScale
}, {
duration: 1000 + self.level * 200,
easing: tween.sineInOut,
repeat: -1,
yoyo: true
});
};
self.updatePosition = function () {
var startX = self.area === 0 ? TOP_AREA_X : BOTTOM_AREA_X;
var startY = self.area === 0 ? TOP_AREA_Y : BOTTOM_AREA_Y;
self.x = startX + self.gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = startY + self.gridY * CELL_SIZE + CELL_SIZE / 2;
};
self.getRange = function () {
var baseRange = self.range;
var rangeMultiplier = 1 + (self.level - 1) * 0.15;
if (self.orbType === 'sniper' && self.level === self.maxLevel) {
rangeMultiplier *= 2; // Max level sniper gets huge range boost
}
return baseRange * rangeMultiplier;
};
self.getCurrentDamage = function () {
var damageMultiplier = 1 + (self.level - 1) * 0.5;
if (self.level === self.maxLevel) {
damageMultiplier *= 2; // Max level gets double damage bonus
}
return Math.floor(self.damage * damageMultiplier);
};
self.getCurrentFireRate = function () {
var rateMultiplier = 1 + (self.level - 1) * 0.3;
return Math.max(10, Math.floor(self.fireRate / rateMultiplier));
};
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = Infinity;
var orbRange = self.getRange();
for (var i = 0; i < activeBlobs.length; i++) {
var blob = activeBlobs[i];
if (!blob.active) continue;
var dx = blob.x - self.x;
var dy = blob.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= orbRange && distance < closestDistance) {
closestDistance = distance;
closestEnemy = blob;
}
}
return closestEnemy;
};
self.fire = function () {
if (!self.targetEnemy) return;
var bullet = PoolManager.getBullet();
if (bullet) {
var bulletStartX = self.x + Math.cos(coreGraphics.rotation) * 30;
var bulletStartY = self.y + Math.sin(coreGraphics.rotation) * 30;
bullet.activate(bulletStartX, bulletStartY, self.targetEnemy, self.getCurrentDamage(), self.bulletSpeed + self.level, self.orbType, self.level);
gameLayer.addChild(bullet);
activeBullets.push(bullet);
// Recoil effect
tween.stop(coreGraphics, {
scaleX: true,
scaleY: true
});
tween(coreGraphics, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100,
easing: tween.quadOut,
onFinish: function onFinish() {
tween(coreGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.elasticOut
});
}
});
}
};
self.canMergeWith = function (otherOrb) {
return otherOrb && otherOrb.active && otherOrb.orbType === self.orbType && otherOrb.level === self.level && self.level < self.maxLevel;
};
self.checkMagneticAttraction = function (otherOrb) {
if (!self.canMergeWith(otherOrb)) {
self.magneticTarget = null;
return;
}
var dx = otherOrb.x - self.x;
var dy = otherOrb.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < CELL_SIZE * 1.5) {
self.magneticTarget = otherOrb;
// Subtle attraction animation
tween.stop(self, {
x: true,
y: true
});
tween(self, {
x: self.x + dx * 0.1,
y: self.y + dy * 0.1
}, {
duration: 100,
easing: tween.quadOut
});
} else {
self.magneticTarget = null;
}
};
self.mergeWith = function (otherOrb, targetArea) {
if (!self.canMergeWith(otherOrb)) return false;
// Find empty spot in target area
var emptySpot = GameManager.findEmptySpot(targetArea);
if (!emptySpot) {
// Target area is full, merge stays in current area
emptySpot = GameManager.findEmptySpot(self.area);
if (!emptySpot) return false;
targetArea = self.area;
}
// Create new merged orb
var newOrb = PoolManager.getEnergyOrb();
if (newOrb) {
newOrb.activate(self.orbType, emptySpot.x, emptySpot.y, targetArea);
newOrb.level = self.level + 1;
newOrb.updateVisuals();
if (targetArea === 0) {
topAreaLayer.addChild(newOrb);
topAreaOrbs[emptySpot.x][emptySpot.y] = newOrb;
} else {
bottomAreaLayer.addChild(newOrb);
bottomAreaOrbs[emptySpot.x][emptySpot.y] = newOrb;
}
// Merge animation
tween(newOrb, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
tween(newOrb, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.elasticOut
});
}
});
// Remove old orbs
GameManager.removeOrb(self);
GameManager.removeOrb(otherOrb);
return true;
}
return false;
};
self.update = function () {
if (!self.active) return;
// Find and track target
self.targetEnemy = self.findTarget();
if (self.targetEnemy) {
// Rotate core towards target
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var angle = Math.atan2(dy, dx);
tween.stop(coreGraphics, {
rotation: true
});
tween(coreGraphics, {
rotation: angle
}, {
duration: 200,
easing: tween.quadOut
});
// Fire at target
if (LK.ticks - self.lastFired >= self.getCurrentFireRate()) {
self.fire();
self.lastFired = LK.ticks;
}
}
};
self.reset = function () {
self.orbType = 'default';
self.level = 1;
self.gridX = 0;
self.gridY = 0;
self.area = 0;
self.lastFired = 0;
self.targetEnemy = null;
self.isDragging = false;
self.magneticTarget = null;
tween.stop(self);
tween.stop(orbGraphics);
tween.stop(coreGraphics);
orbGraphics.scaleX = orbGraphics.scaleY = 1;
coreGraphics.scaleX = coreGraphics.scaleY = 1;
coreGraphics.rotation = 0;
};
return self;
});
var GenerateButton = Container.expand(function (area) {
var self = Container.call(this);
self.area = area; // 0 for top, 1 for bottom
self.cost = 10;
var buttonGraphics = self.attachAsset('generateButton', {
anchorX: 0.5,
anchorY: 0.5
});
var costText = new Text2('Generate: ' + self.cost, {
size: 30,
fill: 0xFFFFFF,
weight: 800
});
costText.anchor.set(0.5, 0.5);
self.addChild(costText);
self.updateCost = function () {
// Fibonacci-like progression
var newCost = Math.floor(10 * Math.pow(1.618, GameManager.getGenerateCount(self.area)));
self.cost = newCost;
costText.setText('Generate: ' + self.cost);
// Update button color based on affordability
if (gold >= self.cost) {
buttonGraphics.tint = 0x00AA00;
} else {
buttonGraphics.tint = 0x888888;
}
};
self.generate = function () {
if (gold < self.cost) return false;
var emptySpot = GameManager.findEmptySpot(self.area);
if (!emptySpot) return false;
// Random tower type
var types = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison'];
var randomType = types[Math.floor(Math.random() * types.length)];
var newOrb = PoolManager.getEnergyOrb();
if (newOrb) {
newOrb.activate(randomType, emptySpot.x, emptySpot.y, self.area);
if (self.area === 0) {
topAreaLayer.addChild(newOrb);
topAreaOrbs[emptySpot.x][emptySpot.y] = newOrb;
} else {
bottomAreaLayer.addChild(newOrb);
bottomAreaOrbs[emptySpot.x][emptySpot.y] = newOrb;
}
setGold(gold - self.cost);
GameManager.incrementGenerateCount(self.area);
self.updateCost();
// Spawn animation
newOrb.alpha = 0;
newOrb.scaleX = newOrb.scaleY = 0.5;
tween(newOrb, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.elasticOut
});
return true;
}
return false;
};
self.down = function () {
self.generate();
};
self.update = function () {
self.updateCost();
};
return self;
});
var Notification = Container.expand(function (message) {
var self = Container.call(this);
var notificationGraphics = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
var notificationText = new Text2(message, {
size: 50,
fill: 0x000000,
weight: 800
});
notificationText.anchor.set(0.5, 0.5);
notificationGraphics.width = notificationText.width + 30;
self.addChild(notificationText);
self.alpha = 1;
var fadeOutTime = 180; // 3 seconds
self.update = function () {
if (fadeOutTime > 0) {
fadeOutTime--;
self.alpha = Math.min(fadeOutTime / 60, 1);
} else {
self.destroy();
}
};
return self;
});
/****
* Game Manager
****/
var WaveManager = Container.expand(function () {
var self = Container.call(this);
self.currentWave = 0;
self.maxWaves = 50;
self.waveTimer = 0;
self.waveDelay = 300; // 5 seconds at 60fps
self.waveActive = false;
self.enemiesSpawned = 0;
self.enemiesToSpawn = 0;
self.spawnTimer = 0;
self.spawnDelay = 30; // 0.5 seconds between spawns
var waveText = new Text2('Wave: 1', {
size: 60,
fill: 0xFFFF00,
weight: 800
});
waveText.anchor.set(0.5, 0.5);
self.addChild(waveText);
self.startNextWave = function () {
self.currentWave++;
self.waveActive = true;
self.enemiesSpawned = 0;
self.spawnTimer = 0;
// Determine wave composition
self.enemiesToSpawn = 10 + Math.floor(self.currentWave * 1.5);
waveText.setText('Wave: ' + self.currentWave);
// Show wave start notification
var notification = new Notification('Wave ' + self.currentWave + ' incoming!');
notification.x = SCREEN_WIDTH / 2;
notification.y = SCREEN_HEIGHT / 2;
gameLayer.addChild(notification);
};
self.getWaveMaterial = function () {
// Introduce new materials over time
if (self.currentWave <= 5) return 'slime';
if (self.currentWave <= 10) return Math.random() < 0.7 ? 'slime' : 'metal';
if (self.currentWave <= 15) {
var materials = ['slime', 'metal', 'ice'];
return materials[Math.floor(Math.random() * materials.length)];
}
if (self.currentWave <= 25) {
var materials = ['slime', 'metal', 'ice', 'lava'];
return materials[Math.floor(Math.random() * materials.length)];
}
// All materials available
var materials = ['slime', 'metal', 'ice', 'lava', 'shadow'];
return materials[Math.floor(Math.random() * materials.length)];
};
self.spawnEnemy = function () {
var blob = PoolManager.getBlob();
if (blob) {
var material = self.getWaveMaterial();
blob.activate(material, self.currentWave);
gameLayer.addChild(blob);
activeBlobs.push(blob);
self.enemiesSpawned++;
}
};
self.update = function () {
if (!self.waveActive) {
self.waveTimer++;
if (self.waveTimer >= self.waveDelay) {
self.waveTimer = 0;
self.startNextWave();
}
} else {
// Spawn enemies
if (self.enemiesSpawned < self.enemiesToSpawn) {
self.spawnTimer++;
if (self.spawnTimer >= self.spawnDelay) {
self.spawnTimer = 0;
self.spawnEnemy();
}
} else {
// Check if wave is complete
var activeEnemies = 0;
for (var i = 0; i < activeBlobs.length; i++) {
if (activeBlobs[i].active) activeEnemies++;
}
if (activeEnemies === 0) {
self.waveActive = false;
if (self.currentWave >= self.maxWaves) {
LK.showYouWin();
}
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
/****
* Object Pools
****/
/****
* Game Constants
****/
var PoolManager = {
energyOrbPool: [],
blobPool: [],
bulletPool: [],
particlePool: [],
effectPool: [],
// Pool sizes
ENERGY_ORB_POOL_SIZE: 30,
BLOB_POOL_SIZE: 100,
BULLET_POOL_SIZE: 200,
PARTICLE_POOL_SIZE: 500,
EFFECT_POOL_SIZE: 100,
init: function init() {
// Initialize energy orb pool
for (var i = 0; i < this.ENERGY_ORB_POOL_SIZE; i++) {
var orb = new EnergyOrb();
orb.active = false;
orb.visible = false;
this.energyOrbPool.push(orb);
}
// Initialize blob pool
for (var i = 0; i < this.BLOB_POOL_SIZE; i++) {
var blob = new BlobEnemy();
blob.active = false;
blob.visible = false;
this.blobPool.push(blob);
}
// Initialize bullet pool
for (var i = 0; i < this.BULLET_POOL_SIZE; i++) {
var bullet = new Bullet();
bullet.active = false;
bullet.visible = false;
this.bulletPool.push(bullet);
}
// Initialize particle pool
for (var i = 0; i < this.PARTICLE_POOL_SIZE; i++) {
var particle = new DeathParticle();
particle.active = false;
particle.visible = false;
this.particlePool.push(particle);
}
// Initialize effect pool
for (var i = 0; i < this.EFFECT_POOL_SIZE; i++) {
var effect = new EffectIndicator();
effect.active = false;
effect.visible = false;
this.effectPool.push(effect);
}
},
getEnergyOrb: function getEnergyOrb() {
for (var i = 0; i < this.energyOrbPool.length; i++) {
if (!this.energyOrbPool[i].active) {
return this.energyOrbPool[i];
}
}
return null; // Pool exhausted
},
getBlob: function getBlob() {
for (var i = 0; i < this.blobPool.length; i++) {
if (!this.blobPool[i].active) {
return this.blobPool[i];
}
}
return null;
},
getBullet: function getBullet() {
for (var i = 0; i < this.bulletPool.length; i++) {
if (!this.bulletPool[i].active) {
return this.bulletPool[i];
}
}
return null;
},
getParticle: function getParticle() {
for (var i = 0; i < this.particlePool.length; i++) {
if (!this.particlePool[i].active) {
return this.particlePool[i];
}
}
return null;
},
getEffect: function getEffect() {
for (var i = 0; i < this.effectPool.length; i++) {
if (!this.effectPool[i].active) {
return this.effectPool[i];
}
}
return null;
},
returnEnergyOrb: function returnEnergyOrb(orb) {
orb.active = false;
orb.visible = false;
orb.reset();
},
returnBlob: function returnBlob(blob) {
blob.active = false;
blob.visible = false;
blob.reset();
if (blob.shadow) {
blob.shadow.visible = false;
}
},
returnBullet: function returnBullet(bullet) {
bullet.active = false;
bullet.visible = false;
bullet.reset();
},
returnParticle: function returnParticle(particle) {
particle.active = false;
particle.visible = false;
particle.reset();
},
returnEffect: function returnEffect(effect) {
effect.active = false;
effect.visible = false;
effect.reset();
}
};
/****
* Path System
****/
var PathSystem = {
pathPoints: [],
init: function init() {
// Create backwards "5" path between the two unit areas
var topAreaBottom = GRID_START_Y + GRID_ROWS * CELL_SIZE + AREA_SPACING / 2;
var bottomAreaTop = topAreaBottom + AREA_SPACING;
var centerY = (topAreaBottom + bottomAreaTop) / 2;
var leftX = GRID_START_X - CELL_SIZE;
var rightX = GRID_START_X + GRID_COLS * CELL_SIZE + CELL_SIZE;
var midLeftX = GRID_START_X + CELL_SIZE;
var midRightX = GRID_START_X + (GRID_COLS - 1) * CELL_SIZE;
this.pathPoints = [
// Start from top-left
{
x: leftX,
y: centerY - CELL_SIZE * 2
},
// Across to the right
{
x: rightX,
y: centerY - CELL_SIZE * 2
},
// Down to center
{
x: rightX,
y: centerY
},
// Back across to left (center horizontal)
{
x: midLeftX,
y: centerY
},
// Down to lower section
{
x: midLeftX,
y: centerY + CELL_SIZE * 2
},
// Across to bottom-right
{
x: rightX,
y: centerY + CELL_SIZE * 2
}];
},
getPositionAlongPath: function getPositionAlongPath(progress) {
if (progress <= 0) return {
x: this.pathPoints[0].x,
y: this.pathPoints[0].y
};
if (progress >= 1) return {
x: this.pathPoints[this.pathPoints.length - 1].x,
y: this.pathPoints[this.pathPoints.length - 1].y
};
var totalSegments = this.pathPoints.length - 1;
var segmentProgress = progress * totalSegments;
var currentSegment = Math.floor(segmentProgress);
var segmentRatio = segmentProgress - currentSegment;
if (currentSegment >= totalSegments) {
return {
x: this.pathPoints[this.pathPoints.length - 1].x,
y: this.pathPoints[this.pathPoints.length - 1].y
};
}
var start = this.pathPoints[currentSegment];
var end = this.pathPoints[currentSegment + 1];
return {
x: start.x + (end.x - start.x) * segmentRatio,
y: start.y + (end.y - start.y) * segmentRatio
};
},
getPathLength: function getPathLength() {
var totalLength = 0;
for (var i = 0; i < this.pathPoints.length - 1; i++) {
var dx = this.pathPoints[i + 1].x - this.pathPoints[i].x;
var dy = this.pathPoints[i + 1].y - this.pathPoints[i].y;
totalLength += Math.sqrt(dx * dx + dy * dy);
}
return totalLength;
}
};
/****
* Game Manager
****/
var GameManager = {
topAreaGenerateCount: 0,
bottomAreaGenerateCount: 0,
findEmptySpot: function findEmptySpot(area) {
var orbs = area === 0 ? topAreaOrbs : bottomAreaOrbs;
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
if (!orbs[x][y]) {
return {
x: x,
y: y
};
}
}
}
return null;
},
removeOrb: function removeOrb(orb) {
var orbs = orb.area === 0 ? topAreaOrbs : bottomAreaOrbs;
var layer = orb.area === 0 ? topAreaLayer : bottomAreaLayer;
orbs[orb.gridX][orb.gridY] = null;
layer.removeChild(orb);
PoolManager.returnEnergyOrb(orb);
},
getOrbAt: function getOrbAt(area, gridX, gridY) {
var orbs = area === 0 ? topAreaOrbs : bottomAreaOrbs;
return orbs[gridX] && orbs[gridX][gridY];
},
getGenerateCount: function getGenerateCount(area) {
return area === 0 ? this.topAreaGenerateCount : this.bottomAreaGenerateCount;
},
incrementGenerateCount: function incrementGenerateCount(area) {
if (area === 0) {
this.topAreaGenerateCount++;
} else {
this.bottomAreaGenerateCount++;
}
}
};
var CELL_SIZE = 76;
var GRID_COLS = 3;
var GRID_ROWS = 5;
var AREA_SPACING = CELL_SIZE * 3;
var SCREEN_WIDTH = 2048;
var SCREEN_HEIGHT = 2732;
var GRID_START_X = (SCREEN_WIDTH - GRID_COLS * CELL_SIZE) / 2;
var TOP_AREA_Y = 300;
var BOTTOM_AREA_Y = TOP_AREA_Y + GRID_ROWS * CELL_SIZE + AREA_SPACING;
var TOP_AREA_X = GRID_START_X;
var BOTTOM_AREA_X = GRID_START_X;
/****
* Game Variables
****/
var gold = 50;
var lives = 20;
var score = 0;
var activeBlobs = [];
var activeBullets = [];
var topAreaOrbs = [];
var bottomAreaOrbs = [];
var selectedOrb = null;
var isDragging = false;
var dragStartPos = {
x: 0,
y: 0
};
/****
* Initialize Arrays
****/
for (var x = 0; x < GRID_COLS; x++) {
topAreaOrbs[x] = [];
bottomAreaOrbs[x] = [];
for (var y = 0; y < GRID_ROWS; y++) {
topAreaOrbs[x][y] = null;
bottomAreaOrbs[x][y] = null;
}
}
/****
* UI Elements
****/
var goldText = new Text2('Gold: ' + gold, {
size: 60,
fill: 0xFFD700,
weight: 800
});
goldText.anchor.set(0.5, 0.5);
var livesText = new Text2('Lives: ' + lives, {
size: 60,
fill: 0x00FF00,
weight: 800
});
livesText.anchor.set(0.5, 0.5);
var scoreText = new Text2('Score: ' + score, {
size: 60,
fill: 0xFF0000,
weight: 800
});
scoreText.anchor.set(0.5, 0.5);
LK.gui.top.addChild(goldText);
LK.gui.top.addChild(livesText);
LK.gui.top.addChild(scoreText);
goldText.x = -400;
goldText.y = 50;
livesText.x = 0;
livesText.y = 50;
scoreText.x = 400;
scoreText.y = 50;
function updateUI() {
goldText.setText('Gold: ' + gold);
livesText.setText('Lives: ' + lives);
scoreText.setText('Score: ' + score);
}
function setGold(value) {
gold = value;
updateUI();
}
/****
* Layer Setup
****/
var gameLayer = new Container();
var topAreaLayer = new Container();
var bottomAreaLayer = new Container();
var effectLayer = new Container();
game.addChild(gameLayer);
game.addChild(topAreaLayer);
game.addChild(bottomAreaLayer);
game.addChild(effectLayer);
/****
* Generate Buttons
****/
var topGenerateButton = new GenerateButton(0);
topGenerateButton.x = TOP_AREA_X + GRID_COLS * CELL_SIZE + 150;
topGenerateButton.y = TOP_AREA_Y + GRID_ROWS * CELL_SIZE / 2;
game.addChild(topGenerateButton);
var bottomGenerateButton = new GenerateButton(1);
bottomGenerateButton.x = BOTTOM_AREA_X + GRID_COLS * CELL_SIZE + 150;
bottomGenerateButton.y = BOTTOM_AREA_Y + GRID_ROWS * CELL_SIZE / 2;
game.addChild(bottomGenerateButton);
/****
* Wave Manager
****/
var waveManager = new WaveManager();
waveManager.x = SCREEN_WIDTH / 2;
waveManager.y = SCREEN_HEIGHT - 100;
game.addChild(waveManager);
/****
* Input Handling
****/
game.down = function (x, y, obj) {
// Check if clicking on an orb
var clickedOrb = null;
var clickedArea = -1;
// Check top area
if (x >= TOP_AREA_X && x <= TOP_AREA_X + GRID_COLS * CELL_SIZE && y >= TOP_AREA_Y && y <= TOP_AREA_Y + GRID_ROWS * CELL_SIZE) {
var gridX = Math.floor((x - TOP_AREA_X) / CELL_SIZE);
var gridY = Math.floor((y - TOP_AREA_Y) / CELL_SIZE);
clickedOrb = GameManager.getOrbAt(0, gridX, gridY);
clickedArea = 0;
}
// Check bottom area
if (!clickedOrb && x >= BOTTOM_AREA_X && x <= BOTTOM_AREA_X + GRID_COLS * CELL_SIZE && y >= BOTTOM_AREA_Y && y <= BOTTOM_AREA_Y + GRID_ROWS * CELL_SIZE) {
var gridX = Math.floor((x - BOTTOM_AREA_X) / CELL_SIZE);
var gridY = Math.floor((y - BOTTOM_AREA_Y) / CELL_SIZE);
clickedOrb = GameManager.getOrbAt(1, gridX, gridY);
clickedArea = 1;
}
if (clickedOrb) {
selectedOrb = clickedOrb;
isDragging = true;
dragStartPos.x = x;
dragStartPos.y = y;
selectedOrb.isDragging = true;
// Bring to front
var layer = selectedOrb.area === 0 ? topAreaLayer : bottomAreaLayer;
layer.removeChild(selectedOrb);
layer.addChild(selectedOrb);
}
};
game.move = function (x, y, obj) {
if (isDragging && selectedOrb) {
selectedOrb.x = selectedOrb.x + (x - dragStartPos.x);
selectedOrb.y = selectedOrb.y + (y - dragStartPos.y);
dragStartPos.x = x;
dragStartPos.y = y;
// Check for magnetic attraction
var orbs = selectedOrb.area === 0 ? topAreaOrbs : bottomAreaOrbs;
for (var gx = 0; gx < GRID_COLS; gx++) {
for (var gy = 0; gy < GRID_ROWS; gy++) {
var otherOrb = orbs[gx][gy];
if (otherOrb && otherOrb !== selectedOrb) {
selectedOrb.checkMagneticAttraction(otherOrb);
}
}
}
}
};
game.up = function (x, y, obj) {
if (isDragging && selectedOrb) {
isDragging = false;
selectedOrb.isDragging = false;
// Check for merge
var mergeHappened = false;
var targetArea = -1;
// Determine which area we're over
if (x >= TOP_AREA_X && x <= TOP_AREA_X + GRID_COLS * CELL_SIZE && y >= TOP_AREA_Y && y <= TOP_AREA_Y + GRID_ROWS * CELL_SIZE) {
targetArea = 0;
} else if (x >= BOTTOM_AREA_X && x <= BOTTOM_AREA_X + GRID_COLS * CELL_SIZE && y >= BOTTOM_AREA_Y && y <= BOTTOM_AREA_Y + GRID_ROWS * CELL_SIZE) {
targetArea = 1;
}
if (targetArea !== -1) {
var gridX = Math.floor((x - (targetArea === 0 ? TOP_AREA_X : BOTTOM_AREA_X)) / CELL_SIZE);
var gridY = Math.floor((y - (targetArea === 0 ? TOP_AREA_Y : BOTTOM_AREA_Y)) / CELL_SIZE);
if (gridX >= 0 && gridX < GRID_COLS && gridY >= 0 && gridY < GRID_ROWS) {
var targetOrb = GameManager.getOrbAt(targetArea, gridX, gridY);
if (targetOrb && selectedOrb.canMergeWith(targetOrb)) {
// Merge the orbs
var otherArea = targetArea === 0 ? 1 : 0;
mergeHappened = selectedOrb.mergeWith(targetOrb, otherArea);
}
}
}
if (!mergeHappened) {
// Return orb to original position
selectedOrb.updatePosition();
// Smooth return animation
tween(selectedOrb, {
x: selectedOrb.area === 0 ? TOP_AREA_X + selectedOrb.gridX * CELL_SIZE + CELL_SIZE / 2 : BOTTOM_AREA_X + selectedOrb.gridX * CELL_SIZE + CELL_SIZE / 2,
y: selectedOrb.area === 0 ? TOP_AREA_Y + selectedOrb.gridY * CELL_SIZE + CELL_SIZE / 2 : BOTTOM_AREA_Y + selectedOrb.gridY * CELL_SIZE + CELL_SIZE / 2
}, {
duration: 300,
easing: tween.elasticOut
});
}
selectedOrb.magneticTarget = null;
selectedOrb = null;
}
};
/****
* Grid Visual Indicators
****/
function drawGridLines() {
// Top area grid
for (var x = 0; x <= GRID_COLS; x++) {
var line = new Container();
var lineGraphics = line.attachAsset('cell', {
anchorX: 0,
anchorY: 0
});
lineGraphics.width = 2;
lineGraphics.height = GRID_ROWS * CELL_SIZE;
lineGraphics.tint = 0x444444;
lineGraphics.alpha = 0.3;
line.x = TOP_AREA_X + x * CELL_SIZE;
line.y = TOP_AREA_Y;
gameLayer.addChild(line);
}
for (var y = 0; y <= GRID_ROWS; y++) {
var line = new Container();
var lineGraphics = line.attachAsset('cell', {
anchorX: 0,
anchorY: 0
});
lineGraphics.width = GRID_COLS * CELL_SIZE;
lineGraphics.height = 2;
lineGraphics.tint = 0x444444;
lineGraphics.alpha = 0.3;
line.x = TOP_AREA_X;
line.y = TOP_AREA_Y + y * CELL_SIZE;
gameLayer.addChild(line);
}
// Bottom area grid
for (var x = 0; x <= GRID_COLS; x++) {
var line = new Container();
var lineGraphics = line.attachAsset('cell', {
anchorX: 0,
anchorY: 0
});
lineGraphics.width = 2;
lineGraphics.height = GRID_ROWS * CELL_SIZE;
lineGraphics.tint = 0x444444;
lineGraphics.alpha = 0.3;
line.x = BOTTOM_AREA_X + x * CELL_SIZE;
line.y = BOTTOM_AREA_Y;
gameLayer.addChild(line);
}
for (var y = 0; y <= GRID_ROWS; y++) {
var line = new Container();
var lineGraphics = line.attachAsset('cell', {
anchorX: 0,
anchorY: 0
});
lineGraphics.width = GRID_COLS * CELL_SIZE;
lineGraphics.height = 2;
lineGraphics.tint = 0x444444;
lineGraphics.alpha = 0.3;
line.x = BOTTOM_AREA_X;
line.y = BOTTOM_AREA_Y + y * CELL_SIZE;
gameLayer.addChild(line);
}
}
/****
* Path Visualization
****/
function drawPath() {
for (var i = 0; i < PathSystem.pathPoints.length - 1; i++) {
var start = PathSystem.pathPoints[i];
var end = PathSystem.pathPoints[i + 1];
var dx = end.x - start.x;
var dy = end.y - start.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var segments = Math.floor(distance / 20);
for (var j = 0; j < segments; j++) {
var progress = j / segments;
var x = start.x + dx * progress;
var y = start.y + dy * progress;
var pathDot = new Container();
var dotGraphics = pathDot.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
dotGraphics.width = dotGraphics.height = 8;
dotGraphics.tint = 0x666666;
dotGraphics.alpha = 0.6;
pathDot.x = x;
pathDot.y = y;
gameLayer.addChild(pathDot);
}
}
}
/****
* Initialization
****/
PoolManager.init();
PathSystem.init();
drawGridLines();
drawPath();
// Start first wave after a delay
waveManager.waveTimer = 240; // 4 second delay before first wave
/****
* Main Game Loop
****/
game.update = function () {
// Update active blobs
for (var i = activeBlobs.length - 1; i >= 0; i--) {
var blob = activeBlobs[i];
if (blob.active) {
blob.update();
} else {
activeBlobs.splice(i, 1);
gameLayer.removeChild(blob);
}
}
// Update active bullets
for (var i = activeBullets.length - 1; i >= 0; i--) {
var bullet = activeBullets[i];
if (bullet.active) {
bullet.update();
} else {
activeBullets.splice(i, 1);
gameLayer.removeChild(bullet);
}
}
// Update all orbs
for (var x = 0; x < GRID_COLS; x++) {
for (var y = 0; y < GRID_ROWS; y++) {
if (topAreaOrbs[x][y]) {
topAreaOrbs[x][y].update();
}
if (bottomAreaOrbs[x][y]) {
bottomAreaOrbs[x][y].update();
}
}
}
// Update active particles
var allParticles = gameLayer.children.filter(function (child) {
return child instanceof DeathParticle;
});
for (var i = 0; i < allParticles.length; i++) {
allParticles[i].update();
}
// Update active effects
var allEffects = gameLayer.children.filter(function (child) {
return child instanceof EffectIndicator;
});
for (var i = 0; i < allEffects.length; i++) {
allEffects[i].update();
}
// Update notifications
var allNotifications = gameLayer.children.filter(function (child) {
return child instanceof Notification;
});
for (var i = 0; i < allNotifications.length; i++) {
allNotifications[i].update();
}
// Update wave manager
waveManager.update();
// Update generate buttons
topGenerateButton.update();
bottomGenerateButton.update();
// Check win condition
if (waveManager.currentWave >= waveManager.maxWaves && activeBlobs.length === 0) {
LK.showYouWin();
}
// Check lose condition
if (lives <= 0) {
LK.showGameOver();
}
updateUI();
};
/****
* Debug Features (Remove in production)
****/
if (LK.debug) {
// Add some starting orbs for testing
setTimeout(function () {
var testOrb1 = PoolManager.getEnergyOrb();
if (testOrb1) {
testOrb1.activate('rapid', 1, 1, 0);
topAreaLayer.addChild(testOrb1);
topAreaOrbs[1][1] = testOrb1;
}
var testOrb2 = PoolManager.getEnergyOrb();
if (testOrb2) {
testOrb2.activate('sniper', 1, 3, 1);
bottomAreaLayer.addChild(testOrb2);
bottomAreaOrbs[1][3] = testOrb2;
}
}, 1000);
}
/****
* Performance Monitoring (Optional)
****/
var PerformanceMonitor = {
frameCount: 0,
lastTime: Date.now(),
fps: 60,
update: function update() {
this.frameCount++;
var currentTime = Date.now();
if (currentTime - this.lastTime >= 1000) {
this.fps = this.frameCount;
this.frameCount = 0;
this.lastTime = currentTime;
// Log performance warnings
if (this.fps < 30) {
console.warn('Low FPS detected:', this.fps);
console.log('Active objects - Blobs:', activeBlobs.length, 'Bullets:', activeBullets.length);
}
}
}
};
// Add performance monitoring to game loop if in debug mode
if (LK.debug) {
var originalUpdate = game.update;
game.update = function () {
PerformanceMonitor.update();
originalUpdate.call(this);
};
}
/****
* Utility Functions
****/
function getDistanceBetweenPoints(p1, p2) {
var dx = p2.x - p1.x;
var dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function lerp(start, end, factor) {
return start + (end - start) * factor;
}
// Fibonacci sequence for costs
function fibonacci(n) {
if (n <= 1) return 1;
var a = 1,
b = 1;
for (var i = 2; i <= n; i++) {
var temp = a + b;
a = b;
b = temp;
}
return b;
}
/****
* Save/Load System (Future Enhancement)
****/
var SaveSystem = {
save: function save() {
var saveData = {
gold: gold,
lives: lives,
score: score,
wave: waveManager.currentWave,
topOrbs: [],
bottomOrbs: []
};
// Save orb positions and levels
for (var x = 0; x < GRID_COLS; x++) {
for (var y = 0; y < GRID_ROWS; y++) {
if (topAreaOrbs[x][y]) {
saveData.topOrbs.push({
x: x,
y: y,
type: topAreaOrbs[x][y].orbType,
level: topAreaOrbs[x][y].level
});
}
if (bottomAreaOrbs[x][y]) {
saveData.bottomOrbs.push({
x: x,
y: y,
type: bottomAreaOrbs[x][y].orbType,
level: bottomAreaOrbs[x][y].level
});
}
}
}
localStorage.setItem('mergeDefenseSave', JSON.stringify(saveData));
},
load: function load() {
var saveData = localStorage.getItem('mergeDefenseSave');
if (!saveData) return false;
try {
var data = JSON.parse(saveData);
gold = data.gold;
lives = data.lives;
score = data.score;
waveManager.currentWave = data.wave;
// Restore orbs
data.topOrbs.forEach(function (orbData) {
var orb = PoolManager.getEnergyOrb();
if (orb) {
orb.activate(orbData.type, orbData.x, orbData.y, 0);
orb.level = orbData.level;
orb.updateVisuals();
topAreaLayer.addChild(orb);
topAreaOrbs[orbData.x][orbData.y] = orb;
}
});
data.bottomOrbs.forEach(function (orbData) {
var orb = PoolManager.getEnergyOrb();
if (orb) {
orb.activate(orbData.type, orbData.x, orbData.y, 1);
orb.level = orbData.level;
orb.updateVisuals();
bottomAreaLayer.addChild(orb);
bottomAreaOrbs[orbData.x][orbData.y] = orb;
}
});
updateUI();
return true;
} catch (e) {
console.error('Failed to load save data:', e);
return false;
}
}
};
/****
* Audio System (Placeholder for future implementation)
****/
var AudioSystem = {
playSound: function playSound(soundName) {
// Placeholder for sound effects
// Will be implemented when audio assets are available
console.log('Playing sound:', soundName);
},
playMusic: function playMusic(musicName) {
// Placeholder for background music
console.log('Playing music:', musicName);
}
};
/****
* Effects and Polish
****/
function createScreenShake(intensity, duration) {
var originalX = game.x;
var originalY = game.y;
var shakeTimer = duration;
var _shakeUpdate = function shakeUpdate() {
if (shakeTimer > 0) {
game.x = originalX + (Math.random() - 0.5) * intensity;
game.y = originalY + (Math.random() - 0.5) * intensity;
shakeTimer--;
requestAnimationFrame(_shakeUpdate);
} else {
game.x = originalX;
game.y = originalY;
}
};
_shakeUpdate();
}
function createFloatingText(text, x, y, color) {
var floatingText = new Text2(text, {
size: 40,
fill: color || 0xFFFFFF,
weight: 800
});
floatingText.anchor.set(0.5, 0.5);
floatingText.x = x;
floatingText.y = y;
floatingText.alpha = 1;
gameLayer.addChild(floatingText);
tween(floatingText, {
y: y - 50,
alpha: 0
}, {
duration: 1000,
easing: tween.quadOut,
onFinish: function onFinish() {
gameLayer.removeChild(floatingText);
}
});
}
/****
* Game Over and Victory Screens
****/
LK.showGameOver = function () {
var overlay = new Container();
var bg = overlay.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
bg.width = SCREEN_WIDTH;
bg.height = SCREEN_HEIGHT;
bg.tint = 0x000000;
bg.alpha = 0.8;
var gameOverText = new Text2('GAME OVER', {
size: 100,
fill: 0xFF0000,
weight: 800
});
gameOverText.anchor.set(0.5, 0.5);
gameOverText.y = -100;
overlay.addChild(gameOverText);
var finalScoreText = new Text2('Final Score: ' + score, {
size: 60,
fill: 0xFFFFFF,
weight: 600
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.y = 0;
overlay.addChild(finalScoreText);
var restartText = new Text2('Tap to Restart', {
size: 50,
fill: 0x00FF00,
weight: 600
});
restartText.anchor.set(0.5, 0.5);
restartText.y = 100;
overlay.addChild(restartText);
overlay.x = SCREEN_WIDTH / 2;
overlay.y = SCREEN_HEIGHT / 2;
game.addChild(overlay);
overlay.down = function () {
location.reload(); // Simple restart
};
};
LK.showYouWin = function () {
var overlay = new Container();
var bg = overlay.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
bg.width = SCREEN_WIDTH;
bg.height = SCREEN_HEIGHT;
bg.tint = 0x000000;
bg.alpha = 0.8;
var victoryText = new Text2('VICTORY!', {
size: 100,
fill: 0x00FF00,
weight: 800
});
victoryText.anchor.set(0.5, 0.5);
victoryText.y = -100;
overlay.addChild(victoryText);
var finalScoreText = new Text2('Final Score: ' + score, {
size: 60,
fill: 0xFFFFFF,
weight: 600
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.y = 0;
overlay.addChild(finalScoreText);
var restartText = new Text2('Tap to Play Again', {
size: 50,
fill: 0x00FF00,
weight: 600
});
restartText.anchor.set(0.5, 0.5);
restartText.y = 100;
overlay.addChild(restartText);
overlay.x = SCREEN_WIDTH / 2;
overlay.y = SCREEN_HEIGHT / 2;
game.addChild(overlay);
overlay.down = function () {
location.reload(); // Simple restart
};
// Victory animation
tween(victoryText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.elasticOut,
repeat: -1,
yoyo: true
});
}; ===================================================================
--- original.js
+++ change.js
@@ -5,269 +5,40 @@
/****
* Classes
****/
-/****
-* Bullet System
-****/
-var Bullet = Container.expand(function () {
+var BlobEnemy = Container.expand(function () {
var self = Container.call(this);
self.active = false;
- self.target = null;
- self.damage = 25;
- self.speed = 8;
- self.type = 'basic';
- var bulletGraphics = self.attachAsset('bullet', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- self.activate = function (startX, startY, target, damage, type) {
- self.active = true;
- self.x = startX;
- self.y = startY;
- self.target = target;
- self.damage = damage;
- self.type = type;
- self.visible = true;
- // Set bullet appearance based on type
- switch (type) {
- case 'rapid':
- bulletGraphics.tint = 0x00AAFF;
- bulletGraphics.width = bulletGraphics.height = 10;
- break;
- case 'sniper':
- bulletGraphics.tint = 0xFF5500;
- bulletGraphics.width = bulletGraphics.height = 8;
- self.speed = 15;
- break;
- case 'splash':
- bulletGraphics.tint = 0x33CC00;
- bulletGraphics.width = bulletGraphics.height = 20;
- break;
- case 'slow':
- bulletGraphics.tint = 0x9900FF;
- bulletGraphics.width = bulletGraphics.height = 15;
- break;
- case 'poison':
- bulletGraphics.tint = 0x00FFAA;
- bulletGraphics.width = bulletGraphics.height = 15;
- break;
- default:
- bulletGraphics.tint = 0xFFFF00;
- bulletGraphics.width = bulletGraphics.height = 15;
- break;
- }
- };
- self.update = function () {
- if (!self.active || !self.target || !self.target.active) {
- PoolManager.returnBullet(self);
- return;
- }
- // Move toward target
- var dx = self.target.x - self.x;
- var dy = self.target.y - self.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < self.speed) {
- // Hit target
- self.target.takeDamage(self.damage);
- // Special effects based on bullet type
- if (self.type === 'splash') {
- self.createSplashDamage();
- }
- PoolManager.returnBullet(self);
- } else {
- // Move toward target
- var angle = Math.atan2(dy, dx);
- self.x += Math.cos(angle) * self.speed;
- self.y += Math.sin(angle) * self.speed;
- }
- };
- self.createSplashDamage = function () {
- var splashRadius = 60;
- for (var i = 0; i < PoolManager.enemies.length; i++) {
- var enemy = PoolManager.enemies[i];
- if (!enemy.active || enemy === self.target) {
- continue;
- }
- var distance = CoordUtils.distance(self.x, self.y, enemy.x, enemy.y);
- if (distance <= splashRadius) {
- enemy.takeDamage(self.damage * 0.5);
- }
- }
- };
- return self;
-});
-/****
-* Embryo System
-****/
-var Embryo = Container.expand(function () {
- var self = Container.call(this);
- self.level = 1;
- self.experience = 0;
- self.maxExperience = 100;
- self.baseSize = 80;
- self.currentSize = self.baseSize;
- self.aspectRatio = 1.0; // Will become elliptical as it grows
- // Growth stages
- self.growthStages = [{
- level: 1,
- size: 80,
- slots: 4,
- ratio: 1.0,
- exp: 0
- }, {
- level: 2,
- size: 100,
- slots: 6,
- ratio: 1.1,
- exp: 100
- }, {
- level: 3,
- size: 120,
- slots: 8,
- ratio: 1.2,
- exp: 250
- }, {
- level: 4,
- size: 140,
- slots: 10,
- ratio: 1.3,
- exp: 450
- }, {
- level: 5,
- size: 160,
- slots: 12,
- ratio: 1.4,
- exp: 700
- }, {
- level: 6,
- size: 180,
- slots: 14,
- ratio: 1.5,
- exp: 1000
- }];
- // Visual components
- var embryoGraphics = self.attachAsset('embryo', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- var coreGraphics = self.attachAsset('embryoCore', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- self.orbitalSlots = [];
- self.getCurrentStage = function () {
- for (var i = self.growthStages.length - 1; i >= 0; i--) {
- if (self.experience >= self.growthStages[i].exp) {
- return self.growthStages[i];
- }
- }
- return self.growthStages[0];
- };
- self.initializeOrbitalSlots = function () {
- var currentStage = self.getCurrentStage();
- var slotCount = currentStage.slots;
- var radius = currentStage.size + 60;
- self.orbitalSlots = [];
- for (var i = 0; i < slotCount; i++) {
- var angle = i / slotCount * Math.PI * 2;
- self.orbitalSlots.push({
- angle: angle,
- radius: radius,
- occupied: false,
- tower: null,
- speed: 0.02 // radians per frame
- });
- }
- };
- self.initializeOrbitalSlots();
- self.addExperience = function (amount) {
- var prevLevel = self.level;
- self.experience += amount;
- var newStage = self.getCurrentStage();
- if (newStage.level > prevLevel) {
- self.levelUp(newStage);
- }
- };
- self.levelUp = function (newStage) {
- self.level = newStage.level;
- self.currentSize = newStage.size;
- self.aspectRatio = newStage.ratio;
- // Animate growth
- tween(embryoGraphics, {
- width: self.currentSize,
- height: self.currentSize * self.aspectRatio
- }, {
- duration: 1000,
- easing: tween.elasticOut
- });
- tween(coreGraphics, {
- width: self.currentSize * 0.6,
- height: self.currentSize * 0.6 * self.aspectRatio
- }, {
- duration: 1000,
- easing: tween.elasticOut
- });
- // Reinitialize orbital slots
- self.initializeOrbitalSlots();
- // Notification
- var notification = game.addChild(new Notification("Embryo Level Up! New slots available!"));
- notification.x = SCREEN_WIDTH / 2;
- notification.y = SCREEN_HEIGHT / 2;
- };
- self.getAvailableSlot = function () {
- for (var i = 0; i < self.orbitalSlots.length; i++) {
- if (!self.orbitalSlots[i].occupied) {
- return self.orbitalSlots[i];
- }
- }
- return null;
- };
- self.update = function () {
- // Breathing animation
- var breathScale = 1 + Math.sin(LK.ticks * 0.05) * 0.05;
- coreGraphics.scaleX = breathScale;
- coreGraphics.scaleY = breathScale;
- // Update orbital slots
- for (var i = 0; i < self.orbitalSlots.length; i++) {
- var slot = self.orbitalSlots[i];
- slot.angle = CoordUtils.normalizeAngle(slot.angle + slot.speed);
- if (slot.tower) {
- var pos = CoordUtils.polarToCartesian(slot.radius, slot.angle);
- slot.tower.x = self.x + pos.x;
- slot.tower.y = self.y + pos.y;
- }
- }
- };
- return self;
-});
-/****
-* Enemy System
-****/
-var Enemy = Container.expand(function () {
- var self = Container.call(this);
- self.active = false;
self.material = 'slime';
self.health = 100;
self.maxHealth = 100;
self.speed = 1.2;
+ self.pathProgress = 0;
+ self.waveNumber = 1;
// Jump states
- self.jumpState = 'paused'; // 'paused', 'preparing', 'jumping', 'landing'
+ self.jumpState = 'paused';
self.jumpTimer = 0;
- self.pauseDuration = 60; // frames to pause between jumps
- self.jumpDuration = 30; // frames for actual jump arc
+ self.pauseDuration = 60;
+ self.jumpDuration = 30;
self.startPosition = {
x: 0,
y: 0
};
self.targetPosition = {
x: 0,
y: 0
};
- self.jumpDistance = 80; // Distance covered per jump
- self.jumpHeight = 40; // How high the arc goes
- self.baseY = 0; // Ground level for this enemy
- // Visual components
+ self.jumpDistance = 80;
+ self.jumpHeight = 40;
+ self.baseY = 0;
+ // Status effects
+ self.slowed = false;
+ self.poisoned = false;
+ self.slowDuration = 0;
+ self.poisonDuration = 0;
+ self.poisonDamage = 0;
+ self.originalSpeed = self.speed;
var blobGraphics = self.attachAsset('blob', {
anchorX: 0.5,
anchorY: 0.5
});
@@ -283,80 +54,155 @@
healthBarOutline.y = healthBar.y = -30;
healthBarOutline.x = -36;
healthBar.x = -35;
healthBar.tint = 0x00ff00;
- self.activate = function (material, spawnAngle) {
+ self.activate = function (material, waveNumber) {
self.active = true;
+ self.visible = true;
self.material = material || 'slime';
- self.health = self.maxHealth;
+ self.waveNumber = waveNumber || 1;
+ self.pathProgress = 0;
self.jumpState = 'paused';
self.jumpTimer = self.pauseDuration;
+ // Reset status effects
+ self.slowed = false;
+ self.poisoned = false;
+ self.slowDuration = 0;
+ self.poisonDuration = 0;
+ // Set material properties
+ self.setMaterial(self.material);
+ // Scale health with wave number
+ var healthMultiplier = Math.pow(1.15, self.waveNumber);
+ self.maxHealth = Math.round(self.maxHealth * healthMultiplier);
+ self.health = self.maxHealth;
+ // Start at beginning of path
+ var startPos = PathSystem.getPositionAlongPath(0);
+ self.x = startPos.x;
+ self.y = startPos.y;
+ self.baseY = self.y;
// Reset blob scale
blobGraphics.scaleX = 1;
blobGraphics.scaleY = 1;
- // Set material properties
- self.setMaterial(self.material);
- // Position at edge of screen
- var spawnRadius = Math.max(SCREEN_WIDTH, SCREEN_HEIGHT) * 0.6;
- var pos = CoordUtils.polarToCartesian(spawnRadius, spawnAngle);
- self.x = SCREEN_WIDTH / 2 + pos.x;
- self.y = SCREEN_HEIGHT / 2 + pos.y;
- self.baseY = self.y; // Remember ground level
- self.visible = true;
};
self.setMaterial = function (material) {
switch (material) {
case 'slime':
blobGraphics.tint = 0x00FF00;
- self.speed = 1.2;
+ self.speed = 0.8;
self.pauseDuration = 60;
self.jumpDistance = 80;
self.jumpHeight = 40;
+ self.maxHealth = 100;
break;
case 'metal':
blobGraphics.tint = 0xC0C0C0;
- self.speed = 1.0;
+ self.speed = 0.6;
self.pauseDuration = 80;
self.jumpDistance = 60;
self.jumpHeight = 25;
self.maxHealth = 150;
break;
case 'lava':
blobGraphics.tint = 0xFF4500;
- self.speed = 1.4;
+ self.speed = 1.0;
self.pauseDuration = 45;
self.jumpDistance = 90;
self.jumpHeight = 50;
+ self.maxHealth = 120;
break;
case 'ice':
blobGraphics.tint = 0x87CEEB;
- self.speed = 0.8;
+ self.speed = 0.5;
self.pauseDuration = 90;
self.jumpDistance = 50;
self.jumpHeight = 30;
+ self.maxHealth = 80;
break;
case 'shadow':
blobGraphics.tint = 0x4B0082;
- self.speed = 1.6;
+ self.speed = 1.2;
self.pauseDuration = 30;
self.jumpDistance = 100;
self.jumpHeight = 60;
+ self.maxHealth = 200;
break;
}
- self.health = self.maxHealth;
+ self.originalSpeed = self.speed;
};
+ self.takeDamage = function (damage, damageType, towerLevel) {
+ self.health -= damage;
+ healthBar.width = Math.max(0, self.health / self.maxHealth * 70);
+ // Apply special effects
+ if (damageType === 'splash') {
+ var effect = PoolManager.getEffect();
+ if (effect) {
+ effect.activate(self.x, self.y, 'splash');
+ gameLayer.addChild(effect);
+ }
+ } else if (damageType === 'slow' && self.material !== 'ice') {
+ // Ice is immune to slow
+ if (!self.slowed) {
+ var slowPct = 0.5 + (towerLevel - 1) * 0.05; // 50% to 75% slow
+ self.speed = self.originalSpeed * (1 - slowPct);
+ self.slowed = true;
+ self.slowDuration = 180;
+ var effect = PoolManager.getEffect();
+ if (effect) {
+ effect.activate(self.x, self.y, 'slow');
+ gameLayer.addChild(effect);
+ }
+ } else {
+ self.slowDuration = 180; // Reset duration
+ }
+ } else if (damageType === 'poison' && self.material !== 'metal') {
+ // Metal is immune to poison
+ if (!self.poisoned) {
+ self.poisoned = true;
+ self.poisonDamage = damage * 0.2;
+ self.poisonDuration = 300;
+ var effect = PoolManager.getEffect();
+ if (effect) {
+ effect.activate(self.x, self.y, 'poison');
+ gameLayer.addChild(effect);
+ }
+ }
+ } else if (damageType === 'sniper') {
+ var effect = PoolManager.getEffect();
+ if (effect) {
+ effect.activate(self.x, self.y, 'sniper');
+ gameLayer.addChild(effect);
+ }
+ }
+ if (self.health <= 0) {
+ self.die();
+ }
+ };
+ self.die = function () {
+ // Create death particles
+ for (var i = 0; i < 8; i++) {
+ var particle = PoolManager.getParticle();
+ if (particle) {
+ particle.activate(self.x, self.y, self.material);
+ gameLayer.addChild(particle);
+ }
+ }
+ // Give gold based on enemy type and wave
+ var goldEarned = Math.floor(5 + self.waveNumber * 0.5);
+ if (self.material === 'shadow') goldEarned *= 2; // Shadow enemies give double gold
+ setGold(gold + goldEarned);
+ score += 10 + self.waveNumber;
+ PoolManager.returnBlob(self);
+ };
self.startJumpPreparation = function () {
self.jumpState = 'preparing';
- // Calculate jump target
- var dx = embryo.x - self.x;
- var dy = embryo.y - self.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- var angle = Math.atan2(dy, dx);
+ // Calculate next position along path
+ var nextProgress = Math.min(1, self.pathProgress + self.jumpDistance / PathSystem.getPathLength());
+ var nextPos = PathSystem.getPositionAlongPath(nextProgress);
self.startPosition.x = self.x;
self.startPosition.y = self.baseY;
- self.targetPosition.x = self.x + Math.cos(angle) * self.jumpDistance;
- self.targetPosition.y = self.baseY + Math.sin(angle) * self.jumpDistance;
- // Pre-jump compression animation
+ self.targetPosition.x = nextPos.x;
+ self.targetPosition.y = nextPos.y;
+ // Pre-jump compression
tween.stop(blobGraphics, {
scaleX: true,
scaleY: true
});
@@ -383,15 +229,17 @@
scaleX: 0.8,
scaleY: 1.8
}, {
duration: self.jumpDuration * 16.67,
- // Convert frames to ms
easing: tween.sineInOut
});
};
self.landJump = function () {
self.jumpState = 'landing';
- self.baseY = self.targetPosition.y; // Update ground level
+ self.baseY = self.targetPosition.y;
+ // Update path progress
+ var pathLength = PathSystem.getPathLength();
+ self.pathProgress = Math.min(1, self.pathProgress + self.jumpDistance / pathLength);
// Landing compression
tween.stop(blobGraphics, {
scaleX: true,
scaleY: true
@@ -402,183 +250,219 @@
}, {
duration: 200,
easing: tween.quadOut,
onFinish: function onFinish() {
- // Bounce back to normal
tween(blobGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.elasticOut,
onFinish: function onFinish() {
- // Start pause
self.jumpState = 'paused';
self.jumpTimer = self.pauseDuration;
}
});
}
});
};
- // Helper function to calculate arc position
self.getArcPosition = function (progress) {
- // Linear interpolation for horizontal movement
var x = self.startPosition.x + (self.targetPosition.x - self.startPosition.x) * progress;
- // Calculate the base line between start and end points
- var startY = self.startPosition.y;
- var endY = self.targetPosition.y;
- var baseY = startY + (endY - startY) * progress;
- // Create an upward arc that peaks at the midpoint
- // This arc always goes UP from the baseline, regardless of direction
- var arcHeight = self.jumpHeight * 4 * progress * (1 - progress);
- // Final Y position is the baseline MINUS the arc height (negative = up)
- var y = baseY - arcHeight;
+ var startHeight = self.startPosition.y;
+ var endHeight = self.targetPosition.y;
+ var peakHeight = Math.min(startHeight, endHeight) - self.jumpHeight;
+ var arcHeight = peakHeight + 4 * self.jumpHeight * progress * (1 - progress);
+ var y = startHeight + (endHeight - startHeight) * progress + (arcHeight - startHeight - (endHeight - startHeight) * progress);
return {
x: x,
y: y
};
};
- self.takeDamage = function (damage) {
- self.health -= damage;
- healthBar.width = self.health / self.maxHealth * 70;
- if (self.health <= 0) {
- self.die();
+ self.update = function () {
+ if (!self.active) return;
+ // Handle status effects
+ if (self.slowed) {
+ self.slowDuration--;
+ if (self.slowDuration <= 0) {
+ self.speed = self.originalSpeed;
+ self.slowed = false;
+ }
}
- };
- self.die = function () {
- // Create death particles
- self.createDeathEffect();
- // Give experience to embryo
- embryo.addExperience(10);
- // Give gold to player
- setGold(gold + 5);
- // Return to pool
- PoolManager.returnEnemy(self);
- };
- self.createDeathEffect = function () {
- var particleCount = 8;
- for (var i = 0; i < particleCount; i++) {
- var particle = PoolManager.getParticle();
- if (particle) {
- particle.activate(self.x, self.y, self.material);
- gameLayer.addChild(particle);
+ if (self.poisoned) {
+ if (LK.ticks % 30 === 0) {
+ // Poison damage every 30 frames
+ self.health -= self.poisonDamage;
+ healthBar.width = Math.max(0, self.health / self.maxHealth * 70);
+ if (self.health <= 0) {
+ self.die();
+ return;
+ }
}
+ self.poisonDuration--;
+ if (self.poisonDuration <= 0) {
+ self.poisoned = false;
+ }
}
- };
- self.update = function () {
- if (!self.active) {
- return;
- }
- // Check if reached embryo
- var dx = embryo.x - self.x;
- var dy = embryo.y - self.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < embryo.currentSize / 2 + 20) {
- // Damage player
+ // Check if reached end of path
+ if (self.pathProgress >= 1) {
lives--;
updateUI();
- PoolManager.returnEnemy(self);
+ PoolManager.returnBlob(self);
+ if (lives <= 0) {
+ LK.showGameOver();
+ }
return;
}
- // State machine for jump behavior
+ // Jump state machine
switch (self.jumpState) {
case 'paused':
- // Count down pause timer - NO MOVEMENT
self.jumpTimer--;
if (self.jumpTimer <= 0) {
self.startJumpPreparation();
}
break;
case 'preparing':
- // Animation handles this state - NO MOVEMENT
+ // Animation handles this
break;
case 'jumping':
- // Move along arc from start to target position
self.jumpTimer--;
var progress = (self.jumpDuration - self.jumpTimer) / self.jumpDuration;
- // Get position along the arc
var arcPos = self.getArcPosition(progress);
self.x = arcPos.x;
self.y = arcPos.y;
if (self.jumpTimer <= 0) {
- // Ensure we end up exactly at target
self.x = self.targetPosition.x;
self.y = self.targetPosition.y;
self.landJump();
}
break;
case 'landing':
- // Animation handles this state - NO MOVEMENT
+ // Animation handles this
break;
}
};
+ self.reset = function () {
+ self.material = 'slime';
+ self.health = 100;
+ self.maxHealth = 100;
+ self.speed = 1.2;
+ self.pathProgress = 0;
+ self.waveNumber = 1;
+ self.jumpState = 'paused';
+ self.jumpTimer = 0;
+ self.slowed = false;
+ self.poisoned = false;
+ self.slowDuration = 0;
+ self.poisonDuration = 0;
+ tween.stop(blobGraphics);
+ blobGraphics.scaleX = 1;
+ blobGraphics.scaleY = 1;
+ blobGraphics.tint = 0x00FF00;
+ };
return self;
});
-/****
-* Notification System
-****/
-var Notification = Container.expand(function (message) {
+var Bullet = Container.expand(function () {
var self = Container.call(this);
- var bg = self.attachAsset('notification', {
+ self.active = false;
+ self.targetEnemy = null;
+ self.damage = 10;
+ self.speed = 5;
+ self.type = 'default';
+ self.sourceTowerLevel = 1;
+ var bulletGraphics = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
- var text = new Text2(message, {
- size: 40,
- fill: 0x000000,
- weight: 800
- });
- text.anchor.set(0.5, 0.5);
- self.addChild(text);
- bg.width = text.width + 40;
- bg.height = text.height + 20;
- self.alpha = 0;
- // Fade in
- tween(self, {
- alpha: 1
- }, {
- duration: 300,
- onFinish: function onFinish() {
- // Fade out after delay
- LK.setTimeout(function () {
- tween(self, {
- alpha: 0
- }, {
- duration: 300,
- onFinish: function onFinish() {
- self.destroy();
- }
- });
- }, 2000);
+ bulletGraphics.width = 15;
+ bulletGraphics.height = 15;
+ self.activate = function (startX, startY, targetEnemy, damage, speed, type, sourceTowerLevel) {
+ self.active = true;
+ self.visible = true;
+ self.x = startX;
+ self.y = startY;
+ self.targetEnemy = targetEnemy;
+ self.damage = damage || 10;
+ self.speed = speed || 5;
+ self.type = type || 'default';
+ self.sourceTowerLevel = sourceTowerLevel || 1;
+ // Customize appearance based on type
+ switch (self.type) {
+ case 'rapid':
+ bulletGraphics.tint = 0x00AAFF;
+ bulletGraphics.width = bulletGraphics.height = 12;
+ break;
+ case 'sniper':
+ bulletGraphics.tint = 0xFF5500;
+ bulletGraphics.width = bulletGraphics.height = 8;
+ break;
+ case 'splash':
+ bulletGraphics.tint = 0x33CC00;
+ bulletGraphics.width = bulletGraphics.height = 20;
+ break;
+ case 'slow':
+ bulletGraphics.tint = 0x9900FF;
+ bulletGraphics.width = bulletGraphics.height = 18;
+ break;
+ case 'poison':
+ bulletGraphics.tint = 0x00FFAA;
+ bulletGraphics.width = bulletGraphics.height = 18;
+ break;
+ default:
+ bulletGraphics.tint = 0xFFFFFF;
+ bulletGraphics.width = bulletGraphics.height = 15;
}
- });
+ };
+ self.update = function () {
+ if (!self.active || !self.targetEnemy || !self.targetEnemy.active) {
+ PoolManager.returnBullet(self);
+ return;
+ }
+ var dx = self.targetEnemy.x - self.x;
+ var dy = self.targetEnemy.y - self.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance < self.speed) {
+ // Hit target
+ self.targetEnemy.takeDamage(self.damage, self.type, self.sourceTowerLevel);
+ PoolManager.returnBullet(self);
+ } else {
+ var angle = Math.atan2(dy, dx);
+ self.x += Math.cos(angle) * self.speed;
+ self.y += Math.sin(angle) * self.speed;
+ }
+ };
+ self.reset = function () {
+ self.targetEnemy = null;
+ self.damage = 10;
+ self.speed = 5;
+ self.type = 'default';
+ self.sourceTowerLevel = 1;
+ };
return self;
});
-/****
-* Particle System
-****/
-var Particle = Container.expand(function () {
+var DeathParticle = Container.expand(function () {
var self = Container.call(this);
self.active = false;
- self.life = 60;
+ self.velocity = {
+ x: 0,
+ y: 0
+ };
+ self.life = 0;
self.maxLife = 60;
- self.velocityX = 0;
- self.velocityY = 0;
var particleGraphics = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
self.activate = function (x, y, material) {
self.active = true;
+ self.visible = true;
self.x = x;
self.y = y;
self.life = self.maxLife;
- self.visible = true;
// Random velocity
var angle = Math.random() * Math.PI * 2;
var speed = 2 + Math.random() * 4;
- self.velocityX = Math.cos(angle) * speed;
- self.velocityY = Math.sin(angle) * speed;
+ self.velocity.x = Math.cos(angle) * speed;
+ self.velocity.y = Math.sin(angle) * speed;
// Set color based on material
switch (material) {
case 'slime':
particleGraphics.tint = 0x00FF00;
@@ -596,608 +480,1537 @@
particleGraphics.tint = 0x4B0082;
break;
default:
particleGraphics.tint = 0xFFFFFF;
- break;
}
- self.scaleX = self.scaleY = 1;
+ particleGraphics.width = particleGraphics.height = 4 + Math.random() * 8;
self.alpha = 1;
};
self.update = function () {
- if (!self.active) {
- return;
- }
+ if (!self.active) return;
+ self.x += self.velocity.x;
+ self.y += self.velocity.y;
+ self.velocity.y += 0.2; // Gravity
self.life--;
+ self.alpha = self.life / self.maxLife;
if (self.life <= 0) {
PoolManager.returnParticle(self);
- return;
}
- // Move
- self.x += self.velocityX;
- self.y += self.velocityY;
- // Fade out
- var lifeRatio = self.life / self.maxLife;
- self.alpha = lifeRatio;
- self.scaleX = self.scaleY = lifeRatio;
- // Gravity
- self.velocityY += 0.1;
- self.velocityX *= 0.98;
};
+ self.reset = function () {
+ self.velocity.x = 0;
+ self.velocity.y = 0;
+ self.life = 0;
+ };
return self;
});
-/****
-* Wave System
-****/
-/****
-* Tower System
-****/
-var Tower = Container.expand(function (type) {
+var EffectIndicator = Container.expand(function () {
var self = Container.call(this);
- self.type = type || 'basic';
+ self.active = false;
+ self.effectType = 'splash';
+ var effectGraphics = self.attachAsset('rangeCircle', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ effectGraphics.blendMode = 1;
+ self.activate = function (x, y, type) {
+ self.active = true;
+ self.visible = true;
+ self.x = x;
+ self.y = y;
+ self.effectType = type;
+ switch (type) {
+ case 'splash':
+ effectGraphics.tint = 0x33CC00;
+ effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5;
+ break;
+ case 'slow':
+ effectGraphics.tint = 0x9900FF;
+ effectGraphics.width = effectGraphics.height = CELL_SIZE;
+ break;
+ case 'poison':
+ effectGraphics.tint = 0x00FFAA;
+ effectGraphics.width = effectGraphics.height = CELL_SIZE;
+ break;
+ case 'sniper':
+ effectGraphics.tint = 0xFF5500;
+ effectGraphics.width = effectGraphics.height = CELL_SIZE;
+ break;
+ }
+ effectGraphics.alpha = 0.7;
+ self.alpha = 0;
+ self.scaleX = 0.5;
+ self.scaleY = 0.5;
+ // Animate effect
+ tween(self, {
+ alpha: 0.8,
+ scaleX: 1.5,
+ scaleY: 1.5
+ }, {
+ duration: 200,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ tween(self, {
+ alpha: 0,
+ scaleX: 2,
+ scaleY: 2
+ }, {
+ duration: 300,
+ easing: tween.easeIn,
+ onFinish: function onFinish() {
+ PoolManager.returnEffect(self);
+ }
+ });
+ }
+ });
+ };
+ self.reset = function () {
+ self.effectType = 'splash';
+ tween.stop(self);
+ };
+ return self;
+});
+var EnergyOrb = Container.expand(function () {
+ var self = Container.call(this);
+ self.active = false;
+ self.orbType = 'default';
self.level = 1;
- self.damage = 25;
- self.range = 120;
- self.fireRate = 60; // frames between shots
+ self.maxLevel = 6;
+ self.gridX = 0;
+ self.gridY = 0;
+ self.area = 0; // 0 for top area, 1 for bottom area
+ self.range = 3 * CELL_SIZE;
+ self.damage = 10;
+ self.fireRate = 60;
+ self.bulletSpeed = 5;
self.lastFired = 0;
- self.orbitalSlot = null;
- // Visual components
- var baseGraphics = self.attachAsset('tower', {
+ self.targetEnemy = null;
+ self.isDragging = false;
+ self.magneticTarget = null;
+ var orbGraphics = self.attachAsset('energyOrb', {
anchorX: 0.5,
anchorY: 0.5
});
- var gunContainer = new Container();
- self.addChild(gunContainer);
- var gunGraphics = gunContainer.attachAsset('defense', {
+ var coreGraphics = self.attachAsset('energyOrb', {
anchorX: 0.5,
anchorY: 0.5
});
- // Set tower properties based on type
- self.setType = function (type) {
- self.type = type;
+ coreGraphics.width = 40;
+ coreGraphics.height = 40;
+ // Level indicators
+ var levelIndicators = [];
+ for (var i = 0; i < 6; i++) {
+ var dot = self.attachAsset('particle', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ dot.width = 6;
+ dot.height = 6;
+ dot.x = (i - 2.5) * 12;
+ dot.y = CELL_SIZE * 0.6;
+ dot.tint = 0x444444;
+ levelIndicators.push(dot);
+ }
+ self.activate = function (orbType, gridX, gridY, area) {
+ self.active = true;
+ self.visible = true;
+ self.orbType = orbType || 'default';
+ self.level = 1;
+ self.gridX = gridX;
+ self.gridY = gridY;
+ self.area = area;
+ self.lastFired = 0;
+ self.targetEnemy = null;
+ self.setOrbType(self.orbType);
+ self.updateVisuals();
+ self.updatePosition();
+ };
+ self.setOrbType = function (type) {
+ self.orbType = type;
switch (type) {
case 'rapid':
- baseGraphics.tint = 0x00AAFF;
self.fireRate = 30;
- self.damage = 15;
- self.range = 100;
+ self.damage = 5;
+ self.range = 2.5 * CELL_SIZE;
+ self.bulletSpeed = 7;
+ orbGraphics.tint = coreGraphics.tint = 0x00AAFF;
break;
case 'sniper':
- baseGraphics.tint = 0xFF5500;
self.fireRate = 90;
- self.damage = 60;
- self.range = 180;
+ self.damage = 25;
+ self.range = 5 * CELL_SIZE;
+ self.bulletSpeed = 25;
+ orbGraphics.tint = coreGraphics.tint = 0xFF5500;
break;
case 'splash':
- baseGraphics.tint = 0x33CC00;
self.fireRate = 75;
- self.damage = 35;
- self.range = 80;
+ self.damage = 15;
+ self.range = 2 * CELL_SIZE;
+ self.bulletSpeed = 4;
+ orbGraphics.tint = coreGraphics.tint = 0x33CC00;
break;
case 'slow':
- baseGraphics.tint = 0x9900FF;
self.fireRate = 50;
- self.damage = 20;
- self.range = 110;
+ self.damage = 8;
+ self.range = 3.5 * CELL_SIZE;
+ self.bulletSpeed = 5;
+ orbGraphics.tint = coreGraphics.tint = 0x9900FF;
break;
case 'poison':
- baseGraphics.tint = 0x00FFAA;
self.fireRate = 70;
- self.damage = 30;
- self.range = 90;
+ self.damage = 12;
+ self.range = 3.2 * CELL_SIZE;
+ self.bulletSpeed = 5;
+ orbGraphics.tint = coreGraphics.tint = 0x00FFAA;
break;
default:
- baseGraphics.tint = 0xAAAAAA;
- break;
+ self.fireRate = 60;
+ self.damage = 10;
+ self.range = 3 * CELL_SIZE;
+ self.bulletSpeed = 5;
+ orbGraphics.tint = coreGraphics.tint = 0xAAAAAA;
}
};
- self.setType(type);
+ self.updateVisuals = function () {
+ var baseSize = 60 + self.level * 8;
+ orbGraphics.width = orbGraphics.height = baseSize;
+ var coreSize = 30 + self.level * 4;
+ coreGraphics.width = coreGraphics.height = coreSize;
+ // Update level indicators
+ for (var i = 0; i < levelIndicators.length; i++) {
+ if (i < self.level) {
+ levelIndicators[i].tint = 0xFFFFFF;
+ levelIndicators[i].alpha = 1;
+ } else {
+ levelIndicators[i].tint = 0x444444;
+ levelIndicators[i].alpha = 0.5;
+ }
+ }
+ // Pulsing animation based on level
+ var pulseScale = 1 + self.level * 0.02;
+ tween.stop(orbGraphics, {
+ scaleX: true,
+ scaleY: true
+ });
+ tween(orbGraphics, {
+ scaleX: pulseScale,
+ scaleY: pulseScale
+ }, {
+ duration: 1000 + self.level * 200,
+ easing: tween.sineInOut,
+ repeat: -1,
+ yoyo: true
+ });
+ };
+ self.updatePosition = function () {
+ var startX = self.area === 0 ? TOP_AREA_X : BOTTOM_AREA_X;
+ var startY = self.area === 0 ? TOP_AREA_Y : BOTTOM_AREA_Y;
+ self.x = startX + self.gridX * CELL_SIZE + CELL_SIZE / 2;
+ self.y = startY + self.gridY * CELL_SIZE + CELL_SIZE / 2;
+ };
+ self.getRange = function () {
+ var baseRange = self.range;
+ var rangeMultiplier = 1 + (self.level - 1) * 0.15;
+ if (self.orbType === 'sniper' && self.level === self.maxLevel) {
+ rangeMultiplier *= 2; // Max level sniper gets huge range boost
+ }
+ return baseRange * rangeMultiplier;
+ };
+ self.getCurrentDamage = function () {
+ var damageMultiplier = 1 + (self.level - 1) * 0.5;
+ if (self.level === self.maxLevel) {
+ damageMultiplier *= 2; // Max level gets double damage bonus
+ }
+ return Math.floor(self.damage * damageMultiplier);
+ };
+ self.getCurrentFireRate = function () {
+ var rateMultiplier = 1 + (self.level - 1) * 0.3;
+ return Math.max(10, Math.floor(self.fireRate / rateMultiplier));
+ };
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = Infinity;
- for (var i = 0; i < PoolManager.enemies.length; i++) {
- var enemy = PoolManager.enemies[i];
- if (!enemy.active) {
- continue;
- }
- var distance = CoordUtils.distance(self.x, self.y, enemy.x, enemy.y);
- if (distance <= self.range && distance < closestDistance) {
+ var orbRange = self.getRange();
+ for (var i = 0; i < activeBlobs.length; i++) {
+ var blob = activeBlobs[i];
+ if (!blob.active) continue;
+ var dx = blob.x - self.x;
+ var dy = blob.y - self.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance <= orbRange && distance < closestDistance) {
closestDistance = distance;
- closestEnemy = enemy;
+ closestEnemy = blob;
}
}
return closestEnemy;
};
- self.fire = function (target) {
+ self.fire = function () {
+ if (!self.targetEnemy) return;
var bullet = PoolManager.getBullet();
if (bullet) {
- bullet.activate(self.x, self.y, target, self.damage, self.type);
+ var bulletStartX = self.x + Math.cos(coreGraphics.rotation) * 30;
+ var bulletStartY = self.y + Math.sin(coreGraphics.rotation) * 30;
+ bullet.activate(bulletStartX, bulletStartY, self.targetEnemy, self.getCurrentDamage(), self.bulletSpeed + self.level, self.orbType, self.level);
gameLayer.addChild(bullet);
+ activeBullets.push(bullet);
+ // Recoil effect
+ tween.stop(coreGraphics, {
+ scaleX: true,
+ scaleY: true
+ });
+ tween(coreGraphics, {
+ scaleX: 0.8,
+ scaleY: 0.8
+ }, {
+ duration: 100,
+ easing: tween.quadOut,
+ onFinish: function onFinish() {
+ tween(coreGraphics, {
+ scaleX: 1,
+ scaleY: 1
+ }, {
+ duration: 150,
+ easing: tween.elasticOut
+ });
+ }
+ });
}
- // Recoil animation
- tween.stop(gunContainer, {
- scaleX: true,
- scaleY: true
- });
- gunContainer.scaleX = 0.8;
- gunContainer.scaleY = 0.8;
- tween(gunContainer, {
- scaleX: 1,
- scaleY: 1
- }, {
- duration: 150,
- easing: tween.elasticOut
- });
};
+ self.canMergeWith = function (otherOrb) {
+ return otherOrb && otherOrb.active && otherOrb.orbType === self.orbType && otherOrb.level === self.level && self.level < self.maxLevel;
+ };
+ self.checkMagneticAttraction = function (otherOrb) {
+ if (!self.canMergeWith(otherOrb)) {
+ self.magneticTarget = null;
+ return;
+ }
+ var dx = otherOrb.x - self.x;
+ var dy = otherOrb.y - self.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance < CELL_SIZE * 1.5) {
+ self.magneticTarget = otherOrb;
+ // Subtle attraction animation
+ tween.stop(self, {
+ x: true,
+ y: true
+ });
+ tween(self, {
+ x: self.x + dx * 0.1,
+ y: self.y + dy * 0.1
+ }, {
+ duration: 100,
+ easing: tween.quadOut
+ });
+ } else {
+ self.magneticTarget = null;
+ }
+ };
+ self.mergeWith = function (otherOrb, targetArea) {
+ if (!self.canMergeWith(otherOrb)) return false;
+ // Find empty spot in target area
+ var emptySpot = GameManager.findEmptySpot(targetArea);
+ if (!emptySpot) {
+ // Target area is full, merge stays in current area
+ emptySpot = GameManager.findEmptySpot(self.area);
+ if (!emptySpot) return false;
+ targetArea = self.area;
+ }
+ // Create new merged orb
+ var newOrb = PoolManager.getEnergyOrb();
+ if (newOrb) {
+ newOrb.activate(self.orbType, emptySpot.x, emptySpot.y, targetArea);
+ newOrb.level = self.level + 1;
+ newOrb.updateVisuals();
+ if (targetArea === 0) {
+ topAreaLayer.addChild(newOrb);
+ topAreaOrbs[emptySpot.x][emptySpot.y] = newOrb;
+ } else {
+ bottomAreaLayer.addChild(newOrb);
+ bottomAreaOrbs[emptySpot.x][emptySpot.y] = newOrb;
+ }
+ // Merge animation
+ tween(newOrb, {
+ scaleX: 1.5,
+ scaleY: 1.5
+ }, {
+ duration: 200,
+ easing: tween.elasticOut,
+ onFinish: function onFinish() {
+ tween(newOrb, {
+ scaleX: 1,
+ scaleY: 1
+ }, {
+ duration: 300,
+ easing: tween.elasticOut
+ });
+ }
+ });
+ // Remove old orbs
+ GameManager.removeOrb(self);
+ GameManager.removeOrb(otherOrb);
+ return true;
+ }
+ return false;
+ };
self.update = function () {
- // Find and shoot at enemies
- if (LK.ticks - self.lastFired >= self.fireRate) {
- var target = self.findTarget();
- if (target) {
- // Aim at target
- var dx = target.x - self.x;
- var dy = target.y - self.y;
- var angle = Math.atan2(dy, dx);
- gunContainer.rotation = angle;
- self.fire(target);
+ if (!self.active) return;
+ // Find and track target
+ self.targetEnemy = self.findTarget();
+ if (self.targetEnemy) {
+ // Rotate core towards target
+ var dx = self.targetEnemy.x - self.x;
+ var dy = self.targetEnemy.y - self.y;
+ var angle = Math.atan2(dy, dx);
+ tween.stop(coreGraphics, {
+ rotation: true
+ });
+ tween(coreGraphics, {
+ rotation: angle
+ }, {
+ duration: 200,
+ easing: tween.quadOut
+ });
+ // Fire at target
+ if (LK.ticks - self.lastFired >= self.getCurrentFireRate()) {
+ self.fire();
self.lastFired = LK.ticks;
}
}
};
+ self.reset = function () {
+ self.orbType = 'default';
+ self.level = 1;
+ self.gridX = 0;
+ self.gridY = 0;
+ self.area = 0;
+ self.lastFired = 0;
+ self.targetEnemy = null;
+ self.isDragging = false;
+ self.magneticTarget = null;
+ tween.stop(self);
+ tween.stop(orbGraphics);
+ tween.stop(coreGraphics);
+ orbGraphics.scaleX = orbGraphics.scaleY = 1;
+ coreGraphics.scaleX = coreGraphics.scaleY = 1;
+ coreGraphics.rotation = 0;
+ };
return self;
});
+var GenerateButton = Container.expand(function (area) {
+ var self = Container.call(this);
+ self.area = area; // 0 for top, 1 for bottom
+ self.cost = 10;
+ var buttonGraphics = self.attachAsset('generateButton', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ var costText = new Text2('Generate: ' + self.cost, {
+ size: 30,
+ fill: 0xFFFFFF,
+ weight: 800
+ });
+ costText.anchor.set(0.5, 0.5);
+ self.addChild(costText);
+ self.updateCost = function () {
+ // Fibonacci-like progression
+ var newCost = Math.floor(10 * Math.pow(1.618, GameManager.getGenerateCount(self.area)));
+ self.cost = newCost;
+ costText.setText('Generate: ' + self.cost);
+ // Update button color based on affordability
+ if (gold >= self.cost) {
+ buttonGraphics.tint = 0x00AA00;
+ } else {
+ buttonGraphics.tint = 0x888888;
+ }
+ };
+ self.generate = function () {
+ if (gold < self.cost) return false;
+ var emptySpot = GameManager.findEmptySpot(self.area);
+ if (!emptySpot) return false;
+ // Random tower type
+ var types = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison'];
+ var randomType = types[Math.floor(Math.random() * types.length)];
+ var newOrb = PoolManager.getEnergyOrb();
+ if (newOrb) {
+ newOrb.activate(randomType, emptySpot.x, emptySpot.y, self.area);
+ if (self.area === 0) {
+ topAreaLayer.addChild(newOrb);
+ topAreaOrbs[emptySpot.x][emptySpot.y] = newOrb;
+ } else {
+ bottomAreaLayer.addChild(newOrb);
+ bottomAreaOrbs[emptySpot.x][emptySpot.y] = newOrb;
+ }
+ setGold(gold - self.cost);
+ GameManager.incrementGenerateCount(self.area);
+ self.updateCost();
+ // Spawn animation
+ newOrb.alpha = 0;
+ newOrb.scaleX = newOrb.scaleY = 0.5;
+ tween(newOrb, {
+ alpha: 1,
+ scaleX: 1,
+ scaleY: 1
+ }, {
+ duration: 300,
+ easing: tween.elasticOut
+ });
+ return true;
+ }
+ return false;
+ };
+ self.down = function () {
+ self.generate();
+ };
+ self.update = function () {
+ self.updateCost();
+ };
+ return self;
+});
+var Notification = Container.expand(function (message) {
+ var self = Container.call(this);
+ var notificationGraphics = self.attachAsset('notification', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ var notificationText = new Text2(message, {
+ size: 50,
+ fill: 0x000000,
+ weight: 800
+ });
+ notificationText.anchor.set(0.5, 0.5);
+ notificationGraphics.width = notificationText.width + 30;
+ self.addChild(notificationText);
+ self.alpha = 1;
+ var fadeOutTime = 180; // 3 seconds
+ self.update = function () {
+ if (fadeOutTime > 0) {
+ fadeOutTime--;
+ self.alpha = Math.min(fadeOutTime / 60, 1);
+ } else {
+ self.destroy();
+ }
+ };
+ return self;
+});
/****
-* UI System
+* Game Manager
****/
-var TowerSelector = Container.expand(function () {
+var WaveManager = Container.expand(function () {
var self = Container.call(this);
- self.towerTypes = ['basic', 'rapid', 'sniper', 'splash', 'slow', 'poison'];
- self.buttons = [];
- var buttonWidth = 100;
- var spacing = 120;
- var startX = -((self.towerTypes.length - 1) * spacing) / 2;
- self.createTowerButton = function (type, index) {
- var button = new Container();
- var bg = button.attachAsset('tower', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- bg.width = bg.height = buttonWidth;
- // Set color based on type
- switch (type) {
- case 'rapid':
- bg.tint = 0x00AAFF;
- break;
- case 'sniper':
- bg.tint = 0xFF5500;
- break;
- case 'splash':
- bg.tint = 0x33CC00;
- break;
- case 'slow':
- bg.tint = 0x9900FF;
- break;
- case 'poison':
- bg.tint = 0x00FFAA;
- break;
- default:
- bg.tint = 0xAAAAAA;
- break;
+ self.currentWave = 0;
+ self.maxWaves = 50;
+ self.waveTimer = 0;
+ self.waveDelay = 300; // 5 seconds at 60fps
+ self.waveActive = false;
+ self.enemiesSpawned = 0;
+ self.enemiesToSpawn = 0;
+ self.spawnTimer = 0;
+ self.spawnDelay = 30; // 0.5 seconds between spawns
+ var waveText = new Text2('Wave: 1', {
+ size: 60,
+ fill: 0xFFFF00,
+ weight: 800
+ });
+ waveText.anchor.set(0.5, 0.5);
+ self.addChild(waveText);
+ self.startNextWave = function () {
+ self.currentWave++;
+ self.waveActive = true;
+ self.enemiesSpawned = 0;
+ self.spawnTimer = 0;
+ // Determine wave composition
+ self.enemiesToSpawn = 10 + Math.floor(self.currentWave * 1.5);
+ waveText.setText('Wave: ' + self.currentWave);
+ // Show wave start notification
+ var notification = new Notification('Wave ' + self.currentWave + ' incoming!');
+ notification.x = SCREEN_WIDTH / 2;
+ notification.y = SCREEN_HEIGHT / 2;
+ gameLayer.addChild(notification);
+ };
+ self.getWaveMaterial = function () {
+ // Introduce new materials over time
+ if (self.currentWave <= 5) return 'slime';
+ if (self.currentWave <= 10) return Math.random() < 0.7 ? 'slime' : 'metal';
+ if (self.currentWave <= 15) {
+ var materials = ['slime', 'metal', 'ice'];
+ return materials[Math.floor(Math.random() * materials.length)];
}
- var label = new Text2(type, {
- size: 24,
- fill: 0xFFFFFF,
- weight: 800
- });
- label.anchor.set(0.5, 0.5);
- button.addChild(label);
- button.down = function () {
- self.placeTower(type);
- };
- return button;
+ if (self.currentWave <= 25) {
+ var materials = ['slime', 'metal', 'ice', 'lava'];
+ return materials[Math.floor(Math.random() * materials.length)];
+ }
+ // All materials available
+ var materials = ['slime', 'metal', 'ice', 'lava', 'shadow'];
+ return materials[Math.floor(Math.random() * materials.length)];
};
- self.placeTower = function (type) {
- var slot = embryo.getAvailableSlot();
- if (slot && gold >= self.getTowerCost(type)) {
- var tower = new Tower(type);
- tower.orbitalSlot = slot;
- slot.occupied = true;
- slot.tower = tower;
- gameLayer.addChild(tower);
- setGold(gold - self.getTowerCost(type));
- var notification = new Notification("Tower placed!");
- game.addChild(notification);
- notification.x = SCREEN_WIDTH / 2;
- notification.y = SCREEN_HEIGHT / 2 + 100;
- } else if (!slot) {
- var notification = new Notification("No available slots!");
- game.addChild(notification);
- notification.x = SCREEN_WIDTH / 2;
- notification.y = SCREEN_HEIGHT / 2 + 100;
- } else {
- var notification = new Notification("Not enough gold!");
- game.addChild(notification);
- notification.x = SCREEN_WIDTH / 2;
- notification.y = SCREEN_HEIGHT / 2 + 100;
+ self.spawnEnemy = function () {
+ var blob = PoolManager.getBlob();
+ if (blob) {
+ var material = self.getWaveMaterial();
+ blob.activate(material, self.currentWave);
+ gameLayer.addChild(blob);
+ activeBlobs.push(blob);
+ self.enemiesSpawned++;
}
};
- self.getTowerCost = function (type) {
- switch (type) {
- case 'rapid':
- return 25;
- case 'sniper':
- return 40;
- case 'splash':
- return 50;
- case 'slow':
- return 60;
- case 'poison':
- return 70;
- default:
- return 15;
+ self.update = function () {
+ if (!self.waveActive) {
+ self.waveTimer++;
+ if (self.waveTimer >= self.waveDelay) {
+ self.waveTimer = 0;
+ self.startNextWave();
+ }
+ } else {
+ // Spawn enemies
+ if (self.enemiesSpawned < self.enemiesToSpawn) {
+ self.spawnTimer++;
+ if (self.spawnTimer >= self.spawnDelay) {
+ self.spawnTimer = 0;
+ self.spawnEnemy();
+ }
+ } else {
+ // Check if wave is complete
+ var activeEnemies = 0;
+ for (var i = 0; i < activeBlobs.length; i++) {
+ if (activeBlobs[i].active) activeEnemies++;
+ }
+ if (activeEnemies === 0) {
+ self.waveActive = false;
+ if (self.currentWave >= self.maxWaves) {
+ LK.showYouWin();
+ }
+ }
+ }
}
};
- for (var i = 0; i < self.towerTypes.length; i++) {
- var button = self.createTowerButton(self.towerTypes[i], i);
- button.x = startX + i * spacing;
- self.addChild(button);
- self.buttons.push(button);
- }
return self;
});
/****
* Initialize Game
****/
-/****
-* Game Constants
-****/
-/****
-* Game Variables
-****/
var game = new LK.Game({
- backgroundColor: 0x222222
+ backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
/****
-* Object Pooling System
+* Object Pools
****/
+/****
+* Game Constants
+****/
var PoolManager = {
- enemies: [],
- bullets: [],
- particles: [],
+ energyOrbPool: [],
+ blobPool: [],
+ bulletPool: [],
+ particlePool: [],
+ effectPool: [],
+ // Pool sizes
+ ENERGY_ORB_POOL_SIZE: 30,
+ BLOB_POOL_SIZE: 100,
+ BULLET_POOL_SIZE: 200,
+ PARTICLE_POOL_SIZE: 500,
+ EFFECT_POOL_SIZE: 100,
init: function init() {
- // Pre-allocate pools
- for (var i = 0; i < 200; i++) {
- this.enemies.push(this.createEnemy());
+ // Initialize energy orb pool
+ for (var i = 0; i < this.ENERGY_ORB_POOL_SIZE; i++) {
+ var orb = new EnergyOrb();
+ orb.active = false;
+ orb.visible = false;
+ this.energyOrbPool.push(orb);
}
- for (var i = 0; i < 500; i++) {
- this.bullets.push(this.createBullet());
+ // Initialize blob pool
+ for (var i = 0; i < this.BLOB_POOL_SIZE; i++) {
+ var blob = new BlobEnemy();
+ blob.active = false;
+ blob.visible = false;
+ this.blobPool.push(blob);
}
- for (var i = 0; i < 1000; i++) {
- this.particles.push(this.createParticle());
+ // Initialize bullet pool
+ for (var i = 0; i < this.BULLET_POOL_SIZE; i++) {
+ var bullet = new Bullet();
+ bullet.active = false;
+ bullet.visible = false;
+ this.bulletPool.push(bullet);
}
+ // Initialize particle pool
+ for (var i = 0; i < this.PARTICLE_POOL_SIZE; i++) {
+ var particle = new DeathParticle();
+ particle.active = false;
+ particle.visible = false;
+ this.particlePool.push(particle);
+ }
+ // Initialize effect pool
+ for (var i = 0; i < this.EFFECT_POOL_SIZE; i++) {
+ var effect = new EffectIndicator();
+ effect.active = false;
+ effect.visible = false;
+ this.effectPool.push(effect);
+ }
},
- getEnemy: function getEnemy() {
- for (var i = 0; i < this.enemies.length; i++) {
- if (!this.enemies[i].active) {
- return this.enemies[i];
+ getEnergyOrb: function getEnergyOrb() {
+ for (var i = 0; i < this.energyOrbPool.length; i++) {
+ if (!this.energyOrbPool[i].active) {
+ return this.energyOrbPool[i];
}
}
- // If pool exhausted, create new one
- var enemy = this.createEnemy();
- this.enemies.push(enemy);
- return enemy;
+ return null; // Pool exhausted
},
+ getBlob: function getBlob() {
+ for (var i = 0; i < this.blobPool.length; i++) {
+ if (!this.blobPool[i].active) {
+ return this.blobPool[i];
+ }
+ }
+ return null;
+ },
getBullet: function getBullet() {
- for (var i = 0; i < this.bullets.length; i++) {
- if (!this.bullets[i].active) {
- return this.bullets[i];
+ for (var i = 0; i < this.bulletPool.length; i++) {
+ if (!this.bulletPool[i].active) {
+ return this.bulletPool[i];
}
}
- var bullet = this.createBullet();
- this.bullets.push(bullet);
- return bullet;
+ return null;
},
getParticle: function getParticle() {
- for (var i = 0; i < this.particles.length; i++) {
- if (!this.particles[i].active) {
- return this.particles[i];
+ for (var i = 0; i < this.particlePool.length; i++) {
+ if (!this.particlePool[i].active) {
+ return this.particlePool[i];
}
}
- var particle = this.createParticle();
- this.particles.push(particle);
- return particle;
+ return null;
},
- createEnemy: function createEnemy() {
- var enemy = new Enemy();
- enemy.active = false;
- return enemy;
+ getEffect: function getEffect() {
+ for (var i = 0; i < this.effectPool.length; i++) {
+ if (!this.effectPool[i].active) {
+ return this.effectPool[i];
+ }
+ }
+ return null;
},
- createBullet: function createBullet() {
- var bullet = new Bullet();
- bullet.active = false;
- return bullet;
+ returnEnergyOrb: function returnEnergyOrb(orb) {
+ orb.active = false;
+ orb.visible = false;
+ orb.reset();
},
- createParticle: function createParticle() {
- var particle = new Particle();
- particle.active = false;
- return particle;
- },
- returnEnemy: function returnEnemy(enemy) {
- enemy.active = false;
- if (enemy.parent) {
- enemy.parent.removeChild(enemy);
+ returnBlob: function returnBlob(blob) {
+ blob.active = false;
+ blob.visible = false;
+ blob.reset();
+ if (blob.shadow) {
+ blob.shadow.visible = false;
}
},
returnBullet: function returnBullet(bullet) {
bullet.active = false;
- if (bullet.parent) {
- bullet.parent.removeChild(bullet);
- }
+ bullet.visible = false;
+ bullet.reset();
},
returnParticle: function returnParticle(particle) {
particle.active = false;
- if (particle.parent) {
- particle.parent.removeChild(particle);
- }
+ particle.visible = false;
+ particle.reset();
+ },
+ returnEffect: function returnEffect(effect) {
+ effect.active = false;
+ effect.visible = false;
+ effect.reset();
}
};
/****
-* Coordinate System Utilities
+* Path System
****/
-var CoordUtils = {
- // Convert polar to cartesian coordinates
- polarToCartesian: function polarToCartesian(radius, angle) {
- return {
- x: radius * Math.cos(angle),
- y: radius * Math.sin(angle)
- };
+var PathSystem = {
+ pathPoints: [],
+ init: function init() {
+ // Create backwards "5" path between the two unit areas
+ var topAreaBottom = GRID_START_Y + GRID_ROWS * CELL_SIZE + AREA_SPACING / 2;
+ var bottomAreaTop = topAreaBottom + AREA_SPACING;
+ var centerY = (topAreaBottom + bottomAreaTop) / 2;
+ var leftX = GRID_START_X - CELL_SIZE;
+ var rightX = GRID_START_X + GRID_COLS * CELL_SIZE + CELL_SIZE;
+ var midLeftX = GRID_START_X + CELL_SIZE;
+ var midRightX = GRID_START_X + (GRID_COLS - 1) * CELL_SIZE;
+ this.pathPoints = [
+ // Start from top-left
+ {
+ x: leftX,
+ y: centerY - CELL_SIZE * 2
+ },
+ // Across to the right
+ {
+ x: rightX,
+ y: centerY - CELL_SIZE * 2
+ },
+ // Down to center
+ {
+ x: rightX,
+ y: centerY
+ },
+ // Back across to left (center horizontal)
+ {
+ x: midLeftX,
+ y: centerY
+ },
+ // Down to lower section
+ {
+ x: midLeftX,
+ y: centerY + CELL_SIZE * 2
+ },
+ // Across to bottom-right
+ {
+ x: rightX,
+ y: centerY + CELL_SIZE * 2
+ }];
},
- // Convert cartesian to polar coordinates
- cartesianToPolar: function cartesianToPolar(x, y) {
- var radius = Math.sqrt(x * x + y * y);
- var angle = Math.atan2(y, x);
+ getPositionAlongPath: function getPositionAlongPath(progress) {
+ if (progress <= 0) return {
+ x: this.pathPoints[0].x,
+ y: this.pathPoints[0].y
+ };
+ if (progress >= 1) return {
+ x: this.pathPoints[this.pathPoints.length - 1].x,
+ y: this.pathPoints[this.pathPoints.length - 1].y
+ };
+ var totalSegments = this.pathPoints.length - 1;
+ var segmentProgress = progress * totalSegments;
+ var currentSegment = Math.floor(segmentProgress);
+ var segmentRatio = segmentProgress - currentSegment;
+ if (currentSegment >= totalSegments) {
+ return {
+ x: this.pathPoints[this.pathPoints.length - 1].x,
+ y: this.pathPoints[this.pathPoints.length - 1].y
+ };
+ }
+ var start = this.pathPoints[currentSegment];
+ var end = this.pathPoints[currentSegment + 1];
return {
- radius: radius,
- angle: angle
+ x: start.x + (end.x - start.x) * segmentRatio,
+ y: start.y + (end.y - start.y) * segmentRatio
};
},
- // Get distance between two points
- distance: function distance(x1, y1, x2, y2) {
- var dx = x2 - x1;
- var dy = y2 - y1;
- return Math.sqrt(dx * dx + dy * dy);
- },
- // Normalize angle to 0-2π range
- normalizeAngle: function normalizeAngle(angle) {
- while (angle < 0) {
- angle += Math.PI * 2;
+ getPathLength: function getPathLength() {
+ var totalLength = 0;
+ for (var i = 0; i < this.pathPoints.length - 1; i++) {
+ var dx = this.pathPoints[i + 1].x - this.pathPoints[i].x;
+ var dy = this.pathPoints[i + 1].y - this.pathPoints[i].y;
+ totalLength += Math.sqrt(dx * dx + dy * dy);
}
- while (angle >= Math.PI * 2) {
- angle -= Math.PI * 2;
- }
- return angle;
+ return totalLength;
}
};
/****
-* Wave System
+* Game Manager
****/
-var WaveManager = {
- currentWave: 0,
- totalWaves: 30,
- waveTimer: 0,
- waveDelay: 300,
- // frames between waves
- enemiesPerWave: 15,
- materials: ['slime', 'metal', 'lava', 'ice', 'shadow'],
- update: function update() {
- this.waveTimer--;
- if (this.waveTimer <= 0 && this.currentWave < this.totalWaves) {
- this.spawnWave();
- this.waveTimer = this.waveDelay;
+var GameManager = {
+ topAreaGenerateCount: 0,
+ bottomAreaGenerateCount: 0,
+ findEmptySpot: function findEmptySpot(area) {
+ var orbs = area === 0 ? topAreaOrbs : bottomAreaOrbs;
+ for (var y = 0; y < GRID_ROWS; y++) {
+ for (var x = 0; x < GRID_COLS; x++) {
+ if (!orbs[x][y]) {
+ return {
+ x: x,
+ y: y
+ };
+ }
+ }
}
+ return null;
},
- spawnWave: function spawnWave() {
- this.currentWave++;
- var material = this.materials[(this.currentWave - 1) % this.materials.length];
- var enemyCount = this.enemiesPerWave + Math.floor(this.currentWave / 5);
- for (var i = 0; i < enemyCount; i++) {
- LK.setTimeout(function (mat) {
- return function () {
- var enemy = PoolManager.getEnemy();
- if (enemy) {
- var spawnAngle = Math.random() * Math.PI * 2;
- enemy.activate(mat, spawnAngle);
- gameLayer.addChild(enemy);
- }
- };
- }(material), i * 100); // Stagger spawning
+ removeOrb: function removeOrb(orb) {
+ var orbs = orb.area === 0 ? topAreaOrbs : bottomAreaOrbs;
+ var layer = orb.area === 0 ? topAreaLayer : bottomAreaLayer;
+ orbs[orb.gridX][orb.gridY] = null;
+ layer.removeChild(orb);
+ PoolManager.returnEnergyOrb(orb);
+ },
+ getOrbAt: function getOrbAt(area, gridX, gridY) {
+ var orbs = area === 0 ? topAreaOrbs : bottomAreaOrbs;
+ return orbs[gridX] && orbs[gridX][gridY];
+ },
+ getGenerateCount: function getGenerateCount(area) {
+ return area === 0 ? this.topAreaGenerateCount : this.bottomAreaGenerateCount;
+ },
+ incrementGenerateCount: function incrementGenerateCount(area) {
+ if (area === 0) {
+ this.topAreaGenerateCount++;
+ } else {
+ this.bottomAreaGenerateCount++;
}
- var notification = new Notification("Wave " + this.currentWave + " - " + material + " blobs!");
- game.addChild(notification);
- notification.x = SCREEN_WIDTH / 2;
- notification.y = 100;
}
};
-/****
-* Game Constants
-****/
+var CELL_SIZE = 76;
+var GRID_COLS = 3;
+var GRID_ROWS = 5;
+var AREA_SPACING = CELL_SIZE * 3;
var SCREEN_WIDTH = 2048;
var SCREEN_HEIGHT = 2732;
-var gameLayer = new Container();
-var uiLayer = new Container();
-var embryo;
-var towerSelector;
-var gold = 100;
+var GRID_START_X = (SCREEN_WIDTH - GRID_COLS * CELL_SIZE) / 2;
+var TOP_AREA_Y = 300;
+var BOTTOM_AREA_Y = TOP_AREA_Y + GRID_ROWS * CELL_SIZE + AREA_SPACING;
+var TOP_AREA_X = GRID_START_X;
+var BOTTOM_AREA_X = GRID_START_X;
+/****
+* Game Variables
+****/
+var gold = 50;
var lives = 20;
var score = 0;
-// UI Text Elements
+var activeBlobs = [];
+var activeBullets = [];
+var topAreaOrbs = [];
+var bottomAreaOrbs = [];
+var selectedOrb = null;
+var isDragging = false;
+var dragStartPos = {
+ x: 0,
+ y: 0
+};
+/****
+* Initialize Arrays
+****/
+for (var x = 0; x < GRID_COLS; x++) {
+ topAreaOrbs[x] = [];
+ bottomAreaOrbs[x] = [];
+ for (var y = 0; y < GRID_ROWS; y++) {
+ topAreaOrbs[x][y] = null;
+ bottomAreaOrbs[x][y] = null;
+ }
+}
+/****
+* UI Elements
+****/
var goldText = new Text2('Gold: ' + gold, {
- size: 48,
+ size: 60,
fill: 0xFFD700,
weight: 800
});
goldText.anchor.set(0.5, 0.5);
var livesText = new Text2('Lives: ' + lives, {
- size: 48,
+ size: 60,
fill: 0x00FF00,
weight: 800
});
livesText.anchor.set(0.5, 0.5);
var scoreText = new Text2('Score: ' + score, {
- size: 48,
+ size: 60,
fill: 0xFF0000,
weight: 800
});
scoreText.anchor.set(0.5, 0.5);
-var waveText = new Text2('Wave: 0', {
- size: 48,
- fill: 0x00AAFF,
- weight: 800
-});
-waveText.anchor.set(0.5, 0.5);
-/****
-* Utility Functions
-****/
+LK.gui.top.addChild(goldText);
+LK.gui.top.addChild(livesText);
+LK.gui.top.addChild(scoreText);
+goldText.x = -400;
+goldText.y = 50;
+livesText.x = 0;
+livesText.y = 50;
+scoreText.x = 400;
+scoreText.y = 50;
function updateUI() {
goldText.setText('Gold: ' + gold);
livesText.setText('Lives: ' + lives);
scoreText.setText('Score: ' + score);
- waveText.setText('Wave: ' + WaveManager.currentWave);
}
function setGold(value) {
gold = value;
updateUI();
}
/****
-* Game Initialization
+* Layer Setup
****/
-function initializeGame() {
- // Initialize object pools
- PoolManager.init();
- // Create embryo at center
- embryo = new Embryo();
- embryo.x = SCREEN_WIDTH / 2;
- embryo.y = SCREEN_HEIGHT / 2;
- gameLayer.addChild(embryo);
- // Create tower selector
- towerSelector = new TowerSelector();
- towerSelector.x = SCREEN_WIDTH / 2;
- towerSelector.y = SCREEN_HEIGHT - 150;
- uiLayer.addChild(towerSelector);
- // Position UI elements
- goldText.x = 150;
- goldText.y = 80;
- uiLayer.addChild(goldText);
- livesText.x = 400;
- livesText.y = 80;
- uiLayer.addChild(livesText);
- scoreText.x = 650;
- scoreText.y = 80;
- uiLayer.addChild(scoreText);
- waveText.x = 900;
- waveText.y = 80;
- uiLayer.addChild(waveText);
- // Add layers to game
- game.addChild(gameLayer);
- game.addChild(uiLayer);
- // Start first wave after delay
- WaveManager.waveTimer = 180; // 3 seconds
- updateUI();
-}
+var gameLayer = new Container();
+var topAreaLayer = new Container();
+var bottomAreaLayer = new Container();
+var effectLayer = new Container();
+game.addChild(gameLayer);
+game.addChild(topAreaLayer);
+game.addChild(bottomAreaLayer);
+game.addChild(effectLayer);
/****
-* Camera System
+* Generate Buttons
****/
-var Camera = {
- zoom: 1,
- targetZoom: 1,
- update: function update() {
- // Adjust zoom based on embryo size
- var baseZoom = 1;
- var embryoSize = embryo ? embryo.currentSize : 80;
- this.targetZoom = Math.max(0.6, baseZoom - (embryoSize - 80) / 400);
- // Smooth zoom transition
- this.zoom += (this.targetZoom - this.zoom) * 0.05;
- // Apply zoom to game layer
- gameLayer.scaleX = gameLayer.scaleY = this.zoom;
- // Keep embryo centered
- if (embryo) {
- gameLayer.x = SCREEN_WIDTH / 2 - embryo.x * this.zoom;
- gameLayer.y = SCREEN_HEIGHT / 2 - embryo.y * this.zoom;
+var topGenerateButton = new GenerateButton(0);
+topGenerateButton.x = TOP_AREA_X + GRID_COLS * CELL_SIZE + 150;
+topGenerateButton.y = TOP_AREA_Y + GRID_ROWS * CELL_SIZE / 2;
+game.addChild(topGenerateButton);
+var bottomGenerateButton = new GenerateButton(1);
+bottomGenerateButton.x = BOTTOM_AREA_X + GRID_COLS * CELL_SIZE + 150;
+bottomGenerateButton.y = BOTTOM_AREA_Y + GRID_ROWS * CELL_SIZE / 2;
+game.addChild(bottomGenerateButton);
+/****
+* Wave Manager
+****/
+var waveManager = new WaveManager();
+waveManager.x = SCREEN_WIDTH / 2;
+waveManager.y = SCREEN_HEIGHT - 100;
+game.addChild(waveManager);
+/****
+* Input Handling
+****/
+game.down = function (x, y, obj) {
+ // Check if clicking on an orb
+ var clickedOrb = null;
+ var clickedArea = -1;
+ // Check top area
+ if (x >= TOP_AREA_X && x <= TOP_AREA_X + GRID_COLS * CELL_SIZE && y >= TOP_AREA_Y && y <= TOP_AREA_Y + GRID_ROWS * CELL_SIZE) {
+ var gridX = Math.floor((x - TOP_AREA_X) / CELL_SIZE);
+ var gridY = Math.floor((y - TOP_AREA_Y) / CELL_SIZE);
+ clickedOrb = GameManager.getOrbAt(0, gridX, gridY);
+ clickedArea = 0;
+ }
+ // Check bottom area
+ if (!clickedOrb && x >= BOTTOM_AREA_X && x <= BOTTOM_AREA_X + GRID_COLS * CELL_SIZE && y >= BOTTOM_AREA_Y && y <= BOTTOM_AREA_Y + GRID_ROWS * CELL_SIZE) {
+ var gridX = Math.floor((x - BOTTOM_AREA_X) / CELL_SIZE);
+ var gridY = Math.floor((y - BOTTOM_AREA_Y) / CELL_SIZE);
+ clickedOrb = GameManager.getOrbAt(1, gridX, gridY);
+ clickedArea = 1;
+ }
+ if (clickedOrb) {
+ selectedOrb = clickedOrb;
+ isDragging = true;
+ dragStartPos.x = x;
+ dragStartPos.y = y;
+ selectedOrb.isDragging = true;
+ // Bring to front
+ var layer = selectedOrb.area === 0 ? topAreaLayer : bottomAreaLayer;
+ layer.removeChild(selectedOrb);
+ layer.addChild(selectedOrb);
+ }
+};
+game.move = function (x, y, obj) {
+ if (isDragging && selectedOrb) {
+ selectedOrb.x = selectedOrb.x + (x - dragStartPos.x);
+ selectedOrb.y = selectedOrb.y + (y - dragStartPos.y);
+ dragStartPos.x = x;
+ dragStartPos.y = y;
+ // Check for magnetic attraction
+ var orbs = selectedOrb.area === 0 ? topAreaOrbs : bottomAreaOrbs;
+ for (var gx = 0; gx < GRID_COLS; gx++) {
+ for (var gy = 0; gy < GRID_ROWS; gy++) {
+ var otherOrb = orbs[gx][gy];
+ if (otherOrb && otherOrb !== selectedOrb) {
+ selectedOrb.checkMagneticAttraction(otherOrb);
+ }
+ }
}
}
};
+game.up = function (x, y, obj) {
+ if (isDragging && selectedOrb) {
+ isDragging = false;
+ selectedOrb.isDragging = false;
+ // Check for merge
+ var mergeHappened = false;
+ var targetArea = -1;
+ // Determine which area we're over
+ if (x >= TOP_AREA_X && x <= TOP_AREA_X + GRID_COLS * CELL_SIZE && y >= TOP_AREA_Y && y <= TOP_AREA_Y + GRID_ROWS * CELL_SIZE) {
+ targetArea = 0;
+ } else if (x >= BOTTOM_AREA_X && x <= BOTTOM_AREA_X + GRID_COLS * CELL_SIZE && y >= BOTTOM_AREA_Y && y <= BOTTOM_AREA_Y + GRID_ROWS * CELL_SIZE) {
+ targetArea = 1;
+ }
+ if (targetArea !== -1) {
+ var gridX = Math.floor((x - (targetArea === 0 ? TOP_AREA_X : BOTTOM_AREA_X)) / CELL_SIZE);
+ var gridY = Math.floor((y - (targetArea === 0 ? TOP_AREA_Y : BOTTOM_AREA_Y)) / CELL_SIZE);
+ if (gridX >= 0 && gridX < GRID_COLS && gridY >= 0 && gridY < GRID_ROWS) {
+ var targetOrb = GameManager.getOrbAt(targetArea, gridX, gridY);
+ if (targetOrb && selectedOrb.canMergeWith(targetOrb)) {
+ // Merge the orbs
+ var otherArea = targetArea === 0 ? 1 : 0;
+ mergeHappened = selectedOrb.mergeWith(targetOrb, otherArea);
+ }
+ }
+ }
+ if (!mergeHappened) {
+ // Return orb to original position
+ selectedOrb.updatePosition();
+ // Smooth return animation
+ tween(selectedOrb, {
+ x: selectedOrb.area === 0 ? TOP_AREA_X + selectedOrb.gridX * CELL_SIZE + CELL_SIZE / 2 : BOTTOM_AREA_X + selectedOrb.gridX * CELL_SIZE + CELL_SIZE / 2,
+ y: selectedOrb.area === 0 ? TOP_AREA_Y + selectedOrb.gridY * CELL_SIZE + CELL_SIZE / 2 : BOTTOM_AREA_Y + selectedOrb.gridY * CELL_SIZE + CELL_SIZE / 2
+ }, {
+ duration: 300,
+ easing: tween.elasticOut
+ });
+ }
+ selectedOrb.magneticTarget = null;
+ selectedOrb = null;
+ }
+};
/****
-* Game Update Loop
+* Grid Visual Indicators
****/
-game.update = function () {
- // Update all systems
- WaveManager.update();
- Camera.update();
- // Update embryo
- if (embryo) {
- embryo.update();
+function drawGridLines() {
+ // Top area grid
+ for (var x = 0; x <= GRID_COLS; x++) {
+ var line = new Container();
+ var lineGraphics = line.attachAsset('cell', {
+ anchorX: 0,
+ anchorY: 0
+ });
+ lineGraphics.width = 2;
+ lineGraphics.height = GRID_ROWS * CELL_SIZE;
+ lineGraphics.tint = 0x444444;
+ lineGraphics.alpha = 0.3;
+ line.x = TOP_AREA_X + x * CELL_SIZE;
+ line.y = TOP_AREA_Y;
+ gameLayer.addChild(line);
}
- // Update all active enemies
- for (var i = 0; i < PoolManager.enemies.length; i++) {
- var enemy = PoolManager.enemies[i];
- if (enemy.active) {
- enemy.update();
+ for (var y = 0; y <= GRID_ROWS; y++) {
+ var line = new Container();
+ var lineGraphics = line.attachAsset('cell', {
+ anchorX: 0,
+ anchorY: 0
+ });
+ lineGraphics.width = GRID_COLS * CELL_SIZE;
+ lineGraphics.height = 2;
+ lineGraphics.tint = 0x444444;
+ lineGraphics.alpha = 0.3;
+ line.x = TOP_AREA_X;
+ line.y = TOP_AREA_Y + y * CELL_SIZE;
+ gameLayer.addChild(line);
+ }
+ // Bottom area grid
+ for (var x = 0; x <= GRID_COLS; x++) {
+ var line = new Container();
+ var lineGraphics = line.attachAsset('cell', {
+ anchorX: 0,
+ anchorY: 0
+ });
+ lineGraphics.width = 2;
+ lineGraphics.height = GRID_ROWS * CELL_SIZE;
+ lineGraphics.tint = 0x444444;
+ lineGraphics.alpha = 0.3;
+ line.x = BOTTOM_AREA_X + x * CELL_SIZE;
+ line.y = BOTTOM_AREA_Y;
+ gameLayer.addChild(line);
+ }
+ for (var y = 0; y <= GRID_ROWS; y++) {
+ var line = new Container();
+ var lineGraphics = line.attachAsset('cell', {
+ anchorX: 0,
+ anchorY: 0
+ });
+ lineGraphics.width = GRID_COLS * CELL_SIZE;
+ lineGraphics.height = 2;
+ lineGraphics.tint = 0x444444;
+ lineGraphics.alpha = 0.3;
+ line.x = BOTTOM_AREA_X;
+ line.y = BOTTOM_AREA_Y + y * CELL_SIZE;
+ gameLayer.addChild(line);
+ }
+}
+/****
+* Path Visualization
+****/
+function drawPath() {
+ for (var i = 0; i < PathSystem.pathPoints.length - 1; i++) {
+ var start = PathSystem.pathPoints[i];
+ var end = PathSystem.pathPoints[i + 1];
+ var dx = end.x - start.x;
+ var dy = end.y - start.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ var segments = Math.floor(distance / 20);
+ for (var j = 0; j < segments; j++) {
+ var progress = j / segments;
+ var x = start.x + dx * progress;
+ var y = start.y + dy * progress;
+ var pathDot = new Container();
+ var dotGraphics = pathDot.attachAsset('particle', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ dotGraphics.width = dotGraphics.height = 8;
+ dotGraphics.tint = 0x666666;
+ dotGraphics.alpha = 0.6;
+ pathDot.x = x;
+ pathDot.y = y;
+ gameLayer.addChild(pathDot);
}
}
- // Update all active bullets
- for (var i = 0; i < PoolManager.bullets.length; i++) {
- var bullet = PoolManager.bullets[i];
+}
+/****
+* Initialization
+****/
+PoolManager.init();
+PathSystem.init();
+drawGridLines();
+drawPath();
+// Start first wave after a delay
+waveManager.waveTimer = 240; // 4 second delay before first wave
+/****
+* Main Game Loop
+****/
+game.update = function () {
+ // Update active blobs
+ for (var i = activeBlobs.length - 1; i >= 0; i--) {
+ var blob = activeBlobs[i];
+ if (blob.active) {
+ blob.update();
+ } else {
+ activeBlobs.splice(i, 1);
+ gameLayer.removeChild(blob);
+ }
+ }
+ // Update active bullets
+ for (var i = activeBullets.length - 1; i >= 0; i--) {
+ var bullet = activeBullets[i];
if (bullet.active) {
bullet.update();
+ } else {
+ activeBullets.splice(i, 1);
+ gameLayer.removeChild(bullet);
}
}
- // Update all active particles
- for (var i = 0; i < PoolManager.particles.length; i++) {
- var particle = PoolManager.particles[i];
- if (particle.active) {
- particle.update();
+ // Update all orbs
+ for (var x = 0; x < GRID_COLS; x++) {
+ for (var y = 0; y < GRID_ROWS; y++) {
+ if (topAreaOrbs[x][y]) {
+ topAreaOrbs[x][y].update();
+ }
+ if (bottomAreaOrbs[x][y]) {
+ bottomAreaOrbs[x][y].update();
+ }
}
}
- // Update all towers
- for (var i = 0; i < embryo.orbitalSlots.length; i++) {
- var slot = embryo.orbitalSlots[i];
- if (slot.tower) {
- slot.tower.update();
- }
+ // Update active particles
+ var allParticles = gameLayer.children.filter(function (child) {
+ return child instanceof DeathParticle;
+ });
+ for (var i = 0; i < allParticles.length; i++) {
+ allParticles[i].update();
}
- // Check win/lose conditions
+ // Update active effects
+ var allEffects = gameLayer.children.filter(function (child) {
+ return child instanceof EffectIndicator;
+ });
+ for (var i = 0; i < allEffects.length; i++) {
+ allEffects[i].update();
+ }
+ // Update notifications
+ var allNotifications = gameLayer.children.filter(function (child) {
+ return child instanceof Notification;
+ });
+ for (var i = 0; i < allNotifications.length; i++) {
+ allNotifications[i].update();
+ }
+ // Update wave manager
+ waveManager.update();
+ // Update generate buttons
+ topGenerateButton.update();
+ bottomGenerateButton.update();
+ // Check win condition
+ if (waveManager.currentWave >= waveManager.maxWaves && activeBlobs.length === 0) {
+ LK.showYouWin();
+ }
+ // Check lose condition
if (lives <= 0) {
LK.showGameOver();
- } else if (WaveManager.currentWave >= WaveManager.totalWaves) {
- // Check if all enemies are defeated
- var activeEnemies = 0;
- for (var i = 0; i < PoolManager.enemies.length; i++) {
- if (PoolManager.enemies[i].active) {
- activeEnemies++;
+ }
+ updateUI();
+};
+/****
+* Debug Features (Remove in production)
+****/
+if (LK.debug) {
+ // Add some starting orbs for testing
+ setTimeout(function () {
+ var testOrb1 = PoolManager.getEnergyOrb();
+ if (testOrb1) {
+ testOrb1.activate('rapid', 1, 1, 0);
+ topAreaLayer.addChild(testOrb1);
+ topAreaOrbs[1][1] = testOrb1;
+ }
+ var testOrb2 = PoolManager.getEnergyOrb();
+ if (testOrb2) {
+ testOrb2.activate('sniper', 1, 3, 1);
+ bottomAreaLayer.addChild(testOrb2);
+ bottomAreaOrbs[1][3] = testOrb2;
+ }
+ }, 1000);
+}
+/****
+* Performance Monitoring (Optional)
+****/
+var PerformanceMonitor = {
+ frameCount: 0,
+ lastTime: Date.now(),
+ fps: 60,
+ update: function update() {
+ this.frameCount++;
+ var currentTime = Date.now();
+ if (currentTime - this.lastTime >= 1000) {
+ this.fps = this.frameCount;
+ this.frameCount = 0;
+ this.lastTime = currentTime;
+ // Log performance warnings
+ if (this.fps < 30) {
+ console.warn('Low FPS detected:', this.fps);
+ console.log('Active objects - Blobs:', activeBlobs.length, 'Bullets:', activeBullets.length);
}
}
- if (activeEnemies === 0) {
- LK.showYouWin();
+ }
+};
+// Add performance monitoring to game loop if in debug mode
+if (LK.debug) {
+ var originalUpdate = game.update;
+ game.update = function () {
+ PerformanceMonitor.update();
+ originalUpdate.call(this);
+ };
+}
+/****
+* Utility Functions
+****/
+function getDistanceBetweenPoints(p1, p2) {
+ var dx = p2.x - p1.x;
+ var dy = p2.y - p1.y;
+ return Math.sqrt(dx * dx + dy * dy);
+}
+function clamp(value, min, max) {
+ return Math.max(min, Math.min(max, value));
+}
+function lerp(start, end, factor) {
+ return start + (end - start) * factor;
+}
+// Fibonacci sequence for costs
+function fibonacci(n) {
+ if (n <= 1) return 1;
+ var a = 1,
+ b = 1;
+ for (var i = 2; i <= n; i++) {
+ var temp = a + b;
+ a = b;
+ b = temp;
+ }
+ return b;
+}
+/****
+* Save/Load System (Future Enhancement)
+****/
+var SaveSystem = {
+ save: function save() {
+ var saveData = {
+ gold: gold,
+ lives: lives,
+ score: score,
+ wave: waveManager.currentWave,
+ topOrbs: [],
+ bottomOrbs: []
+ };
+ // Save orb positions and levels
+ for (var x = 0; x < GRID_COLS; x++) {
+ for (var y = 0; y < GRID_ROWS; y++) {
+ if (topAreaOrbs[x][y]) {
+ saveData.topOrbs.push({
+ x: x,
+ y: y,
+ type: topAreaOrbs[x][y].orbType,
+ level: topAreaOrbs[x][y].level
+ });
+ }
+ if (bottomAreaOrbs[x][y]) {
+ saveData.bottomOrbs.push({
+ x: x,
+ y: y,
+ type: bottomAreaOrbs[x][y].orbType,
+ level: bottomAreaOrbs[x][y].level
+ });
+ }
+ }
}
+ localStorage.setItem('mergeDefenseSave', JSON.stringify(saveData));
+ },
+ load: function load() {
+ var saveData = localStorage.getItem('mergeDefenseSave');
+ if (!saveData) return false;
+ try {
+ var data = JSON.parse(saveData);
+ gold = data.gold;
+ lives = data.lives;
+ score = data.score;
+ waveManager.currentWave = data.wave;
+ // Restore orbs
+ data.topOrbs.forEach(function (orbData) {
+ var orb = PoolManager.getEnergyOrb();
+ if (orb) {
+ orb.activate(orbData.type, orbData.x, orbData.y, 0);
+ orb.level = orbData.level;
+ orb.updateVisuals();
+ topAreaLayer.addChild(orb);
+ topAreaOrbs[orbData.x][orbData.y] = orb;
+ }
+ });
+ data.bottomOrbs.forEach(function (orbData) {
+ var orb = PoolManager.getEnergyOrb();
+ if (orb) {
+ orb.activate(orbData.type, orbData.x, orbData.y, 1);
+ orb.level = orbData.level;
+ orb.updateVisuals();
+ bottomAreaLayer.addChild(orb);
+ bottomAreaOrbs[orbData.x][orbData.y] = orb;
+ }
+ });
+ updateUI();
+ return true;
+ } catch (e) {
+ console.error('Failed to load save data:', e);
+ return false;
+ }
}
};
/****
-* Game Input Handlers
+* Audio System (Placeholder for future implementation)
****/
-game.down = function (x, y, obj) {
- // Handle UI interactions
- // Tower selector handles its own input
+var AudioSystem = {
+ playSound: function playSound(soundName) {
+ // Placeholder for sound effects
+ // Will be implemented when audio assets are available
+ console.log('Playing sound:', soundName);
+ },
+ playMusic: function playMusic(musicName) {
+ // Placeholder for background music
+ console.log('Playing music:', musicName);
+ }
};
-game.move = function (x, y, obj) {
- // Handle drag interactions if needed
-};
-game.up = function (x, y, obj) {
- // Handle release interactions if needed
-};
/****
-* Start Game
+* Effects and Polish
****/
-initializeGame();
\ No newline at end of file
+function createScreenShake(intensity, duration) {
+ var originalX = game.x;
+ var originalY = game.y;
+ var shakeTimer = duration;
+ var _shakeUpdate = function shakeUpdate() {
+ if (shakeTimer > 0) {
+ game.x = originalX + (Math.random() - 0.5) * intensity;
+ game.y = originalY + (Math.random() - 0.5) * intensity;
+ shakeTimer--;
+ requestAnimationFrame(_shakeUpdate);
+ } else {
+ game.x = originalX;
+ game.y = originalY;
+ }
+ };
+ _shakeUpdate();
+}
+function createFloatingText(text, x, y, color) {
+ var floatingText = new Text2(text, {
+ size: 40,
+ fill: color || 0xFFFFFF,
+ weight: 800
+ });
+ floatingText.anchor.set(0.5, 0.5);
+ floatingText.x = x;
+ floatingText.y = y;
+ floatingText.alpha = 1;
+ gameLayer.addChild(floatingText);
+ tween(floatingText, {
+ y: y - 50,
+ alpha: 0
+ }, {
+ duration: 1000,
+ easing: tween.quadOut,
+ onFinish: function onFinish() {
+ gameLayer.removeChild(floatingText);
+ }
+ });
+}
+/****
+* Game Over and Victory Screens
+****/
+LK.showGameOver = function () {
+ var overlay = new Container();
+ var bg = overlay.attachAsset('notification', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ bg.width = SCREEN_WIDTH;
+ bg.height = SCREEN_HEIGHT;
+ bg.tint = 0x000000;
+ bg.alpha = 0.8;
+ var gameOverText = new Text2('GAME OVER', {
+ size: 100,
+ fill: 0xFF0000,
+ weight: 800
+ });
+ gameOverText.anchor.set(0.5, 0.5);
+ gameOverText.y = -100;
+ overlay.addChild(gameOverText);
+ var finalScoreText = new Text2('Final Score: ' + score, {
+ size: 60,
+ fill: 0xFFFFFF,
+ weight: 600
+ });
+ finalScoreText.anchor.set(0.5, 0.5);
+ finalScoreText.y = 0;
+ overlay.addChild(finalScoreText);
+ var restartText = new Text2('Tap to Restart', {
+ size: 50,
+ fill: 0x00FF00,
+ weight: 600
+ });
+ restartText.anchor.set(0.5, 0.5);
+ restartText.y = 100;
+ overlay.addChild(restartText);
+ overlay.x = SCREEN_WIDTH / 2;
+ overlay.y = SCREEN_HEIGHT / 2;
+ game.addChild(overlay);
+ overlay.down = function () {
+ location.reload(); // Simple restart
+ };
+};
+LK.showYouWin = function () {
+ var overlay = new Container();
+ var bg = overlay.attachAsset('notification', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ bg.width = SCREEN_WIDTH;
+ bg.height = SCREEN_HEIGHT;
+ bg.tint = 0x000000;
+ bg.alpha = 0.8;
+ var victoryText = new Text2('VICTORY!', {
+ size: 100,
+ fill: 0x00FF00,
+ weight: 800
+ });
+ victoryText.anchor.set(0.5, 0.5);
+ victoryText.y = -100;
+ overlay.addChild(victoryText);
+ var finalScoreText = new Text2('Final Score: ' + score, {
+ size: 60,
+ fill: 0xFFFFFF,
+ weight: 600
+ });
+ finalScoreText.anchor.set(0.5, 0.5);
+ finalScoreText.y = 0;
+ overlay.addChild(finalScoreText);
+ var restartText = new Text2('Tap to Play Again', {
+ size: 50,
+ fill: 0x00FF00,
+ weight: 600
+ });
+ restartText.anchor.set(0.5, 0.5);
+ restartText.y = 100;
+ overlay.addChild(restartText);
+ overlay.x = SCREEN_WIDTH / 2;
+ overlay.y = SCREEN_HEIGHT / 2;
+ game.addChild(overlay);
+ overlay.down = function () {
+ location.reload(); // Simple restart
+ };
+ // Victory animation
+ tween(victoryText, {
+ scaleX: 1.2,
+ scaleY: 1.2
+ }, {
+ duration: 500,
+ easing: tween.elasticOut,
+ repeat: -1,
+ yoyo: true
+ });
+};
\ No newline at end of file
A long rack of different colored poker chips seen from above. Anime style.. In-Game asset. 2d. High contrast. No shadows
A graphic for the center of a joker card.
a 2:3 format thin black border with nothing in the center. In-Game asset. 2d. High contrast. No shadows
A small white explosion particle.. In-Game asset. 2d. High contrast. No shadows
Make the blue a lighter blue.
Make this in a white instead of blue. Keep everything else the same.
A couple different sized stacks of these chips beside each other.
Just the spade from this picture with a blue snowflake in the middle of it.
Just the heart from this picture with a flame in the cent t of it.
Just the club from this picture with 1. **Fan/Spray Symbol** - Three or more lines radiating outward from a central point, yellow in color, in the center of the club.
Just the diamond from this picture with a dollar sign in the center
A white circle with a lightening gradient towards the edge.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A simple golden line break.. In-Game asset. 2d. High contrast. No shadows
A fanned card hand that shows a royal flush in spades. Anime style. In-Game asset. 2d. High contrast. No shadows
An SVG of the word 'Battle'. text in yellow with a black outline. In-Game asset. 2d. High contrast. No shadows
change the text to say "Mods"
The four card suits arranged in 2x2 grid layout, no lines. Anime style. In-Game asset. 2d. High contrast. No shadows
A single ice crystal. anime style. In-Game asset. 2d. High contrast. No shadows
Change the text to say ‘Refund’. Change the cards to a trash can.
A completely blank playing card with textured surface. Slightly used edges with a couple nicks out of it. Black background. In-Game asset. 2d. High contrast. No shadows
A 3:2 ratio rectangular green button that says “PvP” using this yellow font.
Change the text to say ‘Co-op’
Change the font to say ‘Victory!’
Change the text to say ‘Defeat!’
A 2:3 ratio rectangular picture that shows two card playing cats in a casino very close face to face with teeth bared and fists clenched as if they’re about to fight. Each cat has a different card suit pattern on the fur of their forehead. One is wearing a suit and the other is wearing tan leather jacket with a striped tank top underneath. Anime style.. In-Game asset. 2d. High contrast. No shadows
Show these same cats smiling and instead of clenched fists they’re grasping hands because they’re friends.
Incorporate these two cats heads into a game logo for a poker based tower defense that includes the name “Double Down Defense”. Put their heads offset on either side with eyes open and looking at the logo.
A small treasure chest with poker themed graphics on it. Anime style. In-Game asset. 2d. High contrast. No shadows
The hearts card suit symbol with two linked hearts in the center of it. Anime style.. In-Game asset. 2d. High contrast. No shadows
The diamond card suit with a coin in the center. The coin has a ‘2X’ in the center. Anime style.. In-Game asset. 2d. High contrast. No shadows
Just the club from this picture with a clock in the center.
Just the spade from this image with a land mine in the center of it.
Just the mine from this image.
Just the heart from this image with a piggy bank in the center.
Just the diamond from this picture with a sword with a small arrow pointing up in the center of the diamond.
Just the club from this picture with an icon in the center of it that represents a projectile bouncing at an angle off of a surface.
Just the spade with a skull in the center of it. Anime style.
This chest with the top open and nothing inside.
Change the text to say Shop
An old style cash register. The numeric read out says 7.77. Anime style.. In-Game asset. 2d. High contrast. No shadows
A giant question mark. Anime style.. In-Game asset. 2d. High contrast. No shadows
A shield with a spade and heart card suit coat of arms on it with a sword crossed downwards, behind it. icon. Anime style.. In-Game asset. 2d. High contrast. No shadows
Change the text to say ‘Draw’
The back of a playing card. Blue pattern. Anime style.. In-Game asset. 2d. High contrast. No shadows
The back of a playing card. Red pattern with a heart in the center. Anime style.. In-Game asset. 2d. High contrast. No shadows
Change the blue color to gold and put a blue glowing shield in the center.
Change the image of the shield in the center into a red question mark lined with gold.
Change the image of a shield in the center into a robotic cheetah head. Change the background color in the center to green.
Change the image of the shield into the center to a steel padlock with a keyhole on it. Change the background color in the center to yellow.
Change the shield in the center to a picture of a winding snake and the background color in the center in purple.
Change the word to say Slots
An icon of a slot machine. Anime style. High definition.. In-Game asset. 2d. High contrast. No shadows
A “Spin” button for an electronic slot machine. Button is red and font is in yellow. Anime style. High definition.. In-Game asset. 2d. High contrast. No shadows
Create a symbol that’s just the white cats head looking straight forward with X’s for eyes and his tongue hanging out of the side of his mouth.
Change the text to say Paytable and adjust button shape to accommodate.
Change the button to say $10 and make it square.
Change the button to say $25
Change the button to say $50
Change the button to say $100
Change the button color to light green.
Change the button color to light green.
Change the button color to light green.
Change the button color to light green.
Add an icon of the four card suits in the center of the heart. Anime style.
Just the spade from this picture with an icon of a cartoon spy with a hood and a mask on in the center of the spade.
Just the club from this picture with a small bubbling green vial of poison in the center of it. Anime style.
Just the diamond from this picture with an icon of a scale in the center.
titleSong
Music
pvpMusic
Music
getReady
Sound effect
gameStart
Sound effect
cardLand
Sound effect
shootSound
Sound effect
levelUp
Sound effect
buttonPress
Sound effect
pokerChipDie
Sound effect
roulette
Sound effect
victory
Sound effect
defeat
Sound effect
slotButton
Sound effect
slotSpin
Sound effect
reelStop
Sound effect
slotWin
Sound effect