User prompt
The tower drag system seems to take precedence over upgrading towers. It should not
User prompt
Clicking a tower directly should show an upgrade menu rather than triggering the place new tower dragger.
User prompt
Cost of placing the base tower should be 5 gold. You should only get one gold for killing an enemy.
User prompt
Attach the labels to topCenter. And align elements accordingly
User prompt
The 3 labels should be centered on the screen. Lives should be at the center
User prompt
The core label is off the screen on the right. Make sure that these 3 labels are visible on the screen and centered. But make sure it does not overlap on the left.
User prompt
Add the following tracked elements to the game. 1. Gold (Golden color, default: 80) 2. Lives (Green, default: 20) 3. Score (Red, default: 0) Create labels at the top of the screen for each of these values. Please take care to not put anything in the top left corner as that's where the pause button is For now simply add the labels and the logic to update their values.
User prompt
Add the following tracked elements to the game. 1. Gold (Golden color, default: 80) 2. Lives (Green, default: 20) 3. Score (Red, default: 0) Create labels at the top of the screen for each of these values. Please take care to not put anything in the top left corner as that's where the pause button is
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'self.down = function (x, y, obj) { __$(546); self.upgrade(); }')' in or related to this line: 'self.down = function (x, y, obj) {' Line Number: 946
User prompt
Add the following tracked elements to the game. 1. Gold (Golden color, default: 80) 2. Lives (Green, default: 20) 3. Score (Red, default: 0) Create labels at the top of the screen for each of these values. Please take care to not put anything in the top left corner as that's where the pause button is
Code edit (6 edits merged)
Please save this source code
User prompt
Use alpha instead of tint to indicate the state of upgrade levels
Code edit (3 edits merged)
Please save this source code
User prompt
The enemy health bar shrinks toward the center. Instead it should shrink towards the left. Do note that the location of the health bar should remain the same.
User prompt
Enemy health bars are currently center aligned. We want it to be left aligned. Take care to ensure the background is still correctly aligned behind the red progressbar
User prompt
For whatever reason tracking of enemies seem to fail over time. Please debug this and fig it. This seems to be a state issue where towers somehow break and stops tracking enemies.
User prompt
Targeting seems to fail sometimes. Towers should always target and shoot at the enemy in range that is the closet to escaping.
Code edit (1 edits merged)
Please save this source code
Code edit (2 edits merged)
Please save this source code
User prompt
Enemies can currently outrun bullets. Find a clever way to solve this issue. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Our towers should be able to shoot at our enemies. The current tower type is a simple bullet type tower. So make it just shoot bullets at our enemies. Make sure that we only shoot at enemies that are still alive after having been hit by all bullets that have already been fired at said enemy
User prompt
Sometimes when we try to build a tower by releasing the screen, the tower fails to build because an enemy is in the way. In those cases please show a notification that it failed and why.
User prompt
Sometimes when we try to build a tower by releasing the screen, the tower fails to build because an enemy is in the way. In those cases please show a notification that it failed and why.
User prompt
When a tower fails to build because an enemy was in the way, show a notification informing the user of that it happened and why
User prompt
When a tower fails to build because an enemy was in the way, show a notification informing the user of that it happened and why
/****
* Classes
****/
// Enemy class
// Bullet class
var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) {
var self = Container.call(this);
// Bullet properties
self.targetEnemy = targetEnemy;
self.damage = damage || 10;
self.speed = speed || 5;
self.x = startX;
self.y = startY;
// Visual representation of bullet
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
// Update method - called every frame
self.update = function () {
// If target is gone, destroy this bullet
if (!self.targetEnemy || !self.targetEnemy.parent) {
self.destroy();
return;
}
// Move toward target
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If we hit the target
if (distance < self.speed) {
// Deal damage to enemy
self.targetEnemy.health -= self.damage;
// Update health bar visual
if (self.targetEnemy.health <= 0) {
// Enemy is defeated
self.targetEnemy.health = 0;
} else {
// Update health bar width based on remaining health percentage
self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70;
}
// Remove this bullet
self.destroy();
} else {
// Move toward target
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
}
};
return self;
});
// DebugCell class
var DebugCell = Container.expand(function () {
var self = Container.call(this);
var cellGraphics = self.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5
});
cellGraphics.tint = Math.random() * 0xffffff;
// Add a debug arrow to the debug cells
var debugArrows = [];
// Add a number label to the debug cells
var numberLabel = new Text2('0', {
size: 30,
fill: 0xFFFFFF,
weight: 800
});
numberLabel.anchor.set(.5, .5);
self.addChild(numberLabel);
self.update = function () {};
self.down = function () {
return;
if (self.cell.type == 0 || self.cell.type == 1) {
self.cell.type = self.cell.type == 1 ? 0 : 1;
if (grid.pathFind()) {
self.cell.type = self.cell.type == 1 ? 0 : 1;
grid.pathFind();
var notification = game.addChild(new Notification("Path is blocked!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
grid.renderDebug();
}
};
self.removeArrows = function () {
while (debugArrows.length) {
self.removeChild(debugArrows.pop());
}
};
self.render = function (data) {
switch (data.type) {
case 0:
case 2:
{
if (data.pathId != pathId) {
self.removeArrows();
numberLabel.setText("-");
cellGraphics.tint = 0x880000;
return;
}
numberLabel.visible = true;
var tint = Math.floor(data.score / maxScore * 0x88);
cellGraphics.tint = 0x88 - tint << 8 | tint;
while (debugArrows.length > data.targets.length) {
self.removeChild(debugArrows.pop());
}
for (var a = 0; a < data.targets.length; a++) {
var destination = data.targets[a];
var ox = destination.x - data.x;
var oy = destination.y - data.y;
var angle = Math.atan2(oy, ox);
if (!debugArrows[a]) {
debugArrows[a] = LK.getAsset('arrow', {
anchorX: -.5,
anchorY: 0.5
});
debugArrows[a].alpha = .5;
self.addChildAt(debugArrows[a], 1);
// debugArrows =
}
debugArrows[a].rotation = angle;
// debugArrow.rotation = angle;
}
break;
}
case 1:
{
self.removeArrows();
cellGraphics.tint = 0xaaaaaa;
numberLabel.visible = false;
break;
}
case 3:
{
self.removeArrows();
cellGraphics.tint = 0x008800;
numberLabel.visible = false;
break;
}
}
numberLabel.setText(Math.floor(data.score / 1000) / 10);
};
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = .05;
self.cellX = 0;
self.cellY = 0;
self.currentCellX = 0;
self.currentCellY = 0;
self.currentTarget = undefined;
self.maxHealth = 100; // Starting max health
self.health = self.maxHealth; // Current health
self.bulletsTargetingThis = []; // Track bullets targeting this enemy
// Add health bar to the enemy
var healthBarOutline = self.attachAsset('healthBarOutline', {
anchorX: 0,
anchorY: 0.5
});
var healthBarBG = self.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0.5
});
var healthBar = self.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0.5
});
healthBarBG.y = healthBarOutline.y = healthBar.y = -enemyGraphics.height / 2 - 10; // Position the health bar above the enemy
healthBarOutline.x = -healthBarOutline.width / 2;
healthBarBG.x = healthBar.x = -healthBar.width / 2;
healthBar.tint = 0x00ff00;
healthBarBG.tint = 0xff0000;
self.healthBar = healthBar;
});
//Class for the gr0x412e2eles all logic related to grid movement and interaction
var Grid = Container.expand(function (gridWidth, gridHeight) {
var self = Container.call(this);
self.cells = [];
self.spawns = [];
self.goals = [];
for (var i = 0; i < gridWidth; i++) {
self.cells[i] = [];
for (var j = 0; j < gridHeight; j++) {
self.cells[i][j] = {
score: 0,
pathId: 0,
towersInRange: [] // Cache for towers in range
};
}
}
/*
Cell Types
0: Transparent floor
1: Wall
2: Spawn
3: Goal
*/
for (var i = 0; i < gridWidth; i++) {
for (var j = 0; j < gridHeight; j++) {
// Set the type to 1 (wall) if the cell is on the edge of the grid, otherwise set it to 0 (ground)
var cell = self.cells[i][j];
var cellType = i === 0 || i === gridWidth - 1 || j <= 4 || j >= gridHeight - 4 ? 1 : 0;
if (i > 11 - 3 && i <= 11 + 3) {
if (j === 0) {
cellType = 2;
self.spawns.push(cell);
} else if (j <= 4) {
cellType = 0;
} else if (j === gridHeight - 1) {
cellType = 3;
self.goals.push(cell);
} else if (j >= gridHeight - 4) {
cellType = 0;
}
}
cell.type = cellType;
cell.x = i;
cell.y = j;
/*if (cell.type == 0) {
if (Math.random() > .5) {
cell.type = 1;
}
}*/
//Cache neighbours for speed
cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1];
cell.up = self.cells[i - 1] && self.cells[i - 1][j];
cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1];
cell.left = self.cells[i][j - 1];
cell.right = self.cells[i][j + 1];
cell.downLeft = self.cells[i + 1] && self.cells[i + 1][j - 1];
cell.down = self.cells[i + 1] && self.cells[i + 1][j];
cell.downRight = self.cells[i + 1] && self.cells[i + 1][j + 1];
cell.neighbors = [cell.upLeft, cell.up, cell.upRight, cell.right, cell.downRight, cell.down, cell.downLeft, cell.left];
cell.targets = [];
if (j > 3 && j <= gridHeight - 4) {
var debugCell = new DebugCell();
self.addChild(debugCell);
debugCell.cell = cell;
debugCell.x = i * CELL_SIZE;
debugCell.y = j * CELL_SIZE;
cell.debugCell = debugCell;
}
}
}
self.getCell = function (x, y) {
return self.cells[x] && self.cells[x][y];
};
self.pathFind = function () {
var before = new Date().getTime();
var toProcess = self.goals.concat([]);
maxScore = 0;
pathId += 1;
for (var a = 0; a < toProcess.length; a++) {
toProcess[a].pathId = pathId;
}
function processNode(node, targetValue, targetNode) {
if (node && node.type != 1) {
if (node.pathId < pathId || targetValue < node.score) {
node.targets = [targetNode];
} else if (node.pathId == pathId && targetValue == node.score) {
node.targets.push(targetNode);
}
if (node.pathId < pathId || targetValue < node.score) {
node.score = targetValue;
if (node.pathId != pathId) {
toProcess.push(node);
}
node.pathId = pathId;
if (targetValue > maxScore) {
maxScore = targetValue;
}
}
}
}
while (toProcess.length) {
var nodes = toProcess;
toProcess = [];
for (var a = 0; a < nodes.length; a++) {
var node = nodes[a];
var targetScore = node.score + 14142;
// Check for diagonal movement and ensure both adjacent cells are not walls
if (node.up && node.left && node.up.type != 1 && node.left.type != 1) {
processNode(node.upLeft, targetScore, node);
}
if (node.up && node.right && node.up.type != 1 && node.right.type != 1) {
processNode(node.upRight, targetScore, node);
}
if (node.down && node.right && node.down.type != 1 && node.right.type != 1) {
processNode(node.downRight, targetScore, node);
}
if (node.down && node.left && node.down.type != 1 && node.left.type != 1) {
processNode(node.downLeft, targetScore, node);
}
targetScore = node.score + 10000;
processNode(node.up, targetScore, node);
processNode(node.right, targetScore, node);
processNode(node.down, targetScore, node);
processNode(node.left, targetScore, node);
}
}
for (var a = 0; a < self.spawns.length; a++) {
if (self.spawns[a].pathId != pathId) {
console.warn("Spawn blocked");
return true;
}
}
for (var a = 0; a < enemies.length; a++) {
var enemy = enemies[a];
var target = self.getCell(enemy.cellX, enemy.cellY);
if (enemy.currentTarget) {
if (enemy.currentTarget.pathId != pathId) {
if (!target || target.pathId != pathId) {
console.warn("Enemy blocked");
return true;
}
}
} else if (!target || target.pathId != pathId) {
console.warn("Enemy blocked");
return true;
}
}
console.log("Speed", new Date().getTime() - before);
};
self.renderDebug = function () {
for (var i = 0; i < gridWidth; i++) {
for (var j = 0; j < gridHeight; j++) {
var debugCell = self.cells[i][j].debugCell;
if (debugCell) {
debugCell.render(self.cells[i][j]);
}
}
}
};
self.updateEnemy = function (enemy) {
var cell = grid.getCell(enemy.cellX, enemy.cellY);
if (cell.type == 3) {
return true;
}
if (!enemy.currentTarget) {
enemy.currentTarget = cell.targets[0];
}
if (enemy.currentTarget) {
//Allows enemies to change direction right away.
if (cell.score < enemy.currentTarget.score) {
enemy.currentTarget = cell;
}
var ox = enemy.currentTarget.x - enemy.currentCellX;
var oy = enemy.currentTarget.y - enemy.currentCellY;
var dist = Math.sqrt(ox * ox + oy * oy);
if (dist < enemy.speed) {
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
enemy.currentTarget = undefined;
return;
}
var angle = Math.atan2(oy, ox);
enemy.currentCellX += Math.cos(angle) * enemy.speed;
enemy.currentCellY += Math.sin(angle) * enemy.speed;
}
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
};
});
// Notification class
var Notification = Container.expand(function (message) {
var self = Container.call(this);
var notificationGraphics = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
var notificationText = new Text2(message, {
size: 50,
fill: 0x000000,
weight: 800
});
notificationText.anchor.set(0.5, 0.5);
notificationGraphics.width = notificationText.width + 30;
self.addChild(notificationText);
self.alpha = 1;
var fadeOutTime = 120; // 2 seconds at 60FPS
self.update = function () {
if (fadeOutTime > 0) {
fadeOutTime--;
self.alpha = Math.min(fadeOutTime / 120 * 2, 1);
} else {
self.destroy();
}
};
return self;
});
// Tower class that can be placed on the grid
var Tower = Container.expand(function (id) {
var self = Container.call(this);
// Tower attributes
self.id = id || 'default';
self.level = 1;
self.maxLevel = 6;
self.gridX = 0;
self.gridY = 0;
self.range = 3 * CELL_SIZE; // initial range (3 grid cells)
self.cellsInRange = []; // Cache for cells in range
self.fireRate = 60; // frames between shots
self.bulletSpeed = 5;
self.damage = 10;
self.lastFired = 0;
self.targetEnemy = null;
// Create the tower base (a square)
var baseGraphics = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5
});
// Level indicators (dots at bottom of tower)
var levelIndicators = [];
var maxDots = self.maxLevel;
var dotSpacing = baseGraphics.width / (maxDots + 1);
var dotSize = CELL_SIZE / 6;
for (var i = 0; i < maxDots; i++) {
var dot = new Container();
var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
towerLevelIndicator.width = dotSize;
towerLevelIndicator.height = dotSize;
towerLevelIndicator.tint = 0xCCCCCC; // Gray dots by default
// Position dot at bottom of tower
dot.x = -CELL_SIZE + dotSpacing * (i + 1);
dot.y = CELL_SIZE * 0.7;
self.addChild(dot);
levelIndicators.push(dot);
}
// Create the gun turret that will rotate
var gunContainer = new Container();
self.addChild(gunContainer);
var gunGraphics = gunContainer.attachAsset('defense', {
anchorX: 0.5,
anchorY: 0.5
});
//gunGraphics.y = -CELL_SIZE * 0.3; // Position the gun slightly up from center
// Update the level indicators
self.updateLevelIndicators = function () {
for (var i = 0; i < maxDots; i++) {
var dot = levelIndicators[i];
var towerLevelIndicator = dot.children[0];
// Light up dots up to current level
if (i < self.level) {
towerLevelIndicator.alpha = 1; // Fully visible for active levels
} else {
towerLevelIndicator.alpha = 0.3; // Semi-transparent for inactive levels
}
}
};
// Initialize level indicators
self.updateLevelIndicators();
// Method to upgrade the tower
self.upgrade = function () {
if (self.level < self.maxLevel) {
// Calculate upgrade cost based on level
var upgradeCost = 10 + self.level * 5;
// Check if player has enough gold
if (gold >= upgradeCost) {
gold -= upgradeCost;
self.level++;
// Increase stats based on level
self.range = (3 + self.level * 0.5) * CELL_SIZE;
self.fireRate = Math.max(20, 60 - self.level * 8); // Faster firing rate
self.damage = 10 + self.level * 5;
self.bulletSpeed = 5 + self.level * 0.5;
// Update level indicators
self.updateLevelIndicators();
// Update UI to reflect gold change
updateUI();
return true;
} else {
// Show notification for not enough gold
var notification = game.addChild(new Notification("Not enough gold to upgrade!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return false;
}
}
return false;
};
// Method to find the nearest enemy within range
self.findTarget = function () {
var closestEnemy = null;
var closestScore = Infinity; // Use pathfinding score to determine proximity to goal
for (var i = 0; i < self.cellsInRange.length; i++) {
var cell = self.cellsInRange[i];
for (var j = 0; j < enemies.length; j++) {
var enemy = enemies[j];
if (enemy.cellX === cell.x && enemy.cellY === cell.y) {
// Check if this enemy is closer to the goal based on pathfinding score
if (cell.score < closestScore) {
closestScore = cell.score;
closestEnemy = enemy;
}
}
}
}
// Reset targetEnemy if no valid target is found
if (!closestEnemy) {
self.targetEnemy = null;
}
return closestEnemy;
};
// Update method
self.update = function () {
// Always find the target every frame to ensure state consistency
self.targetEnemy = self.findTarget();
// Rotate gun toward target
if (self.targetEnemy) {
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var angle = Math.atan2(dy, dx);
// Rotate the gun container
gunContainer.rotation = angle;
// Fire at target if cooldown is complete
if (LK.ticks - self.lastFired >= self.fireRate) {
self.fire();
self.lastFired = LK.ticks;
}
}
};
// Check if enemy is in range
self.isInRange = function (enemy) {
if (!enemy) {
return false;
}
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance <= self.range;
};
// Fire at the current target
self.fire = function () {
if (self.targetEnemy) {
// Verify the target is alive and calculate damage including future damage
var potentialDamage = 0;
for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) {
potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage;
}
// Only shoot if enemy would still be alive after all current bullets hit
if (self.targetEnemy.health > potentialDamage) {
// Calculate bullet starting position
var bulletX = self.x + Math.cos(gunContainer.rotation) * 40;
var bulletY = self.y + Math.sin(gunContainer.rotation) * 40;
// Create a new bullet
var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed);
game.addChild(bullet);
// Add bullet to global bullets array and to enemy's tracking array
bullets.push(bullet);
self.targetEnemy.bulletsTargetingThis.push(bullet);
}
}
};
// Place tower on the grid
self.placeOnGrid = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
// Adjust by half a cell size to align with grid cell centers
self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2;
// Mark these grid cells as occupied
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(gridX + i, gridY + j);
if (cell) {
cell.type = 1; // Mark as wall/occupied
}
}
}
// Calculate cells in range
self.cellsInRange = [];
for (var i = -Math.floor(self.range / CELL_SIZE); i <= Math.floor(self.range / CELL_SIZE); i++) {
for (var j = -Math.floor(self.range / CELL_SIZE); j <= Math.floor(self.range / CELL_SIZE); j++) {
var cell = grid.getCell(gridX + i, gridY + j);
if (cell) {
self.cellsInRange.push(cell);
cell.towersInRange.push(self);
}
}
}
};
return self;
});
// TowerPreview class for showing where towers can be placed
var TowerPreview = Container.expand(function () {
var self = Container.call(this);
// Define tower range (in grid cells)
var towerRange = 3;
var rangeInPixels = towerRange * CELL_SIZE;
// Create the range indicator (circle)
var rangeIndicator = new Container();
self.addChild(rangeIndicator);
// Since we don't have direct access to Graphics,
// we'll create a white semi-transparent circle shape asset
var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
rangeGraphics.alpha = 0.3; // Make it semi-transparent
// Create the preview square (2x2 grid cells)
var previewGraphics = self.attachAsset('towerpreview', {
anchorX: 0.5,
anchorY: 0.5
});
// Set the size to cover 2x2 grid cells
previewGraphics.width = CELL_SIZE * 2;
previewGraphics.height = CELL_SIZE * 2;
// Initial state
self.canPlace = false;
self.gridX = 0;
self.gridY = 0;
self.blockedByEnemy = false; // Track if placement is blocked by enemy
// Update the preview color based on placement possibility
self.updatePlacementStatus = function () {
// First check if placement is valid from a grid perspective
var validGridPlacement = true;
// Check if we're in the valid placement area (main square of the board)
if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) {
validGridPlacement = false;
} else {
// Check each of the 2x2 cells
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(self.gridX + i, self.gridY + j);
// Can't place if any cell is undefined or not of type 0 (empty ground)
if (!cell || cell.type !== 0) {
validGridPlacement = false;
break;
}
}
if (!validGridPlacement) {
break;
}
}
}
// Now check if any enemy is occupying the space where the tower would be placed
self.blockedByEnemy = false;
if (validGridPlacement) {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) {
self.blockedByEnemy = true;
break;
}
}
}
// Set final placement status
self.canPlace = validGridPlacement && !self.blockedByEnemy;
// If we can place, tint green, otherwise red
previewGraphics.tint = self.canPlace ? 0x00FF00 : 0xFF0000;
};
// Check if the tower can be placed at the current position
self.checkPlacement = function () {
self.updatePlacementStatus();
};
// Snap the preview to the grid
self.snapToGrid = function (x, y) {
// Convert screen coordinates to grid coordinates
// Offset by the grid's position
var gridPosX = x - grid.x;
var gridPosY = y - grid.y;
// Convert to grid coordinates and round down to get the top-left cell
self.gridX = Math.floor(gridPosX / CELL_SIZE);
self.gridY = Math.floor(gridPosY / CELL_SIZE);
// Position the preview at the intersection of the four cells
// Adding CELL_SIZE to gridX and gridY gives us the center point
// Adjust by half a cell size to align with grid cell centers
self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2; // Center of the 2x2 grid
self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2; // Center of the 2x2 grid
// Check if we can place the tower here
self.checkPlacement();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x333333 //Init game with black background
});
/****
* Game Code
****/
// Define cell size as a global constant
var CELL_SIZE = 76;
var pathId = 1;
var maxScore = 0;
var enemies = [];
var towers = [];
var bullets = []; // Array to store active bullets
var defenses = [];
// Game resources and scores
var gold = 80;
var lives = 20;
var score = 0;
// Create UI elements for resources
var goldText = new Text2('Gold: ' + gold, {
size: 40,
fill: 0xFFD700,
weight: 800
});
goldText.anchor.set(0.5, 0.5);
var livesText = new Text2('Lives: ' + lives, {
size: 40,
fill: 0x00FF00,
weight: 800
});
livesText.anchor.set(0.5, 0.5);
var scoreText = new Text2('Score: ' + score, {
size: 40,
fill: 0xFF0000,
weight: 800
});
scoreText.anchor.set(0.5, 0.5);
// Position UI elements at the top of the screen
// Avoid top left corner (pause button area)
// Center the UI elements with proper spacing
var topMargin = 60;
var centerX = 2048 / 2;
var spacing = 300; // Reduced spacing between elements
// Position the elements centered on the screen
// First, attach to the top center container
LK.gui.top.addChild(goldText);
LK.gui.top.addChild(livesText);
LK.gui.top.addChild(scoreText);
// Position lives text in the center
livesText.x = 0; // Center position (relative to top center)
livesText.y = topMargin;
// Position gold text to the left of lives
goldText.x = -spacing;
goldText.y = topMargin;
// Position score text to the right of lives
scoreText.x = spacing;
scoreText.y = topMargin;
// Helper function to update UI
function updateUI() {
goldText.setText('Gold: ' + gold);
livesText.setText('Lives: ' + lives);
scoreText.setText('Score: ' + score);
}
// Create separate layers for debug tiles, towers, and enemies
var debugLayer = new Container();
var towerLayer = new Container();
var enemyLayer = new Container();
var grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 250 - CELL_SIZE * 4;
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
// Add layers to the game in the correct order
game.addChild(debugLayer);
game.addChild(towerLayer);
game.addChild(enemyLayer);
var offset = 0;
// Create tower preview
var towerPreview = new TowerPreview();
game.addChild(towerPreview);
towerPreview.visible = false;
// Handle drag events for tower placement
var isDragging = false;
// Check if placing a tower would block the path
function wouldBlockPath(gridX, gridY) {
// Temporarily mark cells as walls
var cells = [];
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(gridX + i, gridY + j);
if (cell) {
cells.push({
cell: cell,
originalType: cell.type
});
cell.type = 1; // Mark as wall
}
}
}
// Check if path is still valid
var blocked = grid.pathFind();
// Restore original cell types
for (var i = 0; i < cells.length; i++) {
cells[i].cell.type = cells[i].originalType;
}
// Re-find path with original cells
grid.pathFind();
grid.renderDebug();
return blocked;
}
// Place a tower at the grid position
function placeTower(gridX, gridY) {
// Tower cost
var towerCost = 5;
// Check if player has enough gold
if (gold >= towerCost) {
var tower = new Tower('default');
tower.placeOnGrid(gridX, gridY);
towerLayer.addChild(tower);
towers.push(tower);
// Deduct gold
gold -= towerCost;
updateUI();
// Update grid pathing
grid.pathFind();
grid.renderDebug();
return true;
} else {
// Not enough gold
var notification = game.addChild(new Notification("Not enough gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return false;
}
}
game.down = function (x, y, obj) {
// Check if clicking directly on a tower
if (obj && obj.parent && obj.parent instanceof Tower) {
// Don't activate tower preview when clicking on a tower
return;
}
towerPreview.visible = true;
isDragging = true;
towerPreview.snapToGrid(x, y);
};
game.move = function (x, y, obj) {
if (isDragging) {
towerPreview.snapToGrid(x, y);
}
};
game.up = function (x, y, obj) {
isDragging = false;
// Check if player clicked directly on a tower
if (obj && obj.parent && obj.parent instanceof Tower) {
var tower = obj.parent;
// Attempt to upgrade the tower
if (tower.upgrade()) {
var notification = game.addChild(new Notification("Tower upgraded!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
// Hide tower preview if upgrading an existing tower
towerPreview.visible = false;
return;
}
// Check placement possibilities
if (towerPreview.canPlace) {
// Check if placing here would block the path
if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) {
// Place the tower (placeTower will handle gold check internally)
placeTower(towerPreview.gridX, towerPreview.gridY);
} else {
// Show notification that we can't place here
var notification = game.addChild(new Notification("Tower would block the path!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
} else if (towerPreview.blockedByEnemy) {
// Show notification that an enemy is in the way
var notification = game.addChild(new Notification("Cannot build: Enemy in the way!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
} else if (towerPreview.visible) {
// Generic notification for other reasons
var notification = game.addChild(new Notification("Cannot build here!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
towerPreview.visible = false;
};
game.update = function () {
// Spawn enemies periodically
if (++offset % 100 == 0) {
var target = grid.spawns[Math.random() * grid.spawns.length >> 0];
var enemy = enemyLayer.addChild(new Enemy());
enemy.currentCellX = enemy.cellX = target.x;
enemy.currentCellY = enemy.cellY = target.y;
enemies.push(enemy);
}
// Update enemies
for (var a = enemies.length - 1; a >= 0; a--) {
var enemy = enemies[a];
// Check if enemy is defeated
if (enemy.health <= 0) {
// Clear bullet targeting references
for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) {
var bullet = enemy.bulletsTargetingThis[i];
bullet.targetEnemy = null; // This will cause bullet to destroy itself on next update
}
// Award gold and score for defeating enemy
gold += 1;
score += 5;
updateUI();
// Remove enemy
enemyLayer.removeChild(enemy);
enemies.splice(a, 1);
continue;
}
// Update enemy movement
if (grid.updateEnemy(enemy)) {
// Enemy reached goal
enemyLayer.removeChild(enemy);
enemies.splice(a, 1);
// Reduce lives when enemy reaches goal
lives = Math.max(0, lives - 1);
updateUI();
// Check if game over (no lives left)
if (lives <= 0) {
LK.showGameOver();
}
}
}
// Update towers
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].update();
// Remove destroyed bullets
if (!bullets[i].parent) {
// If this bullet has a target, remove the bullet from the target's tracking array
if (bullets[i].targetEnemy) {
var targetEnemy = bullets[i].targetEnemy;
var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]);
if (bulletIndex !== -1) {
targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1);
}
}
// Ensure bullet is removed from global bullets array
bullets.splice(i, 1);
}
}
// Update tower preview placement status on every frame
// This ensures the placement status updates in real-time as enemies move
if (towerPreview.visible) {
towerPreview.checkPlacement();
}
}; ===================================================================
--- original.js
+++ change.js
@@ -821,8 +821,13 @@
return false;
}
}
game.down = function (x, y, obj) {
+ // Check if clicking directly on a tower
+ if (obj && obj.parent && obj.parent instanceof Tower) {
+ // Don't activate tower preview when clicking on a tower
+ return;
+ }
towerPreview.visible = true;
isDragging = true;
towerPreview.snapToGrid(x, y);
};
@@ -841,8 +846,10 @@
var notification = game.addChild(new Notification("Tower upgraded!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
+ // Hide tower preview if upgrading an existing tower
+ towerPreview.visible = false;
return;
}
// Check placement possibilities
if (towerPreview.canPlace) {
White circle with two eyes, seen from above.. In-Game asset. 2d. High contrast. No shadows
White simple circular enemy seen from above, black outline. Black eyes, with a single shield in-font of it. Black and white only. Blue background.
White circle with black outline. Blue background.. In-Game asset. 2d. High contrast. No shadows