/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// BuildTile class (for tower placement)
var BuildTile = Container.expand(function () {
var self = Container.call(this);
// Attach tile asset (box, light gray)
var tileAsset = self.attachAsset('buildTile', {
anchorX: 0.5,
anchorY: 0.5
});
self.occupied = false;
self.tower = null;
// For highlighting
self.highlight = function (on) {
tileAsset.tint = on ? 0x2ecc40 : 0xbdc3c7;
};
return self;
});
// Bullet class
var Bullet = Container.expand(function () {
var self = Container.call(this);
// Attach bullet asset (small yellow box)
var bulletAsset = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.target = null;
self.damage = 5;
self.speed = 24;
self.update = function () {
if (!self.target || self.target.health <= 0 || self.target.reachedBase) {
self.destroyed = true;
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 32) {
// Hit
self.target.takeDamage(self.damage);
self.destroyed = true;
return;
}
var moveDist = Math.min(self.speed, dist);
self.x += dx / dist * moveDist;
self.y += dy / dist * moveDist;
};
return self;
});
// Enemy (Creep) class
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Attach enemy asset (ellipse, purple)
var enemyAsset = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Enemy stats
self.maxHealth = Math.round(ENEMY_BASE_HEALTH * currentWave * ENEMY_HEALTH_WAVE_MULTIPLIER);
self.health = self.maxHealth;
self.speed = 2 + (currentWave - 1) * 0.2; // Slightly faster each wave
self.reward = 5 + (currentWave - 1) * 2;
// Path progress
self.pathIndex = 0;
self.pathProgress = 0;
// For hit flash
self.isFlashing = false;
// Move along path
self.update = function () {
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) {
self.pathIndex++;
self.pathProgress = 0;
return;
}
var moveDist = self.speed;
self.pathProgress += moveDist;
if (self.pathProgress >= dist) {
self.pathIndex++;
self.pathProgress = 0;
if (self.pathIndex >= path.length - 1) {
// Reached end
self.x = to.x;
self.y = to.y;
self.reachedBase = true;
return;
}
}
var t = self.pathProgress / dist;
self.x = from.x + dx * t;
self.y = from.y + dy * t;
};
// Take damage
self.takeDamage = function (dmg) {
self.health -= dmg;
if (!self.isFlashing) {
self.isFlashing = true;
tween(enemyAsset, {
tint: 0xffffff
}, {
duration: 80,
onFinish: function onFinish() {
tween(enemyAsset, {
tint: 0x8e44ad
}, {
duration: 120,
onFinish: function onFinish() {
self.isFlashing = false;
}
});
}
});
}
};
return self;
});
// Tower class
var Tower = Container.expand(function () {
var self = Container.call(this);
// Attach tower asset (box, blue)
var towerAsset = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5
});
// Double-tap boost: track last tap time
self._lastTapTime = 0;
// Level label (bottom-left)
self.level = 1; // Default level is 1
self.levelLabel = new Text2(self.level + '', {
size: 36,
fill: "#fff"
});
self.levelLabel.anchor.set(0, 1); // bottom-left
// Position at bottom-left of tower asset
self.levelLabel.x = -50;
self.levelLabel.y = 50;
self.addChild(self.levelLabel);
// Delete button (top-right of tower)
self.deleteBtn = new Text2('✖', {
size: 40,
fill: "#fff"
});
self.deleteBtn.anchor.set(1, 0); // top-right
self.deleteBtn.x = 50;
self.deleteBtn.y = -50;
self.addChild(self.deleteBtn);
// Delete logic
self.deleteBtn.down = function (x, y, obj) {
// Remove from towers array
for (var i = towers.length - 1; i >= 0; i--) {
if (towers[i] === self) {
towers.splice(i, 1);
break;
}
}
// Remove from parent
self.destroy();
};
// Each tower instance manages its own stats and upgrades
self.range = 320;
self.damage = 5;
self.fireRate = 60; // frames per shot
self.cooldown = 0;
// For upgrade flash
self.isFlashing = false;
// Show range circle (for placement)
self.rangeCircle = self.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.15
});
self.rangeCircle.width = self.range * 2;
self.rangeCircle.height = self.range * 2;
self.rangeCircle.visible = false;
// Double-tap to boost this tower only
self.down = function (x, y, obj) {
var now = Date.now();
if (self._lastTapTime && now - self._lastTapTime < 400) {
// Double-tap detected: try to upgrade this tower
// Find color index for this tower
var colorIdx = 0;
if (self.children && self.children.length > 0) {
var tint = self.children[0].tint;
for (var i = 0; i < TOWER_SELECT_COLORS.length; i++) {
if (TOWER_SELECT_COLORS[i] === tint) {
colorIdx = i;
break;
}
}
}
// Calculate upgrade cost: base * current level
var upgradeCost = towerCurrentPrices[colorIdx] * self.level;
if (money >= upgradeCost) {
money -= upgradeCost;
// Increase all tower prices by tower price multiplier globally
for (var i = 0; i < towerCurrentPrices.length; i++) {
towerCurrentPrices[i] = Math.round(towerCurrentPrices[i] * TOWER_PRICE_INCREASE_MULTIPLIER);
}
// Actually upgrade tower
self.level++;
self.damage = Math.round(self.damage * TOWER_DAMAGE_UPGRADE_MULTIPLIER);
self.fireRate = Math.max(1, Math.round(self.fireRate / TOWER_SPEED_UPGRADE_MULTIPLIER));
// Update level label
self.levelLabel.setText(self.level + '');
if (typeof moneyTxt !== "undefined") {
moneyTxt.setText('Money: ' + money);
}
}
}
self._lastTapTime = now;
};
// Upgrade tower (for upgrade button or other triggers)
self.upgrade = function () {
if (self.level >= 3) {
return false;
}
self.level++;
if (self.levelLabel) {
self.levelLabel.setText(self.level + '');
}
// Find color index for this tower
var colorIdx = 0;
if (self.children && self.children.length > 0) {
var tint = self.children[0].tint;
for (var i = 0; i < TOWER_SELECT_COLORS.length; i++) {
if (TOWER_SELECT_COLORS[i] === tint) {
colorIdx = i;
break;
}
}
}
// Upgrade logic: scale up stats based on color
if (typeof TOWER_STATS !== "undefined" && TOWER_STATS[colorIdx]) {
self.damage += Math.round(TOWER_STATS[colorIdx].damage * 0.7);
self.range += Math.round(TOWER_STATS[colorIdx].range * 0.12);
self.fireRate = Math.max(20, self.fireRate - Math.round(TOWER_STATS[colorIdx].fireRate * 0.18));
} else {
self.damage += TOWER_DAMAGE_BLUE;
self.range += Math.round(TOWER_RANGE_STANDARD * 0.125);
self.fireRate = Math.max(30, self.fireRate - 10);
}
self.rangeCircle.width = self.range * 2;
self.rangeCircle.height = self.range * 2;
// Flash for upgrade
if (!self.isFlashing) {
self.isFlashing = true;
tween(towerAsset, {
tint: 0xffff00
}, {
duration: 120,
onFinish: function onFinish() {
tween(towerAsset, {
tint: 0x3498db
}, {
duration: 180,
onFinish: function onFinish() {
self.isFlashing = false;
}
});
}
});
}
return true;
};
// Tower attack logic
self.update = function () {
if (self.cooldown > 0) {
self.cooldown--;
return;
}
// Find nearest enemy in range
var nearest = null;
var minDist = 99999;
// Determine if this is a purple tower (Mor)
var isPurple = false;
if (self.children && self.children.length > 0 && self.children[0].tint === 0x8e44ad) {
isPurple = true;
}
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
var dx = e.x - self.x;
var dy = e.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if ((isPurple || dist <= self.range) && dist < minDist) {
nearest = e;
minDist = dist;
}
}
if (nearest) {
// Shoot
var b = new Bullet();
b.x = self.x;
b.y = self.y;
b.target = nearest;
b.damage = self.damage;
b.speed = 24;
b.lastX = b.x;
b.lastY = b.y;
bullets.push(b);
game.addChild(b);
self.cooldown = self.fireRate;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// --- Game Constants and Multipliers ---
// Tower stats constants
var TOWER_DAMAGE_BLUE = 5; // Mavi (blue) tower damage
var TOWER_DAMAGE_RED = 15; // Kırmızı (red) tower damage
var TOWER_DAMAGE_GREEN = 5; // Yeşil (green) tower damage
var TOWER_DAMAGE_YELLOW = 50; // Sarı (yellow) tower damage
var TOWER_DAMAGE_PURPLE = 15; // Mor (purple) tower damage
var TOWER_FIRE_RATE_BLUE = 60; // Mavi (blue) tower fire rate (frames per shot)
var TOWER_FIRE_RATE_RED = 60; // Kırmızı (red) tower fire rate
var TOWER_FIRE_RATE_GREEN = 20; // Yeşil (green) tower fire rate (3x faster than blue)
var TOWER_FIRE_RATE_YELLOW = 120; // Sarı (yellow) tower fire rate (2 seconds per shot)
var TOWER_FIRE_RATE_PURPLE = 120; // Mor (purple) tower fire rate (2 seconds per shot)
var TOWER_RANGE_STANDARD = 320; // Standard tower range
var TOWER_RANGE_GLOBAL = 2048; // Global range for purple tower
// Game progression multipliers
var ENEMY_BASE_HEALTH = 10; // Base health for enemies
var ENEMY_HEALTH_WAVE_MULTIPLIER = 1.25; // Health multiplier per wave
var TOWER_PRICE_INCREASE_MULTIPLIER = 1.25; // Tower price increase multiplier
// Tower upgrade multipliers
var TOWER_DAMAGE_UPGRADE_MULTIPLIER = 1.1; // Damage increase on upgrade
var TOWER_SPEED_UPGRADE_MULTIPLIER = 1.1; // Fire rate improvement on upgrade
// --- Grid system ---
// Set grid to 10x12 with margins, leave 1 row margin at bottom, and add 5 tower selection squares below grid
var GRID_COLS = 10;
var GRID_ROWS = 12;
var GRID_SIZE = 160;
// Calculate margins to center grid in 2048x2732, leaving 1 row margin at bottom for spacing
var GRID_TOTAL_WIDTH = GRID_COLS * GRID_SIZE;
var GRID_TOTAL_HEIGHT = GRID_ROWS * GRID_SIZE;
var GRID_MARGIN_X = Math.floor((2048 - GRID_TOTAL_WIDTH) / 2);
var GRID_MARGIN_Y = Math.floor((2732 - (GRID_TOTAL_HEIGHT + GRID_SIZE)) / 2); // +GRID_SIZE for 1 row margin at bottom
// Helper to snap to grid (with margin)
function snapToGrid(x, y) {
return {
x: GRID_MARGIN_X + Math.round((x - GRID_MARGIN_X) / GRID_SIZE) * GRID_SIZE,
y: GRID_MARGIN_Y + Math.round((y - GRID_MARGIN_Y) / GRID_SIZE) * GRID_SIZE
};
}
function generateSimplePath() {
var pathArr = [];
var visited = {}; // Ziyaret edilen hücreler ve geçiş yönleri: { h: boolean, v: boolean }
var yukariDonusYapildi = false; // YENİ: Yukarı dönüş yapılıp yapılmadığını takip eder
function cellKey(c, r) {
return c + "," + r;
}
var startCols = [];
for (var c = 1; c < GRID_COLS - 1; c++) {
startCols.push(c);
}
if (startCols.length === 0) {
return [snapToGrid(GRID_MARGIN_X, GRID_MARGIN_Y)];
}
var startCol = startCols[Math.floor(Math.random() * startCols.length)];
var curCol = startCol;
var curRow = 0;
pathArr.push(snapToGrid(GRID_MARGIN_X + curCol * GRID_SIZE, GRID_MARGIN_Y + curRow * GRID_SIZE));
var startKey = cellKey(curCol, curRow);
visited[startKey] = {
h: false,
v: true
}; // Başlangıçta aşağı varsayımı
curRow = 1;
if (GRID_ROWS < 2) {
return pathArr;
}
pathArr.push(snapToGrid(GRID_MARGIN_X + curCol * GRID_SIZE, GRID_MARGIN_Y + curRow * GRID_SIZE));
var firstStepKey = cellKey(curCol, curRow);
visited[firstStepKey] = visited[firstStepKey] || {
h: false,
v: false
};
visited[firstStepKey].v = true;
var endCols = [];
for (var c = 1; c < GRID_COLS - 1; c++) {
endCols.push(c);
}
if (endCols.length === 0) {
return pathArr;
}
var endCol = endCols[Math.floor(Math.random() * endCols.length)];
var endRow = GRID_ROWS - 1;
var preEndRow = GRID_ROWS - 2;
if (preEndRow < curRow) {
if (GRID_ROWS > 0) {
pathArr.push(snapToGrid(GRID_MARGIN_X + endCol * GRID_SIZE, GRID_MARGIN_Y + endRow * GRID_SIZE));
// visited[cellKey(endCol, endRow)] ...
}
return pathArr;
}
var lastDir = "down";
var straightCount = 1;
var maxRetries = GRID_COLS * GRID_ROWS * 15; // Biraz daha fazla deneme hakkı
var retries = 0;
while (!(curRow === preEndRow && curCol === endCol)) {
retries++;
if (retries > maxRetries) {
// console.warn("Path Gen: Max retries! Yukari Donus: " + yukariDonusYapildi);
break;
}
var potentialMoves = [];
// Potansiyel hamleleri oluştur (önceliklendirme olmadan tüm yönleri düşün)
if (curRow < preEndRow && lastDir !== "up") {
potentialMoves.push({
name: "down",
dx: 0,
dy: 1
});
}
if (curRow > 1 && lastDir !== "down") {
potentialMoves.push({
name: "up",
dx: 0,
dy: -1
});
}
if (curCol < GRID_COLS - 2 && lastDir !== "left") {
potentialMoves.push({
name: "right",
dx: 1,
dy: 0
});
}
if (curCol > 1 && lastDir !== "right") {
potentialMoves.push({
name: "left",
dx: -1,
dy: 0
});
}
// Eğer hiçbir potansiyel hamle yoksa (örn. 180 derece dönüş engeli nedeniyle), temel hamleleri tekrar ekle
if (potentialMoves.length === 0) {
if (curRow < preEndRow) {
potentialMoves.push({
name: "down",
dx: 0,
dy: 1
});
}
if (curRow > 1) {
potentialMoves.push({
name: "up",
dx: 0,
dy: -1
});
}
if (curCol < GRID_COLS - 2) {
potentialMoves.push({
name: "right",
dx: 1,
dy: 0
});
}
if (curCol > 1) {
potentialMoves.push({
name: "left",
dx: -1,
dy: 0
});
}
}
var validMovesRaw = [];
for (var i = 0; i < potentialMoves.length; i++) {
var move = potentialMoves[i];
var testCol = curCol + move.dx;
var testRow = curRow + move.dy;
var isHorizMove = move.dy === 0;
if (testCol < 1 || testCol > GRID_COLS - 2 || testRow < 1 || testRow > preEndRow) {
continue;
}
var vDataTarget = visited[cellKey(testCol, testRow)];
if (vDataTarget) {
if (isHorizMove && vDataTarget.h) {
continue;
}
if (!isHorizMove && vDataTarget.v) {
continue;
}
}
if (move.name === "up" && lastDir !== "up") {
// Sadece gerçek bir "yukarı dönüş" ise kontrol et
var upTurnSpacingOk = true;
for (var side = -1; side <= 1; side += 2) {
for (var dist = 1; dist <= 3; dist++) {
var checkC = testCol + side * dist;
if (checkC < 1 || checkC > GRID_COLS - 2) {
upTurnSpacingOk = false;
break;
}
var nKey = cellKey(checkC, testRow);
var vDataNeighbor = visited[nKey];
if (vDataNeighbor && (vDataNeighbor.h || vDataNeighbor.v)) {
upTurnSpacingOk = false;
break;
}
}
if (!upTurnSpacingOk) {
break;
}
}
if (!upTurnSpacingOk) {
continue;
}
}
var parallelSpacingOk = true;
if (isHorizMove) {
var nAboveKey = cellKey(testCol, testRow - 1);
var vAbove = visited[nAboveKey];
var nBelowKey = cellKey(testCol, testRow + 1);
var vBelow = visited[nBelowKey];
if (vAbove && vAbove.h && testRow - 1 !== curRow || vBelow && vBelow.h && testRow + 1 !== curRow) {
parallelSpacingOk = false;
}
} else {
var nLeftKey = cellKey(testCol - 1, testRow);
var vLeft = visited[nLeftKey];
var nRightKey = cellKey(testCol + 1, testRow);
var vRight = visited[nRightKey];
if (vLeft && vLeft.v && testCol - 1 !== curCol || vRight && vRight.v && testCol + 1 !== curCol) {
parallelSpacingOk = false;
}
}
if (!parallelSpacingOk) {
continue;
}
validMovesRaw.push(move);
}
if (validMovesRaw.length === 0) {
/*console.warn("No valid raw moves for " + curCol + "," + curRow);*/
break;
}
var currentActionableMoves = validMovesRaw;
if (straightCount >= 4) {
var nonStraightMoves = currentActionableMoves.filter(function (m) {
return m.name !== lastDir;
});
if (nonStraightMoves.length > 0) {
currentActionableMoves = nonStraightMoves;
}
}
if (currentActionableMoves.length === 0 && validMovesRaw.length > 0) {
currentActionableMoves = validMovesRaw;
}
if (currentActionableMoves.length === 0) {
/*console.warn("No actionable after straight count for " + curCol + "," + curRow);*/
break;
}
var selectedMove = null;
var potentialUpwardTurn = null;
if (!yukariDonusYapildi) {
currentActionableMoves.forEach(function (m) {
if (m.name === "up" && lastDir !== "up") {
potentialUpwardTurn = m; // Kurallara uyan bir yukarı dönüş bulundu
}
});
}
// Hamle Seçim Mantığı
if (potentialUpwardTurn) {
// Eğer yukarı dönüş yapılmadıysa ve mümkünse, onu önceliklendir
selectedMove = potentialUpwardTurn;
} else {
// Normal hamle seçim mantığı (düz gitme, hedefe yönelme vb.)
var moveInLastDir = null;
currentActionableMoves.forEach(function (m) {
if (m.name === lastDir) {
moveInLastDir = m;
}
});
var preferredMovesTowardsTarget = [];
currentActionableMoves.forEach(function (m) {
var candCol = curCol + m.dx;
var candRow = curRow + m.dy;
if (candCol === endCol && candRow === preEndRow) {
preferredMovesTowardsTarget.unshift(m);
} else {
var distOld = Math.abs(curCol - endCol) + Math.abs(curRow - preEndRow);
var distNew = Math.abs(candCol - endCol) + Math.abs(candRow - preEndRow);
if (distNew < distOld) {
preferredMovesTowardsTarget.push(m);
}
}
});
var straightAndPreferredTarget = null;
if (moveInLastDir && straightCount < 4) {
preferredMovesTowardsTarget.forEach(function (pm) {
if (pm.name === moveInLastDir.name) {
straightAndPreferredTarget = moveInLastDir;
}
});
}
if (straightAndPreferredTarget && Math.random() < 0.75) {
// %75 düz ve hedefe yönelik
selectedMove = straightAndPreferredTarget;
} else if (preferredMovesTowardsTarget.length > 0 && Math.random() < 0.80) {
// %80 hedefe yönelik
selectedMove = preferredMovesTowardsTarget[Math.floor(Math.random() * preferredMovesTowardsTarget.length)];
} else if (moveInLastDir && straightCount < 4 && Math.random() < 0.65) {
// %65 düz git
selectedMove = moveInLastDir;
} else if (currentActionableMoves.length > 0) {
// Son çare
selectedMove = currentActionableMoves[Math.floor(Math.random() * currentActionableMoves.length)];
} else {
break;
}
}
if (!selectedMove && currentActionableMoves.length > 0) {
// Eğer yukarıdaki mantıkla seçilemediyse (örn. potentialUpwardTurn vardı ama seçilmedi)
selectedMove = currentActionableMoves[Math.floor(Math.random() * currentActionableMoves.length)];
}
if (!selectedMove) {
/*console.warn("No move selected for " + curCol + "," + curRow);*/
break;
}
// Seçilen hamleyi uygula ve durumu güncelle
if (selectedMove.name === "up" && lastDir !== "up") {
yukariDonusYapildi = true;
}
var movedHorizontally = selectedMove.dy === 0;
if (selectedMove.name === lastDir) {
straightCount++;
} else {
straightCount = 1;
}
lastDir = selectedMove.name;
curCol += selectedMove.dx;
curRow += selectedMove.dy;
var newKey = cellKey(curCol, curRow);
visited[newKey] = visited[newKey] || {
h: false,
v: false
};
if (movedHorizontally) {
visited[newKey].h = true;
} else {
visited[newKey].v = true;
}
pathArr.push(snapToGrid(GRID_MARGIN_X + curCol * GRID_SIZE, GRID_MARGIN_Y + curRow * GRID_SIZE));
} // while sonu
// Eğer döngü bittiğinde hala yukarı dönüş yapılmadıysa ve yol çok kısaysa, bu bir sorun olabilir.
// Ancak algoritma, mümkün olan en kısa sürede yukarı dönüş yapmaya çalışacaktır.
// if (!yukariDonusYapildi && pathArr.length > (GRID_ROWS / 2)) console.log("Uyarı: Yol tamamlandı ancak yukarı dönüş yapılamadı.");
if (GRID_ROWS > 0) {
pathArr.push(snapToGrid(GRID_MARGIN_X + endCol * GRID_SIZE, GRID_MARGIN_Y + endRow * GRID_SIZE));
// visited[cellKey(endCol, endRow)] ...
}
return pathArr;
}
var path = generateSimplePath();
// --- Start Game Button Logic ---
var gameStarted = false;
var startBtn = new Text2('▶ Start Game', {
size: 120,
fill: 0x27AE60
});
startBtn.anchor.set(0.5, 0.5);
LK.gui.center.addChild(startBtn);
function setStartBtnVisible(visible) {
startBtn.visible = visible;
}
setStartBtnVisible(true);
startBtn.down = function (x, y, obj) {
if (!gameStarted) {
gameStarted = true;
setStartBtnVisible(false);
setNextWaveBtnVisible(true);
startWave(); // Start the first wave immediately
}
};
// Hide all gameplay UI until game starts
setNextWaveBtnVisible(false);
if (typeof kpTxt !== "undefined") {
kpTxt.visible = false;
}
if (typeof healthTxt !== "undefined") {
healthTxt.visible = false;
}
if (typeof waveTxt !== "undefined") {
waveTxt.visible = false;
}
if (typeof nextWaveBtn !== "undefined") {
nextWaveBtn.visible = false;
}
// autoNextWaveBtn is always visible; do not hide
// Show gameplay UI when game starts
function showGameplayUI() {
kpTxt.visible = true;
healthTxt.visible = true;
waveTxt.visible = true;
nextWaveBtn.visible = false;
nextWaveBtn.x = 0;
nextWaveBtn.y = kpTxt.y + kpTxt.height + 20;
// autoNextWaveBtn is always visible and positioned above waveTxt; do not reposition or show/hide here
kpTxt.setText('Points: ' + knowledgePoints);
healthTxt.setText('Base: ' + baseHealth);
waveTxt.setText('Wave: ' + currentWave + '/' + maxWaves);
}
var _origStartWave = startWave;
startWave = function startWave() {
if (!gameStarted) {
return;
}
showGameplayUI();
_origStartWave();
};
// Buildable tile logic removed for now
var buildTilePositions = [];
// Global game state
var enemies = [];
var towers = [];
var bullets = [];
// buildTiles removed for now
var baseHealth = 10;
var scoreo = 0; // Killed enemy count, used as score only
var money = 50; // Used for purchases, start with 50
var knowledgePoints = 30; // Deprecated, kept for UI compatibility
var currentWave = 1;
var maxWaves = 999999; // Effectively infinite waves
var waveInProgress = false;
var waveTimer = null;
var spawnIndex = 0;
var spawnDelay = 40; // frames between spawns
var enemiesToSpawn = 0;
var selectedTile = null;
// Tower price system
var towerBasePrices = [30, 50, 40, 60, 80]; // Starting price for each tower type (by power)
var towerCurrentPrices = towerBasePrices.slice(); // Current price for each type
// Asset initialization
// Draw grid background (light gray squares) with margins and 10x12 grid area
for (var row = 0; row < GRID_ROWS; row++) {
for (var col = 0; col < GRID_COLS; col++) {
var gridNode = LK.getAsset('buildTile', {
anchorX: 0.5,
anchorY: 0.5,
width: GRID_SIZE - 4,
height: GRID_SIZE - 4,
color: 0xf4f4f4,
shape: 'box',
alpha: 0.18
});
gridNode.x = GRID_MARGIN_X + col * GRID_SIZE;
gridNode.y = GRID_MARGIN_Y + row * GRID_SIZE;
game.addChild(gridNode);
}
}
// Draw 5 colored tower selection squares below the grid, centered horizontally
var TOWER_SELECT_COUNT = 5;
var TOWER_SELECT_SIZE = 140;
var TOWER_SELECT_SPACING = 60;
var TOWER_SELECT_COLORS = [0x3498db, 0xe74c3c, 0x2ecc40, 0xf1c40f, 0x8e44ad];
var TOWER_SELECT_LABELS = ['Mavi', 'Kırmızı', 'Yeşil', 'Sarı', 'Mor'];
// Tower stats for each color: {damage, range, fireRate}
var TOWER_STATS = [{
damage: TOWER_DAMAGE_BLUE,
range: TOWER_RANGE_STANDARD,
fireRate: TOWER_FIRE_RATE_BLUE
},
// Mavi
{
damage: TOWER_DAMAGE_RED,
range: TOWER_RANGE_STANDARD,
fireRate: TOWER_FIRE_RATE_RED
},
// Kırmızı
{
damage: TOWER_DAMAGE_GREEN,
range: TOWER_RANGE_STANDARD,
fireRate: TOWER_FIRE_RATE_GREEN
},
// Yeşil
{
damage: TOWER_DAMAGE_YELLOW,
range: TOWER_RANGE_STANDARD,
fireRate: TOWER_FIRE_RATE_YELLOW
},
// Sarı
{
damage: TOWER_DAMAGE_PURPLE,
range: TOWER_RANGE_GLOBAL,
fireRate: TOWER_FIRE_RATE_PURPLE
} // Mor (global range)
];
var towerSelectSquares = [];
var totalWidth = TOWER_SELECT_COUNT * TOWER_SELECT_SIZE + (TOWER_SELECT_COUNT - 1) * TOWER_SELECT_SPACING;
var startX = Math.floor((2048 - totalWidth) / 2) + TOWER_SELECT_SIZE / 2;
var selectY = GRID_MARGIN_Y + GRID_ROWS * GRID_SIZE + GRID_SIZE / 2;
for (var i = 0; i < TOWER_SELECT_COUNT; i++) {
var square = LK.getAsset('buildTile', {
anchorX: 0.5,
anchorY: 0.5,
width: TOWER_SELECT_SIZE,
height: TOWER_SELECT_SIZE,
color: TOWER_SELECT_COLORS[i],
shape: 'box',
alpha: 0.95
});
square.x = startX + i * (TOWER_SELECT_SIZE + TOWER_SELECT_SPACING);
square.y = selectY;
game.addChild(square);
// Add label for each tower color
var label = new Text2(TOWER_SELECT_LABELS[i], {
size: 48,
fill: "#fff"
});
label.anchor.set(0.5, 0);
label.x = square.x;
label.y = square.y + TOWER_SELECT_SIZE / 2 + 8;
game.addChild(label);
towerSelectSquares.push(square);
}
// Draw path (for visual reference)
for (var i = 0; i < path.length - 1; i++) {
var from = path[i];
var to = path[i + 1];
var dx = to.x - from.x;
var dy = to.y - from.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var steps = Math.floor(dist / 40);
for (var s = 0; s <= steps; s++) {
var t = s / steps;
var px = from.x + dx * t;
var py = from.y + dy * t;
var node = LK.getAsset('pathDot', {
anchorX: 0.5,
anchorY: 0.5,
width: 32,
height: 32,
color: 0x7f8c8d,
shape: 'ellipse'
});
node.x = px;
node.y = py;
game.addChild(node);
}
}
// Buildable tile objects removed for now
// --- Tower Selection Bar ---
var towerTypes = [];
var selectedTowerType = null;
var towerButtons = [];
var towerBtnY = 2400;
var towerBtnSpacing = 260;
var towerBtnStartX = 400;
var draggingTowerType = null;
var draggingTowerGhost = null;
// No tower selection buttons since normal tower is removed
// --- Drag-and-drop tower placement from colored squares ---
for (var i = 0; i < towerSelectSquares.length; i++) {
(function (colorIdx) {
var square = towerSelectSquares[colorIdx];
square.down = function (x, y, obj) {
// Clean up any previous ghost
if (draggingTowerGhost) {
if (draggingTowerGhost.rangeCircle) {
draggingTowerGhost.rangeCircle.destroy();
}
draggingTowerGhost.destroy();
draggingTowerGhost = null;
draggingTowerType = null;
}
draggingTowerType = colorIdx;
draggingTowerGhost = new Tower();
// Snap to grid for initial drag position
var gridX = Math.round((x - GRID_MARGIN_X) / GRID_SIZE);
var gridY = Math.round((y - GRID_MARGIN_Y) / GRID_SIZE);
var snapX = GRID_MARGIN_X + gridX * GRID_SIZE;
var snapY = GRID_MARGIN_Y + gridY * GRID_SIZE;
draggingTowerGhost.x = snapX;
draggingTowerGhost.y = snapY;
// Set color of ghost tower
if (draggingTowerGhost.children && draggingTowerGhost.children.length > 0) {
draggingTowerGhost.children[0].tint = TOWER_SELECT_COLORS[colorIdx];
}
// Set stats based on color for ghost
if (typeof TOWER_STATS !== "undefined" && TOWER_STATS[colorIdx]) {
draggingTowerGhost.damage = TOWER_STATS[colorIdx].damage;
draggingTowerGhost.range = TOWER_STATS[colorIdx].range;
draggingTowerGhost.fireRate = TOWER_STATS[colorIdx].fireRate;
if (draggingTowerGhost.rangeCircle) {
draggingTowerGhost.rangeCircle.width = draggingTowerGhost.range * 2;
draggingTowerGhost.rangeCircle.height = draggingTowerGhost.range * 2;
}
}
// Show range circle while dragging, and center it on the ghost
if (draggingTowerGhost.rangeCircle) {
draggingTowerGhost.rangeCircle.visible = true;
draggingTowerGhost.rangeCircle.x = 0;
draggingTowerGhost.rangeCircle.y = 0;
}
draggingTowerGhost.alpha = 0.7;
game.addChild(draggingTowerGhost);
};
})(i);
}
// Helper: check if a grid cell is on the path
function isCellOnPath(gridX, gridY) {
for (var i = 0; i < path.length; i++) {
var px = path[i].x;
var py = path[i].y;
var cellX = Math.round((px - GRID_MARGIN_X) / GRID_SIZE);
var cellY = Math.round((py - GRID_MARGIN_Y) / GRID_SIZE);
if (cellX === gridX && cellY === gridY) {
return true;
}
}
return false;
}
// Helper: check if a tower already exists at grid cell
function isTowerAtCell(gridX, gridY) {
for (var i = 0; i < towers.length; i++) {
var t = towers[i];
var tx = Math.round((t.x - GRID_MARGIN_X) / GRID_SIZE);
var ty = Math.round((t.y - GRID_MARGIN_Y) / GRID_SIZE);
if (tx === gridX && ty === gridY) {
return true;
}
}
return false;
}
// Dragging ghost tower with finger/mouse
game.move = function (x, y, obj) {
if (draggingTowerGhost) {
// Snap ghost to grid for feedback
var gridX = Math.round((x - GRID_MARGIN_X) / GRID_SIZE);
var gridY = Math.round((y - GRID_MARGIN_Y) / GRID_SIZE);
var snapX = GRID_MARGIN_X + gridX * GRID_SIZE;
var snapY = GRID_MARGIN_Y + gridY * GRID_SIZE;
draggingTowerGhost.x = snapX;
draggingTowerGhost.y = snapY;
// Always center rangeCircle on the ghost (0,0 in local coordinates)
if (draggingTowerGhost.rangeCircle) {
draggingTowerGhost.rangeCircle.x = 0;
draggingTowerGhost.rangeCircle.y = 0;
}
// Visual feedback: tint red if invalid, normal if valid
var valid = true;
// Only allow inside grid
if (gridX < 0 || gridX >= GRID_COLS || gridY < 0 || gridY >= GRID_ROWS || isCellOnPath(gridX, gridY) || isTowerAtCell(gridX, gridY)) {
valid = false;
}
if (draggingTowerGhost.children && draggingTowerGhost.children.length > 0) {
draggingTowerGhost.children[0].tint = valid ? TOWER_SELECT_COLORS[draggingTowerType] : 0x888888;
}
draggingTowerGhost.alpha = valid ? 0.7 : 0.35;
}
};
// Place tower on grid on up
game.up = function (x, y, obj) {
if (draggingTowerGhost && draggingTowerType !== null) {
var gridX = Math.round((draggingTowerGhost.x - GRID_MARGIN_X) / GRID_SIZE);
var gridY = Math.round((draggingTowerGhost.y - GRID_MARGIN_Y) / GRID_SIZE);
var valid = true;
if (gridX < 0 || gridX >= GRID_COLS || gridY < 0 || gridY >= GRID_ROWS || isCellOnPath(gridX, gridY) || isTowerAtCell(gridX, gridY)) {
valid = false;
}
if (valid) {
// Place tower if enough money (use dynamic price)
var buildCost = towerCurrentPrices[draggingTowerType];
if (money >= buildCost) {
money -= buildCost;
// Increase all tower prices by tower price multiplier
for (var i = 0; i < towerCurrentPrices.length; i++) {
towerCurrentPrices[i] = Math.round(towerCurrentPrices[i] * TOWER_PRICE_INCREASE_MULTIPLIER);
}
var tower = new Tower();
tower.x = GRID_MARGIN_X + gridX * GRID_SIZE;
tower.y = GRID_MARGIN_Y + gridY * GRID_SIZE;
// Set color
if (tower.children && tower.children.length > 0) {
tower.children[0].tint = TOWER_SELECT_COLORS[draggingTowerType];
}
// Set stats based on color
if (typeof TOWER_STATS !== "undefined" && TOWER_STATS[draggingTowerType]) {
tower.damage = TOWER_STATS[draggingTowerType].damage;
tower.range = TOWER_STATS[draggingTowerType].range;
tower.fireRate = TOWER_STATS[draggingTowerType].fireRate;
if (tower.rangeCircle) {
tower.rangeCircle.width = tower.range * 2;
tower.rangeCircle.height = tower.range * 2;
}
}
towers.push(tower);
game.addChild(tower);
moneyTxt.setText('Money: ' + money);
}
}
// Clean up ghost
if (draggingTowerGhost.rangeCircle) {
draggingTowerGhost.rangeCircle.destroy();
}
draggingTowerGhost.destroy();
draggingTowerGhost = null;
draggingTowerType = null;
return;
}
// If not placing, just clear ghost
if (draggingTowerGhost) {
if (draggingTowerGhost.rangeCircle) {
draggingTowerGhost.rangeCircle.destroy();
}
draggingTowerGhost.destroy();
draggingTowerGhost = null;
draggingTowerType = null;
}
};
// Base indicator (ellipse, red)
var baseNode = LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5,
width: 160,
height: 160,
color: 0xe74c3c,
shape: 'ellipse'
});
baseNode.x = path[path.length - 1].x;
baseNode.y = path[path.length - 1].y;
game.addChild(baseNode);
// GUI: Points (KP) - Center top, smaller size, renamed to Points
var kpTxt = new Text2('Score: ' + scoreo, {
size: 48,
fill: 0x2ecc40 // bright green, not transparent
});
kpTxt.anchor.set(0.5, 0); // center aligned, top
kpTxt.x = 0;
kpTxt.y = 10;
LK.gui.top.addChild(kpTxt);
// GUI: Money - Bottom right
var moneyTxt = new Text2('Money: ' + money, {
size: 48,
fill: 0xf1c40f
});
moneyTxt.anchor.set(1, 1); // right aligned, bottom
moneyTxt.x = -40; // 40px from right edge
moneyTxt.y = -40; // 40px from bottom edge
LK.gui.bottomRight.addChild(moneyTxt);
// GUI: Base Health - Top right, smaller size
var healthTxt = new Text2('Base: ' + baseHealth, {
size: 48,
fill: 0xE74C3C
});
healthTxt.anchor.set(1, 0); // right aligned, top
healthTxt.x = -40; // right margin
healthTxt.y = 10;
LK.gui.topRight.addChild(healthTxt);
// GUI: Wave - Bottom left, smaller size
var waveTxt = new Text2('Wave: ' + currentWave + '/' + maxWaves, {
size: 48,
fill: 0x2980B9
});
waveTxt.anchor.set(0, 1); // left aligned, bottom
waveTxt.x = 40;
waveTxt.y = -40;
// Auto-next wave toggle (now above waveTxt, same size)
var autoNextWave = false;
var autoNextWaveBtn = new Text2('Auto: OFF', {
size: 48,
fill: 0x2980B9
});
autoNextWaveBtn.anchor.set(0, 1);
autoNextWaveBtn.x = 40;
autoNextWaveBtn.y = waveTxt.y - waveTxt.height - 8; // 8px gap above waveTxt
LK.gui.bottomLeft.addChild(autoNextWaveBtn);
LK.gui.bottomLeft.addChild(waveTxt);
autoNextWaveBtn.visible = true;
autoNextWaveBtn.down = function (x, y, obj) {
autoNextWave = !autoNextWave;
autoNextWaveBtn.setText('Auto: ' + (autoNextWave ? 'ON' : 'OFF'));
};
// Show/hide next wave button
function setNextWaveBtnVisible(visible) {
// Defensive: Only set visible if nextWaveBtn is defined and has a visible property
if (typeof nextWaveBtn !== "undefined" && nextWaveBtn && typeof nextWaveBtn.visible !== "undefined") {
nextWaveBtn.visible = visible;
}
}
setNextWaveBtnVisible(false);
// Start next wave
function startWave() {
if (waveInProgress || currentWave > maxWaves) {
return;
}
waveInProgress = true;
setNextWaveBtnVisible(false);
enemiesToSpawn = 6 + currentWave * 2;
spawnIndex = 0;
}
// Next wave button (centered below Points text, always present but hidden when not needed)
var nextWaveBtn = new Text2('Next Wave', {
size: 64,
fill: 0x2980B9
});
nextWaveBtn.anchor.set(0.5, 0);
nextWaveBtn.x = 0;
nextWaveBtn.y = kpTxt.y + kpTxt.height + 20;
LK.gui.top.addChild(nextWaveBtn);
nextWaveBtn.visible = false;
// Next wave button event
nextWaveBtn.down = function (x, y, obj) {
if (!waveInProgress && currentWave <= maxWaves) {
startWave();
}
};
// Build/upgrade tower on tile
function tryBuildOrUpgrade(tile) {
// All upgrades and purchases use only money
if (tile.occupied) {
// Try upgrade
var colorIdx = 0;
if (tile.tower.children && tile.tower.children.length > 0) {
var tint = tile.tower.children[0].tint;
for (var i = 0; i < TOWER_SELECT_COLORS.length; i++) {
if (TOWER_SELECT_COLORS[i] === tint) {
colorIdx = i;
break;
}
}
}
var upgradeCost = towerCurrentPrices[colorIdx] * tile.tower.level;
if (tile.tower.level < 3) {
if (money >= upgradeCost) {
money -= upgradeCost;
// Increase all tower prices by tower price multiplier globally
for (var i = 0; i < towerCurrentPrices.length; i++) {
towerCurrentPrices[i] = Math.round(towerCurrentPrices[i] * TOWER_PRICE_INCREASE_MULTIPLIER);
}
tile.tower.upgrade();
moneyTxt.setText('Money: ' + money);
}
}
} else {
// Build new tower
var buildCost = towerCurrentPrices[0];
if (money >= buildCost) {
money -= buildCost;
// Increase all tower prices by tower price multiplier globally
for (var i = 0; i < towerCurrentPrices.length; i++) {
towerCurrentPrices[i] = Math.round(towerCurrentPrices[i] * TOWER_PRICE_INCREASE_MULTIPLIER);
}
var tower = new Tower();
tower.x = tile.x;
tower.y = tile.y;
towers.push(tower);
game.addChild(tower);
tile.occupied = true;
tile.tower = tower;
moneyTxt.setText('Money: ' + money);
}
}
// Score is only for kill count, handled in enemy kill logic
}
// Highlight build tiles on touch
game.down = function (x, y, obj) {
// No tap-to-place logic; only drag-and-drop is supported for tower placement
};
// Remove highlight/range on up
// (handled by drag-and-drop up logic above)
// Main game update
game.update = function () {
// Spawn enemies for wave
if (waveInProgress && enemiesToSpawn > 0 && LK.ticks % spawnDelay === 0) {
var enemy = new Enemy();
enemy.x = path[0].x;
enemy.y = path[0].y;
enemy.pathIndex = 0;
enemy.pathProgress = 0;
enemies.push(enemy);
game.addChild(enemy);
enemiesToSpawn--;
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
e.update();
if (e.health <= 0) {
// Enemy defeated
scoreo += 1;
// Give money: 10 * currentWave
var reward = currentWave * 10;
money += reward;
kpTxt.setText('Score: ' + scoreo);
moneyTxt.setText('Money: ' + money);
e.destroy();
enemies.splice(i, 1);
} else if (e.reachedBase) {
// Enemy reached base
baseHealth--;
healthTxt.setText('Base: ' + baseHealth);
e.destroy();
enemies.splice(i, 1);
healthTxt.setText('Base: ' + baseHealth);
LK.effects.flashScreen(0xe74c3c, 400);
if (baseHealth <= 0) {
LK.showGameOver();
return;
}
}
}
// Update towers
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var b = bullets[i];
b.update();
if (b.destroyed) {
b.destroy();
bullets.splice(i, 1);
}
}
// End wave if all enemies defeated and none left to spawn
if (waveInProgress && enemies.length === 0 && enemiesToSpawn === 0) {
waveInProgress = false;
currentWave++;
// No win condition for infinite waves
waveTxt.setText('Wave: ' + currentWave + '/' + maxWaves);
// nextWaveBtn is repositioned, but autoNextWaveBtn is always visible and fixed above waveTxt
nextWaveBtn.x = 0;
nextWaveBtn.y = kpTxt.y + kpTxt.height + 20;
// autoNextWaveBtn position is fixed above waveTxt; do not reposition or show/hide here
if (autoNextWave) {
setNextWaveBtnVisible(false);
// Start next wave automatically after a short delay for feedback
LK.setTimeout(function () {
if (!waveInProgress && currentWave <= maxWaves) {
startWave();
}
}, 700);
} else {
setNextWaveBtnVisible(true);
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// BuildTile class (for tower placement)
var BuildTile = Container.expand(function () {
var self = Container.call(this);
// Attach tile asset (box, light gray)
var tileAsset = self.attachAsset('buildTile', {
anchorX: 0.5,
anchorY: 0.5
});
self.occupied = false;
self.tower = null;
// For highlighting
self.highlight = function (on) {
tileAsset.tint = on ? 0x2ecc40 : 0xbdc3c7;
};
return self;
});
// Bullet class
var Bullet = Container.expand(function () {
var self = Container.call(this);
// Attach bullet asset (small yellow box)
var bulletAsset = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.target = null;
self.damage = 5;
self.speed = 24;
self.update = function () {
if (!self.target || self.target.health <= 0 || self.target.reachedBase) {
self.destroyed = true;
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 32) {
// Hit
self.target.takeDamage(self.damage);
self.destroyed = true;
return;
}
var moveDist = Math.min(self.speed, dist);
self.x += dx / dist * moveDist;
self.y += dy / dist * moveDist;
};
return self;
});
// Enemy (Creep) class
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Attach enemy asset (ellipse, purple)
var enemyAsset = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Enemy stats
self.maxHealth = Math.round(ENEMY_BASE_HEALTH * currentWave * ENEMY_HEALTH_WAVE_MULTIPLIER);
self.health = self.maxHealth;
self.speed = 2 + (currentWave - 1) * 0.2; // Slightly faster each wave
self.reward = 5 + (currentWave - 1) * 2;
// Path progress
self.pathIndex = 0;
self.pathProgress = 0;
// For hit flash
self.isFlashing = false;
// Move along path
self.update = function () {
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) {
self.pathIndex++;
self.pathProgress = 0;
return;
}
var moveDist = self.speed;
self.pathProgress += moveDist;
if (self.pathProgress >= dist) {
self.pathIndex++;
self.pathProgress = 0;
if (self.pathIndex >= path.length - 1) {
// Reached end
self.x = to.x;
self.y = to.y;
self.reachedBase = true;
return;
}
}
var t = self.pathProgress / dist;
self.x = from.x + dx * t;
self.y = from.y + dy * t;
};
// Take damage
self.takeDamage = function (dmg) {
self.health -= dmg;
if (!self.isFlashing) {
self.isFlashing = true;
tween(enemyAsset, {
tint: 0xffffff
}, {
duration: 80,
onFinish: function onFinish() {
tween(enemyAsset, {
tint: 0x8e44ad
}, {
duration: 120,
onFinish: function onFinish() {
self.isFlashing = false;
}
});
}
});
}
};
return self;
});
// Tower class
var Tower = Container.expand(function () {
var self = Container.call(this);
// Attach tower asset (box, blue)
var towerAsset = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5
});
// Double-tap boost: track last tap time
self._lastTapTime = 0;
// Level label (bottom-left)
self.level = 1; // Default level is 1
self.levelLabel = new Text2(self.level + '', {
size: 36,
fill: "#fff"
});
self.levelLabel.anchor.set(0, 1); // bottom-left
// Position at bottom-left of tower asset
self.levelLabel.x = -50;
self.levelLabel.y = 50;
self.addChild(self.levelLabel);
// Delete button (top-right of tower)
self.deleteBtn = new Text2('✖', {
size: 40,
fill: "#fff"
});
self.deleteBtn.anchor.set(1, 0); // top-right
self.deleteBtn.x = 50;
self.deleteBtn.y = -50;
self.addChild(self.deleteBtn);
// Delete logic
self.deleteBtn.down = function (x, y, obj) {
// Remove from towers array
for (var i = towers.length - 1; i >= 0; i--) {
if (towers[i] === self) {
towers.splice(i, 1);
break;
}
}
// Remove from parent
self.destroy();
};
// Each tower instance manages its own stats and upgrades
self.range = 320;
self.damage = 5;
self.fireRate = 60; // frames per shot
self.cooldown = 0;
// For upgrade flash
self.isFlashing = false;
// Show range circle (for placement)
self.rangeCircle = self.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.15
});
self.rangeCircle.width = self.range * 2;
self.rangeCircle.height = self.range * 2;
self.rangeCircle.visible = false;
// Double-tap to boost this tower only
self.down = function (x, y, obj) {
var now = Date.now();
if (self._lastTapTime && now - self._lastTapTime < 400) {
// Double-tap detected: try to upgrade this tower
// Find color index for this tower
var colorIdx = 0;
if (self.children && self.children.length > 0) {
var tint = self.children[0].tint;
for (var i = 0; i < TOWER_SELECT_COLORS.length; i++) {
if (TOWER_SELECT_COLORS[i] === tint) {
colorIdx = i;
break;
}
}
}
// Calculate upgrade cost: base * current level
var upgradeCost = towerCurrentPrices[colorIdx] * self.level;
if (money >= upgradeCost) {
money -= upgradeCost;
// Increase all tower prices by tower price multiplier globally
for (var i = 0; i < towerCurrentPrices.length; i++) {
towerCurrentPrices[i] = Math.round(towerCurrentPrices[i] * TOWER_PRICE_INCREASE_MULTIPLIER);
}
// Actually upgrade tower
self.level++;
self.damage = Math.round(self.damage * TOWER_DAMAGE_UPGRADE_MULTIPLIER);
self.fireRate = Math.max(1, Math.round(self.fireRate / TOWER_SPEED_UPGRADE_MULTIPLIER));
// Update level label
self.levelLabel.setText(self.level + '');
if (typeof moneyTxt !== "undefined") {
moneyTxt.setText('Money: ' + money);
}
}
}
self._lastTapTime = now;
};
// Upgrade tower (for upgrade button or other triggers)
self.upgrade = function () {
if (self.level >= 3) {
return false;
}
self.level++;
if (self.levelLabel) {
self.levelLabel.setText(self.level + '');
}
// Find color index for this tower
var colorIdx = 0;
if (self.children && self.children.length > 0) {
var tint = self.children[0].tint;
for (var i = 0; i < TOWER_SELECT_COLORS.length; i++) {
if (TOWER_SELECT_COLORS[i] === tint) {
colorIdx = i;
break;
}
}
}
// Upgrade logic: scale up stats based on color
if (typeof TOWER_STATS !== "undefined" && TOWER_STATS[colorIdx]) {
self.damage += Math.round(TOWER_STATS[colorIdx].damage * 0.7);
self.range += Math.round(TOWER_STATS[colorIdx].range * 0.12);
self.fireRate = Math.max(20, self.fireRate - Math.round(TOWER_STATS[colorIdx].fireRate * 0.18));
} else {
self.damage += TOWER_DAMAGE_BLUE;
self.range += Math.round(TOWER_RANGE_STANDARD * 0.125);
self.fireRate = Math.max(30, self.fireRate - 10);
}
self.rangeCircle.width = self.range * 2;
self.rangeCircle.height = self.range * 2;
// Flash for upgrade
if (!self.isFlashing) {
self.isFlashing = true;
tween(towerAsset, {
tint: 0xffff00
}, {
duration: 120,
onFinish: function onFinish() {
tween(towerAsset, {
tint: 0x3498db
}, {
duration: 180,
onFinish: function onFinish() {
self.isFlashing = false;
}
});
}
});
}
return true;
};
// Tower attack logic
self.update = function () {
if (self.cooldown > 0) {
self.cooldown--;
return;
}
// Find nearest enemy in range
var nearest = null;
var minDist = 99999;
// Determine if this is a purple tower (Mor)
var isPurple = false;
if (self.children && self.children.length > 0 && self.children[0].tint === 0x8e44ad) {
isPurple = true;
}
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
var dx = e.x - self.x;
var dy = e.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if ((isPurple || dist <= self.range) && dist < minDist) {
nearest = e;
minDist = dist;
}
}
if (nearest) {
// Shoot
var b = new Bullet();
b.x = self.x;
b.y = self.y;
b.target = nearest;
b.damage = self.damage;
b.speed = 24;
b.lastX = b.x;
b.lastY = b.y;
bullets.push(b);
game.addChild(b);
self.cooldown = self.fireRate;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// --- Game Constants and Multipliers ---
// Tower stats constants
var TOWER_DAMAGE_BLUE = 5; // Mavi (blue) tower damage
var TOWER_DAMAGE_RED = 15; // Kırmızı (red) tower damage
var TOWER_DAMAGE_GREEN = 5; // Yeşil (green) tower damage
var TOWER_DAMAGE_YELLOW = 50; // Sarı (yellow) tower damage
var TOWER_DAMAGE_PURPLE = 15; // Mor (purple) tower damage
var TOWER_FIRE_RATE_BLUE = 60; // Mavi (blue) tower fire rate (frames per shot)
var TOWER_FIRE_RATE_RED = 60; // Kırmızı (red) tower fire rate
var TOWER_FIRE_RATE_GREEN = 20; // Yeşil (green) tower fire rate (3x faster than blue)
var TOWER_FIRE_RATE_YELLOW = 120; // Sarı (yellow) tower fire rate (2 seconds per shot)
var TOWER_FIRE_RATE_PURPLE = 120; // Mor (purple) tower fire rate (2 seconds per shot)
var TOWER_RANGE_STANDARD = 320; // Standard tower range
var TOWER_RANGE_GLOBAL = 2048; // Global range for purple tower
// Game progression multipliers
var ENEMY_BASE_HEALTH = 10; // Base health for enemies
var ENEMY_HEALTH_WAVE_MULTIPLIER = 1.25; // Health multiplier per wave
var TOWER_PRICE_INCREASE_MULTIPLIER = 1.25; // Tower price increase multiplier
// Tower upgrade multipliers
var TOWER_DAMAGE_UPGRADE_MULTIPLIER = 1.1; // Damage increase on upgrade
var TOWER_SPEED_UPGRADE_MULTIPLIER = 1.1; // Fire rate improvement on upgrade
// --- Grid system ---
// Set grid to 10x12 with margins, leave 1 row margin at bottom, and add 5 tower selection squares below grid
var GRID_COLS = 10;
var GRID_ROWS = 12;
var GRID_SIZE = 160;
// Calculate margins to center grid in 2048x2732, leaving 1 row margin at bottom for spacing
var GRID_TOTAL_WIDTH = GRID_COLS * GRID_SIZE;
var GRID_TOTAL_HEIGHT = GRID_ROWS * GRID_SIZE;
var GRID_MARGIN_X = Math.floor((2048 - GRID_TOTAL_WIDTH) / 2);
var GRID_MARGIN_Y = Math.floor((2732 - (GRID_TOTAL_HEIGHT + GRID_SIZE)) / 2); // +GRID_SIZE for 1 row margin at bottom
// Helper to snap to grid (with margin)
function snapToGrid(x, y) {
return {
x: GRID_MARGIN_X + Math.round((x - GRID_MARGIN_X) / GRID_SIZE) * GRID_SIZE,
y: GRID_MARGIN_Y + Math.round((y - GRID_MARGIN_Y) / GRID_SIZE) * GRID_SIZE
};
}
function generateSimplePath() {
var pathArr = [];
var visited = {}; // Ziyaret edilen hücreler ve geçiş yönleri: { h: boolean, v: boolean }
var yukariDonusYapildi = false; // YENİ: Yukarı dönüş yapılıp yapılmadığını takip eder
function cellKey(c, r) {
return c + "," + r;
}
var startCols = [];
for (var c = 1; c < GRID_COLS - 1; c++) {
startCols.push(c);
}
if (startCols.length === 0) {
return [snapToGrid(GRID_MARGIN_X, GRID_MARGIN_Y)];
}
var startCol = startCols[Math.floor(Math.random() * startCols.length)];
var curCol = startCol;
var curRow = 0;
pathArr.push(snapToGrid(GRID_MARGIN_X + curCol * GRID_SIZE, GRID_MARGIN_Y + curRow * GRID_SIZE));
var startKey = cellKey(curCol, curRow);
visited[startKey] = {
h: false,
v: true
}; // Başlangıçta aşağı varsayımı
curRow = 1;
if (GRID_ROWS < 2) {
return pathArr;
}
pathArr.push(snapToGrid(GRID_MARGIN_X + curCol * GRID_SIZE, GRID_MARGIN_Y + curRow * GRID_SIZE));
var firstStepKey = cellKey(curCol, curRow);
visited[firstStepKey] = visited[firstStepKey] || {
h: false,
v: false
};
visited[firstStepKey].v = true;
var endCols = [];
for (var c = 1; c < GRID_COLS - 1; c++) {
endCols.push(c);
}
if (endCols.length === 0) {
return pathArr;
}
var endCol = endCols[Math.floor(Math.random() * endCols.length)];
var endRow = GRID_ROWS - 1;
var preEndRow = GRID_ROWS - 2;
if (preEndRow < curRow) {
if (GRID_ROWS > 0) {
pathArr.push(snapToGrid(GRID_MARGIN_X + endCol * GRID_SIZE, GRID_MARGIN_Y + endRow * GRID_SIZE));
// visited[cellKey(endCol, endRow)] ...
}
return pathArr;
}
var lastDir = "down";
var straightCount = 1;
var maxRetries = GRID_COLS * GRID_ROWS * 15; // Biraz daha fazla deneme hakkı
var retries = 0;
while (!(curRow === preEndRow && curCol === endCol)) {
retries++;
if (retries > maxRetries) {
// console.warn("Path Gen: Max retries! Yukari Donus: " + yukariDonusYapildi);
break;
}
var potentialMoves = [];
// Potansiyel hamleleri oluştur (önceliklendirme olmadan tüm yönleri düşün)
if (curRow < preEndRow && lastDir !== "up") {
potentialMoves.push({
name: "down",
dx: 0,
dy: 1
});
}
if (curRow > 1 && lastDir !== "down") {
potentialMoves.push({
name: "up",
dx: 0,
dy: -1
});
}
if (curCol < GRID_COLS - 2 && lastDir !== "left") {
potentialMoves.push({
name: "right",
dx: 1,
dy: 0
});
}
if (curCol > 1 && lastDir !== "right") {
potentialMoves.push({
name: "left",
dx: -1,
dy: 0
});
}
// Eğer hiçbir potansiyel hamle yoksa (örn. 180 derece dönüş engeli nedeniyle), temel hamleleri tekrar ekle
if (potentialMoves.length === 0) {
if (curRow < preEndRow) {
potentialMoves.push({
name: "down",
dx: 0,
dy: 1
});
}
if (curRow > 1) {
potentialMoves.push({
name: "up",
dx: 0,
dy: -1
});
}
if (curCol < GRID_COLS - 2) {
potentialMoves.push({
name: "right",
dx: 1,
dy: 0
});
}
if (curCol > 1) {
potentialMoves.push({
name: "left",
dx: -1,
dy: 0
});
}
}
var validMovesRaw = [];
for (var i = 0; i < potentialMoves.length; i++) {
var move = potentialMoves[i];
var testCol = curCol + move.dx;
var testRow = curRow + move.dy;
var isHorizMove = move.dy === 0;
if (testCol < 1 || testCol > GRID_COLS - 2 || testRow < 1 || testRow > preEndRow) {
continue;
}
var vDataTarget = visited[cellKey(testCol, testRow)];
if (vDataTarget) {
if (isHorizMove && vDataTarget.h) {
continue;
}
if (!isHorizMove && vDataTarget.v) {
continue;
}
}
if (move.name === "up" && lastDir !== "up") {
// Sadece gerçek bir "yukarı dönüş" ise kontrol et
var upTurnSpacingOk = true;
for (var side = -1; side <= 1; side += 2) {
for (var dist = 1; dist <= 3; dist++) {
var checkC = testCol + side * dist;
if (checkC < 1 || checkC > GRID_COLS - 2) {
upTurnSpacingOk = false;
break;
}
var nKey = cellKey(checkC, testRow);
var vDataNeighbor = visited[nKey];
if (vDataNeighbor && (vDataNeighbor.h || vDataNeighbor.v)) {
upTurnSpacingOk = false;
break;
}
}
if (!upTurnSpacingOk) {
break;
}
}
if (!upTurnSpacingOk) {
continue;
}
}
var parallelSpacingOk = true;
if (isHorizMove) {
var nAboveKey = cellKey(testCol, testRow - 1);
var vAbove = visited[nAboveKey];
var nBelowKey = cellKey(testCol, testRow + 1);
var vBelow = visited[nBelowKey];
if (vAbove && vAbove.h && testRow - 1 !== curRow || vBelow && vBelow.h && testRow + 1 !== curRow) {
parallelSpacingOk = false;
}
} else {
var nLeftKey = cellKey(testCol - 1, testRow);
var vLeft = visited[nLeftKey];
var nRightKey = cellKey(testCol + 1, testRow);
var vRight = visited[nRightKey];
if (vLeft && vLeft.v && testCol - 1 !== curCol || vRight && vRight.v && testCol + 1 !== curCol) {
parallelSpacingOk = false;
}
}
if (!parallelSpacingOk) {
continue;
}
validMovesRaw.push(move);
}
if (validMovesRaw.length === 0) {
/*console.warn("No valid raw moves for " + curCol + "," + curRow);*/
break;
}
var currentActionableMoves = validMovesRaw;
if (straightCount >= 4) {
var nonStraightMoves = currentActionableMoves.filter(function (m) {
return m.name !== lastDir;
});
if (nonStraightMoves.length > 0) {
currentActionableMoves = nonStraightMoves;
}
}
if (currentActionableMoves.length === 0 && validMovesRaw.length > 0) {
currentActionableMoves = validMovesRaw;
}
if (currentActionableMoves.length === 0) {
/*console.warn("No actionable after straight count for " + curCol + "," + curRow);*/
break;
}
var selectedMove = null;
var potentialUpwardTurn = null;
if (!yukariDonusYapildi) {
currentActionableMoves.forEach(function (m) {
if (m.name === "up" && lastDir !== "up") {
potentialUpwardTurn = m; // Kurallara uyan bir yukarı dönüş bulundu
}
});
}
// Hamle Seçim Mantığı
if (potentialUpwardTurn) {
// Eğer yukarı dönüş yapılmadıysa ve mümkünse, onu önceliklendir
selectedMove = potentialUpwardTurn;
} else {
// Normal hamle seçim mantığı (düz gitme, hedefe yönelme vb.)
var moveInLastDir = null;
currentActionableMoves.forEach(function (m) {
if (m.name === lastDir) {
moveInLastDir = m;
}
});
var preferredMovesTowardsTarget = [];
currentActionableMoves.forEach(function (m) {
var candCol = curCol + m.dx;
var candRow = curRow + m.dy;
if (candCol === endCol && candRow === preEndRow) {
preferredMovesTowardsTarget.unshift(m);
} else {
var distOld = Math.abs(curCol - endCol) + Math.abs(curRow - preEndRow);
var distNew = Math.abs(candCol - endCol) + Math.abs(candRow - preEndRow);
if (distNew < distOld) {
preferredMovesTowardsTarget.push(m);
}
}
});
var straightAndPreferredTarget = null;
if (moveInLastDir && straightCount < 4) {
preferredMovesTowardsTarget.forEach(function (pm) {
if (pm.name === moveInLastDir.name) {
straightAndPreferredTarget = moveInLastDir;
}
});
}
if (straightAndPreferredTarget && Math.random() < 0.75) {
// %75 düz ve hedefe yönelik
selectedMove = straightAndPreferredTarget;
} else if (preferredMovesTowardsTarget.length > 0 && Math.random() < 0.80) {
// %80 hedefe yönelik
selectedMove = preferredMovesTowardsTarget[Math.floor(Math.random() * preferredMovesTowardsTarget.length)];
} else if (moveInLastDir && straightCount < 4 && Math.random() < 0.65) {
// %65 düz git
selectedMove = moveInLastDir;
} else if (currentActionableMoves.length > 0) {
// Son çare
selectedMove = currentActionableMoves[Math.floor(Math.random() * currentActionableMoves.length)];
} else {
break;
}
}
if (!selectedMove && currentActionableMoves.length > 0) {
// Eğer yukarıdaki mantıkla seçilemediyse (örn. potentialUpwardTurn vardı ama seçilmedi)
selectedMove = currentActionableMoves[Math.floor(Math.random() * currentActionableMoves.length)];
}
if (!selectedMove) {
/*console.warn("No move selected for " + curCol + "," + curRow);*/
break;
}
// Seçilen hamleyi uygula ve durumu güncelle
if (selectedMove.name === "up" && lastDir !== "up") {
yukariDonusYapildi = true;
}
var movedHorizontally = selectedMove.dy === 0;
if (selectedMove.name === lastDir) {
straightCount++;
} else {
straightCount = 1;
}
lastDir = selectedMove.name;
curCol += selectedMove.dx;
curRow += selectedMove.dy;
var newKey = cellKey(curCol, curRow);
visited[newKey] = visited[newKey] || {
h: false,
v: false
};
if (movedHorizontally) {
visited[newKey].h = true;
} else {
visited[newKey].v = true;
}
pathArr.push(snapToGrid(GRID_MARGIN_X + curCol * GRID_SIZE, GRID_MARGIN_Y + curRow * GRID_SIZE));
} // while sonu
// Eğer döngü bittiğinde hala yukarı dönüş yapılmadıysa ve yol çok kısaysa, bu bir sorun olabilir.
// Ancak algoritma, mümkün olan en kısa sürede yukarı dönüş yapmaya çalışacaktır.
// if (!yukariDonusYapildi && pathArr.length > (GRID_ROWS / 2)) console.log("Uyarı: Yol tamamlandı ancak yukarı dönüş yapılamadı.");
if (GRID_ROWS > 0) {
pathArr.push(snapToGrid(GRID_MARGIN_X + endCol * GRID_SIZE, GRID_MARGIN_Y + endRow * GRID_SIZE));
// visited[cellKey(endCol, endRow)] ...
}
return pathArr;
}
var path = generateSimplePath();
// --- Start Game Button Logic ---
var gameStarted = false;
var startBtn = new Text2('▶ Start Game', {
size: 120,
fill: 0x27AE60
});
startBtn.anchor.set(0.5, 0.5);
LK.gui.center.addChild(startBtn);
function setStartBtnVisible(visible) {
startBtn.visible = visible;
}
setStartBtnVisible(true);
startBtn.down = function (x, y, obj) {
if (!gameStarted) {
gameStarted = true;
setStartBtnVisible(false);
setNextWaveBtnVisible(true);
startWave(); // Start the first wave immediately
}
};
// Hide all gameplay UI until game starts
setNextWaveBtnVisible(false);
if (typeof kpTxt !== "undefined") {
kpTxt.visible = false;
}
if (typeof healthTxt !== "undefined") {
healthTxt.visible = false;
}
if (typeof waveTxt !== "undefined") {
waveTxt.visible = false;
}
if (typeof nextWaveBtn !== "undefined") {
nextWaveBtn.visible = false;
}
// autoNextWaveBtn is always visible; do not hide
// Show gameplay UI when game starts
function showGameplayUI() {
kpTxt.visible = true;
healthTxt.visible = true;
waveTxt.visible = true;
nextWaveBtn.visible = false;
nextWaveBtn.x = 0;
nextWaveBtn.y = kpTxt.y + kpTxt.height + 20;
// autoNextWaveBtn is always visible and positioned above waveTxt; do not reposition or show/hide here
kpTxt.setText('Points: ' + knowledgePoints);
healthTxt.setText('Base: ' + baseHealth);
waveTxt.setText('Wave: ' + currentWave + '/' + maxWaves);
}
var _origStartWave = startWave;
startWave = function startWave() {
if (!gameStarted) {
return;
}
showGameplayUI();
_origStartWave();
};
// Buildable tile logic removed for now
var buildTilePositions = [];
// Global game state
var enemies = [];
var towers = [];
var bullets = [];
// buildTiles removed for now
var baseHealth = 10;
var scoreo = 0; // Killed enemy count, used as score only
var money = 50; // Used for purchases, start with 50
var knowledgePoints = 30; // Deprecated, kept for UI compatibility
var currentWave = 1;
var maxWaves = 999999; // Effectively infinite waves
var waveInProgress = false;
var waveTimer = null;
var spawnIndex = 0;
var spawnDelay = 40; // frames between spawns
var enemiesToSpawn = 0;
var selectedTile = null;
// Tower price system
var towerBasePrices = [30, 50, 40, 60, 80]; // Starting price for each tower type (by power)
var towerCurrentPrices = towerBasePrices.slice(); // Current price for each type
// Asset initialization
// Draw grid background (light gray squares) with margins and 10x12 grid area
for (var row = 0; row < GRID_ROWS; row++) {
for (var col = 0; col < GRID_COLS; col++) {
var gridNode = LK.getAsset('buildTile', {
anchorX: 0.5,
anchorY: 0.5,
width: GRID_SIZE - 4,
height: GRID_SIZE - 4,
color: 0xf4f4f4,
shape: 'box',
alpha: 0.18
});
gridNode.x = GRID_MARGIN_X + col * GRID_SIZE;
gridNode.y = GRID_MARGIN_Y + row * GRID_SIZE;
game.addChild(gridNode);
}
}
// Draw 5 colored tower selection squares below the grid, centered horizontally
var TOWER_SELECT_COUNT = 5;
var TOWER_SELECT_SIZE = 140;
var TOWER_SELECT_SPACING = 60;
var TOWER_SELECT_COLORS = [0x3498db, 0xe74c3c, 0x2ecc40, 0xf1c40f, 0x8e44ad];
var TOWER_SELECT_LABELS = ['Mavi', 'Kırmızı', 'Yeşil', 'Sarı', 'Mor'];
// Tower stats for each color: {damage, range, fireRate}
var TOWER_STATS = [{
damage: TOWER_DAMAGE_BLUE,
range: TOWER_RANGE_STANDARD,
fireRate: TOWER_FIRE_RATE_BLUE
},
// Mavi
{
damage: TOWER_DAMAGE_RED,
range: TOWER_RANGE_STANDARD,
fireRate: TOWER_FIRE_RATE_RED
},
// Kırmızı
{
damage: TOWER_DAMAGE_GREEN,
range: TOWER_RANGE_STANDARD,
fireRate: TOWER_FIRE_RATE_GREEN
},
// Yeşil
{
damage: TOWER_DAMAGE_YELLOW,
range: TOWER_RANGE_STANDARD,
fireRate: TOWER_FIRE_RATE_YELLOW
},
// Sarı
{
damage: TOWER_DAMAGE_PURPLE,
range: TOWER_RANGE_GLOBAL,
fireRate: TOWER_FIRE_RATE_PURPLE
} // Mor (global range)
];
var towerSelectSquares = [];
var totalWidth = TOWER_SELECT_COUNT * TOWER_SELECT_SIZE + (TOWER_SELECT_COUNT - 1) * TOWER_SELECT_SPACING;
var startX = Math.floor((2048 - totalWidth) / 2) + TOWER_SELECT_SIZE / 2;
var selectY = GRID_MARGIN_Y + GRID_ROWS * GRID_SIZE + GRID_SIZE / 2;
for (var i = 0; i < TOWER_SELECT_COUNT; i++) {
var square = LK.getAsset('buildTile', {
anchorX: 0.5,
anchorY: 0.5,
width: TOWER_SELECT_SIZE,
height: TOWER_SELECT_SIZE,
color: TOWER_SELECT_COLORS[i],
shape: 'box',
alpha: 0.95
});
square.x = startX + i * (TOWER_SELECT_SIZE + TOWER_SELECT_SPACING);
square.y = selectY;
game.addChild(square);
// Add label for each tower color
var label = new Text2(TOWER_SELECT_LABELS[i], {
size: 48,
fill: "#fff"
});
label.anchor.set(0.5, 0);
label.x = square.x;
label.y = square.y + TOWER_SELECT_SIZE / 2 + 8;
game.addChild(label);
towerSelectSquares.push(square);
}
// Draw path (for visual reference)
for (var i = 0; i < path.length - 1; i++) {
var from = path[i];
var to = path[i + 1];
var dx = to.x - from.x;
var dy = to.y - from.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var steps = Math.floor(dist / 40);
for (var s = 0; s <= steps; s++) {
var t = s / steps;
var px = from.x + dx * t;
var py = from.y + dy * t;
var node = LK.getAsset('pathDot', {
anchorX: 0.5,
anchorY: 0.5,
width: 32,
height: 32,
color: 0x7f8c8d,
shape: 'ellipse'
});
node.x = px;
node.y = py;
game.addChild(node);
}
}
// Buildable tile objects removed for now
// --- Tower Selection Bar ---
var towerTypes = [];
var selectedTowerType = null;
var towerButtons = [];
var towerBtnY = 2400;
var towerBtnSpacing = 260;
var towerBtnStartX = 400;
var draggingTowerType = null;
var draggingTowerGhost = null;
// No tower selection buttons since normal tower is removed
// --- Drag-and-drop tower placement from colored squares ---
for (var i = 0; i < towerSelectSquares.length; i++) {
(function (colorIdx) {
var square = towerSelectSquares[colorIdx];
square.down = function (x, y, obj) {
// Clean up any previous ghost
if (draggingTowerGhost) {
if (draggingTowerGhost.rangeCircle) {
draggingTowerGhost.rangeCircle.destroy();
}
draggingTowerGhost.destroy();
draggingTowerGhost = null;
draggingTowerType = null;
}
draggingTowerType = colorIdx;
draggingTowerGhost = new Tower();
// Snap to grid for initial drag position
var gridX = Math.round((x - GRID_MARGIN_X) / GRID_SIZE);
var gridY = Math.round((y - GRID_MARGIN_Y) / GRID_SIZE);
var snapX = GRID_MARGIN_X + gridX * GRID_SIZE;
var snapY = GRID_MARGIN_Y + gridY * GRID_SIZE;
draggingTowerGhost.x = snapX;
draggingTowerGhost.y = snapY;
// Set color of ghost tower
if (draggingTowerGhost.children && draggingTowerGhost.children.length > 0) {
draggingTowerGhost.children[0].tint = TOWER_SELECT_COLORS[colorIdx];
}
// Set stats based on color for ghost
if (typeof TOWER_STATS !== "undefined" && TOWER_STATS[colorIdx]) {
draggingTowerGhost.damage = TOWER_STATS[colorIdx].damage;
draggingTowerGhost.range = TOWER_STATS[colorIdx].range;
draggingTowerGhost.fireRate = TOWER_STATS[colorIdx].fireRate;
if (draggingTowerGhost.rangeCircle) {
draggingTowerGhost.rangeCircle.width = draggingTowerGhost.range * 2;
draggingTowerGhost.rangeCircle.height = draggingTowerGhost.range * 2;
}
}
// Show range circle while dragging, and center it on the ghost
if (draggingTowerGhost.rangeCircle) {
draggingTowerGhost.rangeCircle.visible = true;
draggingTowerGhost.rangeCircle.x = 0;
draggingTowerGhost.rangeCircle.y = 0;
}
draggingTowerGhost.alpha = 0.7;
game.addChild(draggingTowerGhost);
};
})(i);
}
// Helper: check if a grid cell is on the path
function isCellOnPath(gridX, gridY) {
for (var i = 0; i < path.length; i++) {
var px = path[i].x;
var py = path[i].y;
var cellX = Math.round((px - GRID_MARGIN_X) / GRID_SIZE);
var cellY = Math.round((py - GRID_MARGIN_Y) / GRID_SIZE);
if (cellX === gridX && cellY === gridY) {
return true;
}
}
return false;
}
// Helper: check if a tower already exists at grid cell
function isTowerAtCell(gridX, gridY) {
for (var i = 0; i < towers.length; i++) {
var t = towers[i];
var tx = Math.round((t.x - GRID_MARGIN_X) / GRID_SIZE);
var ty = Math.round((t.y - GRID_MARGIN_Y) / GRID_SIZE);
if (tx === gridX && ty === gridY) {
return true;
}
}
return false;
}
// Dragging ghost tower with finger/mouse
game.move = function (x, y, obj) {
if (draggingTowerGhost) {
// Snap ghost to grid for feedback
var gridX = Math.round((x - GRID_MARGIN_X) / GRID_SIZE);
var gridY = Math.round((y - GRID_MARGIN_Y) / GRID_SIZE);
var snapX = GRID_MARGIN_X + gridX * GRID_SIZE;
var snapY = GRID_MARGIN_Y + gridY * GRID_SIZE;
draggingTowerGhost.x = snapX;
draggingTowerGhost.y = snapY;
// Always center rangeCircle on the ghost (0,0 in local coordinates)
if (draggingTowerGhost.rangeCircle) {
draggingTowerGhost.rangeCircle.x = 0;
draggingTowerGhost.rangeCircle.y = 0;
}
// Visual feedback: tint red if invalid, normal if valid
var valid = true;
// Only allow inside grid
if (gridX < 0 || gridX >= GRID_COLS || gridY < 0 || gridY >= GRID_ROWS || isCellOnPath(gridX, gridY) || isTowerAtCell(gridX, gridY)) {
valid = false;
}
if (draggingTowerGhost.children && draggingTowerGhost.children.length > 0) {
draggingTowerGhost.children[0].tint = valid ? TOWER_SELECT_COLORS[draggingTowerType] : 0x888888;
}
draggingTowerGhost.alpha = valid ? 0.7 : 0.35;
}
};
// Place tower on grid on up
game.up = function (x, y, obj) {
if (draggingTowerGhost && draggingTowerType !== null) {
var gridX = Math.round((draggingTowerGhost.x - GRID_MARGIN_X) / GRID_SIZE);
var gridY = Math.round((draggingTowerGhost.y - GRID_MARGIN_Y) / GRID_SIZE);
var valid = true;
if (gridX < 0 || gridX >= GRID_COLS || gridY < 0 || gridY >= GRID_ROWS || isCellOnPath(gridX, gridY) || isTowerAtCell(gridX, gridY)) {
valid = false;
}
if (valid) {
// Place tower if enough money (use dynamic price)
var buildCost = towerCurrentPrices[draggingTowerType];
if (money >= buildCost) {
money -= buildCost;
// Increase all tower prices by tower price multiplier
for (var i = 0; i < towerCurrentPrices.length; i++) {
towerCurrentPrices[i] = Math.round(towerCurrentPrices[i] * TOWER_PRICE_INCREASE_MULTIPLIER);
}
var tower = new Tower();
tower.x = GRID_MARGIN_X + gridX * GRID_SIZE;
tower.y = GRID_MARGIN_Y + gridY * GRID_SIZE;
// Set color
if (tower.children && tower.children.length > 0) {
tower.children[0].tint = TOWER_SELECT_COLORS[draggingTowerType];
}
// Set stats based on color
if (typeof TOWER_STATS !== "undefined" && TOWER_STATS[draggingTowerType]) {
tower.damage = TOWER_STATS[draggingTowerType].damage;
tower.range = TOWER_STATS[draggingTowerType].range;
tower.fireRate = TOWER_STATS[draggingTowerType].fireRate;
if (tower.rangeCircle) {
tower.rangeCircle.width = tower.range * 2;
tower.rangeCircle.height = tower.range * 2;
}
}
towers.push(tower);
game.addChild(tower);
moneyTxt.setText('Money: ' + money);
}
}
// Clean up ghost
if (draggingTowerGhost.rangeCircle) {
draggingTowerGhost.rangeCircle.destroy();
}
draggingTowerGhost.destroy();
draggingTowerGhost = null;
draggingTowerType = null;
return;
}
// If not placing, just clear ghost
if (draggingTowerGhost) {
if (draggingTowerGhost.rangeCircle) {
draggingTowerGhost.rangeCircle.destroy();
}
draggingTowerGhost.destroy();
draggingTowerGhost = null;
draggingTowerType = null;
}
};
// Base indicator (ellipse, red)
var baseNode = LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5,
width: 160,
height: 160,
color: 0xe74c3c,
shape: 'ellipse'
});
baseNode.x = path[path.length - 1].x;
baseNode.y = path[path.length - 1].y;
game.addChild(baseNode);
// GUI: Points (KP) - Center top, smaller size, renamed to Points
var kpTxt = new Text2('Score: ' + scoreo, {
size: 48,
fill: 0x2ecc40 // bright green, not transparent
});
kpTxt.anchor.set(0.5, 0); // center aligned, top
kpTxt.x = 0;
kpTxt.y = 10;
LK.gui.top.addChild(kpTxt);
// GUI: Money - Bottom right
var moneyTxt = new Text2('Money: ' + money, {
size: 48,
fill: 0xf1c40f
});
moneyTxt.anchor.set(1, 1); // right aligned, bottom
moneyTxt.x = -40; // 40px from right edge
moneyTxt.y = -40; // 40px from bottom edge
LK.gui.bottomRight.addChild(moneyTxt);
// GUI: Base Health - Top right, smaller size
var healthTxt = new Text2('Base: ' + baseHealth, {
size: 48,
fill: 0xE74C3C
});
healthTxt.anchor.set(1, 0); // right aligned, top
healthTxt.x = -40; // right margin
healthTxt.y = 10;
LK.gui.topRight.addChild(healthTxt);
// GUI: Wave - Bottom left, smaller size
var waveTxt = new Text2('Wave: ' + currentWave + '/' + maxWaves, {
size: 48,
fill: 0x2980B9
});
waveTxt.anchor.set(0, 1); // left aligned, bottom
waveTxt.x = 40;
waveTxt.y = -40;
// Auto-next wave toggle (now above waveTxt, same size)
var autoNextWave = false;
var autoNextWaveBtn = new Text2('Auto: OFF', {
size: 48,
fill: 0x2980B9
});
autoNextWaveBtn.anchor.set(0, 1);
autoNextWaveBtn.x = 40;
autoNextWaveBtn.y = waveTxt.y - waveTxt.height - 8; // 8px gap above waveTxt
LK.gui.bottomLeft.addChild(autoNextWaveBtn);
LK.gui.bottomLeft.addChild(waveTxt);
autoNextWaveBtn.visible = true;
autoNextWaveBtn.down = function (x, y, obj) {
autoNextWave = !autoNextWave;
autoNextWaveBtn.setText('Auto: ' + (autoNextWave ? 'ON' : 'OFF'));
};
// Show/hide next wave button
function setNextWaveBtnVisible(visible) {
// Defensive: Only set visible if nextWaveBtn is defined and has a visible property
if (typeof nextWaveBtn !== "undefined" && nextWaveBtn && typeof nextWaveBtn.visible !== "undefined") {
nextWaveBtn.visible = visible;
}
}
setNextWaveBtnVisible(false);
// Start next wave
function startWave() {
if (waveInProgress || currentWave > maxWaves) {
return;
}
waveInProgress = true;
setNextWaveBtnVisible(false);
enemiesToSpawn = 6 + currentWave * 2;
spawnIndex = 0;
}
// Next wave button (centered below Points text, always present but hidden when not needed)
var nextWaveBtn = new Text2('Next Wave', {
size: 64,
fill: 0x2980B9
});
nextWaveBtn.anchor.set(0.5, 0);
nextWaveBtn.x = 0;
nextWaveBtn.y = kpTxt.y + kpTxt.height + 20;
LK.gui.top.addChild(nextWaveBtn);
nextWaveBtn.visible = false;
// Next wave button event
nextWaveBtn.down = function (x, y, obj) {
if (!waveInProgress && currentWave <= maxWaves) {
startWave();
}
};
// Build/upgrade tower on tile
function tryBuildOrUpgrade(tile) {
// All upgrades and purchases use only money
if (tile.occupied) {
// Try upgrade
var colorIdx = 0;
if (tile.tower.children && tile.tower.children.length > 0) {
var tint = tile.tower.children[0].tint;
for (var i = 0; i < TOWER_SELECT_COLORS.length; i++) {
if (TOWER_SELECT_COLORS[i] === tint) {
colorIdx = i;
break;
}
}
}
var upgradeCost = towerCurrentPrices[colorIdx] * tile.tower.level;
if (tile.tower.level < 3) {
if (money >= upgradeCost) {
money -= upgradeCost;
// Increase all tower prices by tower price multiplier globally
for (var i = 0; i < towerCurrentPrices.length; i++) {
towerCurrentPrices[i] = Math.round(towerCurrentPrices[i] * TOWER_PRICE_INCREASE_MULTIPLIER);
}
tile.tower.upgrade();
moneyTxt.setText('Money: ' + money);
}
}
} else {
// Build new tower
var buildCost = towerCurrentPrices[0];
if (money >= buildCost) {
money -= buildCost;
// Increase all tower prices by tower price multiplier globally
for (var i = 0; i < towerCurrentPrices.length; i++) {
towerCurrentPrices[i] = Math.round(towerCurrentPrices[i] * TOWER_PRICE_INCREASE_MULTIPLIER);
}
var tower = new Tower();
tower.x = tile.x;
tower.y = tile.y;
towers.push(tower);
game.addChild(tower);
tile.occupied = true;
tile.tower = tower;
moneyTxt.setText('Money: ' + money);
}
}
// Score is only for kill count, handled in enemy kill logic
}
// Highlight build tiles on touch
game.down = function (x, y, obj) {
// No tap-to-place logic; only drag-and-drop is supported for tower placement
};
// Remove highlight/range on up
// (handled by drag-and-drop up logic above)
// Main game update
game.update = function () {
// Spawn enemies for wave
if (waveInProgress && enemiesToSpawn > 0 && LK.ticks % spawnDelay === 0) {
var enemy = new Enemy();
enemy.x = path[0].x;
enemy.y = path[0].y;
enemy.pathIndex = 0;
enemy.pathProgress = 0;
enemies.push(enemy);
game.addChild(enemy);
enemiesToSpawn--;
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
e.update();
if (e.health <= 0) {
// Enemy defeated
scoreo += 1;
// Give money: 10 * currentWave
var reward = currentWave * 10;
money += reward;
kpTxt.setText('Score: ' + scoreo);
moneyTxt.setText('Money: ' + money);
e.destroy();
enemies.splice(i, 1);
} else if (e.reachedBase) {
// Enemy reached base
baseHealth--;
healthTxt.setText('Base: ' + baseHealth);
e.destroy();
enemies.splice(i, 1);
healthTxt.setText('Base: ' + baseHealth);
LK.effects.flashScreen(0xe74c3c, 400);
if (baseHealth <= 0) {
LK.showGameOver();
return;
}
}
}
// Update towers
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var b = bullets[i];
b.update();
if (b.destroyed) {
b.destroy();
bullets.splice(i, 1);
}
}
// End wave if all enemies defeated and none left to spawn
if (waveInProgress && enemies.length === 0 && enemiesToSpawn === 0) {
waveInProgress = false;
currentWave++;
// No win condition for infinite waves
waveTxt.setText('Wave: ' + currentWave + '/' + maxWaves);
// nextWaveBtn is repositioned, but autoNextWaveBtn is always visible and fixed above waveTxt
nextWaveBtn.x = 0;
nextWaveBtn.y = kpTxt.y + kpTxt.height + 20;
// autoNextWaveBtn position is fixed above waveTxt; do not reposition or show/hide here
if (autoNextWave) {
setNextWaveBtnVisible(false);
// Start next wave automatically after a short delay for feedback
LK.setTimeout(function () {
if (!waveInProgress && currentWave <= maxWaves) {
startWave();
}
}, 700);
} else {
setNextWaveBtnVisible(true);
}
}
};