/**** * 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);
}
}
});
});
};