/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 25;
self.targetX = 0;
self.targetY = 0;
self.update = function () {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distanceSquared = dx * dx + dy * dy;
var speedSquared = self.speed * self.speed;
if (distanceSquared < speedSquared) {
self.x = self.targetX;
self.y = self.targetY;
self.reachedTarget = true;
} else {
var distance = Math.sqrt(distanceSquared);
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
return self;
});
var Cat = Container.expand(function () {
var self = Container.call(this);
self.catType = 'basic';
self.health = 50;
self.maxHealth = 50;
self.speed = 1;
self.pathIndex = 0;
self.cheeseValue = 10;
var graphics = self.attachAsset('catBasic', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
self.healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
// Health bar foreground
self.healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
self.update = function () {
if (self.pathIndex < gamePath.length) {
var targetTile = gamePath[self.pathIndex];
var dx = targetTile.x - self.x;
var dy = targetTile.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < 25) {
// 5 * 5 = 25
self.pathIndex++;
if (self.pathIndex >= gamePath.length) {
self.reachedCheese = true;
}
} else {
var distance = Math.sqrt(distanceSquared);
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.isDead = true;
LK.effects.flashObject(self, 0xFF0000, 200);
} else {
// Update health bar
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
}
};
return self;
});
var ZombieCat = Cat.expand(function () {
var self = Cat.call(this);
self.catType = 'zombie';
self.health = 40;
self.maxHealth = 40;
self.speed = 2.5;
self.cheeseValue = 20;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('catZombie', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x8B0000; // Dark red tint for zombie
graphics.alpha = 0.8; // Slightly transparent
return self;
});
var FastCat = Cat.expand(function () {
var self = Cat.call(this);
self.catType = 'fast';
self.health = 30;
self.maxHealth = 30;
self.speed = 2;
self.cheeseValue = 15;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('catFast', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
var FastCatBoss = FastCat.expand(function () {
var self = FastCat.call(this);
self.catType = 'fastboss';
self.health = 120;
self.maxHealth = 120;
self.speed = 1.5;
self.cheeseValue = 75;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('catFast', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.8,
scaleY: 1.8
});
graphics.tint = 0x4B0082; // Indigo tint for fast boss
return self;
});
var CatSummoner = Cat.expand(function () {
var self = Cat.call(this);
self.catType = 'summoner';
self.health = 150;
self.maxHealth = 150;
self.speed = 0.8;
self.cheeseValue = 60;
self.summonCooldown = 900; // 15 seconds at 60fps
self.lastSummon = 0;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('catSummoner', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
graphics.tint = 0x4B0082; // Dark purple tint for summoner
// Override update to handle summoning
var originalUpdate = self.update;
self.update = function () {
originalUpdate.call(self);
// Summon zombie cats every 15 seconds
if (LK.ticks - self.lastSummon > self.summonCooldown) {
for (var i = 0; i < 5; i++) {
var zombieCat = new ZombieCat();
// Spawn near the summoner with slight randomness
zombieCat.x = self.x + (Math.random() - 0.5) * 100;
zombieCat.y = self.y + (Math.random() - 0.5) * 100;
// Ensure they start on the path
zombieCat.pathIndex = self.pathIndex;
cats.push(zombieCat);
game.addChild(zombieCat);
// Visual summoning effect
LK.effects.flashObject(zombieCat, 0x8B0000, 500);
}
// Visual summoning effect on summoner
LK.effects.flashObject(self, 0x9932CC, 700);
self.lastSummon = LK.ticks;
}
};
return self;
});
var CatBoss = Cat.expand(function () {
var self = Cat.call(this);
self.catType = 'boss';
self.health = 200;
self.maxHealth = 200;
self.speed = 0.5;
self.cheeseValue = 50;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('catBoss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
graphics.tint = 0x8B0000; // Dark red tint for boss
return self;
});
var CollectedUnitSlot = Container.expand(function (collectedUnit, index) {
var self = Container.call(this);
self.collectedUnit = collectedUnit;
self.isSelected = false;
var slotGraphics = self.attachAsset('inventorySlot', {
anchorX: 0.5,
anchorY: 0.5
});
slotGraphics.tint = collectedUnit.color;
slotGraphics.scaleX = 0.8;
slotGraphics.scaleY = 0.8;
// Add unit preview
var ratAsset = collectedUnit.type === 'archer' || collectedUnit.type === 'sniper' ? 'ratArcher' : collectedUnit.type === 'bomber' ? 'ratBomber' : collectedUnit.type === 'summoner' ? 'ratSummoner' : 'ratWarrior';
var ratPreview = self.attachAsset(ratAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: collectedUnit.type === 'tower' ? 1.2 : collectedUnit.type === 'mega' ? 1.0 : collectedUnit.type === 'giant' ? 0.8 : collectedUnit.type === 'electric' ? 0.6 : collectedUnit.type === 'shadow' ? 0.55 : collectedUnit.type === 'summoner' ? 0.65 : 0.5,
scaleY: collectedUnit.type === 'tower' ? 1.2 : collectedUnit.type === 'mega' ? 1.0 : collectedUnit.type === 'giant' ? 0.8 : collectedUnit.type === 'electric' ? 0.6 : collectedUnit.type === 'shadow' ? 0.55 : collectedUnit.type === 'summoner' ? 0.65 : 0.5,
y: -10
});
// Apply type-specific tints
if (collectedUnit.type === 'magic') {
ratPreview.tint = 0x9932CC; // Purple
} else if (collectedUnit.type === 'sniper') {
ratPreview.tint = 0x8B4513; // Brown
} else if (collectedUnit.type === 'bomber') {
ratPreview.tint = 0xFF6347; // Red-orange
} else if (collectedUnit.type === 'healer') {
ratPreview.tint = 0x00FF7F; // Spring green
} else if (collectedUnit.type === 'giant') {
ratPreview.tint = 0x8B0000; // Dark red
} else if (collectedUnit.type === 'mega') {
ratPreview.tint = 0x4B0082; // Indigo
} else if (collectedUnit.type === 'tower') {
ratPreview.tint = 0x2F4F4F; // Dark slate gray
} else if (collectedUnit.type === 'electric') {
ratPreview.tint = 0x00FFFF; // Cyan
} else if (collectedUnit.type === 'shadow') {
ratPreview.tint = 0x4B0082; // Dark purple
ratPreview.alpha = 0.7; // Semi-transparent
} else if (collectedUnit.type === 'summoner') {
ratPreview.tint = 0x4B0082; // Dark purple
}
// Add rarity text
var rarityText = new Text2(collectedUnit.rarity.charAt(0).toUpperCase(), {
size: 20,
fill: '#FFFFFF'
});
rarityText.anchor.set(0.5, 0.5);
rarityText.y = 25;
self.addChild(rarityText);
self.setSelected = function (selected) {
self.isSelected = selected;
slotGraphics.alpha = selected ? 0.8 : 1.0;
if (selected) {
slotGraphics.scaleX = 0.9;
slotGraphics.scaleY = 0.9;
} else {
slotGraphics.scaleX = 0.8;
slotGraphics.scaleY = 0.8;
}
};
self.down = function (x, y, obj) {
if (gameStarted && !upgradeMode) {
selectedCollectedUnit = self.collectedUnit;
selectedRatType = null; // Deselect basic rat types
// Update all inventory slots selection
for (var i = 0; i < inventorySlots.length; i++) {
inventorySlots[i].setSelected(false);
}
// Update all collected unit slots selection
updateCollectedUnitsDisplay();
}
};
return self;
});
var InventorySlot = Container.expand(function (ratType, cost) {
var self = Container.call(this);
self.ratType = ratType;
self.cost = cost;
self.isSelected = false;
var slotGraphics = self.attachAsset('inventorySlot', {
anchorX: 0.5,
anchorY: 0.5
});
// Add rat preview
var ratAsset = ratType === 'warrior' ? 'ratWarrior' : 'ratArcher';
var ratPreview = self.attachAsset(ratAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6,
y: -15
});
// Add cost text
var costText = new Text2(cost.toString(), {
size: 25,
fill: '#FFD700'
});
costText.anchor.set(0.5, 0.5);
costText.y = 35;
self.addChild(costText);
// Add type label
var typeText = new Text2(ratType.toUpperCase(), {
size: 20,
fill: '#FFFFFF'
});
typeText.anchor.set(0.5, 0.5);
typeText.y = -45;
self.addChild(typeText);
self.setSelected = function (selected) {
self.isSelected = selected;
slotGraphics.tint = selected ? 0x00FF00 : 0xFFFFFF;
slotGraphics.alpha = selected ? 0.8 : 1.0;
};
self.canAfford = function () {
return cheesePoints >= self.cost;
};
self.updateDisplay = function () {
var affordable = self.canAfford();
ratPreview.alpha = affordable ? 1.0 : 0.5;
costText.tint = affordable ? 0xFFD700 : 0xFF0000;
};
self.down = function (x, y, obj) {
if (gameStarted && !upgradeMode) {
selectedRatType = self.ratType;
selectedCollectedUnit = null; // Deselect collected units
// Update all inventory slots selection
for (var i = 0; i < inventorySlots.length; i++) {
inventorySlots[i].setSelected(inventorySlots[i].ratType === selectedRatType);
}
// Update collected unit slots to deselect them
updateCollectedUnitsDisplay();
updateUI();
}
};
return self;
});
var MainMenu = Container.expand(function () {
var self = Container.call(this);
// Menu background
var menuBg = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0,
scaleX: 34,
scaleY: 91,
x: 0,
y: 0
});
menuBg.alpha = 0.8;
self.addChild(menuBg);
// Game title
var titleText = new Text2('RAT DEFENSE', {
size: 120,
fill: '#FFD700'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 800;
self.addChild(titleText);
// Subtitle
var subtitleText = new Text2('Defend Your Cheese!', {
size: 60,
fill: '#FFFFFF'
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 1024;
subtitleText.y = 950;
self.addChild(subtitleText);
// Start button
var startButton = new Text2('START GAME', {
size: 80,
fill: '#00FF00'
});
startButton.anchor.set(0.5, 0.5);
startButton.x = 1024;
startButton.y = 1400;
self.addChild(startButton);
// Instructions
var instructionsText = new Text2('Place rats to defend against cats\nUpgrade your rats for better defense', {
size: 45,
fill: '#CCCCCC'
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 1024;
instructionsText.y = 1600;
self.addChild(instructionsText);
startButton.down = function () {
self.destroy();
gameStarted = true;
initializeGame();
};
return self;
});
var Rat = Container.expand(function () {
var self = Container.call(this);
self.ratType = 'warrior';
self.range = 150;
self.damage = 25;
self.fireRate = 60;
self.lastShot = 0;
self.cost = 50;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (LK.ticks - self.lastShot > self.fireRate) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.lastShot = LK.ticks;
}
}
};
self.findTarget = function () {
var closestCat = null;
var closestDistanceSquared = Infinity;
var rangeSquared = self.range * self.range;
for (var i = 0; i < cats.length; i++) {
var cat = cats[i];
var dx = cat.x - self.x;
var dy = cat.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= rangeSquared && distanceSquared < closestDistanceSquared) {
closestDistanceSquared = distanceSquared;
closestCat = cat;
}
}
return closestCat;
};
self.shoot = function (target) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = target.x;
bullet.targetY = target.y;
bullet.damage = self.damage;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
};
self.upgrade = function () {
if (self.upgradeLevel < self.maxUpgrades) {
self.upgradeLevel++;
// Increase damage by 50% per upgrade
self.damage = Math.floor(self.damage * 1.5);
// Increase range by 25% per upgrade
self.range = Math.floor(self.range * 1.25);
// Visual indicator of upgrade
self.children[0].tint = self.upgradeLevel === 1 ? 0x00FF00 : self.upgradeLevel === 2 ? 0x0000FF : 0xFF00FF;
self.children[0].scaleX = 1 + self.upgradeLevel * 0.2;
self.children[0].scaleY = 1 + self.upgradeLevel * 0.2;
// Update range indicator if visible
self.updateRangeIndicator();
// Add upgrade level stars
self.showUpgradeStars();
return true;
}
return false;
};
self.getUpgradeCost = function () {
return (self.upgradeLevel + 1) * self.cost;
};
self.canUpgrade = function () {
return self.upgradeLevel < self.maxUpgrades;
};
self.showRangeIndicator = function () {
if (!self.rangeIndicator) {
self.rangeIndicator = LK.getAsset('rangeIndicator', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: self.range / 2,
scaleY: self.range / 2,
x: self.x,
y: self.y
});
self.rangeIndicator.alpha = 0.3;
game.addChild(self.rangeIndicator);
}
};
self.hideRangeIndicator = function () {
if (self.rangeIndicator) {
self.rangeIndicator.destroy();
self.rangeIndicator = null;
}
};
self.updateRangeIndicator = function () {
if (self.rangeIndicator) {
self.rangeIndicator.scaleX = self.range / 2;
self.rangeIndicator.scaleY = self.range / 2;
self.rangeIndicator.x = self.x;
self.rangeIndicator.y = self.y;
}
};
self.showUpgradeStars = function () {
// Remove existing stars
if (self.upgradeStars) {
for (var i = 0; i < self.upgradeStars.length; i++) {
self.upgradeStars[i].destroy();
}
}
self.upgradeStars = [];
// Add stars based on upgrade level
for (var i = 0; i < self.upgradeLevel; i++) {
var star = new Text2('★', {
size: 25,
fill: '#FFD700'
});
star.anchor.set(0.5, 0.5);
star.x = self.x - 30 + i * 20;
star.y = self.y - 60;
self.upgradeStars.push(star);
game.addChild(star);
}
};
return self;
});
var ZombieRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'zombie';
self.range = 100;
self.damage = 15;
self.fireRate = 50;
self.cost = 0; // Summoned, not bought
self.upgradeLevel = 0;
self.maxUpgrades = 0; // Cannot be upgraded
self.health = 75;
self.maxHealth = 75;
self.lifespan = 1800; // 30 seconds at 60fps
self.timeAlive = 0;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratZombie', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x8B4513; // Brown tint for zombie
graphics.alpha = 0.8; // Slightly transparent
// Add health bar for zombie rat
self.healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50,
scaleX: 1.0,
scaleY: 0.8
});
self.healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50,
scaleX: 1.0,
scaleY: 0.8
});
// Override update to handle lifespan and movement
var originalUpdate = self.update;
self.pathIndex = gamePath.length - 1; // Start from the end of the path
self.lastX = self.x;
self.lastY = self.y;
self.update = function () {
// Move along the path in reverse direction (towards cats)
if (self.pathIndex >= 0) {
var targetTile = gamePath[self.pathIndex];
var dx = targetTile.x - self.x;
var dy = targetTile.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < 25) {
// Reached current path point, move to previous one
self.pathIndex--;
if (self.pathIndex < 0) {
// Reached start of path, stay there
self.pathIndex = 0;
}
} else {
var distance = Math.sqrt(distanceSquared);
var moveSpeed = 1.5;
self.x += dx / distance * moveSpeed;
self.y += dy / distance * moveSpeed;
}
}
originalUpdate.call(self);
self.timeAlive++;
// Only die from lifespan if lifespan is not infinite
if (self.lifespan !== Infinity && self.timeAlive >= self.lifespan) {
self.isDead = true;
LK.effects.flashObject(self, 0x654321, 300);
}
};
// Zombie rat can take damage from cats
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.isDead = true;
LK.effects.flashObject(self, 0xFF0000, 300);
} else {
// Update health bar
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent * 1.0;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
}
};
return self;
});
var TowerRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'tower';
self.range = 500;
self.damage = 300;
self.fireRate = 180;
self.cost = 750;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.health = 500;
self.maxHealth = 500;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4.5,
scaleY: 4.5
});
graphics.tint = 0x2F4F4F; // Dark slate gray tint for tower rat
// Add health bar for tower rat
self.healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -150,
scaleX: 3.0,
scaleY: 2.5
});
self.healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -150,
scaleX: 3.0,
scaleY: 2.5
});
// Tower rat can take damage from cats
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.isDead = true;
LK.effects.flashObject(self, 0xFF0000, 750);
} else {
// Update health bar
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent * 3.0;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
}
};
return self;
});
var SummonerRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'summoner';
self.range = 200;
self.damage = 20;
self.fireRate = 120;
self.cost = 300;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.summonCooldown = 600; // 10 seconds
self.lastSummon = 0;
self.maxZombies = 2;
self.zombiesCount = 0;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratSummoner', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x4B0082; // Dark purple tint for summoner
// Override update to handle summoning
var originalUpdate = self.update;
self.update = function () {
originalUpdate.call(self);
// Count current zombies
self.zombiesCount = 0;
for (var i = 0; i < rats.length; i++) {
if (rats[i].ratType === 'zombie') {
self.zombiesCount++;
}
}
// Summon zombie if cooldown is ready and we need more
if (LK.ticks - self.lastSummon > self.summonCooldown && self.zombiesCount < self.maxZombies) {
// Find empty placement tile near cheese
var cheeseX = gamePath[gamePath.length - 1].x;
var cheeseY = gamePath[gamePath.length - 1].y;
var bestTile = null;
var bestDistance = Infinity;
for (var j = 0; j < placementTiles.length; j++) {
var tile = placementTiles[j];
var dx = cheeseX - tile.x;
var dy = cheeseY - tile.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if tile is free
var tileOccupied = false;
for (var k = 0; k < rats.length; k++) {
var ratDx = rats[k].x - tile.x;
var ratDy = rats[k].y - tile.y;
if (ratDx * ratDx + ratDy * ratDy < 50 * 50) {
tileOccupied = true;
break;
}
}
if (!tileOccupied && distance < bestDistance) {
bestDistance = distance;
bestTile = tile;
}
}
if (bestTile) {
// Summon zombie rat for free
var zombie = new ZombieRat();
zombie.x = bestTile.x;
zombie.y = bestTile.y;
// Make zombie permanent by removing lifespan
zombie.lifespan = Infinity;
rats.push(zombie);
game.addChild(zombie);
// Update placement indicator for this tile
for (var k = 0; k < placementTiles.length; k++) {
if (placementTiles[k].x === bestTile.x && placementTiles[k].y === bestTile.y) {
placementIndicators[k].tint = 0xFF6347; // Red tint to show occupied
placementIndicators[k].alpha = 0.2; // Make it more transparent
break;
}
}
// Visual summoning effect
LK.effects.flashObject(self, 0x9932CC, 500);
LK.effects.flashObject(zombie, 0x00FF00, 700);
self.lastSummon = LK.ticks;
updateUI();
}
}
};
return self;
});
var ShadowRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'shadow';
self.range = 180;
self.damage = 60;
self.fireRate = 75;
self.cost = 175;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.teleportCooldown = 300; // 5 seconds
self.lastTeleport = 0;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.1,
scaleY: 1.1
});
graphics.tint = 0x4B0082; // Dark purple tint for shadow
graphics.alpha = 0.7; // Semi-transparent for stealth effect
// Override findTarget to prioritize high-value targets
self.findTarget = function () {
var bestTarget = null;
var bestScore = 0;
var rangeSquared = self.range * self.range;
for (var i = 0; i < cats.length; i++) {
var cat = cats[i];
var dx = cat.x - self.x;
var dy = cat.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= rangeSquared) {
// Score based on health and cheese value
var score = cat.health + cat.cheeseValue * 2;
if (score > bestScore) {
bestScore = score;
bestTarget = cat;
}
}
}
return bestTarget;
};
// Override shoot to include teleportation ability
self.shoot = function (target) {
// Normal attack
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = target.x;
bullet.targetY = target.y;
bullet.damage = self.damage;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
// Teleport ability on cooldown
if (LK.ticks - self.lastTeleport > self.teleportCooldown) {
var teleportTargets = [];
for (var i = 0; i < cats.length; i++) {
var cat = cats[i];
var dx = cat.x - self.x;
var dy = cat.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > self.range && distance < self.range * 2) {
teleportTargets.push(cat);
}
}
if (teleportTargets.length > 0) {
// Teleport to attack distant target
var teleportTarget = teleportTargets[Math.floor(Math.random() * teleportTargets.length)];
// Find nearest placement tile to target
var nearestTile = null;
var nearestDistance = Infinity;
for (var j = 0; j < placementTiles.length; j++) {
var tile = placementTiles[j];
var tileDx = teleportTarget.x - tile.x;
var tileDy = teleportTarget.y - tile.y;
var tileDistance = Math.sqrt(tileDx * tileDx + tileDy * tileDy);
// Check if tile is free
var tileOccupied = false;
for (var k = 0; k < rats.length; k++) {
if (rats[k] !== self) {
var ratDx = rats[k].x - tile.x;
var ratDy = rats[k].y - tile.y;
if (ratDx * ratDx + ratDy * ratDy < 50 * 50) {
tileOccupied = true;
break;
}
}
}
if (!tileOccupied && tileDistance < nearestDistance) {
nearestDistance = tileDistance;
nearestTile = tile;
}
}
if (nearestTile) {
// Teleportation effect
LK.effects.flashObject(self, 0x9932CC, 300);
// Update old tile indicator
for (var k = 0; k < placementTiles.length; k++) {
var tile = placementTiles[k];
var dx = self.x - tile.x;
var dy = self.y - tile.y;
if (dx * dx + dy * dy < 50 * 50) {
placementIndicators[k].tint = 0x90EE90;
placementIndicators[k].alpha = 0.6;
break;
}
}
// Teleport to new position
self.x = nearestTile.x;
self.y = nearestTile.y;
// Update new tile indicator
for (var k = 0; k < placementTiles.length; k++) {
var tile = placementTiles[k];
if (tile.x === nearestTile.x && tile.y === nearestTile.y) {
placementIndicators[k].tint = 0xFF6347;
placementIndicators[k].alpha = 0.2;
break;
}
}
self.lastTeleport = LK.ticks;
LK.effects.flashObject(self, 0x4B0082, 500);
}
}
}
};
return self;
});
var MegaRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'mega';
self.range = 400;
self.damage = 200;
self.fireRate = 120;
self.cost = 500;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.health = 300;
self.maxHealth = 300;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.5,
scaleY: 3.5
});
graphics.tint = 0x4B0082; // Indigo tint for mega rat
// Add health bar for mega rat
self.healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -120,
scaleX: 2.0,
scaleY: 2.0
});
self.healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -120,
scaleX: 2.0,
scaleY: 2.0
});
// Mega rat can take damage from cats
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.isDead = true;
LK.effects.flashObject(self, 0xFF0000, 500);
} else {
// Update health bar
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent * 2.0;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
}
};
return self;
});
var MagicRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'magic';
self.range = 180;
self.damage = 35;
self.fireRate = 75;
self.cost = 100;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x9932CC; // Purple tint for magic
return self;
});
var HealerRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'healer';
self.range = 200;
self.damage = 0;
self.fireRate = 180;
self.cost = 80;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.healAmount = 25;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x00FF7F; // Spring green tint for healer
// Override findTarget to find damaged rats instead of cats
self.findTarget = function () {
var mostDamagedRat = null;
var lowestHealthPercent = 1.0;
var rangeSquared = self.range * self.range;
for (var i = 0; i < rats.length; i++) {
var rat = rats[i];
if (rat === self) continue; // Don't heal self
var dx = rat.x - self.x;
var dy = rat.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= rangeSquared && rat.health && rat.maxHealth) {
var healthPercent = rat.health / rat.maxHealth;
if (healthPercent < lowestHealthPercent && healthPercent < 1.0) {
lowestHealthPercent = healthPercent;
mostDamagedRat = rat;
}
}
}
return mostDamagedRat;
};
// Override shoot to heal instead of damage
self.shoot = function (target) {
if (target.health && target.maxHealth) {
target.health = Math.min(target.maxHealth, target.health + self.healAmount);
// Visual healing effect
LK.effects.flashObject(target, 0x00FF00, 300);
}
};
return self;
});
var GiantRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'giant';
self.range = 300;
self.damage = 100;
self.fireRate = 150;
self.cost = 250;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.health = 150;
self.maxHealth = 150;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 2.5
});
graphics.tint = 0x8B0000; // Dark red tint for giant
// Add health bar for giant rat
self.healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -80,
scaleX: 1.5,
scaleY: 1.5
});
self.healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -80,
scaleX: 1.5,
scaleY: 1.5
});
// Giant rat can take damage from cats
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.isDead = true;
LK.effects.flashObject(self, 0xFF0000, 500);
} else {
// Update health bar
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent * 1.5;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
}
};
return self;
});
var ElectricRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'electric';
self.range = 250;
self.damage = 40;
self.fireRate = 90;
self.cost = 200;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
graphics.tint = 0x00FFFF; // Cyan tint for electric
// Override shoot to create chain lightning
self.shoot = function (target) {
var chainTargets = [target];
var chainRange = 100;
var maxChains = 3;
// Find additional targets for chain lightning
for (var chain = 0; chain < maxChains; chain++) {
var lastTarget = chainTargets[chainTargets.length - 1];
var closestCat = null;
var closestDistanceSquared = Infinity;
var chainRangeSquared = chainRange * chainRange;
for (var i = 0; i < cats.length; i++) {
var cat = cats[i];
if (chainTargets.indexOf(cat) === -1) {
// Not already targeted
var dx = cat.x - lastTarget.x;
var dy = cat.y - lastTarget.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= chainRangeSquared && distanceSquared < closestDistanceSquared) {
closestDistanceSquared = distanceSquared;
closestCat = cat;
}
}
}
if (closestCat) {
chainTargets.push(closestCat);
} else {
break;
}
}
// Damage all targets in chain
for (var i = 0; i < chainTargets.length; i++) {
var damage = Math.floor(self.damage * (0.8 + 0.2 * (4 - i) / 4)); // Damage decreases per chain
chainTargets[i].takeDamage(damage);
LK.effects.flashObject(chainTargets[i], 0x00FFFF, 200);
}
// Visual lightning effect
LK.effects.flashScreen(0x00FFFF, 150);
LK.getSound('shoot').play();
};
return self;
});
var BomberRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'bomber';
self.range = 120;
self.damage = 15;
self.fireRate = 90;
self.cost = 125;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratBomber', {
anchorX: 0.5,
anchorY: 0.5
});
// Override shoot to create splash damage
self.shoot = function (target) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = target.x;
bullet.targetY = target.y;
bullet.damage = self.damage;
bullet.isSplash = true; // Mark as splash damage bullet
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
};
return self;
});
var ArcherRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'archer';
self.range = 220;
self.damage = 20;
self.fireRate = 45;
self.cost = 75;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratArcher', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
var SniperRat = ArcherRat.expand(function () {
var self = ArcherRat.call(this);
self.ratType = 'sniper';
self.range = 350;
self.damage = 50;
self.fireRate = 120;
self.cost = 150;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratArcher', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x8B4513; // Brown tint for sniper
return self;
});
var RollScreen = Container.expand(function () {
var self = Container.call(this);
// Background overlay
var overlay = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0,
scaleX: 34,
scaleY: 91,
x: 0,
y: 0
});
overlay.alpha = 0.9;
overlay.tint = 0x000033;
self.addChild(overlay);
// Title
var titleText = new Text2('SUMMON UNITS', {
size: 100,
fill: '#FFD700'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 400;
self.addChild(titleText);
// Roll result area
var resultContainer = new Container();
resultContainer.x = 1024;
resultContainer.y = 1000;
self.addChild(resultContainer);
// Single roll button
var singleRollButton = new Text2('SINGLE ROLL (25 Cheese)', {
size: 60,
fill: '#00FF00'
});
singleRollButton.anchor.set(0.5, 0.5);
singleRollButton.x = 512;
singleRollButton.y = 1600;
self.addChild(singleRollButton);
// Multi roll button
var multiRollButton = new Text2('MULTI ROLL x5 (100 Cheese)', {
size: 60,
fill: '#0080FF'
});
multiRollButton.anchor.set(0.5, 0.5);
multiRollButton.x = 1536;
multiRollButton.y = 1600;
self.addChild(multiRollButton);
// Close button
var closeButton = new Text2('CLOSE', {
size: 50,
fill: '#FF4444'
});
closeButton.anchor.set(0.5, 0.5);
closeButton.x = 1024;
closeButton.y = 2200;
self.addChild(closeButton);
// Roll rarities and chances
var rollTable = [{
type: 'warrior',
rarity: 'common',
chance: 0.3,
color: 0xCCCCCC
}, {
type: 'archer',
rarity: 'common',
chance: 0.17,
color: 0xCCCCCC
}, {
type: 'magic',
rarity: 'common',
chance: 0.10,
color: 0xCCCCCC
}, {
type: 'summoner',
rarity: 'common',
chance: 0.12,
color: 0xCCCCCC
}, {
type: 'bomber',
rarity: 'common',
chance: 0.11,
color: 0xCCCCCC
}, {
type: 'warrior',
rarity: 'rare',
chance: 0.08,
color: 0x0080FF
}, {
type: 'archer',
rarity: 'rare',
chance: 0.06,
color: 0x0080FF
}, {
type: 'magic',
rarity: 'rare',
chance: 0.05,
color: 0x0080FF
}, {
type: 'bomber',
rarity: 'rare',
chance: 0.04,
color: 0x0080FF
}, {
type: 'healer',
rarity: 'rare',
chance: 0.03,
color: 0x0080FF
}, {
type: 'warrior',
rarity: 'epic',
chance: 0.025,
color: 0x9C27B0
}, {
type: 'archer',
rarity: 'epic',
chance: 0.02,
color: 0x9C27B0
}, {
type: 'magic',
rarity: 'epic',
chance: 0.015,
color: 0x9C27B0
}, {
type: 'bomber',
rarity: 'epic',
chance: 0.012,
color: 0x9C27B0
}, {
type: 'healer',
rarity: 'epic',
chance: 0.01,
color: 0x9C27B0
}, {
type: 'sniper',
rarity: 'epic',
chance: 0.008,
color: 0x9C27B0
}, {
type: 'electric',
rarity: 'epic',
chance: 0.006,
color: 0x9C27B0
}, {
type: 'shadow',
rarity: 'epic',
chance: 0.004,
color: 0x9C27B0
}, {
type: 'summoner',
rarity: 'rare',
chance: 0.055,
color: 0x0080FF
}, {
type: 'warrior',
rarity: 'legendary',
chance: 0.015,
color: 0xFF8000
}, {
type: 'archer',
rarity: 'legendary',
chance: 0.01,
color: 0xFF8000
}, {
type: 'sniper',
rarity: 'legendary',
chance: 0.008,
color: 0xFF8000
}, {
type: 'magic',
rarity: 'legendary',
chance: 0.005,
color: 0xFF8000
}, {
type: 'bomber',
rarity: 'legendary',
chance: 0.003,
color: 0xFF8000
}, {
type: 'healer',
rarity: 'legendary',
chance: 0.002,
color: 0xFF8000
}, {
type: 'giant',
rarity: 'legendary',
chance: 0.001,
color: 0xFF8000
}, {
type: 'mega',
rarity: 'legendary',
chance: 0.0005,
color: 0xFF8000
}, {
type: 'tower',
rarity: 'legendary',
chance: 0.0002,
color: 0xFF8000
}];
self.performRoll = function () {
var random = Math.random();
var cumulative = 0;
for (var i = 0; i < rollTable.length; i++) {
cumulative += rollTable[i].chance;
if (random <= cumulative) {
return rollTable[i];
}
}
return rollTable[0]; // Fallback to common warrior
};
self.showRollResult = function (results) {
// Clear previous results
while (resultContainer.children.length > 0) {
resultContainer.removeChild(resultContainer.children[0]);
}
for (var i = 0; i < results.length; i++) {
var result = results[i];
var resultCard = new Container();
// Card background
var cardBg = LK.getAsset('inventorySlot', {
anchorX: 0.5,
anchorY: 0.5
});
cardBg.tint = result.color;
cardBg.scaleX = 1.5;
cardBg.scaleY = 1.5;
resultCard.addChild(cardBg);
// Unit preview
var unitAsset = result.type === 'archer' || result.type === 'sniper' ? 'ratArcher' : result.type === 'bomber' ? 'ratBomber' : result.type === 'summoner' ? 'ratSummoner' : 'ratWarrior';
var unitPreview = LK.getAsset(unitAsset, {
anchorX: 0.5,
anchorY: 0.5,
y: -20,
scaleX: result.type === 'tower' ? 2.5 : result.type === 'mega' ? 2.0 : result.type === 'giant' ? 1.5 : result.type === 'electric' ? 1.2 : result.type === 'shadow' ? 1.1 : result.type === 'summoner' ? 1.3 : 1.0,
scaleY: result.type === 'tower' ? 2.5 : result.type === 'mega' ? 2.0 : result.type === 'giant' ? 1.5 : result.type === 'electric' ? 1.2 : result.type === 'shadow' ? 1.1 : result.type === 'summoner' ? 1.3 : 1.0
});
// Apply type-specific tints
if (result.type === 'magic') {
unitPreview.tint = 0x9932CC; // Purple
} else if (result.type === 'sniper') {
unitPreview.tint = 0x8B4513; // Brown
} else if (result.type === 'bomber') {
unitPreview.tint = 0xFF6347; // Red-orange
} else if (result.type === 'healer') {
unitPreview.tint = 0x00FF7F; // Spring green
} else if (result.type === 'giant') {
unitPreview.tint = 0x8B0000; // Dark red
} else if (result.type === 'mega') {
unitPreview.tint = 0x4B0082; // Indigo
} else if (result.type === 'tower') {
unitPreview.tint = 0x2F4F4F; // Dark slate gray
} else if (result.type === 'electric') {
unitPreview.tint = 0x00FFFF; // Cyan
} else if (result.type === 'shadow') {
unitPreview.tint = 0x4B0082; // Dark purple
unitPreview.alpha = 0.7; // Semi-transparent
} else if (result.type === 'summoner') {
unitPreview.tint = 0x4B0082; // Dark purple
}
resultCard.addChild(unitPreview);
// Rarity text
var rarityText = new Text2(result.rarity.toUpperCase(), {
size: 25,
fill: '#FFFFFF'
});
rarityText.anchor.set(0.5, 0.5);
rarityText.y = 60;
resultCard.addChild(rarityText);
// Type text
var typeText = new Text2(result.type.toUpperCase(), {
size: 20,
fill: '#FFFFFF'
});
typeText.anchor.set(0.5, 0.5);
typeText.y = 85;
resultCard.addChild(typeText);
// Position cards
if (results.length === 1) {
resultCard.x = 0;
} else {
resultCard.x = (i - 2) * 200;
}
resultCard.y = 0;
// Scale animation
resultCard.scaleX = 0;
resultCard.scaleY = 0;
tween(resultCard, {
scaleX: 1,
scaleY: 1
}, {
duration: 500
});
resultContainer.addChild(resultCard);
// Add to player inventory based on rarity
var ratCost = result.type === 'warrior' ? 50 : 75;
if (result.rarity === 'rare') {
ratCost = Math.floor(ratCost * 0.8); // 20% discount for rare
} else if (result.rarity === 'epic') {
ratCost = Math.floor(ratCost * 0.7); // 30% discount for epic
} else if (result.rarity === 'legendary') {
ratCost = Math.floor(ratCost * 0.6); // 40% discount for legendary
}
// Add unit to collected units
var collectedUnit = {
type: result.type,
rarity: result.rarity,
color: result.color,
cost: ratCost
};
collectedUnits.push(collectedUnit);
// Give bonus cheese for higher rarities
if (result.rarity === 'rare') {
cheesePoints += 15;
} else if (result.rarity === 'epic') {
cheesePoints += 35;
} else if (result.rarity === 'legendary') {
cheesePoints += 50;
}
}
updateUI();
};
singleRollButton.down = function () {
if (cheesePoints >= 25) {
cheesePoints -= 25;
var result = self.performRoll();
self.showRollResult([result]);
LK.effects.flashScreen(0x00FF00, 200);
}
};
multiRollButton.down = function () {
if (cheesePoints >= 100) {
cheesePoints -= 100;
var results = [];
for (var i = 0; i < 5; i++) {
results.push(self.performRoll());
}
self.showRollResult(results);
LK.effects.flashScreen(0x0080FF, 300);
}
};
closeButton.down = function () {
self.destroy();
rollScreen = null;
};
return self;
});
var SummoningZone = Container.expand(function () {
var self = Container.call(this);
var zoneGraphics = self.attachAsset('summonZone', {
anchorX: 0.5,
anchorY: 0.5
});
zoneGraphics.alpha = 0.3;
var zoneText = new Text2('GACHA ROLLS', {
size: 40,
fill: '#FFFFFF'
});
zoneText.anchor.set(0.5, 0.5);
zoneText.y = -30;
self.addChild(zoneText);
var instructionText = new Text2('Tap to open roll screen', {
size: 25,
fill: '#CCCCCC'
});
instructionText.anchor.set(0.5, 0.5);
instructionText.y = 0;
self.addChild(instructionText);
var instructionText2 = new Text2('Place rats on green tiles anywhere', {
size: 25,
fill: '#90EE90'
});
instructionText2.anchor.set(0.5, 0.5);
instructionText2.y = 30;
self.addChild(instructionText2);
// Summon button for gacha rolls
var summonButton = new Text2('OPEN ROLLS', {
size: 35,
fill: '#FFD700'
});
summonButton.anchor.set(0.5, 0.5);
summonButton.y = 60;
self.addChild(summonButton);
self.down = function (x, y, obj) {
if (gameStarted && !upgradeMode) {
// Open roll screen for gacha summons
if (!rollScreen) {
rollScreen = new RollScreen();
game.addChild(rollScreen);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game state
var gameStarted = false;
var mainMenu = null;
// Center map offset for mobile display
var mapOffsetX = (2048 - 1200) / 2;
var mapOffsetY = 200;
var gamePath = [{
x: 40 + mapOffsetX,
y: 400 + mapOffsetY
}, {
x: 200 + mapOffsetX,
y: 400 + mapOffsetY
}, {
x: 200 + mapOffsetX,
y: 200 + mapOffsetY
}, {
x: 400 + mapOffsetX,
y: 200 + mapOffsetY
}, {
x: 400 + mapOffsetX,
y: 500 + mapOffsetY
}, {
x: 600 + mapOffsetX,
y: 500 + mapOffsetY
}, {
x: 600 + mapOffsetX,
y: 300 + mapOffsetY
}, {
x: 800 + mapOffsetX,
y: 300 + mapOffsetY
}, {
x: 800 + mapOffsetX,
y: 600 + mapOffsetY
}, {
x: 1000 + mapOffsetX,
y: 600 + mapOffsetY
}, {
x: 1000 + mapOffsetX,
y: 400 + mapOffsetY
}, {
x: 1200 + mapOffsetX,
y: 400 + mapOffsetY
}];
var placementTiles = [{
x: 280 + mapOffsetX,
y: 280 + mapOffsetY
}, {
x: 520 + mapOffsetX,
y: 380 + mapOffsetY
}, {
x: 680 + mapOffsetX,
y: 380 + mapOffsetY
}, {
x: 720 + mapOffsetX,
y: 200 + mapOffsetY
}, {
x: 880 + mapOffsetX,
y: 200 + mapOffsetY
}, {
x: 880 + mapOffsetX,
y: 500 + mapOffsetY
}, {
x: 1080 + mapOffsetX,
y: 500 + mapOffsetY
}, {
x: 280 + mapOffsetX,
y: 120 + mapOffsetY
}, {
x: 680 + mapOffsetX,
y: 580 + mapOffsetY
}, {
x: 360 + mapOffsetX,
y: 160 + mapOffsetY
}, {
x: 760 + mapOffsetX,
y: 160 + mapOffsetY
}, {
x: 1160 + mapOffsetX,
y: 160 + mapOffsetY
}];
var cats = [];
var rats = [];
var bullets = [];
var cheesePoints = 150;
var lives = 5;
var currentWave = 1;
var waveInProgress = false;
var catsToSpawn = 0;
var spawnTimer = 0;
var selectedRatType = 'warrior';
var selectedRat = null;
var upgradeMode = false;
// Initialize game variables
var pathIndicators = [];
var placementIndicators = [];
var cheese = null;
var cheeseText = null;
var livesText = null;
var waveText = null;
var startWaveButton = null;
var ratTypeButton = null;
var upgradeButton = null;
var upgradeInfoText = null;
var summoningZone = null;
var inventorySlots = [];
var rollScreen = null;
// Collected units from rolls
var collectedUnits = [];
var selectedCollectedUnit = null;
function startWave() {
if (!waveInProgress) {
waveInProgress = true;
catsToSpawn = 3 + currentWave * 2;
spawnTimer = 0;
startWaveButton.setText('WAVE IN PROGRESS');
startWaveButton.tint = 0xFF0000;
}
}
function spawnCat() {
var cat;
var random = Math.random();
// Spawn boss cats in later waves
if (currentWave >= 10 && random < 0.1) {
cat = new CatSummoner();
} else if (currentWave >= 8 && random < 0.15) {
cat = new FastCatBoss();
} else if (currentWave >= 5 && random < 0.2) {
cat = new CatBoss();
} else if (random < 0.3 && currentWave > 2) {
cat = new FastCat();
} else {
cat = new Cat();
}
cat.x = gamePath[0].x;
cat.y = gamePath[0].y;
cat.health += currentWave * 10;
cat.maxHealth = cat.health;
cats.push(cat);
game.addChild(cat);
catsToSpawn--;
}
var collectedUnitSlots = [];
function updateCollectedUnitsDisplay() {
// Remove existing collected unit slots
for (var i = 0; i < collectedUnitSlots.length; i++) {
collectedUnitSlots[i].destroy();
}
collectedUnitSlots = [];
// Create new slots for collected units
for (var i = 0; i < collectedUnits.length && i < 8; i++) {
var slot = new CollectedUnitSlot(collectedUnits[i], i);
slot.x = 500 + i % 4 * 100;
slot.y = 2400 + Math.floor(i / 4) * 100;
collectedUnitSlots.push(slot);
game.addChild(slot);
// Select if this is the selected unit
if (selectedCollectedUnit === collectedUnits[i]) {
slot.setSelected(true);
}
}
}
function updateUI() {
if (!cheeseText) return; // Don't update UI if elements aren't initialized yet
cheeseText.setText('Cheese: ' + cheesePoints);
livesText.setText('Lives: ' + lives);
waveText.setText('Wave: ' + currentWave);
if (selectedCollectedUnit) {
ratTypeButton.setText('COLLECTED: ' + selectedCollectedUnit.type.toUpperCase() + ' (' + selectedCollectedUnit.rarity + ')');
} else if (selectedRatType) {
var ratCost = selectedRatType === 'warrior' ? 50 : 75;
ratTypeButton.setText('RAT: ' + selectedRatType.toUpperCase() + ' (' + ratCost + ')');
} else {
ratTypeButton.setText('SELECT A UNIT');
}
upgradeButton.setText('UPGRADE MODE: ' + (upgradeMode ? 'ON' : 'OFF'));
upgradeButton.tint = upgradeMode ? 0x00FF00 : 0x9932CC;
if (selectedRat && upgradeMode) {
var upgradeCost = selectedRat.getUpgradeCost();
var canAfford = cheesePoints >= upgradeCost;
var canUpgrade = selectedRat.canUpgrade();
if (canUpgrade) {
upgradeInfoText.setText('UPGRADE (' + upgradeCost + ') - DMG:' + Math.floor(selectedRat.damage * 1.5) + ' RNG:' + Math.floor(selectedRat.range * 1.25));
upgradeInfoText.tint = canAfford ? 0x00FF00 : 0xFF0000;
} else {
upgradeInfoText.setText('MAX LEVEL REACHED');
upgradeInfoText.tint = 0xFFFF00;
}
} else {
upgradeInfoText.setText(upgradeMode ? 'SELECT A RAT TO UPGRADE' : '');
upgradeInfoText.tint = 0xFFFFFF;
}
// Update inventory slots
if (inventorySlots && inventorySlots.length > 0) {
for (var i = 0; i < inventorySlots.length; i++) {
inventorySlots[i].updateDisplay();
}
}
// Update collected units display
updateCollectedUnitsDisplay();
}
function canPlaceRat(x, y) {
var placementDistanceSquared = 120 * 120; // 14400 - even more forgiving radius for easier targeting
for (var i = 0; i < placementTiles.length; i++) {
var tile = placementTiles[i];
var dx = x - tile.x;
var dy = y - tile.y;
if (dx * dx + dy * dy < placementDistanceSquared) {
// Check if tile is already occupied
for (var j = 0; j < rats.length; j++) {
var ratDx = rats[j].x - tile.x;
var ratDy = rats[j].y - tile.y;
if (ratDx * ratDx + ratDy * ratDy < 50 * 50) {
// Slightly reduced occupied check radius for tighter packing
return false;
}
}
return true;
}
}
return false;
}
function initializeGame() {
// Create visual path indicators to show where cats walk
pathIndicators = [];
for (var i = 0; i < gamePath.length; i++) {
var pathPoint = gamePath[i];
var indicator = LK.getAsset('pathIndicator', {
anchorX: 0.5,
anchorY: 0.5,
x: pathPoint.x,
y: pathPoint.y
});
indicator.alpha = 0.8;
pathIndicators.push(indicator);
game.addChild(indicator);
// Add directional arrows between path points
if (i < gamePath.length - 1) {
var nextPoint = gamePath[i + 1];
var arrow = LK.getAsset('pathArrow', {
anchorX: 0.5,
anchorY: 0.5,
x: (pathPoint.x + nextPoint.x) / 2,
y: (pathPoint.y + nextPoint.y) / 2
});
// Rotate arrow to point in direction of movement
var dx = nextPoint.x - pathPoint.x;
var dy = nextPoint.y - pathPoint.y;
arrow.rotation = Math.atan2(dy, dx);
arrow.alpha = 0.7;
pathIndicators.push(arrow);
game.addChild(arrow);
}
}
// Create visual indicators for placement tiles
placementIndicators = [];
for (var i = 0; i < placementTiles.length; i++) {
var tile = placementTiles[i];
var indicator = LK.getAsset('summonZone', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6,
x: tile.x,
y: tile.y
});
indicator.alpha = 0.6;
indicator.tint = 0x90EE90;
placementIndicators.push(indicator);
game.addChild(indicator);
}
// Create cheese at the end
cheese = LK.getAsset('cheese', {
anchorX: 0.5,
anchorY: 0.5,
x: gamePath[gamePath.length - 1].x,
y: gamePath[gamePath.length - 1].y
});
game.addChild(cheese);
// UI Elements
cheeseText = new Text2('Cheese: ' + cheesePoints, {
size: 60,
fill: '#FFD700'
});
cheeseText.anchor.set(0, 0);
cheeseText.x = 120;
LK.gui.topLeft.addChild(cheeseText);
livesText = new Text2('Lives: ' + lives, {
size: 60,
fill: '#FF6347'
});
livesText.anchor.set(0, 0);
livesText.x = 120;
livesText.y = 80;
LK.gui.topLeft.addChild(livesText);
waveText = new Text2('Wave: ' + currentWave, {
size: 60,
fill: '#87CEEB'
});
waveText.anchor.set(0, 0);
waveText.x = 120;
waveText.y = 160;
LK.gui.topLeft.addChild(waveText);
startWaveButton = new Text2('START WAVE', {
size: 70,
fill: '#00FF00'
});
startWaveButton.anchor.set(0.5, 0.5);
startWaveButton.down = function () {
startWave();
};
LK.gui.bottom.addChild(startWaveButton);
ratTypeButton = new Text2('RAT: WARRIOR (50)', {
size: 55,
fill: '#8B4513'
});
ratTypeButton.anchor.set(0.5, 0.5);
ratTypeButton.y = -120;
ratTypeButton.down = function () {
// Cycle through rat types and update selection
if (selectedRatType === 'warrior') {
selectedRatType = 'archer';
selectedCollectedUnit = null;
// Update inventory slots
for (var i = 0; i < inventorySlots.length; i++) {
inventorySlots[i].setSelected(inventorySlots[i].ratType === 'archer');
}
} else {
selectedRatType = 'warrior';
selectedCollectedUnit = null;
// Update inventory slots
for (var i = 0; i < inventorySlots.length; i++) {
inventorySlots[i].setSelected(inventorySlots[i].ratType === 'warrior');
}
}
updateCollectedUnitsDisplay();
updateUI();
};
LK.gui.bottom.addChild(ratTypeButton);
upgradeButton = new Text2('UPGRADE MODE: OFF', {
size: 50,
fill: '#9932CC'
});
upgradeButton.anchor.set(0.5, 0.5);
upgradeButton.y = -200;
upgradeButton.down = function () {
upgradeMode = !upgradeMode;
selectedRat = null;
updateUI();
};
LK.gui.bottom.addChild(upgradeButton);
upgradeInfoText = new Text2('', {
size: 45,
fill: '#FFFFFF'
});
upgradeInfoText.anchor.set(0.5, 0.5);
upgradeInfoText.y = -280;
LK.gui.bottom.addChild(upgradeInfoText);
// Create summoning zone
summoningZone = new SummoningZone();
summoningZone.x = 1024;
summoningZone.y = 1800;
game.addChild(summoningZone);
// Create inventory slots
inventorySlots = [];
var warriorSlot = new InventorySlot('warrior', 50);
warriorSlot.x = 200;
warriorSlot.y = 2400;
inventorySlots.push(warriorSlot);
game.addChild(warriorSlot);
var archerSlot = new InventorySlot('archer', 75);
archerSlot.x = 350;
archerSlot.y = 2400;
inventorySlots.push(archerSlot);
game.addChild(archerSlot);
// Set initial selection - auto-select warrior for immediate placement
selectedRatType = 'warrior';
selectedCollectedUnit = null;
warriorSlot.setSelected(true);
// Show helpful message
var helpText = new Text2('Tap anywhere on green tiles to place rats!', {
size: 40,
fill: '#90EE90'
});
helpText.anchor.set(0.5, 0.5);
helpText.x = 1024;
helpText.y = 1000;
game.addChild(helpText);
// Fade out help text after 5 seconds
LK.setTimeout(function () {
if (helpText && helpText.parent) {
tween(helpText, {
alpha: 0
}, {
duration: 1000
});
}
}, 5000);
updateUI();
}
function placeRat(x, y) {
var ratCost = selectedRatType === 'warrior' ? 50 : 75;
if (cheesePoints >= ratCost && canPlaceRat(x, y)) {
var rat;
if (selectedRatType === 'warrior') {
rat = new Rat();
} else {
rat = new ArcherRat();
}
// Snap to nearest placement tile
var nearestTile = null;
var nearestDistanceSquared = Infinity;
for (var i = 0; i < placementTiles.length; i++) {
var tile = placementTiles[i];
var dx = x - tile.x;
var dy = y - tile.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < nearestDistanceSquared) {
nearestDistanceSquared = distanceSquared;
nearestTile = tile;
}
}
rat.x = nearestTile.x;
rat.y = nearestTile.y;
rats.push(rat);
game.addChild(rat);
cheesePoints -= ratCost;
// Update placement indicator for this tile
for (var k = 0; k < placementTiles.length; k++) {
if (placementTiles[k].x === nearestTile.x && placementTiles[k].y === nearestTile.y) {
placementIndicators[k].tint = 0xFF6347; // Red tint to show occupied
placementIndicators[k].alpha = 0.2; // Make it more transparent
break;
}
}
LK.getSound('ratPlace').play();
updateUI();
}
}
// Show main menu initially
mainMenu = new MainMenu();
game.addChild(mainMenu);
game.down = function (x, y, obj) {
if (!gameStarted) return;
if (upgradeMode) {
// Check if clicking on a rat for upgrade
var clickedRat = null;
var clickDistanceSquared = 60 * 60; // 3600
for (var i = 0; i < rats.length; i++) {
var rat = rats[i];
var dx = x - rat.x;
var dy = y - rat.y;
if (dx * dx + dy * dy < clickDistanceSquared) {
clickedRat = rat;
break;
}
}
if (clickedRat) {
if (selectedRat === clickedRat) {
// Clicking same rat again - perform upgrade
if (clickedRat.canUpgrade() && cheesePoints >= clickedRat.getUpgradeCost()) {
cheesePoints -= clickedRat.getUpgradeCost();
clickedRat.upgrade();
LK.effects.flashObject(clickedRat, 0x00FF00, 500);
updateUI();
}
} else {
// Select this rat
// Hide range indicator for previously selected rat
if (selectedRat) {
selectedRat.hideRangeIndicator();
}
selectedRat = clickedRat;
// Remove selection indicator from all rats
for (var j = 0; j < rats.length; j++) {
rats[j].children[0].alpha = 1.0;
rats[j].hideRangeIndicator();
}
// Add selection indicator and show range
selectedRat.children[0].alpha = 0.7;
selectedRat.showRangeIndicator();
updateUI();
}
} else {
// Clicked empty space - deselect
if (selectedRat) {
selectedRat.hideRangeIndicator();
}
selectedRat = null;
for (var j = 0; j < rats.length; j++) {
rats[j].children[0].alpha = 1.0;
rats[j].hideRangeIndicator();
}
updateUI();
}
} else {
// Try to place a rat if we have one selected and can place it
if (selectedCollectedUnit && canPlaceRat(x, y)) {
// Place collected unit
var rat;
if (selectedCollectedUnit.type === 'warrior') {
rat = new Rat();
} else if (selectedCollectedUnit.type === 'archer') {
rat = new ArcherRat();
} else if (selectedCollectedUnit.type === 'magic') {
rat = new MagicRat();
} else if (selectedCollectedUnit.type === 'sniper') {
rat = new SniperRat();
} else if (selectedCollectedUnit.type === 'bomber') {
rat = new BomberRat();
} else if (selectedCollectedUnit.type === 'healer') {
rat = new HealerRat();
} else if (selectedCollectedUnit.type === 'giant') {
rat = new GiantRat();
} else if (selectedCollectedUnit.type === 'mega') {
rat = new MegaRat();
} else if (selectedCollectedUnit.type === 'tower') {
rat = new TowerRat();
} else if (selectedCollectedUnit.type === 'electric') {
rat = new ElectricRat();
} else if (selectedCollectedUnit.type === 'shadow') {
rat = new ShadowRat();
} else if (selectedCollectedUnit.type === 'summoner') {
rat = new SummonerRat();
} else {
// Default fallback for any unhandled types
rat = new Rat();
}
// Apply rarity bonuses
if (selectedCollectedUnit.rarity === 'rare') {
rat.damage = Math.floor(rat.damage * 1.3);
rat.range = Math.floor(rat.range * 1.2);
rat.children[0].tint = 0x0080FF;
} else if (selectedCollectedUnit.rarity === 'epic') {
rat.damage = Math.floor(rat.damage * 1.5);
rat.range = Math.floor(rat.range * 1.35);
rat.children[0].tint = 0x9C27B0;
} else if (selectedCollectedUnit.rarity === 'legendary') {
rat.damage = Math.floor(rat.damage * 1.8);
rat.range = Math.floor(rat.range * 1.5);
rat.children[0].tint = 0xFF8000;
}
// Snap to nearest placement tile
var nearestTile = null;
var nearestDistanceSquared = Infinity;
for (var k = 0; k < placementTiles.length; k++) {
var tile = placementTiles[k];
var dx = x - tile.x;
var dy = y - tile.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < nearestDistanceSquared) {
nearestDistanceSquared = distanceSquared;
nearestTile = tile;
}
}
rat.x = nearestTile.x;
rat.y = nearestTile.y;
rats.push(rat);
game.addChild(rat);
// Remove unit from collected units
for (var i = 0; i < collectedUnits.length; i++) {
if (collectedUnits[i] === selectedCollectedUnit) {
collectedUnits.splice(i, 1);
break;
}
}
selectedCollectedUnit = null;
LK.getSound('ratPlace').play();
updateUI();
} else if (selectedRatType && canPlaceRat(x, y)) {
// Place basic rat
var ratCost = selectedRatType === 'warrior' ? 50 : 75;
if (cheesePoints >= ratCost) {
var rat;
if (selectedRatType === 'warrior') {
rat = new Rat();
} else {
rat = new ArcherRat();
}
// Snap to nearest placement tile
var nearestTile = null;
var nearestDistanceSquared = Infinity;
for (var k = 0; k < placementTiles.length; k++) {
var tile = placementTiles[k];
var dx = x - tile.x;
var dy = y - tile.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < nearestDistanceSquared) {
nearestDistanceSquared = distanceSquared;
nearestTile = tile;
}
}
rat.x = nearestTile.x;
rat.y = nearestTile.y;
rats.push(rat);
game.addChild(rat);
cheesePoints -= ratCost;
// Update placement indicator for this tile
for (var k = 0; k < placementTiles.length; k++) {
if (placementTiles[k].x === nearestTile.x && placementTiles[k].y === nearestTile.y) {
placementIndicators[k].tint = 0xFF6347; // Red tint to show occupied
placementIndicators[k].alpha = 0.2; // Make it more transparent
break;
}
}
LK.getSound('ratPlace').play();
// Show cost feedback
var costText = new Text2('-' + ratCost, {
size: 50,
fill: '#FFD700'
});
costText.anchor.set(0.5, 0.5);
costText.x = nearestTile.x;
costText.y = nearestTile.y - 80;
game.addChild(costText);
// Animate cost text
tween(costText, {
y: nearestTile.y - 120,
alpha: 0
}, {
duration: 1000
});
// Remove cost text after animation
LK.setTimeout(function () {
if (costText && costText.parent) {
costText.destroy();
}
}, 1000);
updateUI();
}
}
}
};
game.update = function () {
if (!gameStarted) return;
// Spawn cats during wave
if (waveInProgress && catsToSpawn > 0) {
spawnTimer++;
if (spawnTimer >= 90) {
spawnCat();
spawnTimer = 0;
}
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
if (bullet.reachedTarget) {
// Check collision with cats using squared distance (avoid sqrt)
var collisionDistanceSquared = 45 * 45; // 2025
if (bullet.isSplash) {
// Splash damage affects all cats in area
var splashDistanceSquared = 80 * 80; // 6400
for (var j = cats.length - 1; j >= 0; j--) {
var cat = cats[j];
var dx = bullet.x - cat.x;
var dy = bullet.y - cat.y;
if (dx * dx + dy * dy < splashDistanceSquared) {
cat.takeDamage(bullet.damage);
}
}
// Visual splash effect
LK.effects.flashScreen(0xFFA500, 200);
} else {
// Normal single target damage
for (var j = cats.length - 1; j >= 0; j--) {
var cat = cats[j];
var dx = bullet.x - cat.x;
var dy = bullet.y - cat.y;
if (dx * dx + dy * dy < collisionDistanceSquared) {
cat.takeDamage(bullet.damage);
break;
}
}
}
bullet.destroy();
bullets.splice(i, 1);
}
}
// Update cats and let them attack giant rats
for (var i = cats.length - 1; i >= 0; i--) {
var cat = cats[i];
if (cat.isDead) {
cheesePoints += cat.cheeseValue;
cat.destroy();
cats.splice(i, 1);
LK.getSound('catDefeat').play();
updateUI();
} else if (cat.reachedCheese) {
lives--;
cat.destroy();
cats.splice(i, 1);
updateUI();
if (lives <= 0) {
LK.showGameOver();
}
} else {
// Let cats attack giant, mega, tower and zombie rats that are close
for (var j = 0; j < rats.length; j++) {
var rat = rats[j];
if ((rat.ratType === 'giant' || rat.ratType === 'mega' || rat.ratType === 'tower' || rat.ratType === 'zombie') && rat.health && rat.health > 0) {
var dx = cat.x - rat.x;
var dy = cat.y - rat.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < 80 * 80 && LK.ticks % 60 === 0) {
rat.takeDamage(rat.ratType === 'zombie' ? 15 : 10);
LK.effects.flashObject(rat, 0xFF0000, 200);
}
}
}
// Check collision between zombie rats and cats
for (var j = 0; j < rats.length; j++) {
var rat = rats[j];
if (rat.ratType === 'zombie' && rat.health && rat.health > 0) {
var dx = cat.x - rat.x;
var dy = cat.y - rat.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < 60 * 60 && LK.ticks % 30 === 0) {
// Zombie rat damages cat
cat.takeDamage(15);
// Cat damages zombie rat
rat.takeDamage(10);
LK.effects.flashObject(cat, 0xFF0000, 200);
LK.effects.flashObject(rat, 0xFF0000, 200);
}
}
}
}
}
// Update rats and remove dead giant/mega/tower/zombie rats
for (var i = rats.length - 1; i >= 0; i--) {
var rat = rats[i];
if ((rat.ratType === 'giant' || rat.ratType === 'mega' || rat.ratType === 'tower' || rat.ratType === 'zombie') && rat.isDead) {
rat.destroy();
rats.splice(i, 1);
// Update placement indicator for this tile
for (var k = 0; k < placementTiles.length; k++) {
var tile = placementTiles[k];
var dx = rat.x - tile.x;
var dy = rat.y - tile.y;
if (dx * dx + dy * dy < 50 * 50) {
placementIndicators[k].tint = 0x90EE90;
placementIndicators[k].alpha = 0.6;
break;
}
}
}
}
// Check if wave is complete
if (waveInProgress && catsToSpawn === 0 && cats.length === 0) {
waveInProgress = false;
currentWave++;
if (currentWave > 10) {
LK.showYouWin();
} else {
cheesePoints += 25;
startWaveButton.setText('START WAVE');
startWaveButton.tint = 0x00FF00;
updateUI();
}
}
};
updateUI(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 25;
self.targetX = 0;
self.targetY = 0;
self.update = function () {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distanceSquared = dx * dx + dy * dy;
var speedSquared = self.speed * self.speed;
if (distanceSquared < speedSquared) {
self.x = self.targetX;
self.y = self.targetY;
self.reachedTarget = true;
} else {
var distance = Math.sqrt(distanceSquared);
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
return self;
});
var Cat = Container.expand(function () {
var self = Container.call(this);
self.catType = 'basic';
self.health = 50;
self.maxHealth = 50;
self.speed = 1;
self.pathIndex = 0;
self.cheeseValue = 10;
var graphics = self.attachAsset('catBasic', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
self.healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
// Health bar foreground
self.healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
self.update = function () {
if (self.pathIndex < gamePath.length) {
var targetTile = gamePath[self.pathIndex];
var dx = targetTile.x - self.x;
var dy = targetTile.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < 25) {
// 5 * 5 = 25
self.pathIndex++;
if (self.pathIndex >= gamePath.length) {
self.reachedCheese = true;
}
} else {
var distance = Math.sqrt(distanceSquared);
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.isDead = true;
LK.effects.flashObject(self, 0xFF0000, 200);
} else {
// Update health bar
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
}
};
return self;
});
var ZombieCat = Cat.expand(function () {
var self = Cat.call(this);
self.catType = 'zombie';
self.health = 40;
self.maxHealth = 40;
self.speed = 2.5;
self.cheeseValue = 20;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('catZombie', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x8B0000; // Dark red tint for zombie
graphics.alpha = 0.8; // Slightly transparent
return self;
});
var FastCat = Cat.expand(function () {
var self = Cat.call(this);
self.catType = 'fast';
self.health = 30;
self.maxHealth = 30;
self.speed = 2;
self.cheeseValue = 15;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('catFast', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
var FastCatBoss = FastCat.expand(function () {
var self = FastCat.call(this);
self.catType = 'fastboss';
self.health = 120;
self.maxHealth = 120;
self.speed = 1.5;
self.cheeseValue = 75;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('catFast', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.8,
scaleY: 1.8
});
graphics.tint = 0x4B0082; // Indigo tint for fast boss
return self;
});
var CatSummoner = Cat.expand(function () {
var self = Cat.call(this);
self.catType = 'summoner';
self.health = 150;
self.maxHealth = 150;
self.speed = 0.8;
self.cheeseValue = 60;
self.summonCooldown = 900; // 15 seconds at 60fps
self.lastSummon = 0;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('catSummoner', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
graphics.tint = 0x4B0082; // Dark purple tint for summoner
// Override update to handle summoning
var originalUpdate = self.update;
self.update = function () {
originalUpdate.call(self);
// Summon zombie cats every 15 seconds
if (LK.ticks - self.lastSummon > self.summonCooldown) {
for (var i = 0; i < 5; i++) {
var zombieCat = new ZombieCat();
// Spawn near the summoner with slight randomness
zombieCat.x = self.x + (Math.random() - 0.5) * 100;
zombieCat.y = self.y + (Math.random() - 0.5) * 100;
// Ensure they start on the path
zombieCat.pathIndex = self.pathIndex;
cats.push(zombieCat);
game.addChild(zombieCat);
// Visual summoning effect
LK.effects.flashObject(zombieCat, 0x8B0000, 500);
}
// Visual summoning effect on summoner
LK.effects.flashObject(self, 0x9932CC, 700);
self.lastSummon = LK.ticks;
}
};
return self;
});
var CatBoss = Cat.expand(function () {
var self = Cat.call(this);
self.catType = 'boss';
self.health = 200;
self.maxHealth = 200;
self.speed = 0.5;
self.cheeseValue = 50;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('catBoss', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
graphics.tint = 0x8B0000; // Dark red tint for boss
return self;
});
var CollectedUnitSlot = Container.expand(function (collectedUnit, index) {
var self = Container.call(this);
self.collectedUnit = collectedUnit;
self.isSelected = false;
var slotGraphics = self.attachAsset('inventorySlot', {
anchorX: 0.5,
anchorY: 0.5
});
slotGraphics.tint = collectedUnit.color;
slotGraphics.scaleX = 0.8;
slotGraphics.scaleY = 0.8;
// Add unit preview
var ratAsset = collectedUnit.type === 'archer' || collectedUnit.type === 'sniper' ? 'ratArcher' : collectedUnit.type === 'bomber' ? 'ratBomber' : collectedUnit.type === 'summoner' ? 'ratSummoner' : 'ratWarrior';
var ratPreview = self.attachAsset(ratAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: collectedUnit.type === 'tower' ? 1.2 : collectedUnit.type === 'mega' ? 1.0 : collectedUnit.type === 'giant' ? 0.8 : collectedUnit.type === 'electric' ? 0.6 : collectedUnit.type === 'shadow' ? 0.55 : collectedUnit.type === 'summoner' ? 0.65 : 0.5,
scaleY: collectedUnit.type === 'tower' ? 1.2 : collectedUnit.type === 'mega' ? 1.0 : collectedUnit.type === 'giant' ? 0.8 : collectedUnit.type === 'electric' ? 0.6 : collectedUnit.type === 'shadow' ? 0.55 : collectedUnit.type === 'summoner' ? 0.65 : 0.5,
y: -10
});
// Apply type-specific tints
if (collectedUnit.type === 'magic') {
ratPreview.tint = 0x9932CC; // Purple
} else if (collectedUnit.type === 'sniper') {
ratPreview.tint = 0x8B4513; // Brown
} else if (collectedUnit.type === 'bomber') {
ratPreview.tint = 0xFF6347; // Red-orange
} else if (collectedUnit.type === 'healer') {
ratPreview.tint = 0x00FF7F; // Spring green
} else if (collectedUnit.type === 'giant') {
ratPreview.tint = 0x8B0000; // Dark red
} else if (collectedUnit.type === 'mega') {
ratPreview.tint = 0x4B0082; // Indigo
} else if (collectedUnit.type === 'tower') {
ratPreview.tint = 0x2F4F4F; // Dark slate gray
} else if (collectedUnit.type === 'electric') {
ratPreview.tint = 0x00FFFF; // Cyan
} else if (collectedUnit.type === 'shadow') {
ratPreview.tint = 0x4B0082; // Dark purple
ratPreview.alpha = 0.7; // Semi-transparent
} else if (collectedUnit.type === 'summoner') {
ratPreview.tint = 0x4B0082; // Dark purple
}
// Add rarity text
var rarityText = new Text2(collectedUnit.rarity.charAt(0).toUpperCase(), {
size: 20,
fill: '#FFFFFF'
});
rarityText.anchor.set(0.5, 0.5);
rarityText.y = 25;
self.addChild(rarityText);
self.setSelected = function (selected) {
self.isSelected = selected;
slotGraphics.alpha = selected ? 0.8 : 1.0;
if (selected) {
slotGraphics.scaleX = 0.9;
slotGraphics.scaleY = 0.9;
} else {
slotGraphics.scaleX = 0.8;
slotGraphics.scaleY = 0.8;
}
};
self.down = function (x, y, obj) {
if (gameStarted && !upgradeMode) {
selectedCollectedUnit = self.collectedUnit;
selectedRatType = null; // Deselect basic rat types
// Update all inventory slots selection
for (var i = 0; i < inventorySlots.length; i++) {
inventorySlots[i].setSelected(false);
}
// Update all collected unit slots selection
updateCollectedUnitsDisplay();
}
};
return self;
});
var InventorySlot = Container.expand(function (ratType, cost) {
var self = Container.call(this);
self.ratType = ratType;
self.cost = cost;
self.isSelected = false;
var slotGraphics = self.attachAsset('inventorySlot', {
anchorX: 0.5,
anchorY: 0.5
});
// Add rat preview
var ratAsset = ratType === 'warrior' ? 'ratWarrior' : 'ratArcher';
var ratPreview = self.attachAsset(ratAsset, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6,
y: -15
});
// Add cost text
var costText = new Text2(cost.toString(), {
size: 25,
fill: '#FFD700'
});
costText.anchor.set(0.5, 0.5);
costText.y = 35;
self.addChild(costText);
// Add type label
var typeText = new Text2(ratType.toUpperCase(), {
size: 20,
fill: '#FFFFFF'
});
typeText.anchor.set(0.5, 0.5);
typeText.y = -45;
self.addChild(typeText);
self.setSelected = function (selected) {
self.isSelected = selected;
slotGraphics.tint = selected ? 0x00FF00 : 0xFFFFFF;
slotGraphics.alpha = selected ? 0.8 : 1.0;
};
self.canAfford = function () {
return cheesePoints >= self.cost;
};
self.updateDisplay = function () {
var affordable = self.canAfford();
ratPreview.alpha = affordable ? 1.0 : 0.5;
costText.tint = affordable ? 0xFFD700 : 0xFF0000;
};
self.down = function (x, y, obj) {
if (gameStarted && !upgradeMode) {
selectedRatType = self.ratType;
selectedCollectedUnit = null; // Deselect collected units
// Update all inventory slots selection
for (var i = 0; i < inventorySlots.length; i++) {
inventorySlots[i].setSelected(inventorySlots[i].ratType === selectedRatType);
}
// Update collected unit slots to deselect them
updateCollectedUnitsDisplay();
updateUI();
}
};
return self;
});
var MainMenu = Container.expand(function () {
var self = Container.call(this);
// Menu background
var menuBg = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0,
scaleX: 34,
scaleY: 91,
x: 0,
y: 0
});
menuBg.alpha = 0.8;
self.addChild(menuBg);
// Game title
var titleText = new Text2('RAT DEFENSE', {
size: 120,
fill: '#FFD700'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 800;
self.addChild(titleText);
// Subtitle
var subtitleText = new Text2('Defend Your Cheese!', {
size: 60,
fill: '#FFFFFF'
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 1024;
subtitleText.y = 950;
self.addChild(subtitleText);
// Start button
var startButton = new Text2('START GAME', {
size: 80,
fill: '#00FF00'
});
startButton.anchor.set(0.5, 0.5);
startButton.x = 1024;
startButton.y = 1400;
self.addChild(startButton);
// Instructions
var instructionsText = new Text2('Place rats to defend against cats\nUpgrade your rats for better defense', {
size: 45,
fill: '#CCCCCC'
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 1024;
instructionsText.y = 1600;
self.addChild(instructionsText);
startButton.down = function () {
self.destroy();
gameStarted = true;
initializeGame();
};
return self;
});
var Rat = Container.expand(function () {
var self = Container.call(this);
self.ratType = 'warrior';
self.range = 150;
self.damage = 25;
self.fireRate = 60;
self.lastShot = 0;
self.cost = 50;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (LK.ticks - self.lastShot > self.fireRate) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.lastShot = LK.ticks;
}
}
};
self.findTarget = function () {
var closestCat = null;
var closestDistanceSquared = Infinity;
var rangeSquared = self.range * self.range;
for (var i = 0; i < cats.length; i++) {
var cat = cats[i];
var dx = cat.x - self.x;
var dy = cat.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= rangeSquared && distanceSquared < closestDistanceSquared) {
closestDistanceSquared = distanceSquared;
closestCat = cat;
}
}
return closestCat;
};
self.shoot = function (target) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = target.x;
bullet.targetY = target.y;
bullet.damage = self.damage;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
};
self.upgrade = function () {
if (self.upgradeLevel < self.maxUpgrades) {
self.upgradeLevel++;
// Increase damage by 50% per upgrade
self.damage = Math.floor(self.damage * 1.5);
// Increase range by 25% per upgrade
self.range = Math.floor(self.range * 1.25);
// Visual indicator of upgrade
self.children[0].tint = self.upgradeLevel === 1 ? 0x00FF00 : self.upgradeLevel === 2 ? 0x0000FF : 0xFF00FF;
self.children[0].scaleX = 1 + self.upgradeLevel * 0.2;
self.children[0].scaleY = 1 + self.upgradeLevel * 0.2;
// Update range indicator if visible
self.updateRangeIndicator();
// Add upgrade level stars
self.showUpgradeStars();
return true;
}
return false;
};
self.getUpgradeCost = function () {
return (self.upgradeLevel + 1) * self.cost;
};
self.canUpgrade = function () {
return self.upgradeLevel < self.maxUpgrades;
};
self.showRangeIndicator = function () {
if (!self.rangeIndicator) {
self.rangeIndicator = LK.getAsset('rangeIndicator', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: self.range / 2,
scaleY: self.range / 2,
x: self.x,
y: self.y
});
self.rangeIndicator.alpha = 0.3;
game.addChild(self.rangeIndicator);
}
};
self.hideRangeIndicator = function () {
if (self.rangeIndicator) {
self.rangeIndicator.destroy();
self.rangeIndicator = null;
}
};
self.updateRangeIndicator = function () {
if (self.rangeIndicator) {
self.rangeIndicator.scaleX = self.range / 2;
self.rangeIndicator.scaleY = self.range / 2;
self.rangeIndicator.x = self.x;
self.rangeIndicator.y = self.y;
}
};
self.showUpgradeStars = function () {
// Remove existing stars
if (self.upgradeStars) {
for (var i = 0; i < self.upgradeStars.length; i++) {
self.upgradeStars[i].destroy();
}
}
self.upgradeStars = [];
// Add stars based on upgrade level
for (var i = 0; i < self.upgradeLevel; i++) {
var star = new Text2('★', {
size: 25,
fill: '#FFD700'
});
star.anchor.set(0.5, 0.5);
star.x = self.x - 30 + i * 20;
star.y = self.y - 60;
self.upgradeStars.push(star);
game.addChild(star);
}
};
return self;
});
var ZombieRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'zombie';
self.range = 100;
self.damage = 15;
self.fireRate = 50;
self.cost = 0; // Summoned, not bought
self.upgradeLevel = 0;
self.maxUpgrades = 0; // Cannot be upgraded
self.health = 75;
self.maxHealth = 75;
self.lifespan = 1800; // 30 seconds at 60fps
self.timeAlive = 0;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratZombie', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x8B4513; // Brown tint for zombie
graphics.alpha = 0.8; // Slightly transparent
// Add health bar for zombie rat
self.healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50,
scaleX: 1.0,
scaleY: 0.8
});
self.healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50,
scaleX: 1.0,
scaleY: 0.8
});
// Override update to handle lifespan and movement
var originalUpdate = self.update;
self.pathIndex = gamePath.length - 1; // Start from the end of the path
self.lastX = self.x;
self.lastY = self.y;
self.update = function () {
// Move along the path in reverse direction (towards cats)
if (self.pathIndex >= 0) {
var targetTile = gamePath[self.pathIndex];
var dx = targetTile.x - self.x;
var dy = targetTile.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < 25) {
// Reached current path point, move to previous one
self.pathIndex--;
if (self.pathIndex < 0) {
// Reached start of path, stay there
self.pathIndex = 0;
}
} else {
var distance = Math.sqrt(distanceSquared);
var moveSpeed = 1.5;
self.x += dx / distance * moveSpeed;
self.y += dy / distance * moveSpeed;
}
}
originalUpdate.call(self);
self.timeAlive++;
// Only die from lifespan if lifespan is not infinite
if (self.lifespan !== Infinity && self.timeAlive >= self.lifespan) {
self.isDead = true;
LK.effects.flashObject(self, 0x654321, 300);
}
};
// Zombie rat can take damage from cats
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.isDead = true;
LK.effects.flashObject(self, 0xFF0000, 300);
} else {
// Update health bar
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent * 1.0;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
}
};
return self;
});
var TowerRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'tower';
self.range = 500;
self.damage = 300;
self.fireRate = 180;
self.cost = 750;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.health = 500;
self.maxHealth = 500;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4.5,
scaleY: 4.5
});
graphics.tint = 0x2F4F4F; // Dark slate gray tint for tower rat
// Add health bar for tower rat
self.healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -150,
scaleX: 3.0,
scaleY: 2.5
});
self.healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -150,
scaleX: 3.0,
scaleY: 2.5
});
// Tower rat can take damage from cats
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.isDead = true;
LK.effects.flashObject(self, 0xFF0000, 750);
} else {
// Update health bar
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent * 3.0;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
}
};
return self;
});
var SummonerRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'summoner';
self.range = 200;
self.damage = 20;
self.fireRate = 120;
self.cost = 300;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.summonCooldown = 600; // 10 seconds
self.lastSummon = 0;
self.maxZombies = 2;
self.zombiesCount = 0;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratSummoner', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x4B0082; // Dark purple tint for summoner
// Override update to handle summoning
var originalUpdate = self.update;
self.update = function () {
originalUpdate.call(self);
// Count current zombies
self.zombiesCount = 0;
for (var i = 0; i < rats.length; i++) {
if (rats[i].ratType === 'zombie') {
self.zombiesCount++;
}
}
// Summon zombie if cooldown is ready and we need more
if (LK.ticks - self.lastSummon > self.summonCooldown && self.zombiesCount < self.maxZombies) {
// Find empty placement tile near cheese
var cheeseX = gamePath[gamePath.length - 1].x;
var cheeseY = gamePath[gamePath.length - 1].y;
var bestTile = null;
var bestDistance = Infinity;
for (var j = 0; j < placementTiles.length; j++) {
var tile = placementTiles[j];
var dx = cheeseX - tile.x;
var dy = cheeseY - tile.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if tile is free
var tileOccupied = false;
for (var k = 0; k < rats.length; k++) {
var ratDx = rats[k].x - tile.x;
var ratDy = rats[k].y - tile.y;
if (ratDx * ratDx + ratDy * ratDy < 50 * 50) {
tileOccupied = true;
break;
}
}
if (!tileOccupied && distance < bestDistance) {
bestDistance = distance;
bestTile = tile;
}
}
if (bestTile) {
// Summon zombie rat for free
var zombie = new ZombieRat();
zombie.x = bestTile.x;
zombie.y = bestTile.y;
// Make zombie permanent by removing lifespan
zombie.lifespan = Infinity;
rats.push(zombie);
game.addChild(zombie);
// Update placement indicator for this tile
for (var k = 0; k < placementTiles.length; k++) {
if (placementTiles[k].x === bestTile.x && placementTiles[k].y === bestTile.y) {
placementIndicators[k].tint = 0xFF6347; // Red tint to show occupied
placementIndicators[k].alpha = 0.2; // Make it more transparent
break;
}
}
// Visual summoning effect
LK.effects.flashObject(self, 0x9932CC, 500);
LK.effects.flashObject(zombie, 0x00FF00, 700);
self.lastSummon = LK.ticks;
updateUI();
}
}
};
return self;
});
var ShadowRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'shadow';
self.range = 180;
self.damage = 60;
self.fireRate = 75;
self.cost = 175;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.teleportCooldown = 300; // 5 seconds
self.lastTeleport = 0;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.1,
scaleY: 1.1
});
graphics.tint = 0x4B0082; // Dark purple tint for shadow
graphics.alpha = 0.7; // Semi-transparent for stealth effect
// Override findTarget to prioritize high-value targets
self.findTarget = function () {
var bestTarget = null;
var bestScore = 0;
var rangeSquared = self.range * self.range;
for (var i = 0; i < cats.length; i++) {
var cat = cats[i];
var dx = cat.x - self.x;
var dy = cat.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= rangeSquared) {
// Score based on health and cheese value
var score = cat.health + cat.cheeseValue * 2;
if (score > bestScore) {
bestScore = score;
bestTarget = cat;
}
}
}
return bestTarget;
};
// Override shoot to include teleportation ability
self.shoot = function (target) {
// Normal attack
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = target.x;
bullet.targetY = target.y;
bullet.damage = self.damage;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
// Teleport ability on cooldown
if (LK.ticks - self.lastTeleport > self.teleportCooldown) {
var teleportTargets = [];
for (var i = 0; i < cats.length; i++) {
var cat = cats[i];
var dx = cat.x - self.x;
var dy = cat.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > self.range && distance < self.range * 2) {
teleportTargets.push(cat);
}
}
if (teleportTargets.length > 0) {
// Teleport to attack distant target
var teleportTarget = teleportTargets[Math.floor(Math.random() * teleportTargets.length)];
// Find nearest placement tile to target
var nearestTile = null;
var nearestDistance = Infinity;
for (var j = 0; j < placementTiles.length; j++) {
var tile = placementTiles[j];
var tileDx = teleportTarget.x - tile.x;
var tileDy = teleportTarget.y - tile.y;
var tileDistance = Math.sqrt(tileDx * tileDx + tileDy * tileDy);
// Check if tile is free
var tileOccupied = false;
for (var k = 0; k < rats.length; k++) {
if (rats[k] !== self) {
var ratDx = rats[k].x - tile.x;
var ratDy = rats[k].y - tile.y;
if (ratDx * ratDx + ratDy * ratDy < 50 * 50) {
tileOccupied = true;
break;
}
}
}
if (!tileOccupied && tileDistance < nearestDistance) {
nearestDistance = tileDistance;
nearestTile = tile;
}
}
if (nearestTile) {
// Teleportation effect
LK.effects.flashObject(self, 0x9932CC, 300);
// Update old tile indicator
for (var k = 0; k < placementTiles.length; k++) {
var tile = placementTiles[k];
var dx = self.x - tile.x;
var dy = self.y - tile.y;
if (dx * dx + dy * dy < 50 * 50) {
placementIndicators[k].tint = 0x90EE90;
placementIndicators[k].alpha = 0.6;
break;
}
}
// Teleport to new position
self.x = nearestTile.x;
self.y = nearestTile.y;
// Update new tile indicator
for (var k = 0; k < placementTiles.length; k++) {
var tile = placementTiles[k];
if (tile.x === nearestTile.x && tile.y === nearestTile.y) {
placementIndicators[k].tint = 0xFF6347;
placementIndicators[k].alpha = 0.2;
break;
}
}
self.lastTeleport = LK.ticks;
LK.effects.flashObject(self, 0x4B0082, 500);
}
}
}
};
return self;
});
var MegaRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'mega';
self.range = 400;
self.damage = 200;
self.fireRate = 120;
self.cost = 500;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.health = 300;
self.maxHealth = 300;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.5,
scaleY: 3.5
});
graphics.tint = 0x4B0082; // Indigo tint for mega rat
// Add health bar for mega rat
self.healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -120,
scaleX: 2.0,
scaleY: 2.0
});
self.healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -120,
scaleX: 2.0,
scaleY: 2.0
});
// Mega rat can take damage from cats
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.isDead = true;
LK.effects.flashObject(self, 0xFF0000, 500);
} else {
// Update health bar
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent * 2.0;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
}
};
return self;
});
var MagicRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'magic';
self.range = 180;
self.damage = 35;
self.fireRate = 75;
self.cost = 100;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x9932CC; // Purple tint for magic
return self;
});
var HealerRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'healer';
self.range = 200;
self.damage = 0;
self.fireRate = 180;
self.cost = 80;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.healAmount = 25;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x00FF7F; // Spring green tint for healer
// Override findTarget to find damaged rats instead of cats
self.findTarget = function () {
var mostDamagedRat = null;
var lowestHealthPercent = 1.0;
var rangeSquared = self.range * self.range;
for (var i = 0; i < rats.length; i++) {
var rat = rats[i];
if (rat === self) continue; // Don't heal self
var dx = rat.x - self.x;
var dy = rat.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= rangeSquared && rat.health && rat.maxHealth) {
var healthPercent = rat.health / rat.maxHealth;
if (healthPercent < lowestHealthPercent && healthPercent < 1.0) {
lowestHealthPercent = healthPercent;
mostDamagedRat = rat;
}
}
}
return mostDamagedRat;
};
// Override shoot to heal instead of damage
self.shoot = function (target) {
if (target.health && target.maxHealth) {
target.health = Math.min(target.maxHealth, target.health + self.healAmount);
// Visual healing effect
LK.effects.flashObject(target, 0x00FF00, 300);
}
};
return self;
});
var GiantRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'giant';
self.range = 300;
self.damage = 100;
self.fireRate = 150;
self.cost = 250;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.health = 150;
self.maxHealth = 150;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 2.5
});
graphics.tint = 0x8B0000; // Dark red tint for giant
// Add health bar for giant rat
self.healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -80,
scaleX: 1.5,
scaleY: 1.5
});
self.healthBar = self.attachAsset('healthBar', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -80,
scaleX: 1.5,
scaleY: 1.5
});
// Giant rat can take damage from cats
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.isDead = true;
LK.effects.flashObject(self, 0xFF0000, 500);
} else {
// Update health bar
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent * 1.5;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
}
};
return self;
});
var ElectricRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'electric';
self.range = 250;
self.damage = 40;
self.fireRate = 90;
self.cost = 200;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratWarrior', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
graphics.tint = 0x00FFFF; // Cyan tint for electric
// Override shoot to create chain lightning
self.shoot = function (target) {
var chainTargets = [target];
var chainRange = 100;
var maxChains = 3;
// Find additional targets for chain lightning
for (var chain = 0; chain < maxChains; chain++) {
var lastTarget = chainTargets[chainTargets.length - 1];
var closestCat = null;
var closestDistanceSquared = Infinity;
var chainRangeSquared = chainRange * chainRange;
for (var i = 0; i < cats.length; i++) {
var cat = cats[i];
if (chainTargets.indexOf(cat) === -1) {
// Not already targeted
var dx = cat.x - lastTarget.x;
var dy = cat.y - lastTarget.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= chainRangeSquared && distanceSquared < closestDistanceSquared) {
closestDistanceSquared = distanceSquared;
closestCat = cat;
}
}
}
if (closestCat) {
chainTargets.push(closestCat);
} else {
break;
}
}
// Damage all targets in chain
for (var i = 0; i < chainTargets.length; i++) {
var damage = Math.floor(self.damage * (0.8 + 0.2 * (4 - i) / 4)); // Damage decreases per chain
chainTargets[i].takeDamage(damage);
LK.effects.flashObject(chainTargets[i], 0x00FFFF, 200);
}
// Visual lightning effect
LK.effects.flashScreen(0x00FFFF, 150);
LK.getSound('shoot').play();
};
return self;
});
var BomberRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'bomber';
self.range = 120;
self.damage = 15;
self.fireRate = 90;
self.cost = 125;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratBomber', {
anchorX: 0.5,
anchorY: 0.5
});
// Override shoot to create splash damage
self.shoot = function (target) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = target.x;
bullet.targetY = target.y;
bullet.damage = self.damage;
bullet.isSplash = true; // Mark as splash damage bullet
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
};
return self;
});
var ArcherRat = Rat.expand(function () {
var self = Rat.call(this);
self.ratType = 'archer';
self.range = 220;
self.damage = 20;
self.fireRate = 45;
self.cost = 75;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratArcher', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
var SniperRat = ArcherRat.expand(function () {
var self = ArcherRat.call(this);
self.ratType = 'sniper';
self.range = 350;
self.damage = 50;
self.fireRate = 120;
self.cost = 150;
self.upgradeLevel = 0;
self.maxUpgrades = 3;
self.removeChild(self.children[0]);
var graphics = self.attachAsset('ratArcher', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x8B4513; // Brown tint for sniper
return self;
});
var RollScreen = Container.expand(function () {
var self = Container.call(this);
// Background overlay
var overlay = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0,
scaleX: 34,
scaleY: 91,
x: 0,
y: 0
});
overlay.alpha = 0.9;
overlay.tint = 0x000033;
self.addChild(overlay);
// Title
var titleText = new Text2('SUMMON UNITS', {
size: 100,
fill: '#FFD700'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 400;
self.addChild(titleText);
// Roll result area
var resultContainer = new Container();
resultContainer.x = 1024;
resultContainer.y = 1000;
self.addChild(resultContainer);
// Single roll button
var singleRollButton = new Text2('SINGLE ROLL (25 Cheese)', {
size: 60,
fill: '#00FF00'
});
singleRollButton.anchor.set(0.5, 0.5);
singleRollButton.x = 512;
singleRollButton.y = 1600;
self.addChild(singleRollButton);
// Multi roll button
var multiRollButton = new Text2('MULTI ROLL x5 (100 Cheese)', {
size: 60,
fill: '#0080FF'
});
multiRollButton.anchor.set(0.5, 0.5);
multiRollButton.x = 1536;
multiRollButton.y = 1600;
self.addChild(multiRollButton);
// Close button
var closeButton = new Text2('CLOSE', {
size: 50,
fill: '#FF4444'
});
closeButton.anchor.set(0.5, 0.5);
closeButton.x = 1024;
closeButton.y = 2200;
self.addChild(closeButton);
// Roll rarities and chances
var rollTable = [{
type: 'warrior',
rarity: 'common',
chance: 0.3,
color: 0xCCCCCC
}, {
type: 'archer',
rarity: 'common',
chance: 0.17,
color: 0xCCCCCC
}, {
type: 'magic',
rarity: 'common',
chance: 0.10,
color: 0xCCCCCC
}, {
type: 'summoner',
rarity: 'common',
chance: 0.12,
color: 0xCCCCCC
}, {
type: 'bomber',
rarity: 'common',
chance: 0.11,
color: 0xCCCCCC
}, {
type: 'warrior',
rarity: 'rare',
chance: 0.08,
color: 0x0080FF
}, {
type: 'archer',
rarity: 'rare',
chance: 0.06,
color: 0x0080FF
}, {
type: 'magic',
rarity: 'rare',
chance: 0.05,
color: 0x0080FF
}, {
type: 'bomber',
rarity: 'rare',
chance: 0.04,
color: 0x0080FF
}, {
type: 'healer',
rarity: 'rare',
chance: 0.03,
color: 0x0080FF
}, {
type: 'warrior',
rarity: 'epic',
chance: 0.025,
color: 0x9C27B0
}, {
type: 'archer',
rarity: 'epic',
chance: 0.02,
color: 0x9C27B0
}, {
type: 'magic',
rarity: 'epic',
chance: 0.015,
color: 0x9C27B0
}, {
type: 'bomber',
rarity: 'epic',
chance: 0.012,
color: 0x9C27B0
}, {
type: 'healer',
rarity: 'epic',
chance: 0.01,
color: 0x9C27B0
}, {
type: 'sniper',
rarity: 'epic',
chance: 0.008,
color: 0x9C27B0
}, {
type: 'electric',
rarity: 'epic',
chance: 0.006,
color: 0x9C27B0
}, {
type: 'shadow',
rarity: 'epic',
chance: 0.004,
color: 0x9C27B0
}, {
type: 'summoner',
rarity: 'rare',
chance: 0.055,
color: 0x0080FF
}, {
type: 'warrior',
rarity: 'legendary',
chance: 0.015,
color: 0xFF8000
}, {
type: 'archer',
rarity: 'legendary',
chance: 0.01,
color: 0xFF8000
}, {
type: 'sniper',
rarity: 'legendary',
chance: 0.008,
color: 0xFF8000
}, {
type: 'magic',
rarity: 'legendary',
chance: 0.005,
color: 0xFF8000
}, {
type: 'bomber',
rarity: 'legendary',
chance: 0.003,
color: 0xFF8000
}, {
type: 'healer',
rarity: 'legendary',
chance: 0.002,
color: 0xFF8000
}, {
type: 'giant',
rarity: 'legendary',
chance: 0.001,
color: 0xFF8000
}, {
type: 'mega',
rarity: 'legendary',
chance: 0.0005,
color: 0xFF8000
}, {
type: 'tower',
rarity: 'legendary',
chance: 0.0002,
color: 0xFF8000
}];
self.performRoll = function () {
var random = Math.random();
var cumulative = 0;
for (var i = 0; i < rollTable.length; i++) {
cumulative += rollTable[i].chance;
if (random <= cumulative) {
return rollTable[i];
}
}
return rollTable[0]; // Fallback to common warrior
};
self.showRollResult = function (results) {
// Clear previous results
while (resultContainer.children.length > 0) {
resultContainer.removeChild(resultContainer.children[0]);
}
for (var i = 0; i < results.length; i++) {
var result = results[i];
var resultCard = new Container();
// Card background
var cardBg = LK.getAsset('inventorySlot', {
anchorX: 0.5,
anchorY: 0.5
});
cardBg.tint = result.color;
cardBg.scaleX = 1.5;
cardBg.scaleY = 1.5;
resultCard.addChild(cardBg);
// Unit preview
var unitAsset = result.type === 'archer' || result.type === 'sniper' ? 'ratArcher' : result.type === 'bomber' ? 'ratBomber' : result.type === 'summoner' ? 'ratSummoner' : 'ratWarrior';
var unitPreview = LK.getAsset(unitAsset, {
anchorX: 0.5,
anchorY: 0.5,
y: -20,
scaleX: result.type === 'tower' ? 2.5 : result.type === 'mega' ? 2.0 : result.type === 'giant' ? 1.5 : result.type === 'electric' ? 1.2 : result.type === 'shadow' ? 1.1 : result.type === 'summoner' ? 1.3 : 1.0,
scaleY: result.type === 'tower' ? 2.5 : result.type === 'mega' ? 2.0 : result.type === 'giant' ? 1.5 : result.type === 'electric' ? 1.2 : result.type === 'shadow' ? 1.1 : result.type === 'summoner' ? 1.3 : 1.0
});
// Apply type-specific tints
if (result.type === 'magic') {
unitPreview.tint = 0x9932CC; // Purple
} else if (result.type === 'sniper') {
unitPreview.tint = 0x8B4513; // Brown
} else if (result.type === 'bomber') {
unitPreview.tint = 0xFF6347; // Red-orange
} else if (result.type === 'healer') {
unitPreview.tint = 0x00FF7F; // Spring green
} else if (result.type === 'giant') {
unitPreview.tint = 0x8B0000; // Dark red
} else if (result.type === 'mega') {
unitPreview.tint = 0x4B0082; // Indigo
} else if (result.type === 'tower') {
unitPreview.tint = 0x2F4F4F; // Dark slate gray
} else if (result.type === 'electric') {
unitPreview.tint = 0x00FFFF; // Cyan
} else if (result.type === 'shadow') {
unitPreview.tint = 0x4B0082; // Dark purple
unitPreview.alpha = 0.7; // Semi-transparent
} else if (result.type === 'summoner') {
unitPreview.tint = 0x4B0082; // Dark purple
}
resultCard.addChild(unitPreview);
// Rarity text
var rarityText = new Text2(result.rarity.toUpperCase(), {
size: 25,
fill: '#FFFFFF'
});
rarityText.anchor.set(0.5, 0.5);
rarityText.y = 60;
resultCard.addChild(rarityText);
// Type text
var typeText = new Text2(result.type.toUpperCase(), {
size: 20,
fill: '#FFFFFF'
});
typeText.anchor.set(0.5, 0.5);
typeText.y = 85;
resultCard.addChild(typeText);
// Position cards
if (results.length === 1) {
resultCard.x = 0;
} else {
resultCard.x = (i - 2) * 200;
}
resultCard.y = 0;
// Scale animation
resultCard.scaleX = 0;
resultCard.scaleY = 0;
tween(resultCard, {
scaleX: 1,
scaleY: 1
}, {
duration: 500
});
resultContainer.addChild(resultCard);
// Add to player inventory based on rarity
var ratCost = result.type === 'warrior' ? 50 : 75;
if (result.rarity === 'rare') {
ratCost = Math.floor(ratCost * 0.8); // 20% discount for rare
} else if (result.rarity === 'epic') {
ratCost = Math.floor(ratCost * 0.7); // 30% discount for epic
} else if (result.rarity === 'legendary') {
ratCost = Math.floor(ratCost * 0.6); // 40% discount for legendary
}
// Add unit to collected units
var collectedUnit = {
type: result.type,
rarity: result.rarity,
color: result.color,
cost: ratCost
};
collectedUnits.push(collectedUnit);
// Give bonus cheese for higher rarities
if (result.rarity === 'rare') {
cheesePoints += 15;
} else if (result.rarity === 'epic') {
cheesePoints += 35;
} else if (result.rarity === 'legendary') {
cheesePoints += 50;
}
}
updateUI();
};
singleRollButton.down = function () {
if (cheesePoints >= 25) {
cheesePoints -= 25;
var result = self.performRoll();
self.showRollResult([result]);
LK.effects.flashScreen(0x00FF00, 200);
}
};
multiRollButton.down = function () {
if (cheesePoints >= 100) {
cheesePoints -= 100;
var results = [];
for (var i = 0; i < 5; i++) {
results.push(self.performRoll());
}
self.showRollResult(results);
LK.effects.flashScreen(0x0080FF, 300);
}
};
closeButton.down = function () {
self.destroy();
rollScreen = null;
};
return self;
});
var SummoningZone = Container.expand(function () {
var self = Container.call(this);
var zoneGraphics = self.attachAsset('summonZone', {
anchorX: 0.5,
anchorY: 0.5
});
zoneGraphics.alpha = 0.3;
var zoneText = new Text2('GACHA ROLLS', {
size: 40,
fill: '#FFFFFF'
});
zoneText.anchor.set(0.5, 0.5);
zoneText.y = -30;
self.addChild(zoneText);
var instructionText = new Text2('Tap to open roll screen', {
size: 25,
fill: '#CCCCCC'
});
instructionText.anchor.set(0.5, 0.5);
instructionText.y = 0;
self.addChild(instructionText);
var instructionText2 = new Text2('Place rats on green tiles anywhere', {
size: 25,
fill: '#90EE90'
});
instructionText2.anchor.set(0.5, 0.5);
instructionText2.y = 30;
self.addChild(instructionText2);
// Summon button for gacha rolls
var summonButton = new Text2('OPEN ROLLS', {
size: 35,
fill: '#FFD700'
});
summonButton.anchor.set(0.5, 0.5);
summonButton.y = 60;
self.addChild(summonButton);
self.down = function (x, y, obj) {
if (gameStarted && !upgradeMode) {
// Open roll screen for gacha summons
if (!rollScreen) {
rollScreen = new RollScreen();
game.addChild(rollScreen);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game state
var gameStarted = false;
var mainMenu = null;
// Center map offset for mobile display
var mapOffsetX = (2048 - 1200) / 2;
var mapOffsetY = 200;
var gamePath = [{
x: 40 + mapOffsetX,
y: 400 + mapOffsetY
}, {
x: 200 + mapOffsetX,
y: 400 + mapOffsetY
}, {
x: 200 + mapOffsetX,
y: 200 + mapOffsetY
}, {
x: 400 + mapOffsetX,
y: 200 + mapOffsetY
}, {
x: 400 + mapOffsetX,
y: 500 + mapOffsetY
}, {
x: 600 + mapOffsetX,
y: 500 + mapOffsetY
}, {
x: 600 + mapOffsetX,
y: 300 + mapOffsetY
}, {
x: 800 + mapOffsetX,
y: 300 + mapOffsetY
}, {
x: 800 + mapOffsetX,
y: 600 + mapOffsetY
}, {
x: 1000 + mapOffsetX,
y: 600 + mapOffsetY
}, {
x: 1000 + mapOffsetX,
y: 400 + mapOffsetY
}, {
x: 1200 + mapOffsetX,
y: 400 + mapOffsetY
}];
var placementTiles = [{
x: 280 + mapOffsetX,
y: 280 + mapOffsetY
}, {
x: 520 + mapOffsetX,
y: 380 + mapOffsetY
}, {
x: 680 + mapOffsetX,
y: 380 + mapOffsetY
}, {
x: 720 + mapOffsetX,
y: 200 + mapOffsetY
}, {
x: 880 + mapOffsetX,
y: 200 + mapOffsetY
}, {
x: 880 + mapOffsetX,
y: 500 + mapOffsetY
}, {
x: 1080 + mapOffsetX,
y: 500 + mapOffsetY
}, {
x: 280 + mapOffsetX,
y: 120 + mapOffsetY
}, {
x: 680 + mapOffsetX,
y: 580 + mapOffsetY
}, {
x: 360 + mapOffsetX,
y: 160 + mapOffsetY
}, {
x: 760 + mapOffsetX,
y: 160 + mapOffsetY
}, {
x: 1160 + mapOffsetX,
y: 160 + mapOffsetY
}];
var cats = [];
var rats = [];
var bullets = [];
var cheesePoints = 150;
var lives = 5;
var currentWave = 1;
var waveInProgress = false;
var catsToSpawn = 0;
var spawnTimer = 0;
var selectedRatType = 'warrior';
var selectedRat = null;
var upgradeMode = false;
// Initialize game variables
var pathIndicators = [];
var placementIndicators = [];
var cheese = null;
var cheeseText = null;
var livesText = null;
var waveText = null;
var startWaveButton = null;
var ratTypeButton = null;
var upgradeButton = null;
var upgradeInfoText = null;
var summoningZone = null;
var inventorySlots = [];
var rollScreen = null;
// Collected units from rolls
var collectedUnits = [];
var selectedCollectedUnit = null;
function startWave() {
if (!waveInProgress) {
waveInProgress = true;
catsToSpawn = 3 + currentWave * 2;
spawnTimer = 0;
startWaveButton.setText('WAVE IN PROGRESS');
startWaveButton.tint = 0xFF0000;
}
}
function spawnCat() {
var cat;
var random = Math.random();
// Spawn boss cats in later waves
if (currentWave >= 10 && random < 0.1) {
cat = new CatSummoner();
} else if (currentWave >= 8 && random < 0.15) {
cat = new FastCatBoss();
} else if (currentWave >= 5 && random < 0.2) {
cat = new CatBoss();
} else if (random < 0.3 && currentWave > 2) {
cat = new FastCat();
} else {
cat = new Cat();
}
cat.x = gamePath[0].x;
cat.y = gamePath[0].y;
cat.health += currentWave * 10;
cat.maxHealth = cat.health;
cats.push(cat);
game.addChild(cat);
catsToSpawn--;
}
var collectedUnitSlots = [];
function updateCollectedUnitsDisplay() {
// Remove existing collected unit slots
for (var i = 0; i < collectedUnitSlots.length; i++) {
collectedUnitSlots[i].destroy();
}
collectedUnitSlots = [];
// Create new slots for collected units
for (var i = 0; i < collectedUnits.length && i < 8; i++) {
var slot = new CollectedUnitSlot(collectedUnits[i], i);
slot.x = 500 + i % 4 * 100;
slot.y = 2400 + Math.floor(i / 4) * 100;
collectedUnitSlots.push(slot);
game.addChild(slot);
// Select if this is the selected unit
if (selectedCollectedUnit === collectedUnits[i]) {
slot.setSelected(true);
}
}
}
function updateUI() {
if (!cheeseText) return; // Don't update UI if elements aren't initialized yet
cheeseText.setText('Cheese: ' + cheesePoints);
livesText.setText('Lives: ' + lives);
waveText.setText('Wave: ' + currentWave);
if (selectedCollectedUnit) {
ratTypeButton.setText('COLLECTED: ' + selectedCollectedUnit.type.toUpperCase() + ' (' + selectedCollectedUnit.rarity + ')');
} else if (selectedRatType) {
var ratCost = selectedRatType === 'warrior' ? 50 : 75;
ratTypeButton.setText('RAT: ' + selectedRatType.toUpperCase() + ' (' + ratCost + ')');
} else {
ratTypeButton.setText('SELECT A UNIT');
}
upgradeButton.setText('UPGRADE MODE: ' + (upgradeMode ? 'ON' : 'OFF'));
upgradeButton.tint = upgradeMode ? 0x00FF00 : 0x9932CC;
if (selectedRat && upgradeMode) {
var upgradeCost = selectedRat.getUpgradeCost();
var canAfford = cheesePoints >= upgradeCost;
var canUpgrade = selectedRat.canUpgrade();
if (canUpgrade) {
upgradeInfoText.setText('UPGRADE (' + upgradeCost + ') - DMG:' + Math.floor(selectedRat.damage * 1.5) + ' RNG:' + Math.floor(selectedRat.range * 1.25));
upgradeInfoText.tint = canAfford ? 0x00FF00 : 0xFF0000;
} else {
upgradeInfoText.setText('MAX LEVEL REACHED');
upgradeInfoText.tint = 0xFFFF00;
}
} else {
upgradeInfoText.setText(upgradeMode ? 'SELECT A RAT TO UPGRADE' : '');
upgradeInfoText.tint = 0xFFFFFF;
}
// Update inventory slots
if (inventorySlots && inventorySlots.length > 0) {
for (var i = 0; i < inventorySlots.length; i++) {
inventorySlots[i].updateDisplay();
}
}
// Update collected units display
updateCollectedUnitsDisplay();
}
function canPlaceRat(x, y) {
var placementDistanceSquared = 120 * 120; // 14400 - even more forgiving radius for easier targeting
for (var i = 0; i < placementTiles.length; i++) {
var tile = placementTiles[i];
var dx = x - tile.x;
var dy = y - tile.y;
if (dx * dx + dy * dy < placementDistanceSquared) {
// Check if tile is already occupied
for (var j = 0; j < rats.length; j++) {
var ratDx = rats[j].x - tile.x;
var ratDy = rats[j].y - tile.y;
if (ratDx * ratDx + ratDy * ratDy < 50 * 50) {
// Slightly reduced occupied check radius for tighter packing
return false;
}
}
return true;
}
}
return false;
}
function initializeGame() {
// Create visual path indicators to show where cats walk
pathIndicators = [];
for (var i = 0; i < gamePath.length; i++) {
var pathPoint = gamePath[i];
var indicator = LK.getAsset('pathIndicator', {
anchorX: 0.5,
anchorY: 0.5,
x: pathPoint.x,
y: pathPoint.y
});
indicator.alpha = 0.8;
pathIndicators.push(indicator);
game.addChild(indicator);
// Add directional arrows between path points
if (i < gamePath.length - 1) {
var nextPoint = gamePath[i + 1];
var arrow = LK.getAsset('pathArrow', {
anchorX: 0.5,
anchorY: 0.5,
x: (pathPoint.x + nextPoint.x) / 2,
y: (pathPoint.y + nextPoint.y) / 2
});
// Rotate arrow to point in direction of movement
var dx = nextPoint.x - pathPoint.x;
var dy = nextPoint.y - pathPoint.y;
arrow.rotation = Math.atan2(dy, dx);
arrow.alpha = 0.7;
pathIndicators.push(arrow);
game.addChild(arrow);
}
}
// Create visual indicators for placement tiles
placementIndicators = [];
for (var i = 0; i < placementTiles.length; i++) {
var tile = placementTiles[i];
var indicator = LK.getAsset('summonZone', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6,
x: tile.x,
y: tile.y
});
indicator.alpha = 0.6;
indicator.tint = 0x90EE90;
placementIndicators.push(indicator);
game.addChild(indicator);
}
// Create cheese at the end
cheese = LK.getAsset('cheese', {
anchorX: 0.5,
anchorY: 0.5,
x: gamePath[gamePath.length - 1].x,
y: gamePath[gamePath.length - 1].y
});
game.addChild(cheese);
// UI Elements
cheeseText = new Text2('Cheese: ' + cheesePoints, {
size: 60,
fill: '#FFD700'
});
cheeseText.anchor.set(0, 0);
cheeseText.x = 120;
LK.gui.topLeft.addChild(cheeseText);
livesText = new Text2('Lives: ' + lives, {
size: 60,
fill: '#FF6347'
});
livesText.anchor.set(0, 0);
livesText.x = 120;
livesText.y = 80;
LK.gui.topLeft.addChild(livesText);
waveText = new Text2('Wave: ' + currentWave, {
size: 60,
fill: '#87CEEB'
});
waveText.anchor.set(0, 0);
waveText.x = 120;
waveText.y = 160;
LK.gui.topLeft.addChild(waveText);
startWaveButton = new Text2('START WAVE', {
size: 70,
fill: '#00FF00'
});
startWaveButton.anchor.set(0.5, 0.5);
startWaveButton.down = function () {
startWave();
};
LK.gui.bottom.addChild(startWaveButton);
ratTypeButton = new Text2('RAT: WARRIOR (50)', {
size: 55,
fill: '#8B4513'
});
ratTypeButton.anchor.set(0.5, 0.5);
ratTypeButton.y = -120;
ratTypeButton.down = function () {
// Cycle through rat types and update selection
if (selectedRatType === 'warrior') {
selectedRatType = 'archer';
selectedCollectedUnit = null;
// Update inventory slots
for (var i = 0; i < inventorySlots.length; i++) {
inventorySlots[i].setSelected(inventorySlots[i].ratType === 'archer');
}
} else {
selectedRatType = 'warrior';
selectedCollectedUnit = null;
// Update inventory slots
for (var i = 0; i < inventorySlots.length; i++) {
inventorySlots[i].setSelected(inventorySlots[i].ratType === 'warrior');
}
}
updateCollectedUnitsDisplay();
updateUI();
};
LK.gui.bottom.addChild(ratTypeButton);
upgradeButton = new Text2('UPGRADE MODE: OFF', {
size: 50,
fill: '#9932CC'
});
upgradeButton.anchor.set(0.5, 0.5);
upgradeButton.y = -200;
upgradeButton.down = function () {
upgradeMode = !upgradeMode;
selectedRat = null;
updateUI();
};
LK.gui.bottom.addChild(upgradeButton);
upgradeInfoText = new Text2('', {
size: 45,
fill: '#FFFFFF'
});
upgradeInfoText.anchor.set(0.5, 0.5);
upgradeInfoText.y = -280;
LK.gui.bottom.addChild(upgradeInfoText);
// Create summoning zone
summoningZone = new SummoningZone();
summoningZone.x = 1024;
summoningZone.y = 1800;
game.addChild(summoningZone);
// Create inventory slots
inventorySlots = [];
var warriorSlot = new InventorySlot('warrior', 50);
warriorSlot.x = 200;
warriorSlot.y = 2400;
inventorySlots.push(warriorSlot);
game.addChild(warriorSlot);
var archerSlot = new InventorySlot('archer', 75);
archerSlot.x = 350;
archerSlot.y = 2400;
inventorySlots.push(archerSlot);
game.addChild(archerSlot);
// Set initial selection - auto-select warrior for immediate placement
selectedRatType = 'warrior';
selectedCollectedUnit = null;
warriorSlot.setSelected(true);
// Show helpful message
var helpText = new Text2('Tap anywhere on green tiles to place rats!', {
size: 40,
fill: '#90EE90'
});
helpText.anchor.set(0.5, 0.5);
helpText.x = 1024;
helpText.y = 1000;
game.addChild(helpText);
// Fade out help text after 5 seconds
LK.setTimeout(function () {
if (helpText && helpText.parent) {
tween(helpText, {
alpha: 0
}, {
duration: 1000
});
}
}, 5000);
updateUI();
}
function placeRat(x, y) {
var ratCost = selectedRatType === 'warrior' ? 50 : 75;
if (cheesePoints >= ratCost && canPlaceRat(x, y)) {
var rat;
if (selectedRatType === 'warrior') {
rat = new Rat();
} else {
rat = new ArcherRat();
}
// Snap to nearest placement tile
var nearestTile = null;
var nearestDistanceSquared = Infinity;
for (var i = 0; i < placementTiles.length; i++) {
var tile = placementTiles[i];
var dx = x - tile.x;
var dy = y - tile.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < nearestDistanceSquared) {
nearestDistanceSquared = distanceSquared;
nearestTile = tile;
}
}
rat.x = nearestTile.x;
rat.y = nearestTile.y;
rats.push(rat);
game.addChild(rat);
cheesePoints -= ratCost;
// Update placement indicator for this tile
for (var k = 0; k < placementTiles.length; k++) {
if (placementTiles[k].x === nearestTile.x && placementTiles[k].y === nearestTile.y) {
placementIndicators[k].tint = 0xFF6347; // Red tint to show occupied
placementIndicators[k].alpha = 0.2; // Make it more transparent
break;
}
}
LK.getSound('ratPlace').play();
updateUI();
}
}
// Show main menu initially
mainMenu = new MainMenu();
game.addChild(mainMenu);
game.down = function (x, y, obj) {
if (!gameStarted) return;
if (upgradeMode) {
// Check if clicking on a rat for upgrade
var clickedRat = null;
var clickDistanceSquared = 60 * 60; // 3600
for (var i = 0; i < rats.length; i++) {
var rat = rats[i];
var dx = x - rat.x;
var dy = y - rat.y;
if (dx * dx + dy * dy < clickDistanceSquared) {
clickedRat = rat;
break;
}
}
if (clickedRat) {
if (selectedRat === clickedRat) {
// Clicking same rat again - perform upgrade
if (clickedRat.canUpgrade() && cheesePoints >= clickedRat.getUpgradeCost()) {
cheesePoints -= clickedRat.getUpgradeCost();
clickedRat.upgrade();
LK.effects.flashObject(clickedRat, 0x00FF00, 500);
updateUI();
}
} else {
// Select this rat
// Hide range indicator for previously selected rat
if (selectedRat) {
selectedRat.hideRangeIndicator();
}
selectedRat = clickedRat;
// Remove selection indicator from all rats
for (var j = 0; j < rats.length; j++) {
rats[j].children[0].alpha = 1.0;
rats[j].hideRangeIndicator();
}
// Add selection indicator and show range
selectedRat.children[0].alpha = 0.7;
selectedRat.showRangeIndicator();
updateUI();
}
} else {
// Clicked empty space - deselect
if (selectedRat) {
selectedRat.hideRangeIndicator();
}
selectedRat = null;
for (var j = 0; j < rats.length; j++) {
rats[j].children[0].alpha = 1.0;
rats[j].hideRangeIndicator();
}
updateUI();
}
} else {
// Try to place a rat if we have one selected and can place it
if (selectedCollectedUnit && canPlaceRat(x, y)) {
// Place collected unit
var rat;
if (selectedCollectedUnit.type === 'warrior') {
rat = new Rat();
} else if (selectedCollectedUnit.type === 'archer') {
rat = new ArcherRat();
} else if (selectedCollectedUnit.type === 'magic') {
rat = new MagicRat();
} else if (selectedCollectedUnit.type === 'sniper') {
rat = new SniperRat();
} else if (selectedCollectedUnit.type === 'bomber') {
rat = new BomberRat();
} else if (selectedCollectedUnit.type === 'healer') {
rat = new HealerRat();
} else if (selectedCollectedUnit.type === 'giant') {
rat = new GiantRat();
} else if (selectedCollectedUnit.type === 'mega') {
rat = new MegaRat();
} else if (selectedCollectedUnit.type === 'tower') {
rat = new TowerRat();
} else if (selectedCollectedUnit.type === 'electric') {
rat = new ElectricRat();
} else if (selectedCollectedUnit.type === 'shadow') {
rat = new ShadowRat();
} else if (selectedCollectedUnit.type === 'summoner') {
rat = new SummonerRat();
} else {
// Default fallback for any unhandled types
rat = new Rat();
}
// Apply rarity bonuses
if (selectedCollectedUnit.rarity === 'rare') {
rat.damage = Math.floor(rat.damage * 1.3);
rat.range = Math.floor(rat.range * 1.2);
rat.children[0].tint = 0x0080FF;
} else if (selectedCollectedUnit.rarity === 'epic') {
rat.damage = Math.floor(rat.damage * 1.5);
rat.range = Math.floor(rat.range * 1.35);
rat.children[0].tint = 0x9C27B0;
} else if (selectedCollectedUnit.rarity === 'legendary') {
rat.damage = Math.floor(rat.damage * 1.8);
rat.range = Math.floor(rat.range * 1.5);
rat.children[0].tint = 0xFF8000;
}
// Snap to nearest placement tile
var nearestTile = null;
var nearestDistanceSquared = Infinity;
for (var k = 0; k < placementTiles.length; k++) {
var tile = placementTiles[k];
var dx = x - tile.x;
var dy = y - tile.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < nearestDistanceSquared) {
nearestDistanceSquared = distanceSquared;
nearestTile = tile;
}
}
rat.x = nearestTile.x;
rat.y = nearestTile.y;
rats.push(rat);
game.addChild(rat);
// Remove unit from collected units
for (var i = 0; i < collectedUnits.length; i++) {
if (collectedUnits[i] === selectedCollectedUnit) {
collectedUnits.splice(i, 1);
break;
}
}
selectedCollectedUnit = null;
LK.getSound('ratPlace').play();
updateUI();
} else if (selectedRatType && canPlaceRat(x, y)) {
// Place basic rat
var ratCost = selectedRatType === 'warrior' ? 50 : 75;
if (cheesePoints >= ratCost) {
var rat;
if (selectedRatType === 'warrior') {
rat = new Rat();
} else {
rat = new ArcherRat();
}
// Snap to nearest placement tile
var nearestTile = null;
var nearestDistanceSquared = Infinity;
for (var k = 0; k < placementTiles.length; k++) {
var tile = placementTiles[k];
var dx = x - tile.x;
var dy = y - tile.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < nearestDistanceSquared) {
nearestDistanceSquared = distanceSquared;
nearestTile = tile;
}
}
rat.x = nearestTile.x;
rat.y = nearestTile.y;
rats.push(rat);
game.addChild(rat);
cheesePoints -= ratCost;
// Update placement indicator for this tile
for (var k = 0; k < placementTiles.length; k++) {
if (placementTiles[k].x === nearestTile.x && placementTiles[k].y === nearestTile.y) {
placementIndicators[k].tint = 0xFF6347; // Red tint to show occupied
placementIndicators[k].alpha = 0.2; // Make it more transparent
break;
}
}
LK.getSound('ratPlace').play();
// Show cost feedback
var costText = new Text2('-' + ratCost, {
size: 50,
fill: '#FFD700'
});
costText.anchor.set(0.5, 0.5);
costText.x = nearestTile.x;
costText.y = nearestTile.y - 80;
game.addChild(costText);
// Animate cost text
tween(costText, {
y: nearestTile.y - 120,
alpha: 0
}, {
duration: 1000
});
// Remove cost text after animation
LK.setTimeout(function () {
if (costText && costText.parent) {
costText.destroy();
}
}, 1000);
updateUI();
}
}
}
};
game.update = function () {
if (!gameStarted) return;
// Spawn cats during wave
if (waveInProgress && catsToSpawn > 0) {
spawnTimer++;
if (spawnTimer >= 90) {
spawnCat();
spawnTimer = 0;
}
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
if (bullet.reachedTarget) {
// Check collision with cats using squared distance (avoid sqrt)
var collisionDistanceSquared = 45 * 45; // 2025
if (bullet.isSplash) {
// Splash damage affects all cats in area
var splashDistanceSquared = 80 * 80; // 6400
for (var j = cats.length - 1; j >= 0; j--) {
var cat = cats[j];
var dx = bullet.x - cat.x;
var dy = bullet.y - cat.y;
if (dx * dx + dy * dy < splashDistanceSquared) {
cat.takeDamage(bullet.damage);
}
}
// Visual splash effect
LK.effects.flashScreen(0xFFA500, 200);
} else {
// Normal single target damage
for (var j = cats.length - 1; j >= 0; j--) {
var cat = cats[j];
var dx = bullet.x - cat.x;
var dy = bullet.y - cat.y;
if (dx * dx + dy * dy < collisionDistanceSquared) {
cat.takeDamage(bullet.damage);
break;
}
}
}
bullet.destroy();
bullets.splice(i, 1);
}
}
// Update cats and let them attack giant rats
for (var i = cats.length - 1; i >= 0; i--) {
var cat = cats[i];
if (cat.isDead) {
cheesePoints += cat.cheeseValue;
cat.destroy();
cats.splice(i, 1);
LK.getSound('catDefeat').play();
updateUI();
} else if (cat.reachedCheese) {
lives--;
cat.destroy();
cats.splice(i, 1);
updateUI();
if (lives <= 0) {
LK.showGameOver();
}
} else {
// Let cats attack giant, mega, tower and zombie rats that are close
for (var j = 0; j < rats.length; j++) {
var rat = rats[j];
if ((rat.ratType === 'giant' || rat.ratType === 'mega' || rat.ratType === 'tower' || rat.ratType === 'zombie') && rat.health && rat.health > 0) {
var dx = cat.x - rat.x;
var dy = cat.y - rat.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < 80 * 80 && LK.ticks % 60 === 0) {
rat.takeDamage(rat.ratType === 'zombie' ? 15 : 10);
LK.effects.flashObject(rat, 0xFF0000, 200);
}
}
}
// Check collision between zombie rats and cats
for (var j = 0; j < rats.length; j++) {
var rat = rats[j];
if (rat.ratType === 'zombie' && rat.health && rat.health > 0) {
var dx = cat.x - rat.x;
var dy = cat.y - rat.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared < 60 * 60 && LK.ticks % 30 === 0) {
// Zombie rat damages cat
cat.takeDamage(15);
// Cat damages zombie rat
rat.takeDamage(10);
LK.effects.flashObject(cat, 0xFF0000, 200);
LK.effects.flashObject(rat, 0xFF0000, 200);
}
}
}
}
}
// Update rats and remove dead giant/mega/tower/zombie rats
for (var i = rats.length - 1; i >= 0; i--) {
var rat = rats[i];
if ((rat.ratType === 'giant' || rat.ratType === 'mega' || rat.ratType === 'tower' || rat.ratType === 'zombie') && rat.isDead) {
rat.destroy();
rats.splice(i, 1);
// Update placement indicator for this tile
for (var k = 0; k < placementTiles.length; k++) {
var tile = placementTiles[k];
var dx = rat.x - tile.x;
var dy = rat.y - tile.y;
if (dx * dx + dy * dy < 50 * 50) {
placementIndicators[k].tint = 0x90EE90;
placementIndicators[k].alpha = 0.6;
break;
}
}
}
}
// Check if wave is complete
if (waveInProgress && catsToSpawn === 0 && cats.length === 0) {
waveInProgress = false;
currentWave++;
if (currentWave > 10) {
LK.showYouWin();
} else {
cheesePoints += 25;
startWaveButton.setText('START WAVE');
startWaveButton.tint = 0x00FF00;
updateUI();
}
}
};
updateUI();
Cat. In-Game asset. 2d. High contrast. No shadows
Chese. In-Game asset. 2d. High contrast. No shadows
Rat with a arco. In-Game asset. 2d. High contrast. No shadows
Rata con pistola. In-Game asset. 2d. High contrast. No shadows
bomberRat. In-Game asset. 2d. High contrast. No shadows
zombie rat. In-Game asset. 2d. High contrast. No shadows
wizard rat. In-Game asset. 2d. High contrast. No shadows
wizard cat. In-Game asset. 2d. High contrast. No shadows
zombie ca. In-Game asset. 2d. High contrast. No shadows
strong cat. In-Game asset. 2d. High contrast. No shadows