User prompt
Reduce the unit price to 35 gold
User prompt
Make units a little cheaper
User prompt
You know that 0/1000 waves thingy? Well, remove the /1000 thing
User prompt
Remove the wave limit
User prompt
The game now has INFINITE WAVES!
User prompt
Make goblins drop more gold
User prompt
Make goblins a little weaker
User prompt
Make waves a little easier by making goblins be slower and have less health, but they will get 2% more health and speed every wave
User prompt
Put the unit's name below the level of the tower
Code edit (1 edits merged)
Please save this source code
User prompt
Merge Defenders: Evolution
Initial prompt
Merging defense: a game where you merge units to make strongher units (example: rock thrower + rock thrower = slinger)to kill the goblins from reaching your base! You have 3 lives. (Units (from weakest to stronghest): rock thrower, slinger, spear thrower, archer, Fire archer, crossbowman, Mosquettere, cannoneer, rifleman, grenadier, tank); make sure to use ALL of the units i said and make them cost gold that is obtained through killing enemies or making in-app purchases with real money
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ var Goblin = Container.expand(function (pathPoints, health, speed, value) { var self = Container.call(this); self.pathPoints = pathPoints; self.maxHealth = health; self.health = health; self.speed = speed; self.value = value; self.active = true; self.currentPathIndex = 0; self.pathProgress = 0; var goblinGraphics = self.attachAsset('goblin', { anchorX: 0.5, anchorY: 0.5 }); var healthBar = LK.getAsset('tile', { width: 60, height: 10, color: 0x00FF00, anchorX: 0.5, anchorY: 0.5 }); healthBar.y = -40; self.addChild(healthBar); self.x = pathPoints[0].x; self.y = pathPoints[0].y; self.takeDamage = function (damage) { self.health -= damage; // Update health bar var healthPercent = Math.max(0, self.health / self.maxHealth); healthBar.width = 60 * healthPercent; healthBar.tint = healthPercent > 0.5 ? 0x00FF00 : healthPercent > 0.25 ? 0xFFFF00 : 0xFF0000; if (self.health <= 0) { self.die(); } }; self.die = function () { self.active = false; gold += self.value; goldText.setText("Gold: " + gold); score += self.value; scoreText.setText("Score: " + score); LK.setScore(score); LK.getSound('goblin_death').play(); // Create coin effect var coinText = new Text2("+" + self.value, { size: 40, fill: 0xFFD700 }); coinText.x = self.x; coinText.y = self.y; coinText.anchor.set(0.5, 0.5); game.addChild(coinText); tween(coinText, { y: coinText.y - 100, alpha: 0 }, { duration: 1000, onFinish: function onFinish() { coinText.destroy(); } }); self.destroy(); }; self.reachedBase = function () { self.active = false; lives--; livesText.setText("Lives: " + lives); if (lives <= 0) { endGame(); } self.destroy(); }; self.update = function () { if (!self.active) { return; } if (self.currentPathIndex >= self.pathPoints.length - 1) { self.reachedBase(); return; } var currentPoint = self.pathPoints[self.currentPathIndex]; var nextPoint = self.pathPoints[self.currentPathIndex + 1]; var dx = nextPoint.x - currentPoint.x; var dy = nextPoint.y - currentPoint.y; var dist = Math.sqrt(dx * dx + dy * dy); self.pathProgress = self.currentPathIndex / (self.pathPoints.length - 1); // Calculate how far along this segment we are var currentX = self.x - currentPoint.x; var currentY = self.y - currentPoint.y; var segmentProgress = Math.sqrt(currentX * currentX + currentY * currentY) / dist; // Add segment progress to overall progress self.pathProgress += segmentProgress / (self.pathPoints.length - 1); // Move towards next point var dx = nextPoint.x - self.x; var dy = nextPoint.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.speed) { // Reached the next point self.currentPathIndex++; } else { // Move along the path self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } }; return self; }); var Projectile = Container.expand(function (source, target, damage) { var self = Container.call(this); self.source = source; self.target = target; self.damage = damage; self.speed = 15; self.active = true; var projectileGraphics = self.attachAsset('projectile', { anchorX: 0.5, anchorY: 0.5 }); self.x = source.x; self.y = source.y; self.update = function () { if (!self.active || !self.target || !self.target.active) { self.destroy(); return; } // Calculate direction vector var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 20) { // Hit target self.target.takeDamage(self.damage); LK.getSound('hit').play(); self.active = false; self.destroy(); return; } // Normalize and scale by speed self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; }; return self; }); var ShieldGoblin = Container.expand(function (pathPoints, health, speed, value) { var self = Container.call(this, pathPoints, health, speed, value); self.maxHealth = health * 1.5; // Higher HP self.health = self.maxHealth; self.speed = speed * 0.8; // Slower speed var shieldGoblinGraphics = self.attachAsset('goblin', { anchorX: 0.5, anchorY: 0.5 }); return self; }); var Unit = Container.expand(function (tier) { var self = Container.call(this); self.tier = tier || 1; self.damage = Math.pow(2, self.tier - 1); self.range = 300 + self.tier * 50; self.fireRate = 1000 - self.tier * 50; // ms between shots self.lastShot = 0; self.targets = []; self.isDragging = false; self.merging = false; var unitGraphics = self.attachAsset('unit' + self.tier, { anchorX: 0.5, anchorY: 0.5 }); var tierText = new Text2(self.tier.toString(), { size: 40, fill: 0x000000 }); tierText.anchor.set(0.5, 0.5); self.addChild(tierText); // Add unit name text below the level of the tower var unitNames = ["Rock Thrower", "Slinger", "Spear Thrower", "Archer", "Fire Archer", "Crossbowman", "Musketeer", "Cannoneer", "Rifleman", "Grenadier", "Tank"]; var unitNameText = new Text2(unitNames[self.tier - 1], { size: 30, fill: 0x000000 }); unitNameText.anchor.set(0.5, 0); unitNameText.y = 50; // Position below the tier text self.addChild(unitNameText); self.findTarget = function () { if (self.targets.length === 0) { return null; } // Sort targets by progress self.targets.sort(function (a, b) { return b.pathProgress - a.pathProgress; }); // Return the most progressed goblin return self.targets[0]; }; self.shoot = function (target) { if (!target || !target.active) { return; } var now = Date.now(); if (now - self.lastShot < self.fireRate) { return; } self.lastShot = now; var projectile = new Projectile(self, target, self.damage); projectiles.push(projectile); game.addChild(projectile); LK.getSound('shoot').play(); }; self.update = function () { if (self.merging) { return; } // Clean up destroyed targets self.targets = self.targets.filter(function (target) { return target.active; }); var target = self.findTarget(); if (target) { self.shoot(target); } }; self.down = function (x, y, obj) { if (!placingUnit && !gameOver) { self.isDragging = true; selectedUnit = self; if (self.gridPosition) { // Remove from grid grid[self.gridPosition.y][self.gridPosition.x].unit = null; self.gridPosition = null; } } }; self.checkForMerge = function (gridCell) { if (!gridCell || !gridCell.unit || gridCell.unit === self) { return false; } if (gridCell.unit.tier === self.tier && self.tier < 10) { // Merge units mergeUnits(self, gridCell.unit); return true; } return false; }; return self; }); var Wolf = Container.expand(function (pathPoints, health, speed, value) { var self = Container.call(this, pathPoints, health, speed, value); self.speed = speed * 1.5; // Faster speed self.dodgeChance = 0.2; // 20% chance to dodge attacks self.takeDamage = function (damage) { if (Math.random() > self.dodgeChance) { Goblin.prototype.takeDamage.call(self, damage); } }; var wolfGraphics = self.attachAsset('goblin', { anchorX: 0.5, anchorY: 0.5 }); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x558855 }); /**** * Game Code ****/ // Game configuration var gridWidth = 7; var gridHeight = 6; var tileSize = 120; var grid = []; var pathPoints = []; var goblins = []; var projectiles = []; var units = []; var waves = []; var currentWave = 0; var waveInProgress = false; var selectedUnit = null; var placingUnit = null; var gold = 100; var lives = 3; var score = 0; var gameOver = false; var gridOffsetX = (2048 - gridWidth * tileSize) / 2; var gridOffsetY = 400; var highlight = null; var unitCost = 35; // UI elements var waveText; var goldText; var livesText; var scoreText; var nextWaveButton; var unitBuyButton; function initializeGrid() { for (var y = 0; y < gridHeight; y++) { grid[y] = []; for (var x = 0; x < gridWidth; x++) { grid[y][x] = { x: gridOffsetX + x * tileSize + tileSize / 2, y: gridOffsetY + y * tileSize + tileSize / 2, unit: null, isPath: false }; var tile = LK.getAsset('tile', { anchorX: 0.5, anchorY: 0.5 }); tile.x = grid[y][x].x; tile.y = grid[y][x].y; tile.alpha = 0.5; game.addChild(tile); } } } function createPath() { // Define a simple path through the grid var pathCoordinates = [{ x: 0, y: 2 }, { x: 1, y: 2 }, { x: 2, y: 2 }, { x: 3, y: 2 }, { x: 3, y: 3 }, { x: 3, y: 4 }, { x: 4, y: 4 }, { x: 5, y: 4 }, { x: 6, y: 4 }]; for (var i = 0; i < pathCoordinates.length; i++) { var coord = pathCoordinates[i]; var cell = grid[coord.y][coord.x]; cell.isPath = true; var pathTile = LK.getAsset('path', { anchorX: 0.5, anchorY: 0.5 }); pathTile.x = cell.x; pathTile.y = cell.y; pathTile.alpha = 0.7; game.addChild(pathTile); pathPoints.push({ x: cell.x, y: cell.y }); } // Add the base at the end of the path var lastCoord = pathCoordinates[pathCoordinates.length - 1]; var baseCell = grid[lastCoord.y][lastCoord.x]; var base = LK.getAsset('base', { anchorX: 0.5, anchorY: 0.5 }); base.x = baseCell.x + tileSize; base.y = baseCell.y; game.addChild(base); } function initializeWaves() { // Define waves of increasing difficulty waves = [{ count: 1, // Start with 1 goblin health: 8, speed: 1.5, value: 5, delay: 1000 }]; // Adjust health and speed for each subsequent wave for (var i = 1;; i++) { // Generate an infinite number of waves // Generate a large number of waves if (i % 5 === 0) { // Special wave every 5th wave waves.push({ count: waves[i - 1].count + (Math.random() < 0.5 ? 1 : 2), // Add 1 or 2 goblins randomly health: Math.round(waves[i - 1].health * 1.02), speed: parseFloat((waves[i - 1].speed * 1.02).toFixed(2)), value: 10 + i, delay: 433.33332 // Set delay to 0.43333332 seconds for special wave }); } else { waves.push({ count: waves[i - 1].count + (Math.random() < 0.5 ? 1 : 2), health: Math.round(waves[i - 1].health * 1.02), speed: parseFloat((waves[i - 1].speed * 1.02).toFixed(2)), value: 10 + i, delay: Math.max(200, 1000 - i * 50), type: i >= 7 ? 'shield' : i >= 3 ? 'wolf' : 'goblin' // Add Shield Goblins from wave 7, Wolves from wave 3 }); } } } function startWave() { if (waveInProgress || gameOver) { return; } waveInProgress = true; currentWave++; // Removed wave limit check to make waves infinite waveText.setText("Wave: " + currentWave + " (Infinite)"); nextWaveButton.visible = false; var wave = waves[currentWave - 1]; var goblinsReleased = 0; var spawnInterval = LK.setInterval(function () { spawnGoblin(wave); goblinsReleased++; if (goblinsReleased >= wave.count) { LK.clearInterval(spawnInterval); // Check every second if wave is complete var checkInterval = LK.setInterval(function () { if (goblins.length === 0) { waveInProgress = false; nextWaveButton.visible = true; LK.clearInterval(checkInterval); } }, 1000); } }, wave.delay); } function spawnGoblin(wave) { var goblin; if (wave.type === 'shield') { goblin = new ShieldGoblin(pathPoints, wave.health, wave.speed, wave.value); } else if (wave.type === 'wolf') { goblin = new Wolf(pathPoints, wave.health, wave.speed, wave.value); } else { goblin = new Goblin(pathPoints, wave.health, wave.speed, wave.value); } goblins.push(goblin); game.addChild(goblin); // Add this goblin as a target for all units in range units.forEach(function (unit) { var dx = unit.x - goblin.x; var dy = unit.y - goblin.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= unit.range) { unit.targets.push(goblin); } }); } function createUnit(tier) { var unit = new Unit(tier); units.push(unit); return unit; } function placeUnit(unit, gridX, gridY) { if (gridX < 0 || gridX >= gridWidth || gridY < 0 || gridY >= gridHeight) { return false; } var gridCell = grid[gridY][gridX]; if (gridCell.isPath || gridCell.unit) { return false; } // Place the unit unit.x = gridCell.x; unit.y = gridCell.y; unit.gridPosition = { x: gridX, y: gridY }; gridCell.unit = unit; // Find nearby targets goblins.forEach(function (goblin) { if (goblin.active) { var dx = unit.x - goblin.x; var dy = unit.y - goblin.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= unit.range) { unit.targets.push(goblin); } } }); LK.getSound('place').play(); return true; } function mergeUnits(unit1, unit2) { // Create a new unit of the next tier var newUnit = createUnit(unit1.tier + 1); game.addChild(newUnit); // Place at the position of the second unit newUnit.gridPosition = unit2.gridPosition; grid[unit2.gridPosition.y][unit2.gridPosition.x].unit = newUnit; newUnit.x = unit2.x; newUnit.y = unit2.y; // Remove the merged units unit1.merging = true; unit2.merging = true; // Effect tween(unit1, { x: unit2.x, y: unit2.y, alpha: 0 }, { duration: 300, onFinish: function onFinish() { unit1.destroy(); removeFromArray(units, unit1); } }); tween(unit2, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { unit2.destroy(); removeFromArray(units, unit2); } }); // Scale up effect for new unit newUnit.scale.x = 0.5; newUnit.scale.y = 0.5; tween(newUnit.scale, { x: 1, y: 1 }, { duration: 300, easing: tween.elasticOut }); LK.getSound('merge').play(); return newUnit; } function buyUnit() { if (gold < unitCost || placingUnit || gameOver) { return; } gold -= unitCost; goldText.setText("Gold: " + gold); placingUnit = createUnit(1); game.addChild(placingUnit); // Create highlight if it doesn't exist if (!highlight) { highlight = LK.getAsset('highlight', { anchorX: 0.5, anchorY: 0.5 }); highlight.alpha = 0.3; game.addChild(highlight); } highlight.visible = true; } function createUI() { // Wave text waveText = new Text2("Wave: 0", { size: 50, fill: 0xFFFFFF }); waveText.anchor.set(0.5, 0); LK.gui.top.addChild(waveText); // Gold text goldText = new Text2("Gold: " + gold, { size: 50, fill: 0xFFD700 }); goldText.anchor.set(0, 0); LK.gui.topLeft.addChild(goldText); goldText.x = 120; // Move away from the top left corner // Lives text livesText = new Text2("Lives: " + lives, { size: 50, fill: 0xFF0000 }); livesText.anchor.set(1, 0); LK.gui.topRight.addChild(livesText); // Score text scoreText = new Text2("Score: " + score, { size: 50, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); scoreText.y = 80; // Next wave button nextWaveButton = new Container(); var nextWaveButtonBg = LK.getAsset('tile', { width: 300, height: 100, color: 0x336699, anchorX: 0.5, anchorY: 0.5 }); nextWaveButton.addChild(nextWaveButtonBg); var nextWaveButtonText = new Text2("Next Wave", { size: 50, fill: 0xFFFFFF }); nextWaveButtonText.anchor.set(0.5, 0.5); nextWaveButton.addChild(nextWaveButtonText); nextWaveButton.down = function () { startWave(); }; LK.gui.bottom.addChild(nextWaveButton); nextWaveButton.y = -150; // Buy unit button unitBuyButton = new Container(); var unitBuyButtonBg = LK.getAsset('tile', { width: 300, height: 100, color: 0x669933, anchorX: 0.5, anchorY: 0.5 }); unitBuyButton.addChild(unitBuyButtonBg); var unitBuyButtonText = new Text2("Buy Unit: " + unitCost + "g", { size: 40, fill: 0xFFFFFF }); unitBuyButtonText.anchor.set(0.5, 0.5); unitBuyButton.addChild(unitBuyButtonText); unitBuyButton.down = function () { buyUnit(); }; LK.gui.bottomLeft.addChild(unitBuyButton); unitBuyButton.y = -150; unitBuyButton.x = 170; } function endGame() { if (gameOver) { return; } gameOver = true; if (score > storage.highScore) { storage.highScore = score; } LK.getSound('game_over').play(); LK.showGameOver(); } function removeFromArray(array, item) { var index = array.indexOf(item); if (index !== -1) { array.splice(index, 1); } } function initialize() { initializeGrid(); createPath(); initializeWaves(); createUI(); // Play background music LK.playMusic('bg_music'); // Create highlight for placing units (initially hidden) highlight = LK.getAsset('highlight', { anchorX: 0.5, anchorY: 0.5 }); highlight.alpha = 0.3; highlight.visible = false; game.addChild(highlight); // Start with wave 0 waveText.setText("Wave: 0/" + waves.length); } initialize(); game.move = function (x, y) { if (gameOver) { return; } if (selectedUnit) { selectedUnit.x = x; selectedUnit.y = y; } else if (placingUnit) { // Find the closest grid cell var gridX = Math.floor((x - gridOffsetX) / tileSize); var gridY = Math.floor((y - gridOffsetY) / tileSize); if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) { var cell = grid[gridY][gridX]; placingUnit.x = x; placingUnit.y = y; highlight.x = cell.x; highlight.y = cell.y; highlight.visible = true; // Change highlight color based on valid placement if (cell.isPath || cell.unit) { highlight.tint = 0xFF0000; } else { highlight.tint = 0x00FF00; } } else { highlight.visible = false; } } }; game.down = function (x, y) { if (gameOver) { return; } // If we're placing a new unit if (placingUnit) { var gridX = Math.floor((x - gridOffsetX) / tileSize); var gridY = Math.floor((y - gridOffsetY) / tileSize); if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) { if (placeUnit(placingUnit, gridX, gridY)) { placingUnit = null; highlight.visible = false; } } } }; game.up = function (x, y) { if (gameOver) { return; } if (selectedUnit && selectedUnit.isDragging) { var gridX = Math.floor((x - gridOffsetX) / tileSize); var gridY = Math.floor((y - gridOffsetY) / tileSize); if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) { var gridCell = grid[gridY][gridX]; if (selectedUnit.checkForMerge(gridCell)) { // Merged successfully } else if (!gridCell.isPath && !gridCell.unit) { // Place on empty cell placeUnit(selectedUnit, gridX, gridY); } else { // Invalid placement, return to original position if any if (selectedUnit.gridPosition) { selectedUnit.x = grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].x; selectedUnit.y = grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].y; grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].unit = selectedUnit; } else { // If it didn't have a grid position, destroy it selectedUnit.destroy(); removeFromArray(units, selectedUnit); } } } else { // Dragged off grid, return to original position if any if (selectedUnit.gridPosition) { selectedUnit.x = grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].x; selectedUnit.y = grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].y; grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].unit = selectedUnit; } else { // If it didn't have a grid position, destroy it selectedUnit.destroy(); removeFromArray(units, selectedUnit); } } selectedUnit.isDragging = false; selectedUnit = null; } }; game.update = function () { // Update all active goblins for (var i = goblins.length - 1; i >= 0; i--) { if (goblins[i].active) { goblins[i].update(); } else { removeFromArray(goblins, goblins[i]); } } // Update all active projectiles for (var i = projectiles.length - 1; i >= 0; i--) { if (projectiles[i].active) { projectiles[i].update(); } else { removeFromArray(projectiles, projectiles[i]); } } // Update all units for (var i = 0; i < units.length; i++) { if (!units[i].merging) { units[i].update(); } } // Check if units in range of goblins units.forEach(function (unit) { if (unit.merging) { return; } // Reset targets and find new ones unit.targets = []; goblins.forEach(function (goblin) { if (goblin.active) { var dx = unit.x - goblin.x; var dy = unit.y - goblin.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= unit.range) { unit.targets.push(goblin); } } }); }); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
var Goblin = Container.expand(function (pathPoints, health, speed, value) {
var self = Container.call(this);
self.pathPoints = pathPoints;
self.maxHealth = health;
self.health = health;
self.speed = speed;
self.value = value;
self.active = true;
self.currentPathIndex = 0;
self.pathProgress = 0;
var goblinGraphics = self.attachAsset('goblin', {
anchorX: 0.5,
anchorY: 0.5
});
var healthBar = LK.getAsset('tile', {
width: 60,
height: 10,
color: 0x00FF00,
anchorX: 0.5,
anchorY: 0.5
});
healthBar.y = -40;
self.addChild(healthBar);
self.x = pathPoints[0].x;
self.y = pathPoints[0].y;
self.takeDamage = function (damage) {
self.health -= damage;
// Update health bar
var healthPercent = Math.max(0, self.health / self.maxHealth);
healthBar.width = 60 * healthPercent;
healthBar.tint = healthPercent > 0.5 ? 0x00FF00 : healthPercent > 0.25 ? 0xFFFF00 : 0xFF0000;
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
self.active = false;
gold += self.value;
goldText.setText("Gold: " + gold);
score += self.value;
scoreText.setText("Score: " + score);
LK.setScore(score);
LK.getSound('goblin_death').play();
// Create coin effect
var coinText = new Text2("+" + self.value, {
size: 40,
fill: 0xFFD700
});
coinText.x = self.x;
coinText.y = self.y;
coinText.anchor.set(0.5, 0.5);
game.addChild(coinText);
tween(coinText, {
y: coinText.y - 100,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
coinText.destroy();
}
});
self.destroy();
};
self.reachedBase = function () {
self.active = false;
lives--;
livesText.setText("Lives: " + lives);
if (lives <= 0) {
endGame();
}
self.destroy();
};
self.update = function () {
if (!self.active) {
return;
}
if (self.currentPathIndex >= self.pathPoints.length - 1) {
self.reachedBase();
return;
}
var currentPoint = self.pathPoints[self.currentPathIndex];
var nextPoint = self.pathPoints[self.currentPathIndex + 1];
var dx = nextPoint.x - currentPoint.x;
var dy = nextPoint.y - currentPoint.y;
var dist = Math.sqrt(dx * dx + dy * dy);
self.pathProgress = self.currentPathIndex / (self.pathPoints.length - 1);
// Calculate how far along this segment we are
var currentX = self.x - currentPoint.x;
var currentY = self.y - currentPoint.y;
var segmentProgress = Math.sqrt(currentX * currentX + currentY * currentY) / dist;
// Add segment progress to overall progress
self.pathProgress += segmentProgress / (self.pathPoints.length - 1);
// Move towards next point
var dx = nextPoint.x - self.x;
var dy = nextPoint.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.speed) {
// Reached the next point
self.currentPathIndex++;
} else {
// Move along the path
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
};
return self;
});
var Projectile = Container.expand(function (source, target, damage) {
var self = Container.call(this);
self.source = source;
self.target = target;
self.damage = damage;
self.speed = 15;
self.active = true;
var projectileGraphics = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = source.x;
self.y = source.y;
self.update = function () {
if (!self.active || !self.target || !self.target.active) {
self.destroy();
return;
}
// Calculate direction vector
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 20) {
// Hit target
self.target.takeDamage(self.damage);
LK.getSound('hit').play();
self.active = false;
self.destroy();
return;
}
// Normalize and scale by speed
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
};
return self;
});
var ShieldGoblin = Container.expand(function (pathPoints, health, speed, value) {
var self = Container.call(this, pathPoints, health, speed, value);
self.maxHealth = health * 1.5; // Higher HP
self.health = self.maxHealth;
self.speed = speed * 0.8; // Slower speed
var shieldGoblinGraphics = self.attachAsset('goblin', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
var Unit = Container.expand(function (tier) {
var self = Container.call(this);
self.tier = tier || 1;
self.damage = Math.pow(2, self.tier - 1);
self.range = 300 + self.tier * 50;
self.fireRate = 1000 - self.tier * 50; // ms between shots
self.lastShot = 0;
self.targets = [];
self.isDragging = false;
self.merging = false;
var unitGraphics = self.attachAsset('unit' + self.tier, {
anchorX: 0.5,
anchorY: 0.5
});
var tierText = new Text2(self.tier.toString(), {
size: 40,
fill: 0x000000
});
tierText.anchor.set(0.5, 0.5);
self.addChild(tierText);
// Add unit name text below the level of the tower
var unitNames = ["Rock Thrower", "Slinger", "Spear Thrower", "Archer", "Fire Archer", "Crossbowman", "Musketeer", "Cannoneer", "Rifleman", "Grenadier", "Tank"];
var unitNameText = new Text2(unitNames[self.tier - 1], {
size: 30,
fill: 0x000000
});
unitNameText.anchor.set(0.5, 0);
unitNameText.y = 50; // Position below the tier text
self.addChild(unitNameText);
self.findTarget = function () {
if (self.targets.length === 0) {
return null;
}
// Sort targets by progress
self.targets.sort(function (a, b) {
return b.pathProgress - a.pathProgress;
});
// Return the most progressed goblin
return self.targets[0];
};
self.shoot = function (target) {
if (!target || !target.active) {
return;
}
var now = Date.now();
if (now - self.lastShot < self.fireRate) {
return;
}
self.lastShot = now;
var projectile = new Projectile(self, target, self.damage);
projectiles.push(projectile);
game.addChild(projectile);
LK.getSound('shoot').play();
};
self.update = function () {
if (self.merging) {
return;
}
// Clean up destroyed targets
self.targets = self.targets.filter(function (target) {
return target.active;
});
var target = self.findTarget();
if (target) {
self.shoot(target);
}
};
self.down = function (x, y, obj) {
if (!placingUnit && !gameOver) {
self.isDragging = true;
selectedUnit = self;
if (self.gridPosition) {
// Remove from grid
grid[self.gridPosition.y][self.gridPosition.x].unit = null;
self.gridPosition = null;
}
}
};
self.checkForMerge = function (gridCell) {
if (!gridCell || !gridCell.unit || gridCell.unit === self) {
return false;
}
if (gridCell.unit.tier === self.tier && self.tier < 10) {
// Merge units
mergeUnits(self, gridCell.unit);
return true;
}
return false;
};
return self;
});
var Wolf = Container.expand(function (pathPoints, health, speed, value) {
var self = Container.call(this, pathPoints, health, speed, value);
self.speed = speed * 1.5; // Faster speed
self.dodgeChance = 0.2; // 20% chance to dodge attacks
self.takeDamage = function (damage) {
if (Math.random() > self.dodgeChance) {
Goblin.prototype.takeDamage.call(self, damage);
}
};
var wolfGraphics = self.attachAsset('goblin', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x558855
});
/****
* Game Code
****/
// Game configuration
var gridWidth = 7;
var gridHeight = 6;
var tileSize = 120;
var grid = [];
var pathPoints = [];
var goblins = [];
var projectiles = [];
var units = [];
var waves = [];
var currentWave = 0;
var waveInProgress = false;
var selectedUnit = null;
var placingUnit = null;
var gold = 100;
var lives = 3;
var score = 0;
var gameOver = false;
var gridOffsetX = (2048 - gridWidth * tileSize) / 2;
var gridOffsetY = 400;
var highlight = null;
var unitCost = 35;
// UI elements
var waveText;
var goldText;
var livesText;
var scoreText;
var nextWaveButton;
var unitBuyButton;
function initializeGrid() {
for (var y = 0; y < gridHeight; y++) {
grid[y] = [];
for (var x = 0; x < gridWidth; x++) {
grid[y][x] = {
x: gridOffsetX + x * tileSize + tileSize / 2,
y: gridOffsetY + y * tileSize + tileSize / 2,
unit: null,
isPath: false
};
var tile = LK.getAsset('tile', {
anchorX: 0.5,
anchorY: 0.5
});
tile.x = grid[y][x].x;
tile.y = grid[y][x].y;
tile.alpha = 0.5;
game.addChild(tile);
}
}
}
function createPath() {
// Define a simple path through the grid
var pathCoordinates = [{
x: 0,
y: 2
}, {
x: 1,
y: 2
}, {
x: 2,
y: 2
}, {
x: 3,
y: 2
}, {
x: 3,
y: 3
}, {
x: 3,
y: 4
}, {
x: 4,
y: 4
}, {
x: 5,
y: 4
}, {
x: 6,
y: 4
}];
for (var i = 0; i < pathCoordinates.length; i++) {
var coord = pathCoordinates[i];
var cell = grid[coord.y][coord.x];
cell.isPath = true;
var pathTile = LK.getAsset('path', {
anchorX: 0.5,
anchorY: 0.5
});
pathTile.x = cell.x;
pathTile.y = cell.y;
pathTile.alpha = 0.7;
game.addChild(pathTile);
pathPoints.push({
x: cell.x,
y: cell.y
});
}
// Add the base at the end of the path
var lastCoord = pathCoordinates[pathCoordinates.length - 1];
var baseCell = grid[lastCoord.y][lastCoord.x];
var base = LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5
});
base.x = baseCell.x + tileSize;
base.y = baseCell.y;
game.addChild(base);
}
function initializeWaves() {
// Define waves of increasing difficulty
waves = [{
count: 1,
// Start with 1 goblin
health: 8,
speed: 1.5,
value: 5,
delay: 1000
}];
// Adjust health and speed for each subsequent wave
for (var i = 1;; i++) {
// Generate an infinite number of waves
// Generate a large number of waves
if (i % 5 === 0) {
// Special wave every 5th wave
waves.push({
count: waves[i - 1].count + (Math.random() < 0.5 ? 1 : 2),
// Add 1 or 2 goblins randomly
health: Math.round(waves[i - 1].health * 1.02),
speed: parseFloat((waves[i - 1].speed * 1.02).toFixed(2)),
value: 10 + i,
delay: 433.33332 // Set delay to 0.43333332 seconds for special wave
});
} else {
waves.push({
count: waves[i - 1].count + (Math.random() < 0.5 ? 1 : 2),
health: Math.round(waves[i - 1].health * 1.02),
speed: parseFloat((waves[i - 1].speed * 1.02).toFixed(2)),
value: 10 + i,
delay: Math.max(200, 1000 - i * 50),
type: i >= 7 ? 'shield' : i >= 3 ? 'wolf' : 'goblin' // Add Shield Goblins from wave 7, Wolves from wave 3
});
}
}
}
function startWave() {
if (waveInProgress || gameOver) {
return;
}
waveInProgress = true;
currentWave++;
// Removed wave limit check to make waves infinite
waveText.setText("Wave: " + currentWave + " (Infinite)");
nextWaveButton.visible = false;
var wave = waves[currentWave - 1];
var goblinsReleased = 0;
var spawnInterval = LK.setInterval(function () {
spawnGoblin(wave);
goblinsReleased++;
if (goblinsReleased >= wave.count) {
LK.clearInterval(spawnInterval);
// Check every second if wave is complete
var checkInterval = LK.setInterval(function () {
if (goblins.length === 0) {
waveInProgress = false;
nextWaveButton.visible = true;
LK.clearInterval(checkInterval);
}
}, 1000);
}
}, wave.delay);
}
function spawnGoblin(wave) {
var goblin;
if (wave.type === 'shield') {
goblin = new ShieldGoblin(pathPoints, wave.health, wave.speed, wave.value);
} else if (wave.type === 'wolf') {
goblin = new Wolf(pathPoints, wave.health, wave.speed, wave.value);
} else {
goblin = new Goblin(pathPoints, wave.health, wave.speed, wave.value);
}
goblins.push(goblin);
game.addChild(goblin);
// Add this goblin as a target for all units in range
units.forEach(function (unit) {
var dx = unit.x - goblin.x;
var dy = unit.y - goblin.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= unit.range) {
unit.targets.push(goblin);
}
});
}
function createUnit(tier) {
var unit = new Unit(tier);
units.push(unit);
return unit;
}
function placeUnit(unit, gridX, gridY) {
if (gridX < 0 || gridX >= gridWidth || gridY < 0 || gridY >= gridHeight) {
return false;
}
var gridCell = grid[gridY][gridX];
if (gridCell.isPath || gridCell.unit) {
return false;
}
// Place the unit
unit.x = gridCell.x;
unit.y = gridCell.y;
unit.gridPosition = {
x: gridX,
y: gridY
};
gridCell.unit = unit;
// Find nearby targets
goblins.forEach(function (goblin) {
if (goblin.active) {
var dx = unit.x - goblin.x;
var dy = unit.y - goblin.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= unit.range) {
unit.targets.push(goblin);
}
}
});
LK.getSound('place').play();
return true;
}
function mergeUnits(unit1, unit2) {
// Create a new unit of the next tier
var newUnit = createUnit(unit1.tier + 1);
game.addChild(newUnit);
// Place at the position of the second unit
newUnit.gridPosition = unit2.gridPosition;
grid[unit2.gridPosition.y][unit2.gridPosition.x].unit = newUnit;
newUnit.x = unit2.x;
newUnit.y = unit2.y;
// Remove the merged units
unit1.merging = true;
unit2.merging = true;
// Effect
tween(unit1, {
x: unit2.x,
y: unit2.y,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
unit1.destroy();
removeFromArray(units, unit1);
}
});
tween(unit2, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
unit2.destroy();
removeFromArray(units, unit2);
}
});
// Scale up effect for new unit
newUnit.scale.x = 0.5;
newUnit.scale.y = 0.5;
tween(newUnit.scale, {
x: 1,
y: 1
}, {
duration: 300,
easing: tween.elasticOut
});
LK.getSound('merge').play();
return newUnit;
}
function buyUnit() {
if (gold < unitCost || placingUnit || gameOver) {
return;
}
gold -= unitCost;
goldText.setText("Gold: " + gold);
placingUnit = createUnit(1);
game.addChild(placingUnit);
// Create highlight if it doesn't exist
if (!highlight) {
highlight = LK.getAsset('highlight', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.alpha = 0.3;
game.addChild(highlight);
}
highlight.visible = true;
}
function createUI() {
// Wave text
waveText = new Text2("Wave: 0", {
size: 50,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
LK.gui.top.addChild(waveText);
// Gold text
goldText = new Text2("Gold: " + gold, {
size: 50,
fill: 0xFFD700
});
goldText.anchor.set(0, 0);
LK.gui.topLeft.addChild(goldText);
goldText.x = 120; // Move away from the top left corner
// Lives text
livesText = new Text2("Lives: " + lives, {
size: 50,
fill: 0xFF0000
});
livesText.anchor.set(1, 0);
LK.gui.topRight.addChild(livesText);
// Score text
scoreText = new Text2("Score: " + score, {
size: 50,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
scoreText.y = 80;
// Next wave button
nextWaveButton = new Container();
var nextWaveButtonBg = LK.getAsset('tile', {
width: 300,
height: 100,
color: 0x336699,
anchorX: 0.5,
anchorY: 0.5
});
nextWaveButton.addChild(nextWaveButtonBg);
var nextWaveButtonText = new Text2("Next Wave", {
size: 50,
fill: 0xFFFFFF
});
nextWaveButtonText.anchor.set(0.5, 0.5);
nextWaveButton.addChild(nextWaveButtonText);
nextWaveButton.down = function () {
startWave();
};
LK.gui.bottom.addChild(nextWaveButton);
nextWaveButton.y = -150;
// Buy unit button
unitBuyButton = new Container();
var unitBuyButtonBg = LK.getAsset('tile', {
width: 300,
height: 100,
color: 0x669933,
anchorX: 0.5,
anchorY: 0.5
});
unitBuyButton.addChild(unitBuyButtonBg);
var unitBuyButtonText = new Text2("Buy Unit: " + unitCost + "g", {
size: 40,
fill: 0xFFFFFF
});
unitBuyButtonText.anchor.set(0.5, 0.5);
unitBuyButton.addChild(unitBuyButtonText);
unitBuyButton.down = function () {
buyUnit();
};
LK.gui.bottomLeft.addChild(unitBuyButton);
unitBuyButton.y = -150;
unitBuyButton.x = 170;
}
function endGame() {
if (gameOver) {
return;
}
gameOver = true;
if (score > storage.highScore) {
storage.highScore = score;
}
LK.getSound('game_over').play();
LK.showGameOver();
}
function removeFromArray(array, item) {
var index = array.indexOf(item);
if (index !== -1) {
array.splice(index, 1);
}
}
function initialize() {
initializeGrid();
createPath();
initializeWaves();
createUI();
// Play background music
LK.playMusic('bg_music');
// Create highlight for placing units (initially hidden)
highlight = LK.getAsset('highlight', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.alpha = 0.3;
highlight.visible = false;
game.addChild(highlight);
// Start with wave 0
waveText.setText("Wave: 0/" + waves.length);
}
initialize();
game.move = function (x, y) {
if (gameOver) {
return;
}
if (selectedUnit) {
selectedUnit.x = x;
selectedUnit.y = y;
} else if (placingUnit) {
// Find the closest grid cell
var gridX = Math.floor((x - gridOffsetX) / tileSize);
var gridY = Math.floor((y - gridOffsetY) / tileSize);
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
var cell = grid[gridY][gridX];
placingUnit.x = x;
placingUnit.y = y;
highlight.x = cell.x;
highlight.y = cell.y;
highlight.visible = true;
// Change highlight color based on valid placement
if (cell.isPath || cell.unit) {
highlight.tint = 0xFF0000;
} else {
highlight.tint = 0x00FF00;
}
} else {
highlight.visible = false;
}
}
};
game.down = function (x, y) {
if (gameOver) {
return;
}
// If we're placing a new unit
if (placingUnit) {
var gridX = Math.floor((x - gridOffsetX) / tileSize);
var gridY = Math.floor((y - gridOffsetY) / tileSize);
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
if (placeUnit(placingUnit, gridX, gridY)) {
placingUnit = null;
highlight.visible = false;
}
}
}
};
game.up = function (x, y) {
if (gameOver) {
return;
}
if (selectedUnit && selectedUnit.isDragging) {
var gridX = Math.floor((x - gridOffsetX) / tileSize);
var gridY = Math.floor((y - gridOffsetY) / tileSize);
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
var gridCell = grid[gridY][gridX];
if (selectedUnit.checkForMerge(gridCell)) {
// Merged successfully
} else if (!gridCell.isPath && !gridCell.unit) {
// Place on empty cell
placeUnit(selectedUnit, gridX, gridY);
} else {
// Invalid placement, return to original position if any
if (selectedUnit.gridPosition) {
selectedUnit.x = grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].x;
selectedUnit.y = grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].y;
grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].unit = selectedUnit;
} else {
// If it didn't have a grid position, destroy it
selectedUnit.destroy();
removeFromArray(units, selectedUnit);
}
}
} else {
// Dragged off grid, return to original position if any
if (selectedUnit.gridPosition) {
selectedUnit.x = grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].x;
selectedUnit.y = grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].y;
grid[selectedUnit.gridPosition.y][selectedUnit.gridPosition.x].unit = selectedUnit;
} else {
// If it didn't have a grid position, destroy it
selectedUnit.destroy();
removeFromArray(units, selectedUnit);
}
}
selectedUnit.isDragging = false;
selectedUnit = null;
}
};
game.update = function () {
// Update all active goblins
for (var i = goblins.length - 1; i >= 0; i--) {
if (goblins[i].active) {
goblins[i].update();
} else {
removeFromArray(goblins, goblins[i]);
}
}
// Update all active projectiles
for (var i = projectiles.length - 1; i >= 0; i--) {
if (projectiles[i].active) {
projectiles[i].update();
} else {
removeFromArray(projectiles, projectiles[i]);
}
}
// Update all units
for (var i = 0; i < units.length; i++) {
if (!units[i].merging) {
units[i].update();
}
}
// Check if units in range of goblins
units.forEach(function (unit) {
if (unit.merging) {
return;
}
// Reset targets and find new ones
unit.targets = [];
goblins.forEach(function (goblin) {
if (goblin.active) {
var dx = unit.x - goblin.x;
var dy = unit.y - goblin.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= unit.range) {
unit.targets.push(goblin);
}
}
});
});
};