User prompt
Oyunun ilk haline çevir yolu
User prompt
Yolu virajlarda yap 4 tane biris sağa biris sol biridaha sağ biridaha sol
User prompt
Yolu değiştir 3 tane kıvrım olsun
User prompt
Toplamda 12 tane unit koyalım ve yolları daha çok kıvrımlı yap
User prompt
15 wawe 20 wawe arası sadece kırmızı zombş gelsin
User prompt
Yeşil zombi oldurdunde 1 para kırmızı zombş oldurdunde 2 para gelsin
User prompt
4. Upraded değiştir 1 rer 1 rer vursun hasarı 8 olsun 5. Upgraded 2 tane aynanda atsın her birisi 7.25şar 7.25 şar vursun toplam hasar 15 4. Upgraded fiyatı 50 5. Upgraded fiyatı 100
User prompt
add 3rd upgraded to units, red unit should be added to right and left arm and should hit with its arm and hit 2 times in a row, its damage should be 10, its money should be 30, the price of 2nd upgraded should be 20, the price of 1st upgraded should be 10
User prompt
10 waves, 1 red zombie comes. 11 waves, 2 red zombies come. 1 red zombie comes every round until 20 waves.
User prompt
After the 5th wave, both green and red zombies will come mixed. From wave 10 to wave 20, only red will come.
User prompt
Red zombş heatlh 10
User prompt
Green zombşe health 5
User prompt
Upgraded ettiniz zaman unitlerin tipi değişsin görselleri değişsin
User prompt
Wawe 5 getir zaman 1 kırmızı zombi gelsin sonra wawe 6 2 tane sonra 3 tane sen kafam random ama ilk 5 ile 8 tur arasında en fazla 4 kırmızı zombi gelsin
User prompt
Wawe 5 ten sonra kırmızı zombiler gelsin normal yeşil zombilerin 2x canı olsun
User prompt
Üstlerinde can barı olsun
User prompt
Upgraded 1 fiyat 5 upgraded 2 fiyatı 10
User prompt
Asker koydunuz zaman bidaha ustune tınladımız zaman upgraded cıksın upgraded edince daha çok hasar versin
Code edit (1 edits merged)
Please save this source code
User prompt
Curvy Path Defense
Initial prompt
Towet defense Birtane yol olsun kıvrımlı her tur daha güçlü daha cok canı olan zom iler gelsin yolu takip etsinler yolu en sonunda base olsun eyeri base canı 100 0 ulaşırsa defeay diye bir yazı cıksın bizimde duvarların üstünde asker koyalım sol alta askerler olsun 4 tane 1. Si para 10 olsun oyuna 10 parayla basliyalım her zombi ayrı puan versin daha güçlüler daha çok puan versin 1. 10 para hasarı 20 2. Para 25 hasarı 50 3. Para 50 hasarı 100 4. Son olarak para 250 hasarı 125
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Bullet class var Bullet = Container.expand(function () { var self = Container.call(this); self.target = null; self.speed = 18; self.damage = 1; self.type = 1; self.active = true; self.shooter = null; self.setType = function (type) { self.type = type; var assetId = 'bullet' + type; self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); }; self.update = function () { if (!self.active || !self.target || self.target.dead) { self.destroy(); return; } // Move toward target var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 30) { // Hit self.target.takeDamage(self.damage); self.destroy(); return; } var step = self.speed / (dist || 1); self.x += dx * step; self.y += dy * step; }; return self; }); // Soldier class var Soldier = Container.expand(function () { var self = Container.call(this); self.type = 1; self.fireRate = 60; // ticks between shots self.damage = 1; self.range = 350; self.cooldown = 0; self.level = 1; self.wallSpot = null; self.setType = function (type) { self.type = type; // Remove all previous children (visuals) while (self.children && self.children.length > 0) { self.removeChild(self.children[0]); } var assetId = 'soldier' + type; self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Set stats if (type === 1) { self.fireRate = 60; self.damage = 1; self.range = 350; self.dualHit = false; self.money = 3; } if (type === 2) { self.fireRate = 40; self.damage = 1; self.range = 300; self.dualHit = false; self.money = 10; } if (type === 3) { self.fireRate = 90; self.damage = 3; self.range = 320; self.dualHit = false; self.money = 20; } if (type === 4) { self.fireRate = 60; self.damage = 8; self.range = 400; self.dualHit = false; // 4th upgrade: single hit self.money = 50; } }; self.update = function () { if (self.cooldown > 0) { self.cooldown--; return; } // Find nearest zombie in range var nearest = null; var minDist = 99999; for (var i = 0; i < zombies.length; ++i) { var z = zombies[i]; if (z.dead) continue; var dx = z.x - self.x; var dy = z.y - self.y; var d = Math.sqrt(dx * dx + dy * dy); if (d < self.range && d < minDist) { minDist = d; nearest = z; } } if (nearest) { // Shoot if (self.type === 4 && !self.dualHit) { // 4th upgrade: single hit, 8 dmg var bullet = new Bullet(); bullet.setType(self.type); bullet.x = self.x; bullet.y = self.y; bullet.target = nearest; bullet.damage = self.damage; bullet.shooter = self; bullets.push(bullet); game.addChild(bullet); self.cooldown = self.fireRate; } else if (self.type === 4 && self.dualHit) { // (legacy, should not occur, but keep for safety) for (var i = 0; i < 2; ++i) { var bullet = new Bullet(); bullet.setType(self.type); bullet.x = self.x + (i === 0 ? -30 : 30); bullet.y = self.y; bullet.target = nearest; bullet.damage = self.damage; bullet.shooter = self; bullets.push(bullet); game.addChild(bullet); } self.cooldown = self.fireRate; } else { var bullet = new Bullet(); bullet.setType(self.type); bullet.x = self.x; bullet.y = self.y; bullet.target = nearest; bullet.damage = self.damage; bullet.shooter = self; bullets.push(bullet); game.addChild(bullet); self.cooldown = self.fireRate; } } }; return self; }); // Zombie class var Zombie = Container.expand(function () { var self = Container.call(this); self.isRed = false; // Will be set after construction if needed var sprite = self.attachAsset('zombie', { anchorX: 0.5, anchorY: 0.5 }); // Health bar background var hpBarBg = LK.getAsset('pathnode', { anchorX: 0.5, anchorY: 0.5, width: 60 * 1.5, height: 14 * 1.5, y: -60, alpha: 0.25, color: 0x000000 }); self.setRed = function () { if (!self.isRed) { self.isRed = true; // Remove old sprite if (sprite && sprite.parent === self) self.removeChild(sprite); // Attach red zombie asset var redSprite = self.attachAsset('zombieRed', { anchorX: 0.5, anchorY: 0.5 }); } }; // Add yellow zombie support self.isYellow = false; self.setYellow = function () { if (!self.isYellow) { self.isYellow = true; // Remove old sprite if (sprite && sprite.parent === self) self.removeChild(sprite); // Attach yellow zombie asset var yellowSprite = self.attachAsset('zombieYellow', { anchorX: 0.5, anchorY: 0.5 }); } }; self.addChild(hpBarBg); // Health bar foreground var hpBar = LK.getAsset('pathnode', { anchorX: 0.5, anchorY: 0.5, width: 56 * 1.5, height: 10 * 1.5, y: -60, color: 0x44ff44, // green alpha: 0.85 }); self.addChild(hpBar); self.speed = 1.2; self.maxHp = 5; self.hp = 5; self.reward = 1; self.pathIndex = 0; self.progress = 0; // 0-1 between path[pathIndex] and path[pathIndex+1] self.targetX = 0; self.targetY = 0; self.dead = false; self.setStats = function (hp, speed, reward) { self.maxHp = hp; self.hp = hp; self.speed = speed; self.reward = reward; // Update health bar on spawn if (hpBar) { hpBar.width = 56 * 1.5; } }; self.setPathIndex = function (idx) { self.pathIndex = idx; self.progress = 0; }; self.update = function () { if (self.dead) return; // Move along path if (self.pathIndex >= path.length - 1) return; var from = path[self.pathIndex]; var to = path[self.pathIndex + 1]; var dx = to.x - from.x; var dy = to.y - from.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist === 0) dist = 1; var step = self.speed / dist; self.progress += step; if (self.progress >= 1) { self.pathIndex++; self.progress = 0; if (self.pathIndex >= path.length - 1) { // At base self.x = to.x; self.y = to.y; self.reachedBase = true; return; } } var from = path[self.pathIndex]; var to = path[self.pathIndex + 1]; self.x = from.x + (to.x - from.x) * self.progress; self.y = from.y + (to.y - from.y) * self.progress; // Update health bar position and width if (hpBar && hpBarBg) { hpBarBg.x = 0; hpBarBg.y = -60; hpBar.x = 0; hpBar.y = -60; var ratio = Math.max(0, Math.min(1, self.hp / self.maxHp)); hpBar.width = 56 * 1.5 * ratio; hpBar.color = 0x44ff44; // always green } }; self.takeDamage = function (dmg) { self.hp -= dmg; if (self.hp <= 0 && !self.dead) { self.dead = true; // Animate fade out tween(self, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { self.destroy(); } }); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Intro screen overlay var introOverlay = new Container(); introOverlay.visible = true; // Fullscreen black background var introBg = LK.getAsset('pathnode', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, width: 2048, height: 2732, color: 0x000000, alpha: 1 }); introOverlay.addChild(introBg); // (Instructions removed for fully black intro screen) // "Welcome" text centered at top var welcomeTxt = new Text2('Welcome', { size: 220, fill: 0xffffff }); welcomeTxt.anchor.set(0.5, 0); welcomeTxt.x = 2048 / 2; welcomeTxt.y = 400; introOverlay.addChild(welcomeTxt); // Start button centered at bottom var startBtn = new Text2('Start', { size: 140, fill: 0xffffff }); startBtn.anchor.set(0.5, 0.5); startBtn.x = 2048 / 2; startBtn.y = 2732 - 400; introOverlay.addChild(startBtn); // Start button handler startBtn.down = function (x, y, obj) { introOverlay.visible = false; }; // Add overlay to game game.addChild(introOverlay); // Path and wall spot colors // Path definition (nodes spaced further apart horizontally for a wider and longer path) var path = [{ x: 500, y: 600 }, // Start { x: 500, y: 1200 }, { x: 500, y: 1800 }, // Extend downward { x: 500, y: 2300 }, // Curve right { x: 900, y: 2300 }, { x: 1300, y: 2300 }, // Curve up { x: 1300, y: 1800 }, { x: 1300, y: 1200 }, // Curve right again { x: 1750, y: 1200 }, { x: 1750, y: 1800 }, // Curve up to base { x: 1750, y: 2300 }, { x: 1750, y: 2732 - 200 // near bottom edge, but not out of bounds }]; // Wall spot positions (10 unit placement positions, adjacent to the new, longer path) var wallSpots = [{ x: path[1].x + 120, y: path[1].y }, { x: path[2].x + 120, y: path[2].y }, { x: path[3].x + 120, y: path[3].y }, { x: path[4].x, y: path[4].y - 120 }, { x: path[5].x, y: path[5].y - 120 }, { x: path[6].x - 120, y: path[6].y }, { x: path[7].x - 120, y: path[7].y }, { x: path[8].x - 120, y: path[8].y }, { x: path[9].x, y: path[9].y + 120 }, { x: path[10].x, y: path[10].y + 120 }]; // Wall spot state var wallSpotStates = []; for (var i = 0; i < wallSpots.length; ++i) wallSpotStates[i] = null; // Game state var zombies = []; var bullets = []; var soldiers = []; var coins = 10; var baseHp = 10; var wave = 1; var spawnTimer = 0; var spawnIdx = 0; var zombiesToSpawn = 0; var waveInProgress = false; var baseNode = null; var selectedSoldierType = 1; var placingSoldier = false; var placingSoldierIdx = -1; var gameOver = false; // GUI var coinTxt = new Text2('Coins: 10', { size: 80, fill: 0xFFE680 }); coinTxt.anchor.set(0, 0); LK.gui.top.addChild(coinTxt); var baseHpTxt = new Text2('Base: 10', { size: 80, fill: 0xFFAAAA }); baseHpTxt.anchor.set(1, 0); LK.gui.topRight.addChild(baseHpTxt); var waveTxt = new Text2('Wave: 1', { size: 80, fill: 0xAAFFAA }); waveTxt.anchor.set(0.5, 0); LK.gui.top.addChild(waveTxt); var soldierBtns = []; var soldierCosts = [3, 10, 20, 50]; var soldierNames = ['Basic', 'Rapid', 'Heavy', 'Red Arm']; var soldierBtnY = 0; for (var i = 0; i < 4; ++i) { var btn = new Text2(soldierNames[i] + "\n" + soldierCosts[i] + "c", { size: 60, fill: "#fff" }); btn.anchor.set(0.5, 0); btn.x = 300 + i * 300; btn.y = 0; LK.gui.bottom.addChild(btn); soldierBtns.push(btn); } // Only 4 buttons shown, 4 upgrades available // Draw path (as faint nodes) for (var i = 0; i < path.length; ++i) { var node = LK.getAsset('pathnode', { anchorX: 0.5, anchorY: 0.5, x: path[i].x, y: path[i].y, alpha: 0.18, color: 0x8B5A2B }); game.addChild(node); // Connect lines (not possible, so just nodes) } // Draw wall spots var wallSpotNodes = []; for (var i = 0; i < wallSpots.length; ++i) { var ws = LK.getAsset('wallspot', { anchorX: 0.5, anchorY: 0.5, x: wallSpots[i].x, y: wallSpots[i].y, alpha: 0.5, color: 0x0000ff // blue }); ws.idx = i; wallSpotNodes.push(ws); game.addChild(ws); } // Draw base baseNode = LK.getAsset('base', { anchorX: 0.5, anchorY: 0.5, x: path[path.length - 1].x, y: path[path.length - 1].y }); game.addChild(baseNode); // Handle soldier button selection for (var i = 0; i < soldierBtns.length; ++i) { (function (idx) { soldierBtns[idx].down = function (x, y, obj) { selectedSoldierType = idx + 1; for (var j = 0; j < soldierBtns.length; ++j) { soldierBtns[j].setStyle({ fill: j === idx ? "#ffff00" : "#fff" }); } }; })(i); } soldierBtns[0].setStyle({ fill: 0xFFFF00 }); // Place soldier on wall spot for (var i = 0; i < wallSpotNodes.length; ++i) { (function (idx) { wallSpotNodes[idx].down = function (x, y, obj) { if (gameOver) return; if (wallSpotStates[idx] === null) { // Place soldier if empty var cost = soldierCosts[selectedSoldierType - 1]; if (coins < cost) { // Flash red tween(wallSpotNodes[idx], { tint: 0xff0000 }, { duration: 200, onFinish: function onFinish() { tween(wallSpotNodes[idx], { tint: 0x888888 }, { duration: 200 }); } }); return; } coins -= cost; coinTxt.setText('Coins: ' + coins); // Place soldier var s = new Soldier(); s.setType(selectedSoldierType); s.x = wallSpots[idx].x; s.y = wallSpots[idx].y; s.wallSpot = idx; wallSpotStates[idx] = s; soldiers.push(s); game.addChild(s); // Add upgrade text overlay var upgradeTxt = new Text2('Upgrade', { size: 40, fill: 0x00FF00 }); upgradeTxt.anchor.set(0.5, 1); upgradeTxt.x = s.x; upgradeTxt.y = s.y - 60; upgradeTxt.visible = false; s.upgradeTxt = upgradeTxt; game.addChild(upgradeTxt); // Add tap handler for upgrade s.down = function (x, y, obj) { if (gameOver) return; // Show upgrade text if not max level if (s.level < 4) { s.upgradeTxt.visible = true; // Hide after 1s LK.setTimeout(function () { if (s.upgradeTxt) s.upgradeTxt.visible = false; }, 1000); } // If already at max, do nothing }; // Add tap handler for upgrade text upgradeTxt.down = function (x, y, obj) { if (gameOver) return; if (s.level >= 4) return; // Upgrade cost: Level 2 = 10, Level 3 = 20, Level 4 = 50 var upgradeCost = 0; if (s.level === 1) upgradeCost = 10;else if (s.level === 2) upgradeCost = 20;else if (s.level === 3) upgradeCost = 50; if (coins < upgradeCost) { // Flash red tween(upgradeTxt, { fill: 0xff0000 }, { duration: 200, onFinish: function onFinish() { tween(upgradeTxt, { fill: 0x00ff00 }, { duration: 200 }); } }); return; } coins -= upgradeCost; coinTxt.setText('Coins: ' + coins); s.level += 1; // Change type and visuals on upgrade if (s.level === 2) { s.setType(2); } if (s.level === 3) { s.setType(3); } if (s.level === 4) { s.setType(4); } // Show upgraded text upgradeTxt.setText('Upgraded!'); upgradeTxt.fill = "#ffff00"; LK.setTimeout(function () { if (upgradeTxt) { upgradeTxt.setText('Upgrade'); upgradeTxt.fill = "#00ff00"; upgradeTxt.visible = false; } }, 1000); }; } else { // If already a soldier, tap to show upgrade var s = wallSpotStates[idx]; if (s && s.upgradeTxt && s.level < 5) { s.upgradeTxt.visible = true; LK.setTimeout(function () { if (s.upgradeTxt) s.upgradeTxt.visible = false; }, 1000); } } }; })(i); } // Start first wave // Red zombie spawn plan: wave 10-20, 1 red per wave; wave 11, 2 reds; otherwise, 1 red per wave 1-10 var redZombiePlan = {}; // wave number -> array of 0/1 (1=red) function setupRedZombiePlan() { // For waves 1-9: 1 red per wave for (var w = 1; w <= 9; ++w) { var arr = []; var totalZ = 5 + w * 2; for (var i = 0; i < totalZ; ++i) arr.push(0); arr[0] = 1; // Always first zombie is red (could randomize if desired) // Shuffle so red is not always first for (var i = arr.length - 1; i > 0; --i) { var j = Math.floor(Math.random() * (i + 1)); var tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } redZombiePlan[w] = arr; } // Wave 10: 1 red var arr10 = []; var totalZ10 = 5 + 10 * 2; for (var i = 0; i < totalZ10; ++i) arr10.push(0); arr10[0] = 1; for (var i = arr10.length - 1; i > 0; --i) { var j = Math.floor(Math.random() * (i + 1)); var tmp = arr10[i]; arr10[i] = arr10[j]; arr10[j] = tmp; } redZombiePlan[10] = arr10; // Wave 11: 2 reds var arr11 = []; var totalZ11 = 5 + 11 * 2; for (var i = 0; i < totalZ11; ++i) arr11.push(0); arr11[0] = 1; arr11[1] = 1; for (var i = arr11.length - 1; i > 0; --i) { var j = Math.floor(Math.random() * (i + 1)); var tmp = arr11[i]; arr11[i] = arr11[j]; arr11[j] = tmp; } redZombiePlan[11] = arr11; // Waves 12-20: 1 red per wave for (var w = 12; w <= 20; ++w) { var arr = []; var totalZ = 5 + w * 2; for (var i = 0; i < totalZ; ++i) arr.push(0); arr[0] = 1; for (var i = arr.length - 1; i > 0; --i) { var j = Math.floor(Math.random() * (i + 1)); var tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } redZombiePlan[w] = arr; } } setupRedZombiePlan(); function startWave() { waveInProgress = true; spawnIdx = 0; zombiesToSpawn = 5 + wave * 2; spawnTimer = 0; waveTxt.setText('Wave: ' + wave); } startWave(); // Spawn zombie function spawnZombie() { var z = new Zombie(); // Stats scale with wave var hp = 5; // Green zombie health is always 5 var speed = 1.2 + 0.08 * wave; var reward = 1 + Math.floor(wave / 2); // Red zombie logic var makeRed = false; var makeYellow = false; if (wave >= 15 && wave <= 20) { // Only red zombies between wave 15 and 20 makeRed = true; } else if (wave >= 1 && wave <= 20 && redZombiePlan[wave]) { if (spawnIdx < redZombiePlan[wave].length && redZombiePlan[wave][spawnIdx] === 1) { makeRed = true; } } else if (wave > 20 && wave < 31) { // Between wave 21 and 30, spawn a yellow zombie at a random index per wave // Only one yellow zombie per wave, at a random spawnIdx if (typeof spawnZombie.yellowIdx === "undefined" || spawnZombie.yellowWave !== wave) { // Pick a random index for yellow zombie for this wave spawnZombie.yellowIdx = 20 + Math.floor(Math.random() * Math.max(1, 5 + wave * 2 - 20)); spawnZombie.yellowWave = wave; } if (spawnIdx === spawnZombie.yellowIdx) { makeYellow = true; } makeRed = false; } else if (wave > 30) { // After wave 30, only green zombies makeRed = false; makeYellow = false; } if (makeRed) { z.setRed(); hp = 10; // Red zombie health is always 10 reward = reward * 2; } if (makeYellow) { z.setYellow(); hp = 20; // Yellow zombie health is 20 reward = reward * 3; } z.setStats(hp, speed, reward); z.setPathIndex(0); z.x = path[0].x; z.y = path[0].y; zombies.push(z); game.addChild(z); } // Main game update game.update = function () { if (gameOver) return; // Spawn zombies for wave if (waveInProgress && spawnIdx < zombiesToSpawn) { if (spawnTimer <= 0) { spawnZombie(); spawnIdx++; spawnTimer = 40 - Math.min(wave * 2, 30); // Faster spawns later } else { spawnTimer--; } } // Update zombies for (var i = zombies.length - 1; i >= 0; --i) { var z = zombies[i]; z.update(); if (z.dead) { // Reward coins if (z.isRed) { coins += 2; } else { coins += 1; } coinTxt.setText('Coins: ' + coins); zombies.splice(i, 1); continue; } if (z.pathIndex >= path.length - 1 && !z.dead) { // Reached base baseHp--; baseHpTxt.setText('Base: ' + baseHp); LK.effects.flashObject(baseNode, 0xff0000, 400); z.dead = true; z.destroy(); zombies.splice(i, 1); if (baseHp <= 0) { gameOver = true; LK.effects.flashScreen(0xff0000, 1200); LK.showGameOver(); return; } } } // Update bullets for (var i = bullets.length - 1; i >= 0; --i) { var b = bullets[i]; b.update(); if (!b.parent) { bullets.splice(i, 1); } } // Update soldiers for (var i = 0; i < soldiers.length; ++i) { soldiers[i].update(); } // Check if wave is over if (waveInProgress && spawnIdx >= zombiesToSpawn && zombies.length === 0) { waveInProgress = false; // Next wave after short delay LK.setTimeout(function () { wave++; startWave(); }, 1200); } }; // Allow dragging soldiers to swap positions (optional, MVP skips this) // Prevent placing elements in top left 100x100 (already handled by GUI placement) // No background, music, or sound per requirements // No pause, leaderboard, or game over handling (LK does this)
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bullet class
var Bullet = Container.expand(function () {
var self = Container.call(this);
self.target = null;
self.speed = 18;
self.damage = 1;
self.type = 1;
self.active = true;
self.shooter = null;
self.setType = function (type) {
self.type = type;
var assetId = 'bullet' + type;
self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
self.update = function () {
if (!self.active || !self.target || self.target.dead) {
self.destroy();
return;
}
// Move toward target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 30) {
// Hit
self.target.takeDamage(self.damage);
self.destroy();
return;
}
var step = self.speed / (dist || 1);
self.x += dx * step;
self.y += dy * step;
};
return self;
});
// Soldier class
var Soldier = Container.expand(function () {
var self = Container.call(this);
self.type = 1;
self.fireRate = 60; // ticks between shots
self.damage = 1;
self.range = 350;
self.cooldown = 0;
self.level = 1;
self.wallSpot = null;
self.setType = function (type) {
self.type = type;
// Remove all previous children (visuals)
while (self.children && self.children.length > 0) {
self.removeChild(self.children[0]);
}
var assetId = 'soldier' + type;
self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Set stats
if (type === 1) {
self.fireRate = 60;
self.damage = 1;
self.range = 350;
self.dualHit = false;
self.money = 3;
}
if (type === 2) {
self.fireRate = 40;
self.damage = 1;
self.range = 300;
self.dualHit = false;
self.money = 10;
}
if (type === 3) {
self.fireRate = 90;
self.damage = 3;
self.range = 320;
self.dualHit = false;
self.money = 20;
}
if (type === 4) {
self.fireRate = 60;
self.damage = 8;
self.range = 400;
self.dualHit = false; // 4th upgrade: single hit
self.money = 50;
}
};
self.update = function () {
if (self.cooldown > 0) {
self.cooldown--;
return;
}
// Find nearest zombie in range
var nearest = null;
var minDist = 99999;
for (var i = 0; i < zombies.length; ++i) {
var z = zombies[i];
if (z.dead) continue;
var dx = z.x - self.x;
var dy = z.y - self.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < self.range && d < minDist) {
minDist = d;
nearest = z;
}
}
if (nearest) {
// Shoot
if (self.type === 4 && !self.dualHit) {
// 4th upgrade: single hit, 8 dmg
var bullet = new Bullet();
bullet.setType(self.type);
bullet.x = self.x;
bullet.y = self.y;
bullet.target = nearest;
bullet.damage = self.damage;
bullet.shooter = self;
bullets.push(bullet);
game.addChild(bullet);
self.cooldown = self.fireRate;
} else if (self.type === 4 && self.dualHit) {
// (legacy, should not occur, but keep for safety)
for (var i = 0; i < 2; ++i) {
var bullet = new Bullet();
bullet.setType(self.type);
bullet.x = self.x + (i === 0 ? -30 : 30);
bullet.y = self.y;
bullet.target = nearest;
bullet.damage = self.damage;
bullet.shooter = self;
bullets.push(bullet);
game.addChild(bullet);
}
self.cooldown = self.fireRate;
} else {
var bullet = new Bullet();
bullet.setType(self.type);
bullet.x = self.x;
bullet.y = self.y;
bullet.target = nearest;
bullet.damage = self.damage;
bullet.shooter = self;
bullets.push(bullet);
game.addChild(bullet);
self.cooldown = self.fireRate;
}
}
};
return self;
});
// Zombie class
var Zombie = Container.expand(function () {
var self = Container.call(this);
self.isRed = false; // Will be set after construction if needed
var sprite = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
var hpBarBg = LK.getAsset('pathnode', {
anchorX: 0.5,
anchorY: 0.5,
width: 60 * 1.5,
height: 14 * 1.5,
y: -60,
alpha: 0.25,
color: 0x000000
});
self.setRed = function () {
if (!self.isRed) {
self.isRed = true;
// Remove old sprite
if (sprite && sprite.parent === self) self.removeChild(sprite);
// Attach red zombie asset
var redSprite = self.attachAsset('zombieRed', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
// Add yellow zombie support
self.isYellow = false;
self.setYellow = function () {
if (!self.isYellow) {
self.isYellow = true;
// Remove old sprite
if (sprite && sprite.parent === self) self.removeChild(sprite);
// Attach yellow zombie asset
var yellowSprite = self.attachAsset('zombieYellow', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
self.addChild(hpBarBg);
// Health bar foreground
var hpBar = LK.getAsset('pathnode', {
anchorX: 0.5,
anchorY: 0.5,
width: 56 * 1.5,
height: 10 * 1.5,
y: -60,
color: 0x44ff44,
// green
alpha: 0.85
});
self.addChild(hpBar);
self.speed = 1.2;
self.maxHp = 5;
self.hp = 5;
self.reward = 1;
self.pathIndex = 0;
self.progress = 0; // 0-1 between path[pathIndex] and path[pathIndex+1]
self.targetX = 0;
self.targetY = 0;
self.dead = false;
self.setStats = function (hp, speed, reward) {
self.maxHp = hp;
self.hp = hp;
self.speed = speed;
self.reward = reward;
// Update health bar on spawn
if (hpBar) {
hpBar.width = 56 * 1.5;
}
};
self.setPathIndex = function (idx) {
self.pathIndex = idx;
self.progress = 0;
};
self.update = function () {
if (self.dead) return;
// Move along path
if (self.pathIndex >= path.length - 1) return;
var from = path[self.pathIndex];
var to = path[self.pathIndex + 1];
var dx = to.x - from.x;
var dy = to.y - from.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist === 0) dist = 1;
var step = self.speed / dist;
self.progress += step;
if (self.progress >= 1) {
self.pathIndex++;
self.progress = 0;
if (self.pathIndex >= path.length - 1) {
// At base
self.x = to.x;
self.y = to.y;
self.reachedBase = true;
return;
}
}
var from = path[self.pathIndex];
var to = path[self.pathIndex + 1];
self.x = from.x + (to.x - from.x) * self.progress;
self.y = from.y + (to.y - from.y) * self.progress;
// Update health bar position and width
if (hpBar && hpBarBg) {
hpBarBg.x = 0;
hpBarBg.y = -60;
hpBar.x = 0;
hpBar.y = -60;
var ratio = Math.max(0, Math.min(1, self.hp / self.maxHp));
hpBar.width = 56 * 1.5 * ratio;
hpBar.color = 0x44ff44; // always green
}
};
self.takeDamage = function (dmg) {
self.hp -= dmg;
if (self.hp <= 0 && !self.dead) {
self.dead = true;
// Animate fade out
tween(self, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
}
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Intro screen overlay
var introOverlay = new Container();
introOverlay.visible = true;
// Fullscreen black background
var introBg = LK.getAsset('pathnode', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 2048,
height: 2732,
color: 0x000000,
alpha: 1
});
introOverlay.addChild(introBg);
// (Instructions removed for fully black intro screen)
// "Welcome" text centered at top
var welcomeTxt = new Text2('Welcome', {
size: 220,
fill: 0xffffff
});
welcomeTxt.anchor.set(0.5, 0);
welcomeTxt.x = 2048 / 2;
welcomeTxt.y = 400;
introOverlay.addChild(welcomeTxt);
// Start button centered at bottom
var startBtn = new Text2('Start', {
size: 140,
fill: 0xffffff
});
startBtn.anchor.set(0.5, 0.5);
startBtn.x = 2048 / 2;
startBtn.y = 2732 - 400;
introOverlay.addChild(startBtn);
// Start button handler
startBtn.down = function (x, y, obj) {
introOverlay.visible = false;
};
// Add overlay to game
game.addChild(introOverlay);
// Path and wall spot colors
// Path definition (nodes spaced further apart horizontally for a wider and longer path)
var path = [{
x: 500,
y: 600
},
// Start
{
x: 500,
y: 1200
}, {
x: 500,
y: 1800
},
// Extend downward
{
x: 500,
y: 2300
},
// Curve right
{
x: 900,
y: 2300
}, {
x: 1300,
y: 2300
},
// Curve up
{
x: 1300,
y: 1800
}, {
x: 1300,
y: 1200
},
// Curve right again
{
x: 1750,
y: 1200
}, {
x: 1750,
y: 1800
},
// Curve up to base
{
x: 1750,
y: 2300
}, {
x: 1750,
y: 2732 - 200 // near bottom edge, but not out of bounds
}];
// Wall spot positions (10 unit placement positions, adjacent to the new, longer path)
var wallSpots = [{
x: path[1].x + 120,
y: path[1].y
}, {
x: path[2].x + 120,
y: path[2].y
}, {
x: path[3].x + 120,
y: path[3].y
}, {
x: path[4].x,
y: path[4].y - 120
}, {
x: path[5].x,
y: path[5].y - 120
}, {
x: path[6].x - 120,
y: path[6].y
}, {
x: path[7].x - 120,
y: path[7].y
}, {
x: path[8].x - 120,
y: path[8].y
}, {
x: path[9].x,
y: path[9].y + 120
}, {
x: path[10].x,
y: path[10].y + 120
}];
// Wall spot state
var wallSpotStates = [];
for (var i = 0; i < wallSpots.length; ++i) wallSpotStates[i] = null;
// Game state
var zombies = [];
var bullets = [];
var soldiers = [];
var coins = 10;
var baseHp = 10;
var wave = 1;
var spawnTimer = 0;
var spawnIdx = 0;
var zombiesToSpawn = 0;
var waveInProgress = false;
var baseNode = null;
var selectedSoldierType = 1;
var placingSoldier = false;
var placingSoldierIdx = -1;
var gameOver = false;
// GUI
var coinTxt = new Text2('Coins: 10', {
size: 80,
fill: 0xFFE680
});
coinTxt.anchor.set(0, 0);
LK.gui.top.addChild(coinTxt);
var baseHpTxt = new Text2('Base: 10', {
size: 80,
fill: 0xFFAAAA
});
baseHpTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(baseHpTxt);
var waveTxt = new Text2('Wave: 1', {
size: 80,
fill: 0xAAFFAA
});
waveTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(waveTxt);
var soldierBtns = [];
var soldierCosts = [3, 10, 20, 50];
var soldierNames = ['Basic', 'Rapid', 'Heavy', 'Red Arm'];
var soldierBtnY = 0;
for (var i = 0; i < 4; ++i) {
var btn = new Text2(soldierNames[i] + "\n" + soldierCosts[i] + "c", {
size: 60,
fill: "#fff"
});
btn.anchor.set(0.5, 0);
btn.x = 300 + i * 300;
btn.y = 0;
LK.gui.bottom.addChild(btn);
soldierBtns.push(btn);
}
// Only 4 buttons shown, 4 upgrades available
// Draw path (as faint nodes)
for (var i = 0; i < path.length; ++i) {
var node = LK.getAsset('pathnode', {
anchorX: 0.5,
anchorY: 0.5,
x: path[i].x,
y: path[i].y,
alpha: 0.18,
color: 0x8B5A2B
});
game.addChild(node);
// Connect lines (not possible, so just nodes)
}
// Draw wall spots
var wallSpotNodes = [];
for (var i = 0; i < wallSpots.length; ++i) {
var ws = LK.getAsset('wallspot', {
anchorX: 0.5,
anchorY: 0.5,
x: wallSpots[i].x,
y: wallSpots[i].y,
alpha: 0.5,
color: 0x0000ff // blue
});
ws.idx = i;
wallSpotNodes.push(ws);
game.addChild(ws);
}
// Draw base
baseNode = LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5,
x: path[path.length - 1].x,
y: path[path.length - 1].y
});
game.addChild(baseNode);
// Handle soldier button selection
for (var i = 0; i < soldierBtns.length; ++i) {
(function (idx) {
soldierBtns[idx].down = function (x, y, obj) {
selectedSoldierType = idx + 1;
for (var j = 0; j < soldierBtns.length; ++j) {
soldierBtns[j].setStyle({
fill: j === idx ? "#ffff00" : "#fff"
});
}
};
})(i);
}
soldierBtns[0].setStyle({
fill: 0xFFFF00
});
// Place soldier on wall spot
for (var i = 0; i < wallSpotNodes.length; ++i) {
(function (idx) {
wallSpotNodes[idx].down = function (x, y, obj) {
if (gameOver) return;
if (wallSpotStates[idx] === null) {
// Place soldier if empty
var cost = soldierCosts[selectedSoldierType - 1];
if (coins < cost) {
// Flash red
tween(wallSpotNodes[idx], {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(wallSpotNodes[idx], {
tint: 0x888888
}, {
duration: 200
});
}
});
return;
}
coins -= cost;
coinTxt.setText('Coins: ' + coins);
// Place soldier
var s = new Soldier();
s.setType(selectedSoldierType);
s.x = wallSpots[idx].x;
s.y = wallSpots[idx].y;
s.wallSpot = idx;
wallSpotStates[idx] = s;
soldiers.push(s);
game.addChild(s);
// Add upgrade text overlay
var upgradeTxt = new Text2('Upgrade', {
size: 40,
fill: 0x00FF00
});
upgradeTxt.anchor.set(0.5, 1);
upgradeTxt.x = s.x;
upgradeTxt.y = s.y - 60;
upgradeTxt.visible = false;
s.upgradeTxt = upgradeTxt;
game.addChild(upgradeTxt);
// Add tap handler for upgrade
s.down = function (x, y, obj) {
if (gameOver) return;
// Show upgrade text if not max level
if (s.level < 4) {
s.upgradeTxt.visible = true;
// Hide after 1s
LK.setTimeout(function () {
if (s.upgradeTxt) s.upgradeTxt.visible = false;
}, 1000);
}
// If already at max, do nothing
};
// Add tap handler for upgrade text
upgradeTxt.down = function (x, y, obj) {
if (gameOver) return;
if (s.level >= 4) return;
// Upgrade cost: Level 2 = 10, Level 3 = 20, Level 4 = 50
var upgradeCost = 0;
if (s.level === 1) upgradeCost = 10;else if (s.level === 2) upgradeCost = 20;else if (s.level === 3) upgradeCost = 50;
if (coins < upgradeCost) {
// Flash red
tween(upgradeTxt, {
fill: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(upgradeTxt, {
fill: 0x00ff00
}, {
duration: 200
});
}
});
return;
}
coins -= upgradeCost;
coinTxt.setText('Coins: ' + coins);
s.level += 1;
// Change type and visuals on upgrade
if (s.level === 2) {
s.setType(2);
}
if (s.level === 3) {
s.setType(3);
}
if (s.level === 4) {
s.setType(4);
}
// Show upgraded text
upgradeTxt.setText('Upgraded!');
upgradeTxt.fill = "#ffff00";
LK.setTimeout(function () {
if (upgradeTxt) {
upgradeTxt.setText('Upgrade');
upgradeTxt.fill = "#00ff00";
upgradeTxt.visible = false;
}
}, 1000);
};
} else {
// If already a soldier, tap to show upgrade
var s = wallSpotStates[idx];
if (s && s.upgradeTxt && s.level < 5) {
s.upgradeTxt.visible = true;
LK.setTimeout(function () {
if (s.upgradeTxt) s.upgradeTxt.visible = false;
}, 1000);
}
}
};
})(i);
}
// Start first wave
// Red zombie spawn plan: wave 10-20, 1 red per wave; wave 11, 2 reds; otherwise, 1 red per wave 1-10
var redZombiePlan = {}; // wave number -> array of 0/1 (1=red)
function setupRedZombiePlan() {
// For waves 1-9: 1 red per wave
for (var w = 1; w <= 9; ++w) {
var arr = [];
var totalZ = 5 + w * 2;
for (var i = 0; i < totalZ; ++i) arr.push(0);
arr[0] = 1; // Always first zombie is red (could randomize if desired)
// Shuffle so red is not always first
for (var i = arr.length - 1; i > 0; --i) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
redZombiePlan[w] = arr;
}
// Wave 10: 1 red
var arr10 = [];
var totalZ10 = 5 + 10 * 2;
for (var i = 0; i < totalZ10; ++i) arr10.push(0);
arr10[0] = 1;
for (var i = arr10.length - 1; i > 0; --i) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = arr10[i];
arr10[i] = arr10[j];
arr10[j] = tmp;
}
redZombiePlan[10] = arr10;
// Wave 11: 2 reds
var arr11 = [];
var totalZ11 = 5 + 11 * 2;
for (var i = 0; i < totalZ11; ++i) arr11.push(0);
arr11[0] = 1;
arr11[1] = 1;
for (var i = arr11.length - 1; i > 0; --i) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = arr11[i];
arr11[i] = arr11[j];
arr11[j] = tmp;
}
redZombiePlan[11] = arr11;
// Waves 12-20: 1 red per wave
for (var w = 12; w <= 20; ++w) {
var arr = [];
var totalZ = 5 + w * 2;
for (var i = 0; i < totalZ; ++i) arr.push(0);
arr[0] = 1;
for (var i = arr.length - 1; i > 0; --i) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
redZombiePlan[w] = arr;
}
}
setupRedZombiePlan();
function startWave() {
waveInProgress = true;
spawnIdx = 0;
zombiesToSpawn = 5 + wave * 2;
spawnTimer = 0;
waveTxt.setText('Wave: ' + wave);
}
startWave();
// Spawn zombie
function spawnZombie() {
var z = new Zombie();
// Stats scale with wave
var hp = 5; // Green zombie health is always 5
var speed = 1.2 + 0.08 * wave;
var reward = 1 + Math.floor(wave / 2);
// Red zombie logic
var makeRed = false;
var makeYellow = false;
if (wave >= 15 && wave <= 20) {
// Only red zombies between wave 15 and 20
makeRed = true;
} else if (wave >= 1 && wave <= 20 && redZombiePlan[wave]) {
if (spawnIdx < redZombiePlan[wave].length && redZombiePlan[wave][spawnIdx] === 1) {
makeRed = true;
}
} else if (wave > 20 && wave < 31) {
// Between wave 21 and 30, spawn a yellow zombie at a random index per wave
// Only one yellow zombie per wave, at a random spawnIdx
if (typeof spawnZombie.yellowIdx === "undefined" || spawnZombie.yellowWave !== wave) {
// Pick a random index for yellow zombie for this wave
spawnZombie.yellowIdx = 20 + Math.floor(Math.random() * Math.max(1, 5 + wave * 2 - 20));
spawnZombie.yellowWave = wave;
}
if (spawnIdx === spawnZombie.yellowIdx) {
makeYellow = true;
}
makeRed = false;
} else if (wave > 30) {
// After wave 30, only green zombies
makeRed = false;
makeYellow = false;
}
if (makeRed) {
z.setRed();
hp = 10; // Red zombie health is always 10
reward = reward * 2;
}
if (makeYellow) {
z.setYellow();
hp = 20; // Yellow zombie health is 20
reward = reward * 3;
}
z.setStats(hp, speed, reward);
z.setPathIndex(0);
z.x = path[0].x;
z.y = path[0].y;
zombies.push(z);
game.addChild(z);
}
// Main game update
game.update = function () {
if (gameOver) return;
// Spawn zombies for wave
if (waveInProgress && spawnIdx < zombiesToSpawn) {
if (spawnTimer <= 0) {
spawnZombie();
spawnIdx++;
spawnTimer = 40 - Math.min(wave * 2, 30); // Faster spawns later
} else {
spawnTimer--;
}
}
// Update zombies
for (var i = zombies.length - 1; i >= 0; --i) {
var z = zombies[i];
z.update();
if (z.dead) {
// Reward coins
if (z.isRed) {
coins += 2;
} else {
coins += 1;
}
coinTxt.setText('Coins: ' + coins);
zombies.splice(i, 1);
continue;
}
if (z.pathIndex >= path.length - 1 && !z.dead) {
// Reached base
baseHp--;
baseHpTxt.setText('Base: ' + baseHp);
LK.effects.flashObject(baseNode, 0xff0000, 400);
z.dead = true;
z.destroy();
zombies.splice(i, 1);
if (baseHp <= 0) {
gameOver = true;
LK.effects.flashScreen(0xff0000, 1200);
LK.showGameOver();
return;
}
}
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; --i) {
var b = bullets[i];
b.update();
if (!b.parent) {
bullets.splice(i, 1);
}
}
// Update soldiers
for (var i = 0; i < soldiers.length; ++i) {
soldiers[i].update();
}
// Check if wave is over
if (waveInProgress && spawnIdx >= zombiesToSpawn && zombies.length === 0) {
waveInProgress = false;
// Next wave after short delay
LK.setTimeout(function () {
wave++;
startWave();
}, 1200);
}
};
// Allow dragging soldiers to swap positions (optional, MVP skips this)
// Prevent placing elements in top left 100x100 (already handled by GUI placement)
// No background, music, or sound per requirements
// No pause, leaderboard, or game over handling (LK does this)
Soilder. In-Game asset. 2d. High contrast. No shadows
one-gun soldier. In-Game asset. 2d. High contrast. No shadows
soldier with 2 kate ak47 in his hand. In-Game asset. 2d. High contrast. No shadows
soldier with rifle. In-Game asset. 2d. High contrast. No shadows
Zombie. In-Game asset. 2d. High contrast. No shadows
Zombie. In-Game asset. 2d. High contrast. No shadows