User prompt
Please fix the bug: 'Timeout.tick error: enemy.attack is not a function' in or related to this line: 'enemy.attack(closestTarget);' Line Number: 1015
User prompt
me gustaria que hubiera tres sonidos de cuando las unidades se mueven
User prompt
tambien debe haber estrucutras como piedras, cajas o columna que impida el paso de las unidades, cuando el arceqero esqueleto ataca tambien se le debe ver la flecha de a quien se le ataco ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
cuando el jugador selecione a la unidad al que va a atacar en el 2 click tomalo como confirmacion. ademas la musica solo debe durar hasta que inicie la partida
User prompt
cuando el jugador gana, debe sonar un cancion de victoria
User prompt
al barril se le puede atacar tambien.
User prompt
tambien debe haber un barril rojo que este se pueda destruir y al hacerlo dañara a todas las unidades alrededor de este, este tendra poca vida y reproducira un sonido al ser destruido ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
cuando el jugador selecione el ataque a la unidad enemiga, solo debe hacer un click para que ataque
User prompt
cuando el jugador este selecionado a la unidad al cual va a atacar lo debe de hacer al un solo click, ademas que el arquero esqueleto cuando este tambien ataque se le debe ver la flecha ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
cuando el jugador toque a sus unidades, este debe salir cuanta vida tiene pero que se vea afuera de la lucha
User prompt
lo de la interfaz debe salir al lado del boton de fin del turno, solo debe aparecer ahi, ademas de que cuando el jugador selecione a sus unidades no debe estorbar la interzacc
User prompt
me gustaria ahora que cuando el jugador selecione a sus tropas salga un pequeño interzac para indicar cuanto le queda de vida, esto tambien para cuando seleciona a las unidades enemigas. Cunado el jugador gane debe sonar una trompeta de victoria ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
cuando las unidades enemigas se mueven se deben ver por cada casilla que hace hasta llegar el punto donde quieren. Cunado los arqueros atacan se tiene que ver la flecha que se lanzo. cuando las unidades enemigas reciben daño se tiene que ver que lanzan unas particulas grises ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
cuando la unidad del jugador entra en el area de ataque hacia las unidades enemigas, tiene que ser una opcion de si atacar o no, que tambien se pueda mover como atacar las dos opciones deben estar, ademas de que todas las unidades enemigas son vencidas esto lo vuelve una victoria para el jugador
User prompt
te faltaria que tambien hubiera una opcion donde si ya entra en el area de atque de la unidad del jugador, este pudiera atacar desde antes, tambien que las unidades del jugador reciba daño salga unas particulas de la unidad del jugador quien fue atacado ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
bien pero falta que cuando la unidad del jugador no puede atacar a ninguna unidad enemiga, pasara de forma automatica a la siguiente unidad del jugador
User prompt
cuando es el turno del jugador este puede selecionar a uno de sus unidades(caballero o arquero) para mover
User prompt
cuando es el turno del jugador, este podra selecionar a unos de sus unidades, para que se pueda mover, cuando este sea selecionado este tendra un borde dorado para señalar que lo esta controlando, ademas que el tendra un limite de 3 casillas para mover y para atacar sera cuando el enemigo este alrededor de la casilla de las unidades del jugador y este se pondra en rojo para señalar que se le puede atacar, en la casilla donde esta el enemigo, esto para el caballero en el arquero se puede mover dos casillas pero tiene un rango de 4 para atacar
User prompt
que se vea todo remarcado con grosor negro para diferenciar lo lugares que se puede mover el jugador, ademas de que presiono al jugador ý puede ver el area que puede moverse y despues atacar, cuando haya hecho el movimiento seguira el arquero, cuando ya nadie se pueda mover debe salir un boton que diga pasar turno y cuando se le presiona el enemigo se movera para atacarnos, finalizada sus movimientos volveria ser el turno del jugador
User prompt
podes hacer primero empezie el jugador y que de una señal como un rudio de una trompeta, ademas que el nivels sea lateral sera todo el juego en perspectiva 2D.
Code edit (1 edits merged)
Please save this source code
User prompt
Dark Lord's Domain: Knight & Archer Quest
Initial prompt
un juego de un caballero y arquero que deben pasar por las tierras del señor oscuro, sera por turno las peleas, se puede ganar llegando a la salida o matando a todos los enemigos, seran 3 tipos de enemigos(son todos esqueletos) soldado, mediano en daño,velocidad y vida, archero mediano pero menos vida y mas velocidad, y caballero pesado mas vida, mas daño pero es mas lento.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Archer = Container.expand(function () {
var self = Container.call(this);
var archerGraphics = self.attachAsset('archer', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.hp = 80;
self.maxHp = 80;
self.damage = 25;
self.moveRange = 2;
self.attackRange = 4;
self.hasActed = false;
self.hasMoved = false;
self.takeDamage = function (amount) {
self.hp = Math.max(0, self.hp - amount);
LK.effects.flashObject(self, 0xFF0000, 500);
// Create damage particles
for (var i = 0; i < 8; i++) {
var particle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1,
tint: 0xFF0000
});
particle.x = self.x + (Math.random() - 0.5) * 40;
particle.y = self.y + (Math.random() - 0.5) * 40;
game.addChild(particle);
var targetX = particle.x + (Math.random() - 0.5) * 200;
var targetY = particle.y - Math.random() * 100 - 50;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.05,
scaleY: 0.05
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
if (self.hp <= 0) {
self.destroy();
archer = null;
checkGameOver();
}
};
self.attack = function (target) {
if (target && target.takeDamage) {
// Create arrow projectile
var arrow = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.1,
tint: 0x8B4513
});
arrow.x = self.x;
arrow.y = self.y;
game.addChild(arrow);
// Calculate angle to target
var dx = target.x - self.x;
var dy = target.y - self.y;
arrow.rotation = Math.atan2(dy, dx);
// Animate arrow to target
tween(arrow, {
x: target.x,
y: target.y
}, {
duration: 400,
easing: tween.linear,
onFinish: function onFinish() {
arrow.destroy();
target.takeDamage(self.damage);
}
});
LK.getSound('attack').play();
self.hasActed = true;
self.hasMoved = true;
}
};
self.down = function (x, y, obj) {
if (gameState === 'player_turn' && turnPhase === 'select') {
handleCellClick(self.gridX, self.gridY);
}
showHealthInterface(self);
};
return self;
});
var Barrel = Container.expand(function () {
var self = Container.call(this);
var barrelGraphics = self.attachAsset('barrel', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.hp = 30;
self.maxHp = 30;
self.exploded = false;
self.takeDamage = function (amount) {
if (self.exploded) return;
self.hp = Math.max(0, self.hp - amount);
LK.effects.flashObject(self, 0xFF0000, 500);
if (self.hp <= 0) {
self.explode();
}
};
self.explode = function () {
if (self.exploded) return;
self.exploded = true;
// Play explosion sound
LK.getSound('explosion').play();
// Create explosion particles
for (var i = 0; i < 15; i++) {
var particle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.15,
scaleY: 0.15,
tint: 0xFF4500
});
particle.x = self.x + (Math.random() - 0.5) * 60;
particle.y = self.y + (Math.random() - 0.5) * 60;
game.addChild(particle);
var targetX = particle.x + (Math.random() - 0.5) * 300;
var targetY = particle.y - Math.random() * 150 - 100;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.05,
scaleY: 0.05
}, {
duration: 1000 + Math.random() * 500,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Deal damage to all units within 2 cells radius
var damageAmount = 40;
var explosionRadius = 2;
// Check all grid positions within radius
for (var x = Math.max(0, self.gridX - explosionRadius); x <= Math.min(gridWidth - 1, self.gridX + explosionRadius); x++) {
for (var y = Math.max(0, self.gridY - explosionRadius); y <= Math.min(gridHeight - 1, self.gridY + explosionRadius); y++) {
var distance = getDistance(self.gridX, self.gridY, x, y);
if (distance <= explosionRadius && grid[x][y].occupied) {
var unit = grid[x][y].unit;
if (unit && unit.takeDamage && unit !== self) {
unit.takeDamage(damageAmount);
}
}
}
}
// Remove from grid and destroy
removeFromGrid(self.gridX, self.gridY);
for (var i = barrels.length - 1; i >= 0; i--) {
if (barrels[i] === self) {
barrels.splice(i, 1);
break;
}
}
self.destroy();
};
self.down = function (x, y, obj) {
showHealthInterface(self);
};
return self;
});
var ComicPanel = Container.expand(function () {
var self = Container.call(this);
self.panelImage = null;
self.titleText = null;
self.storyText = null;
self.currentPanel = 0;
self.totalPanels = 3;
self.init = function (panelNumber, title, story) {
// Create background
var background = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 2.5,
tint: 0x000000
});
background.alpha = 0.9;
self.addChild(background);
// Create panel image
self.panelImage = self.attachAsset('comicPanel' + panelNumber, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.panelImage.y = -100;
// Create title
self.titleText = new Text2(title, {
size: 60,
fill: 0xFFD700
});
self.titleText.anchor.set(0.5, 0.5);
self.titleText.y = 200;
self.addChild(self.titleText);
// Create story text
self.storyText = new Text2(story, {
size: 35,
fill: 0xFFFFFF
});
self.storyText.anchor.set(0.5, 0.5);
self.storyText.y = 280;
self.addChild(self.storyText);
// Create continue prompt
var continueText = new Text2('Tap to continue...', {
size: 30,
fill: 0xCCCCCC
});
continueText.anchor.set(0.5, 0.5);
continueText.y = 350;
self.addChild(continueText);
// Animate text appearance
self.titleText.alpha = 0;
self.storyText.alpha = 0;
continueText.alpha = 0;
tween(self.titleText, {
alpha: 1
}, {
duration: 500,
delay: 200
});
tween(self.storyText, {
alpha: 1
}, {
duration: 800,
delay: 600
});
tween(continueText, {
alpha: 1
}, {
duration: 500,
delay: 1200
});
};
self.down = function (x, y, obj) {
LK.getSound('pageFlip').play();
self.destroy();
game.showNextComicPanel();
};
return self;
});
var EndTurnButton = Container.expand(function () {
var self = Container.call(this);
var buttonGraphics = self.attachAsset('endTurnButton', {
anchorX: 0.5,
anchorY: 0.5
});
var buttonText = new Text2('End Turn', {
size: 40,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
self.visible = false;
self.down = function (x, y, obj) {
if (self.visible && gameState === 'player_turn') {
endPlayerTurn();
}
};
return self;
});
var GridCell = Container.expand(function () {
var self = Container.call(this);
var cellGraphics = self.attachAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5
});
cellGraphics.alpha = 0.3;
// Add black border effect by creating a larger black background
var borderGraphics = self.attachAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.05,
scaleY: 1.05,
tint: 0x000000
});
// Move border behind main cell
self.setChildIndex(borderGraphics, 0);
self.gridX = 0;
self.gridY = 0;
self.occupied = false;
self.unit = null;
self.highlighted = false;
self.selectedBorder = null;
self.showSelectedBorder = function () {
if (!self.selectedBorder) {
self.selectedBorder = self.attachAsset('selectedBorder', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.15,
scaleY: 1.15
});
self.setChildIndex(self.selectedBorder, 0);
}
self.selectedBorder.visible = true;
};
self.hideSelectedBorder = function () {
if (self.selectedBorder) {
self.selectedBorder.visible = false;
}
};
self.highlight = function () {
if (!self.highlighted) {
cellGraphics.tint = 0xFFFF00;
// Add thick black border for highlighted cells
if (!self.highlightBorder) {
self.highlightBorder = self.attachAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.1,
scaleY: 1.1,
tint: 0x000000
});
self.setChildIndex(self.highlightBorder, 0);
}
self.highlightBorder.visible = true;
self.highlighted = true;
}
};
self.unhighlight = function () {
if (self.highlighted) {
cellGraphics.tint = 0xFFFFFF;
if (self.highlightBorder) {
self.highlightBorder.visible = false;
}
self.highlighted = false;
}
};
self.down = function (x, y, obj) {
if (gameState === 'player_turn' && self.highlighted) {
handleCellClick(self.gridX, self.gridY);
}
};
return self;
});
var Knight = Container.expand(function () {
var self = Container.call(this);
var knightGraphics = self.attachAsset('knight', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.hp = 100;
self.maxHp = 100;
self.damage = 30;
self.moveRange = 3;
self.attackRange = 1;
self.hasActed = false;
self.hasMoved = false;
self.takeDamage = function (amount) {
self.hp = Math.max(0, self.hp - amount);
LK.effects.flashObject(self, 0xFF0000, 500);
// Create damage particles
for (var i = 0; i < 8; i++) {
var particle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1,
tint: 0xFF0000
});
particle.x = self.x + (Math.random() - 0.5) * 40;
particle.y = self.y + (Math.random() - 0.5) * 40;
game.addChild(particle);
var targetX = particle.x + (Math.random() - 0.5) * 200;
var targetY = particle.y - Math.random() * 100 - 50;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.05,
scaleY: 0.05
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
if (self.hp <= 0) {
self.destroy();
knight = null;
checkGameOver();
}
};
self.attack = function (target) {
if (target && target.takeDamage) {
target.takeDamage(self.damage);
LK.getSound('attack').play();
self.hasActed = true;
self.hasMoved = true;
}
};
self.down = function (x, y, obj) {
if (gameState === 'player_turn' && turnPhase === 'select') {
handleCellClick(self.gridX, self.gridY);
}
showHealthInterface(self);
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
self.gridX = 0;
self.gridY = 0;
self.obstacleType = 'stone'; // 'stone', 'box', 'column'
self.isDestructible = false;
self.hp = 0;
self.maxHp = 0;
self.graphics = null;
self.init = function (type) {
self.obstacleType = type;
if (type === 'stone') {
self.graphics = self.attachAsset('stone', {
anchorX: 0.5,
anchorY: 0.5
});
self.isDestructible = false;
} else if (type === 'box') {
self.graphics = self.attachAsset('box', {
anchorX: 0.5,
anchorY: 0.5
});
self.isDestructible = true;
self.hp = 50;
self.maxHp = 50;
} else if (type === 'column') {
self.graphics = self.attachAsset('column', {
anchorX: 0.5,
anchorY: 0.5
});
self.isDestructible = false;
}
};
self.takeDamage = function (amount) {
if (!self.isDestructible) return;
self.hp = Math.max(0, self.hp - amount);
LK.effects.flashObject(self, 0xFF0000, 500);
// Create damage particles
for (var i = 0; i < 6; i++) {
var particle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.08,
scaleY: 0.08,
tint: 0x8B4513
});
particle.x = self.x + (Math.random() - 0.5) * 30;
particle.y = self.y + (Math.random() - 0.5) * 30;
game.addChild(particle);
var targetX = particle.x + (Math.random() - 0.5) * 150;
var targetY = particle.y - Math.random() * 80 - 40;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.03,
scaleY: 0.03
}, {
duration: 600 + Math.random() * 300,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
if (self.hp <= 0) {
removeFromGrid(self.gridX, self.gridY);
for (var i = obstacles.length - 1; i >= 0; i--) {
if (obstacles[i] === self) {
obstacles.splice(i, 1);
break;
}
}
self.destroy();
}
};
self.down = function (x, y, obj) {
if (self.isDestructible) {
showHealthInterface(self);
}
};
return self;
});
var SkeletonArcher = Container.expand(function () {
var self = Container.call(this);
var archerGraphics = self.attachAsset('skeletonArcher', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.hp = 40;
self.maxHp = 40;
self.damage = 18;
self.moveRange = 3;
self.attackRange = 3;
self.attack = function (target) {
if (target && target.takeDamage) {
// Create arrow projectile
var arrow = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.1,
tint: 0x654321
});
arrow.x = self.x;
arrow.y = self.y;
game.addChild(arrow);
// Calculate angle to target
var dx = target.x - self.x;
var dy = target.y - self.y;
arrow.rotation = Math.atan2(dy, dx);
// Animate arrow to target
tween(arrow, {
x: target.x,
y: target.y
}, {
duration: 400,
easing: tween.linear,
onFinish: function onFinish() {
arrow.destroy();
target.takeDamage(self.damage);
}
});
LK.getSound('attack').play();
}
};
self.takeDamage = function (amount) {
self.hp = Math.max(0, self.hp - amount);
LK.effects.flashObject(self, 0xFF0000, 500);
// Create grey damage particles
for (var i = 0; i < 6; i++) {
var particle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.08,
scaleY: 0.08,
tint: 0x808080
});
particle.x = self.x + (Math.random() - 0.5) * 30;
particle.y = self.y + (Math.random() - 0.5) * 30;
game.addChild(particle);
var targetX = particle.x + (Math.random() - 0.5) * 150;
var targetY = particle.y - Math.random() * 80 - 40;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.03,
scaleY: 0.03
}, {
duration: 600 + Math.random() * 300,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
if (self.hp <= 0) {
removeFromGrid(self.gridX, self.gridY);
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
checkVictory();
}
};
self.down = function (x, y, obj) {
showHealthInterface(self);
};
return self;
});
var SkeletonKnight = Container.expand(function () {
var self = Container.call(this);
var knightGraphics = self.attachAsset('skeletonKnight', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.hp = 100;
self.maxHp = 100;
self.damage = 35;
self.moveRange = 1;
self.attackRange = 1;
self.attack = function (target) {
if (target && target.takeDamage) {
target.takeDamage(self.damage);
LK.getSound('attack').play();
}
};
self.takeDamage = function (amount) {
self.hp = Math.max(0, self.hp - amount);
LK.effects.flashObject(self, 0xFF0000, 500);
// Create grey damage particles
for (var i = 0; i < 6; i++) {
var particle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.08,
scaleY: 0.08,
tint: 0x808080
});
particle.x = self.x + (Math.random() - 0.5) * 30;
particle.y = self.y + (Math.random() - 0.5) * 30;
game.addChild(particle);
var targetX = particle.x + (Math.random() - 0.5) * 150;
var targetY = particle.y - Math.random() * 80 - 40;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.03,
scaleY: 0.03
}, {
duration: 600 + Math.random() * 300,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
if (self.hp <= 0) {
removeFromGrid(self.gridX, self.gridY);
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
checkVictory();
}
};
self.down = function (x, y, obj) {
showHealthInterface(self);
};
return self;
});
var SkeletonSoldier = Container.expand(function () {
var self = Container.call(this);
var soldierGraphics = self.attachAsset('skeletonSoldier', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.hp = 60;
self.maxHp = 60;
self.damage = 20;
self.moveRange = 2;
self.attackRange = 1;
self.attack = function (target) {
if (target && target.takeDamage) {
target.takeDamage(self.damage);
LK.getSound('attack').play();
}
};
self.takeDamage = function (amount) {
self.hp = Math.max(0, self.hp - amount);
LK.effects.flashObject(self, 0xFF0000, 500);
// Create grey damage particles
for (var i = 0; i < 6; i++) {
var particle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.08,
scaleY: 0.08,
tint: 0x808080
});
particle.x = self.x + (Math.random() - 0.5) * 30;
particle.y = self.y + (Math.random() - 0.5) * 30;
game.addChild(particle);
var targetX = particle.x + (Math.random() - 0.5) * 150;
var targetY = particle.y - Math.random() * 80 - 40;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.03,
scaleY: 0.03
}, {
duration: 600 + Math.random() * 300,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
if (self.hp <= 0) {
removeFromGrid(self.gridX, self.gridY);
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
checkVictory();
}
};
self.down = function (x, y, obj) {
showHealthInterface(self);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// Comic story system
var comicStory = [{
title: "The Ancient Kingdom",
story: "Long ago, brave knights and archers defended\nthe realm from the forces of darkness..."
}, {
title: "The Skeleton Army Rises",
story: "But evil has awakened! Skeleton warriors emerge\nfrom ancient tombs to conquer the land..."
}, {
title: "Heroes Must Rise",
story: "Only you can lead our heroes to victory!\nGuide them through enemy territory to escape!"
}];
var currentComicPanel = 0;
var showingComic = true;
var comicContainer = null;
var gridWidth = 8;
var gridHeight = 8;
var cellSize = 200;
var grid = [];
var units = [];
var enemies = [];
var barrels = [];
var obstacles = [];
var knight = null;
var archer = null;
var exitPortal = null;
var selectedUnit = null;
var selectedTarget = null;
var gameState = 'player_turn'; // 'player_turn', 'enemy_turn', 'game_over'
var turnPhase = 'select'; // 'select', 'move', 'attack'
var wave = 1;
// UI Elements
var turnText = new Text2('Player Turn', {
size: 60,
fill: 0xFFFFFF
});
turnText.anchor.set(0.5, 0);
LK.gui.top.addChild(turnText);
var waveText = new Text2('Wave 1', {
size: 50,
fill: 0xFFFFFF
});
waveText.anchor.set(1, 0);
LK.gui.topRight.addChild(waveText);
var instructionText = new Text2('Select a unit to move', {
size: 40,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 1);
LK.gui.bottom.addChild(instructionText);
// Create End Turn button
var endTurnButton = new EndTurnButton();
endTurnButton.x = 2048 - 150;
endTurnButton.y = 2732 - 150;
endTurnButton.visible = false; // Hide during comic
game.addChild(endTurnButton);
// Initialize grid
function initializeGrid() {
// Optimize grid positioning for full screen - leave more space at top for UI
var startX = (2048 - gridWidth * cellSize) / 2;
var startY = 200 + (2732 - 400 - gridHeight * cellSize) / 2; // Leave 200px at top and bottom
for (var x = 0; x < gridWidth; x++) {
grid[x] = [];
for (var y = 0; y < gridHeight; y++) {
var cell = new GridCell();
cell.gridX = x;
cell.gridY = y;
cell.x = startX + x * cellSize + cellSize / 2;
cell.y = startY + y * cellSize + cellSize / 2;
grid[x][y] = cell;
game.addChild(cell);
}
}
}
function placeUnit(unit, gridX, gridY) {
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
if (grid[gridX][gridY].occupied) {
return false;
}
// Remove from old position
if (unit.gridX !== undefined && unit.gridY !== undefined) {
grid[unit.gridX][unit.gridY].occupied = false;
grid[unit.gridX][unit.gridY].unit = null;
}
unit.gridX = gridX;
unit.gridY = gridY;
unit.x = grid[gridX][gridY].x;
unit.y = grid[gridX][gridY].y;
grid[gridX][gridY].occupied = true;
grid[gridX][gridY].unit = unit;
return true;
}
return false;
}
function removeFromGrid(gridX, gridY) {
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
grid[gridX][gridY].occupied = false;
grid[gridX][gridY].unit = null;
}
}
function getDistance(x1, y1, x2, y2) {
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
}
function highlightValidMoves(unit) {
clearHighlights();
// Only show movement highlights if unit hasn't moved yet
if (!unit.hasMoved) {
for (var x = 0; x < gridWidth; x++) {
for (var y = 0; y < gridHeight; y++) {
var distance = getDistance(unit.gridX, unit.gridY, x, y);
if (distance <= unit.moveRange && !grid[x][y].occupied) {
grid[x][y].highlight();
}
}
}
}
}
function highlightValidAttacks(unit) {
var hasValidAttacks = false;
// Only show attack highlights if unit hasn't attacked yet
if (!unit.hasActed) {
for (var x = 0; x < gridWidth; x++) {
for (var y = 0; y < gridHeight; y++) {
var distance = getDistance(unit.gridX, unit.gridY, x, y);
if (distance <= unit.attackRange && grid[x][y].occupied && grid[x][y].unit !== unit) {
// Check if it's an enemy, barrel, or destructible obstacle
var isEnemy = false;
var isBarrel = false;
var isDestructibleObstacle = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === grid[x][y].unit) {
isEnemy = true;
break;
}
}
for (var i = 0; i < barrels.length; i++) {
if (barrels[i] === grid[x][y].unit) {
isBarrel = true;
break;
}
}
for (var i = 0; i < obstacles.length; i++) {
if (obstacles[i] === grid[x][y].unit && obstacles[i].isDestructible && obstacles[i].obstacleType !== 'box') {
isDestructibleObstacle = true;
break;
}
}
if (isEnemy || isBarrel || isDestructibleObstacle) {
hasValidAttacks = true;
var cell = grid[x][y];
cell.highlighted = true;
if (!cell.attackHighlight) {
cell.attackHighlight = cell.attachAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
});
cell.setChildIndex(cell.attackHighlight, cell.children.length - 1);
}
cell.attackHighlight.visible = true;
}
}
}
}
}
return hasValidAttacks;
}
function clearHighlights() {
for (var x = 0; x < gridWidth; x++) {
for (var y = 0; y < gridHeight; y++) {
var cell = grid[x][y];
cell.unhighlight();
cell.hideSelectedBorder();
if (cell.attackHighlight) {
cell.attackHighlight.visible = false;
}
}
}
}
function handleCellClick(gridX, gridY) {
if (gameState !== 'player_turn') return;
var cell = grid[gridX][gridY];
if (turnPhase === 'select') {
// Select a unit
if (cell.occupied && (cell.unit === knight || cell.unit === archer)) {
if (!cell.unit.hasActed || !cell.unit.hasMoved) {
selectedUnit = cell.unit;
// Show golden border for selected unit
grid[selectedUnit.gridX][selectedUnit.gridY].showSelectedBorder();
turnPhase = 'action';
highlightValidMoves(selectedUnit);
var hasAttacks = highlightValidAttacks(selectedUnit);
instructionText.setText('Click to move and/or attack, then click unit again when done');
}
}
} else if (turnPhase === 'action') {
if (cell.highlighted && !cell.occupied && !selectedUnit.hasMoved) {
// Move to this cell
placeUnit(selectedUnit, gridX, gridY);
selectedUnit.hasMoved = true;
// Play random movement sound
var moveSounds = ['move', 'move1', 'move3'];
var randomSound = moveSounds[Math.floor(Math.random() * moveSounds.length)];
LK.getSound(randomSound).play();
// Check if reached exit portal
if (exitPortal && gridX === exitPortal.gridX && gridY === exitPortal.gridY) {
LK.getSound('victory').play();
LK.playMusic('Victoria');
LK.showYouWin();
return;
}
// After moving, update highlights to show new attack options
clearHighlights();
highlightValidMoves(selectedUnit);
var hasAttacks = highlightValidAttacks(selectedUnit);
// If unit has no valid attacks and has moved, auto-switch to next unit
if (!hasAttacks && selectedUnit.hasMoved) {
// Find next available unit
var nextUnit = null;
if (selectedUnit === knight && archer && (!archer.hasActed || !archer.hasMoved)) {
nextUnit = archer;
} else if (selectedUnit === archer && knight && (!knight.hasActed || !knight.hasMoved)) {
nextUnit = knight;
}
if (nextUnit) {
// Mark current unit as done and switch to next
selectedUnit.hasActed = true;
grid[selectedUnit.gridX][selectedUnit.gridY].hideSelectedBorder();
selectedUnit = nextUnit;
grid[selectedUnit.gridX][selectedUnit.gridY].showSelectedBorder();
highlightValidMoves(selectedUnit);
highlightValidAttacks(selectedUnit);
instructionText.setText('Switched to next unit - Click to move and/or attack');
} else {
instructionText.setText('Click to attack or click unit again when done');
}
} else {
instructionText.setText('Click to attack or click unit again when done');
}
} else if (cell.highlighted && cell.occupied && !selectedUnit.hasActed) {
// Check if it's an enemy, barrel, or destructible obstacle
var isEnemy = false;
var isBarrel = false;
var isDestructibleObstacle = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === cell.unit) {
isEnemy = true;
break;
}
}
for (var i = 0; i < barrels.length; i++) {
if (barrels[i] === cell.unit) {
isBarrel = true;
break;
}
}
for (var i = 0; i < obstacles.length; i++) {
if (obstacles[i] === cell.unit && obstacles[i].isDestructible && obstacles[i].obstacleType !== 'box') {
isDestructibleObstacle = true;
break;
}
}
if (isEnemy || isBarrel || isDestructibleObstacle) {
// First click selects target for attack
selectedTarget = cell.unit;
turnPhase = 'confirm_attack';
clearHighlights();
// Highlight only the selected target
cell.highlight();
instructionText.setText('Click target again to confirm attack');
}
} else if (cell.occupied && cell.unit === selectedUnit) {
// Clicking on the selected unit again ends their turn
endPlayerAction();
}
} else if (turnPhase === 'confirm_attack') {
if (cell.occupied && cell.unit === selectedTarget && cell.highlighted) {
// Second click confirms attack
selectedUnit.attack(selectedTarget);
selectedUnit.hasActed = true;
selectedTarget = null;
// After attacking, unit's turn is over
clearHighlights();
endPlayerAction();
} else {
// Click elsewhere cancels attack confirmation
selectedTarget = null;
turnPhase = 'action';
highlightValidMoves(selectedUnit);
highlightValidAttacks(selectedUnit);
instructionText.setText('Click to move and/or attack, then click unit again when done');
}
}
}
function endPlayerAction() {
clearHighlights();
if (selectedUnit) {
// Mark unit as fully done (both moved and acted)
selectedUnit.hasActed = true;
selectedUnit.hasMoved = true;
grid[selectedUnit.gridX][selectedUnit.gridY].hideSelectedBorder();
}
selectedUnit = null;
turnPhase = 'select';
// End Turn button is always available during player turn
endTurnButton.visible = true;
// Check if both units have completed their full turns
var knightDone = !knight || knight.hasActed && knight.hasMoved;
var archerDone = !archer || archer.hasActed && archer.hasMoved;
if (knightDone && archerDone) {
instructionText.setText('All units ready - Click End Turn to continue');
} else {
instructionText.setText('Select next unit to move or Click End Turn to skip');
}
}
function endPlayerTurn() {
endTurnButton.visible = false;
gameState = 'enemy_turn';
turnText.setText('Enemy Turn');
instructionText.setText('Enemies moving...');
// Reset player units
if (knight) {
knight.hasActed = false;
knight.hasMoved = false;
}
if (archer) {
archer.hasActed = false;
archer.hasMoved = false;
}
// Start enemy turn after delay
LK.setTimeout(function () {
executeEnemyTurn();
}, 1000);
}
function executeEnemyTurn() {
var enemyIndex = 0;
function processNextEnemy() {
if (enemyIndex >= enemies.length) {
// All enemies processed, start new player turn
gameState = 'player_turn';
turnText.setText('Player Turn');
instructionText.setText('Select a unit to move');
turnPhase = 'select';
selectedUnit = null;
clearHighlights();
LK.getSound('trumpet').play();
return;
}
var enemy = enemies[enemyIndex];
if (!enemy || enemy.hp <= 0) {
enemyIndex++;
processNextEnemy();
return;
}
// Simple AI: move towards nearest player and attack if in range
var targets = [];
if (knight && knight.hp > 0) targets.push(knight);
if (archer && archer.hp > 0) targets.push(archer);
if (targets.length === 0) {
enemyIndex++;
processNextEnemy();
return;
}
// Find closest target
var closestTarget = targets[0];
var closestDistance = getDistance(enemy.gridX, enemy.gridY, closestTarget.gridX, closestTarget.gridY);
for (var i = 1; i < targets.length; i++) {
var distance = getDistance(enemy.gridX, enemy.gridY, targets[i].gridX, targets[i].gridY);
if (distance < closestDistance) {
closestTarget = targets[i];
closestDistance = distance;
}
}
// Try to attack first
if (closestDistance <= enemy.attackRange) {
if (enemy.attack && typeof enemy.attack === 'function') {
enemy.attack(closestTarget);
}
enemyIndex++;
LK.setTimeout(processNextEnemy, 500);
} else {
// Move towards target
var bestMove = findBestMove(enemy, closestTarget);
if (bestMove) {
// Play random movement sound
var moveSounds = ['move', 'move1', 'move3'];
var randomSound = moveSounds[Math.floor(Math.random() * moveSounds.length)];
LK.getSound(randomSound).play();
moveToPosition(enemy, bestMove.x, bestMove.y, function () {
enemyIndex++;
LK.setTimeout(processNextEnemy, 500);
});
} else {
enemyIndex++;
LK.setTimeout(processNextEnemy, 500);
}
}
}
processNextEnemy();
}
function moveToPosition(unit, targetX, targetY, callback) {
var startX = unit.gridX;
var startY = unit.gridY;
var path = [];
// Simple pathfinding - move one step at a time toward target
var currentX = startX;
var currentY = startY;
while (currentX !== targetX || currentY !== targetY) {
var nextX = currentX;
var nextY = currentY;
if (currentX < targetX) nextX++;else if (currentX > targetX) nextX--;else if (currentY < targetY) nextY++;else if (currentY > targetY) nextY--;
path.push({
x: nextX,
y: nextY
});
currentX = nextX;
currentY = nextY;
}
var stepIndex = 0;
function animateNextStep() {
if (stepIndex >= path.length) {
if (callback) callback();
return;
}
var step = path[stepIndex];
placeUnit(unit, step.x, step.y);
stepIndex++;
LK.setTimeout(animateNextStep, 300);
}
animateNextStep();
}
function findBestMove(enemy, target) {
var bestMove = null;
var bestDistance = getDistance(enemy.gridX, enemy.gridY, target.gridX, target.gridY);
for (var dx = -enemy.moveRange; dx <= enemy.moveRange; dx++) {
for (var dy = -enemy.moveRange; dy <= enemy.moveRange; dy++) {
if (Math.abs(dx) + Math.abs(dy) > enemy.moveRange) continue;
var newX = enemy.gridX + dx;
var newY = enemy.gridY + dy;
if (newX >= 0 && newX < gridWidth && newY >= 0 && newY < gridHeight) {
if (!grid[newX][newY].occupied || newX === enemy.gridX && newY === enemy.gridY) {
var distance = getDistance(newX, newY, target.gridX, target.gridY);
if (distance < bestDistance) {
bestDistance = distance;
bestMove = {
x: newX,
y: newY
};
}
}
}
}
}
return bestMove;
}
function spawnEnemies() {
var enemyTypes = [SkeletonSoldier, SkeletonArcher, SkeletonKnight];
var enemyCount = Math.min(3 + wave, 8);
for (var i = 0; i < enemyCount; i++) {
var enemyType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
var enemy = new enemyType();
// Find random empty position on right side of map
var placed = false;
var attempts = 0;
while (!placed && attempts < 50) {
var x = Math.floor(gridWidth * 0.6 + Math.random() * gridWidth * 0.4);
var y = Math.floor(Math.random() * gridHeight);
if (!grid[x][y].occupied) {
placeUnit(enemy, x, y);
enemies.push(enemy);
game.addChild(enemy);
placed = true;
}
attempts++;
}
}
}
function spawnBarrels() {
var barrelCount = Math.min(2 + Math.floor(wave / 2), 4);
for (var i = 0; i < barrelCount; i++) {
var barrel = new Barrel();
// Find random empty position in middle area of map
var placed = false;
var attempts = 0;
while (!placed && attempts < 50) {
var x = Math.floor(gridWidth * 0.3 + Math.random() * gridWidth * 0.4);
var y = Math.floor(Math.random() * gridHeight);
if (!grid[x][y].occupied) {
placeUnit(barrel, x, y);
barrels.push(barrel);
game.addChild(barrel);
placed = true;
}
attempts++;
}
}
}
function spawnObstacles() {
var obstacleTypes = ['stone', 'box', 'column'];
var obstacleCount = Math.min(3 + Math.floor(wave / 3), 6);
for (var i = 0; i < obstacleCount; i++) {
var obstacle = new Obstacle();
var type = obstacleTypes[Math.floor(Math.random() * obstacleTypes.length)];
obstacle.init(type);
// Find random empty position across the map
var placed = false;
var attempts = 0;
while (!placed && attempts < 50) {
var x = Math.floor(Math.random() * gridWidth);
var y = Math.floor(Math.random() * gridHeight);
// Avoid placing near starting positions and exit
if (x > 2 && x < gridWidth - 3 && !grid[x][y].occupied) {
placeUnit(obstacle, x, y);
obstacles.push(obstacle);
game.addChild(obstacle);
placed = true;
}
attempts++;
}
}
}
function checkVictory() {
if (enemies.length === 0) {
LK.getSound('trompeta').play();
LK.playMusic('Victoria');
LK.showYouWin();
return;
}
}
function showHealthInterface(unit) {
// Remove existing health interface if any
if (game.healthInterface) {
game.healthInterface.destroy();
game.healthInterface = null;
}
// Create health interface
var healthInterface = new Container();
var background = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.8,
tint: 0x000000
});
background.alpha = 0.8;
healthInterface.addChild(background);
// Health text
var healthText = new Text2('HP: ' + unit.hp + '/' + unit.maxHp, {
size: 40,
fill: 0xFFFFFF
});
healthText.anchor.set(0.5, 0.5);
healthInterface.addChild(healthText);
// Position interface next to end turn button, optimized for full screen
healthInterface.x = endTurnButton.x - 350;
healthInterface.y = endTurnButton.y;
game.addChild(healthInterface);
game.healthInterface = healthInterface;
// Auto-hide after 3 seconds
LK.setTimeout(function () {
if (game.healthInterface === healthInterface) {
healthInterface.destroy();
game.healthInterface = null;
}
}, 3000);
}
function checkGameOver() {
if ((!knight || knight.hp <= 0) && (!archer || archer.hp <= 0)) {
LK.showGameOver();
}
}
function showComicStory() {
if (currentComicPanel < comicStory.length) {
// Start comic music on first panel
if (currentComicPanel === 0) {
LK.playMusic('comicMusic');
}
var panel = new ComicPanel();
var storyData = comicStory[currentComicPanel];
panel.init(currentComicPanel + 1, storyData.title, storyData.story);
panel.x = 2048 / 2;
panel.y = 2732 / 2;
game.addChild(panel);
comicContainer = panel;
currentComicPanel++;
} else {
// Comic finished, start the game
startActualGame();
}
}
game.showNextComicPanel = function () {
if (comicContainer) {
comicContainer = null;
}
showComicStory();
};
function startActualGame() {
showingComic = false;
// Stop comic music and start battle music
LK.stopMusic();
LK.playMusic('battleMusic');
// Initialize the actual game
initializeGameplay();
}
function initializeGameplay() {
// Initialize grid
initializeGrid();
// Create and place heroes
knight = new Knight();
archer = new Archer();
game.addChild(knight);
game.addChild(archer);
placeUnit(knight, 1, 3);
placeUnit(archer, 1, 4);
// Create exit portal
exitPortal = LK.getAsset('exitPortal', {
anchorX: 0.5,
anchorY: 0.5
});
exitPortal.gridX = gridWidth - 2;
exitPortal.gridY = 3;
exitPortal.x = grid[exitPortal.gridX][exitPortal.gridY].x;
exitPortal.y = grid[exitPortal.gridX][exitPortal.gridY].y;
exitPortal.alpha = 0.7;
game.addChild(exitPortal);
// Spawn initial enemies
spawnEnemies();
// Spawn initial barrels
spawnBarrels();
// Spawn initial obstacles
spawnObstacles();
// Make End Turn button available from game start
endTurnButton.visible = true;
// Play trumpet sound to signal game start
LK.getSound('trumpet').play();
}
// Start with comic story
showComicStory();
game.update = function () {
// Show/hide UI based on comic state
if (showingComic) {
turnText.visible = false;
waveText.visible = false;
instructionText.visible = false;
endTurnButton.visible = false;
} else {
turnText.visible = true;
waveText.visible = true;
instructionText.visible = true;
// endTurnButton visibility handled by game logic
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Archer = Container.expand(function () {
var self = Container.call(this);
var archerGraphics = self.attachAsset('archer', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.hp = 80;
self.maxHp = 80;
self.damage = 25;
self.moveRange = 2;
self.attackRange = 4;
self.hasActed = false;
self.hasMoved = false;
self.takeDamage = function (amount) {
self.hp = Math.max(0, self.hp - amount);
LK.effects.flashObject(self, 0xFF0000, 500);
// Create damage particles
for (var i = 0; i < 8; i++) {
var particle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1,
tint: 0xFF0000
});
particle.x = self.x + (Math.random() - 0.5) * 40;
particle.y = self.y + (Math.random() - 0.5) * 40;
game.addChild(particle);
var targetX = particle.x + (Math.random() - 0.5) * 200;
var targetY = particle.y - Math.random() * 100 - 50;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.05,
scaleY: 0.05
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
if (self.hp <= 0) {
self.destroy();
archer = null;
checkGameOver();
}
};
self.attack = function (target) {
if (target && target.takeDamage) {
// Create arrow projectile
var arrow = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.1,
tint: 0x8B4513
});
arrow.x = self.x;
arrow.y = self.y;
game.addChild(arrow);
// Calculate angle to target
var dx = target.x - self.x;
var dy = target.y - self.y;
arrow.rotation = Math.atan2(dy, dx);
// Animate arrow to target
tween(arrow, {
x: target.x,
y: target.y
}, {
duration: 400,
easing: tween.linear,
onFinish: function onFinish() {
arrow.destroy();
target.takeDamage(self.damage);
}
});
LK.getSound('attack').play();
self.hasActed = true;
self.hasMoved = true;
}
};
self.down = function (x, y, obj) {
if (gameState === 'player_turn' && turnPhase === 'select') {
handleCellClick(self.gridX, self.gridY);
}
showHealthInterface(self);
};
return self;
});
var Barrel = Container.expand(function () {
var self = Container.call(this);
var barrelGraphics = self.attachAsset('barrel', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.hp = 30;
self.maxHp = 30;
self.exploded = false;
self.takeDamage = function (amount) {
if (self.exploded) return;
self.hp = Math.max(0, self.hp - amount);
LK.effects.flashObject(self, 0xFF0000, 500);
if (self.hp <= 0) {
self.explode();
}
};
self.explode = function () {
if (self.exploded) return;
self.exploded = true;
// Play explosion sound
LK.getSound('explosion').play();
// Create explosion particles
for (var i = 0; i < 15; i++) {
var particle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.15,
scaleY: 0.15,
tint: 0xFF4500
});
particle.x = self.x + (Math.random() - 0.5) * 60;
particle.y = self.y + (Math.random() - 0.5) * 60;
game.addChild(particle);
var targetX = particle.x + (Math.random() - 0.5) * 300;
var targetY = particle.y - Math.random() * 150 - 100;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.05,
scaleY: 0.05
}, {
duration: 1000 + Math.random() * 500,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Deal damage to all units within 2 cells radius
var damageAmount = 40;
var explosionRadius = 2;
// Check all grid positions within radius
for (var x = Math.max(0, self.gridX - explosionRadius); x <= Math.min(gridWidth - 1, self.gridX + explosionRadius); x++) {
for (var y = Math.max(0, self.gridY - explosionRadius); y <= Math.min(gridHeight - 1, self.gridY + explosionRadius); y++) {
var distance = getDistance(self.gridX, self.gridY, x, y);
if (distance <= explosionRadius && grid[x][y].occupied) {
var unit = grid[x][y].unit;
if (unit && unit.takeDamage && unit !== self) {
unit.takeDamage(damageAmount);
}
}
}
}
// Remove from grid and destroy
removeFromGrid(self.gridX, self.gridY);
for (var i = barrels.length - 1; i >= 0; i--) {
if (barrels[i] === self) {
barrels.splice(i, 1);
break;
}
}
self.destroy();
};
self.down = function (x, y, obj) {
showHealthInterface(self);
};
return self;
});
var ComicPanel = Container.expand(function () {
var self = Container.call(this);
self.panelImage = null;
self.titleText = null;
self.storyText = null;
self.currentPanel = 0;
self.totalPanels = 3;
self.init = function (panelNumber, title, story) {
// Create background
var background = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 2.5,
tint: 0x000000
});
background.alpha = 0.9;
self.addChild(background);
// Create panel image
self.panelImage = self.attachAsset('comicPanel' + panelNumber, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.panelImage.y = -100;
// Create title
self.titleText = new Text2(title, {
size: 60,
fill: 0xFFD700
});
self.titleText.anchor.set(0.5, 0.5);
self.titleText.y = 200;
self.addChild(self.titleText);
// Create story text
self.storyText = new Text2(story, {
size: 35,
fill: 0xFFFFFF
});
self.storyText.anchor.set(0.5, 0.5);
self.storyText.y = 280;
self.addChild(self.storyText);
// Create continue prompt
var continueText = new Text2('Tap to continue...', {
size: 30,
fill: 0xCCCCCC
});
continueText.anchor.set(0.5, 0.5);
continueText.y = 350;
self.addChild(continueText);
// Animate text appearance
self.titleText.alpha = 0;
self.storyText.alpha = 0;
continueText.alpha = 0;
tween(self.titleText, {
alpha: 1
}, {
duration: 500,
delay: 200
});
tween(self.storyText, {
alpha: 1
}, {
duration: 800,
delay: 600
});
tween(continueText, {
alpha: 1
}, {
duration: 500,
delay: 1200
});
};
self.down = function (x, y, obj) {
LK.getSound('pageFlip').play();
self.destroy();
game.showNextComicPanel();
};
return self;
});
var EndTurnButton = Container.expand(function () {
var self = Container.call(this);
var buttonGraphics = self.attachAsset('endTurnButton', {
anchorX: 0.5,
anchorY: 0.5
});
var buttonText = new Text2('End Turn', {
size: 40,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
self.visible = false;
self.down = function (x, y, obj) {
if (self.visible && gameState === 'player_turn') {
endPlayerTurn();
}
};
return self;
});
var GridCell = Container.expand(function () {
var self = Container.call(this);
var cellGraphics = self.attachAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5
});
cellGraphics.alpha = 0.3;
// Add black border effect by creating a larger black background
var borderGraphics = self.attachAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.05,
scaleY: 1.05,
tint: 0x000000
});
// Move border behind main cell
self.setChildIndex(borderGraphics, 0);
self.gridX = 0;
self.gridY = 0;
self.occupied = false;
self.unit = null;
self.highlighted = false;
self.selectedBorder = null;
self.showSelectedBorder = function () {
if (!self.selectedBorder) {
self.selectedBorder = self.attachAsset('selectedBorder', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.15,
scaleY: 1.15
});
self.setChildIndex(self.selectedBorder, 0);
}
self.selectedBorder.visible = true;
};
self.hideSelectedBorder = function () {
if (self.selectedBorder) {
self.selectedBorder.visible = false;
}
};
self.highlight = function () {
if (!self.highlighted) {
cellGraphics.tint = 0xFFFF00;
// Add thick black border for highlighted cells
if (!self.highlightBorder) {
self.highlightBorder = self.attachAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.1,
scaleY: 1.1,
tint: 0x000000
});
self.setChildIndex(self.highlightBorder, 0);
}
self.highlightBorder.visible = true;
self.highlighted = true;
}
};
self.unhighlight = function () {
if (self.highlighted) {
cellGraphics.tint = 0xFFFFFF;
if (self.highlightBorder) {
self.highlightBorder.visible = false;
}
self.highlighted = false;
}
};
self.down = function (x, y, obj) {
if (gameState === 'player_turn' && self.highlighted) {
handleCellClick(self.gridX, self.gridY);
}
};
return self;
});
var Knight = Container.expand(function () {
var self = Container.call(this);
var knightGraphics = self.attachAsset('knight', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.hp = 100;
self.maxHp = 100;
self.damage = 30;
self.moveRange = 3;
self.attackRange = 1;
self.hasActed = false;
self.hasMoved = false;
self.takeDamage = function (amount) {
self.hp = Math.max(0, self.hp - amount);
LK.effects.flashObject(self, 0xFF0000, 500);
// Create damage particles
for (var i = 0; i < 8; i++) {
var particle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1,
tint: 0xFF0000
});
particle.x = self.x + (Math.random() - 0.5) * 40;
particle.y = self.y + (Math.random() - 0.5) * 40;
game.addChild(particle);
var targetX = particle.x + (Math.random() - 0.5) * 200;
var targetY = particle.y - Math.random() * 100 - 50;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.05,
scaleY: 0.05
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
if (self.hp <= 0) {
self.destroy();
knight = null;
checkGameOver();
}
};
self.attack = function (target) {
if (target && target.takeDamage) {
target.takeDamage(self.damage);
LK.getSound('attack').play();
self.hasActed = true;
self.hasMoved = true;
}
};
self.down = function (x, y, obj) {
if (gameState === 'player_turn' && turnPhase === 'select') {
handleCellClick(self.gridX, self.gridY);
}
showHealthInterface(self);
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
self.gridX = 0;
self.gridY = 0;
self.obstacleType = 'stone'; // 'stone', 'box', 'column'
self.isDestructible = false;
self.hp = 0;
self.maxHp = 0;
self.graphics = null;
self.init = function (type) {
self.obstacleType = type;
if (type === 'stone') {
self.graphics = self.attachAsset('stone', {
anchorX: 0.5,
anchorY: 0.5
});
self.isDestructible = false;
} else if (type === 'box') {
self.graphics = self.attachAsset('box', {
anchorX: 0.5,
anchorY: 0.5
});
self.isDestructible = true;
self.hp = 50;
self.maxHp = 50;
} else if (type === 'column') {
self.graphics = self.attachAsset('column', {
anchorX: 0.5,
anchorY: 0.5
});
self.isDestructible = false;
}
};
self.takeDamage = function (amount) {
if (!self.isDestructible) return;
self.hp = Math.max(0, self.hp - amount);
LK.effects.flashObject(self, 0xFF0000, 500);
// Create damage particles
for (var i = 0; i < 6; i++) {
var particle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.08,
scaleY: 0.08,
tint: 0x8B4513
});
particle.x = self.x + (Math.random() - 0.5) * 30;
particle.y = self.y + (Math.random() - 0.5) * 30;
game.addChild(particle);
var targetX = particle.x + (Math.random() - 0.5) * 150;
var targetY = particle.y - Math.random() * 80 - 40;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.03,
scaleY: 0.03
}, {
duration: 600 + Math.random() * 300,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
if (self.hp <= 0) {
removeFromGrid(self.gridX, self.gridY);
for (var i = obstacles.length - 1; i >= 0; i--) {
if (obstacles[i] === self) {
obstacles.splice(i, 1);
break;
}
}
self.destroy();
}
};
self.down = function (x, y, obj) {
if (self.isDestructible) {
showHealthInterface(self);
}
};
return self;
});
var SkeletonArcher = Container.expand(function () {
var self = Container.call(this);
var archerGraphics = self.attachAsset('skeletonArcher', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.hp = 40;
self.maxHp = 40;
self.damage = 18;
self.moveRange = 3;
self.attackRange = 3;
self.attack = function (target) {
if (target && target.takeDamage) {
// Create arrow projectile
var arrow = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.1,
tint: 0x654321
});
arrow.x = self.x;
arrow.y = self.y;
game.addChild(arrow);
// Calculate angle to target
var dx = target.x - self.x;
var dy = target.y - self.y;
arrow.rotation = Math.atan2(dy, dx);
// Animate arrow to target
tween(arrow, {
x: target.x,
y: target.y
}, {
duration: 400,
easing: tween.linear,
onFinish: function onFinish() {
arrow.destroy();
target.takeDamage(self.damage);
}
});
LK.getSound('attack').play();
}
};
self.takeDamage = function (amount) {
self.hp = Math.max(0, self.hp - amount);
LK.effects.flashObject(self, 0xFF0000, 500);
// Create grey damage particles
for (var i = 0; i < 6; i++) {
var particle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.08,
scaleY: 0.08,
tint: 0x808080
});
particle.x = self.x + (Math.random() - 0.5) * 30;
particle.y = self.y + (Math.random() - 0.5) * 30;
game.addChild(particle);
var targetX = particle.x + (Math.random() - 0.5) * 150;
var targetY = particle.y - Math.random() * 80 - 40;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.03,
scaleY: 0.03
}, {
duration: 600 + Math.random() * 300,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
if (self.hp <= 0) {
removeFromGrid(self.gridX, self.gridY);
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
checkVictory();
}
};
self.down = function (x, y, obj) {
showHealthInterface(self);
};
return self;
});
var SkeletonKnight = Container.expand(function () {
var self = Container.call(this);
var knightGraphics = self.attachAsset('skeletonKnight', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.hp = 100;
self.maxHp = 100;
self.damage = 35;
self.moveRange = 1;
self.attackRange = 1;
self.attack = function (target) {
if (target && target.takeDamage) {
target.takeDamage(self.damage);
LK.getSound('attack').play();
}
};
self.takeDamage = function (amount) {
self.hp = Math.max(0, self.hp - amount);
LK.effects.flashObject(self, 0xFF0000, 500);
// Create grey damage particles
for (var i = 0; i < 6; i++) {
var particle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.08,
scaleY: 0.08,
tint: 0x808080
});
particle.x = self.x + (Math.random() - 0.5) * 30;
particle.y = self.y + (Math.random() - 0.5) * 30;
game.addChild(particle);
var targetX = particle.x + (Math.random() - 0.5) * 150;
var targetY = particle.y - Math.random() * 80 - 40;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.03,
scaleY: 0.03
}, {
duration: 600 + Math.random() * 300,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
if (self.hp <= 0) {
removeFromGrid(self.gridX, self.gridY);
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
checkVictory();
}
};
self.down = function (x, y, obj) {
showHealthInterface(self);
};
return self;
});
var SkeletonSoldier = Container.expand(function () {
var self = Container.call(this);
var soldierGraphics = self.attachAsset('skeletonSoldier', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.hp = 60;
self.maxHp = 60;
self.damage = 20;
self.moveRange = 2;
self.attackRange = 1;
self.attack = function (target) {
if (target && target.takeDamage) {
target.takeDamage(self.damage);
LK.getSound('attack').play();
}
};
self.takeDamage = function (amount) {
self.hp = Math.max(0, self.hp - amount);
LK.effects.flashObject(self, 0xFF0000, 500);
// Create grey damage particles
for (var i = 0; i < 6; i++) {
var particle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.08,
scaleY: 0.08,
tint: 0x808080
});
particle.x = self.x + (Math.random() - 0.5) * 30;
particle.y = self.y + (Math.random() - 0.5) * 30;
game.addChild(particle);
var targetX = particle.x + (Math.random() - 0.5) * 150;
var targetY = particle.y - Math.random() * 80 - 40;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.03,
scaleY: 0.03
}, {
duration: 600 + Math.random() * 300,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
if (self.hp <= 0) {
removeFromGrid(self.gridX, self.gridY);
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
checkVictory();
}
};
self.down = function (x, y, obj) {
showHealthInterface(self);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// Comic story system
var comicStory = [{
title: "The Ancient Kingdom",
story: "Long ago, brave knights and archers defended\nthe realm from the forces of darkness..."
}, {
title: "The Skeleton Army Rises",
story: "But evil has awakened! Skeleton warriors emerge\nfrom ancient tombs to conquer the land..."
}, {
title: "Heroes Must Rise",
story: "Only you can lead our heroes to victory!\nGuide them through enemy territory to escape!"
}];
var currentComicPanel = 0;
var showingComic = true;
var comicContainer = null;
var gridWidth = 8;
var gridHeight = 8;
var cellSize = 200;
var grid = [];
var units = [];
var enemies = [];
var barrels = [];
var obstacles = [];
var knight = null;
var archer = null;
var exitPortal = null;
var selectedUnit = null;
var selectedTarget = null;
var gameState = 'player_turn'; // 'player_turn', 'enemy_turn', 'game_over'
var turnPhase = 'select'; // 'select', 'move', 'attack'
var wave = 1;
// UI Elements
var turnText = new Text2('Player Turn', {
size: 60,
fill: 0xFFFFFF
});
turnText.anchor.set(0.5, 0);
LK.gui.top.addChild(turnText);
var waveText = new Text2('Wave 1', {
size: 50,
fill: 0xFFFFFF
});
waveText.anchor.set(1, 0);
LK.gui.topRight.addChild(waveText);
var instructionText = new Text2('Select a unit to move', {
size: 40,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 1);
LK.gui.bottom.addChild(instructionText);
// Create End Turn button
var endTurnButton = new EndTurnButton();
endTurnButton.x = 2048 - 150;
endTurnButton.y = 2732 - 150;
endTurnButton.visible = false; // Hide during comic
game.addChild(endTurnButton);
// Initialize grid
function initializeGrid() {
// Optimize grid positioning for full screen - leave more space at top for UI
var startX = (2048 - gridWidth * cellSize) / 2;
var startY = 200 + (2732 - 400 - gridHeight * cellSize) / 2; // Leave 200px at top and bottom
for (var x = 0; x < gridWidth; x++) {
grid[x] = [];
for (var y = 0; y < gridHeight; y++) {
var cell = new GridCell();
cell.gridX = x;
cell.gridY = y;
cell.x = startX + x * cellSize + cellSize / 2;
cell.y = startY + y * cellSize + cellSize / 2;
grid[x][y] = cell;
game.addChild(cell);
}
}
}
function placeUnit(unit, gridX, gridY) {
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
if (grid[gridX][gridY].occupied) {
return false;
}
// Remove from old position
if (unit.gridX !== undefined && unit.gridY !== undefined) {
grid[unit.gridX][unit.gridY].occupied = false;
grid[unit.gridX][unit.gridY].unit = null;
}
unit.gridX = gridX;
unit.gridY = gridY;
unit.x = grid[gridX][gridY].x;
unit.y = grid[gridX][gridY].y;
grid[gridX][gridY].occupied = true;
grid[gridX][gridY].unit = unit;
return true;
}
return false;
}
function removeFromGrid(gridX, gridY) {
if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) {
grid[gridX][gridY].occupied = false;
grid[gridX][gridY].unit = null;
}
}
function getDistance(x1, y1, x2, y2) {
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
}
function highlightValidMoves(unit) {
clearHighlights();
// Only show movement highlights if unit hasn't moved yet
if (!unit.hasMoved) {
for (var x = 0; x < gridWidth; x++) {
for (var y = 0; y < gridHeight; y++) {
var distance = getDistance(unit.gridX, unit.gridY, x, y);
if (distance <= unit.moveRange && !grid[x][y].occupied) {
grid[x][y].highlight();
}
}
}
}
}
function highlightValidAttacks(unit) {
var hasValidAttacks = false;
// Only show attack highlights if unit hasn't attacked yet
if (!unit.hasActed) {
for (var x = 0; x < gridWidth; x++) {
for (var y = 0; y < gridHeight; y++) {
var distance = getDistance(unit.gridX, unit.gridY, x, y);
if (distance <= unit.attackRange && grid[x][y].occupied && grid[x][y].unit !== unit) {
// Check if it's an enemy, barrel, or destructible obstacle
var isEnemy = false;
var isBarrel = false;
var isDestructibleObstacle = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === grid[x][y].unit) {
isEnemy = true;
break;
}
}
for (var i = 0; i < barrels.length; i++) {
if (barrels[i] === grid[x][y].unit) {
isBarrel = true;
break;
}
}
for (var i = 0; i < obstacles.length; i++) {
if (obstacles[i] === grid[x][y].unit && obstacles[i].isDestructible && obstacles[i].obstacleType !== 'box') {
isDestructibleObstacle = true;
break;
}
}
if (isEnemy || isBarrel || isDestructibleObstacle) {
hasValidAttacks = true;
var cell = grid[x][y];
cell.highlighted = true;
if (!cell.attackHighlight) {
cell.attackHighlight = cell.attachAsset('attackRange', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
});
cell.setChildIndex(cell.attackHighlight, cell.children.length - 1);
}
cell.attackHighlight.visible = true;
}
}
}
}
}
return hasValidAttacks;
}
function clearHighlights() {
for (var x = 0; x < gridWidth; x++) {
for (var y = 0; y < gridHeight; y++) {
var cell = grid[x][y];
cell.unhighlight();
cell.hideSelectedBorder();
if (cell.attackHighlight) {
cell.attackHighlight.visible = false;
}
}
}
}
function handleCellClick(gridX, gridY) {
if (gameState !== 'player_turn') return;
var cell = grid[gridX][gridY];
if (turnPhase === 'select') {
// Select a unit
if (cell.occupied && (cell.unit === knight || cell.unit === archer)) {
if (!cell.unit.hasActed || !cell.unit.hasMoved) {
selectedUnit = cell.unit;
// Show golden border for selected unit
grid[selectedUnit.gridX][selectedUnit.gridY].showSelectedBorder();
turnPhase = 'action';
highlightValidMoves(selectedUnit);
var hasAttacks = highlightValidAttacks(selectedUnit);
instructionText.setText('Click to move and/or attack, then click unit again when done');
}
}
} else if (turnPhase === 'action') {
if (cell.highlighted && !cell.occupied && !selectedUnit.hasMoved) {
// Move to this cell
placeUnit(selectedUnit, gridX, gridY);
selectedUnit.hasMoved = true;
// Play random movement sound
var moveSounds = ['move', 'move1', 'move3'];
var randomSound = moveSounds[Math.floor(Math.random() * moveSounds.length)];
LK.getSound(randomSound).play();
// Check if reached exit portal
if (exitPortal && gridX === exitPortal.gridX && gridY === exitPortal.gridY) {
LK.getSound('victory').play();
LK.playMusic('Victoria');
LK.showYouWin();
return;
}
// After moving, update highlights to show new attack options
clearHighlights();
highlightValidMoves(selectedUnit);
var hasAttacks = highlightValidAttacks(selectedUnit);
// If unit has no valid attacks and has moved, auto-switch to next unit
if (!hasAttacks && selectedUnit.hasMoved) {
// Find next available unit
var nextUnit = null;
if (selectedUnit === knight && archer && (!archer.hasActed || !archer.hasMoved)) {
nextUnit = archer;
} else if (selectedUnit === archer && knight && (!knight.hasActed || !knight.hasMoved)) {
nextUnit = knight;
}
if (nextUnit) {
// Mark current unit as done and switch to next
selectedUnit.hasActed = true;
grid[selectedUnit.gridX][selectedUnit.gridY].hideSelectedBorder();
selectedUnit = nextUnit;
grid[selectedUnit.gridX][selectedUnit.gridY].showSelectedBorder();
highlightValidMoves(selectedUnit);
highlightValidAttacks(selectedUnit);
instructionText.setText('Switched to next unit - Click to move and/or attack');
} else {
instructionText.setText('Click to attack or click unit again when done');
}
} else {
instructionText.setText('Click to attack or click unit again when done');
}
} else if (cell.highlighted && cell.occupied && !selectedUnit.hasActed) {
// Check if it's an enemy, barrel, or destructible obstacle
var isEnemy = false;
var isBarrel = false;
var isDestructibleObstacle = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === cell.unit) {
isEnemy = true;
break;
}
}
for (var i = 0; i < barrels.length; i++) {
if (barrels[i] === cell.unit) {
isBarrel = true;
break;
}
}
for (var i = 0; i < obstacles.length; i++) {
if (obstacles[i] === cell.unit && obstacles[i].isDestructible && obstacles[i].obstacleType !== 'box') {
isDestructibleObstacle = true;
break;
}
}
if (isEnemy || isBarrel || isDestructibleObstacle) {
// First click selects target for attack
selectedTarget = cell.unit;
turnPhase = 'confirm_attack';
clearHighlights();
// Highlight only the selected target
cell.highlight();
instructionText.setText('Click target again to confirm attack');
}
} else if (cell.occupied && cell.unit === selectedUnit) {
// Clicking on the selected unit again ends their turn
endPlayerAction();
}
} else if (turnPhase === 'confirm_attack') {
if (cell.occupied && cell.unit === selectedTarget && cell.highlighted) {
// Second click confirms attack
selectedUnit.attack(selectedTarget);
selectedUnit.hasActed = true;
selectedTarget = null;
// After attacking, unit's turn is over
clearHighlights();
endPlayerAction();
} else {
// Click elsewhere cancels attack confirmation
selectedTarget = null;
turnPhase = 'action';
highlightValidMoves(selectedUnit);
highlightValidAttacks(selectedUnit);
instructionText.setText('Click to move and/or attack, then click unit again when done');
}
}
}
function endPlayerAction() {
clearHighlights();
if (selectedUnit) {
// Mark unit as fully done (both moved and acted)
selectedUnit.hasActed = true;
selectedUnit.hasMoved = true;
grid[selectedUnit.gridX][selectedUnit.gridY].hideSelectedBorder();
}
selectedUnit = null;
turnPhase = 'select';
// End Turn button is always available during player turn
endTurnButton.visible = true;
// Check if both units have completed their full turns
var knightDone = !knight || knight.hasActed && knight.hasMoved;
var archerDone = !archer || archer.hasActed && archer.hasMoved;
if (knightDone && archerDone) {
instructionText.setText('All units ready - Click End Turn to continue');
} else {
instructionText.setText('Select next unit to move or Click End Turn to skip');
}
}
function endPlayerTurn() {
endTurnButton.visible = false;
gameState = 'enemy_turn';
turnText.setText('Enemy Turn');
instructionText.setText('Enemies moving...');
// Reset player units
if (knight) {
knight.hasActed = false;
knight.hasMoved = false;
}
if (archer) {
archer.hasActed = false;
archer.hasMoved = false;
}
// Start enemy turn after delay
LK.setTimeout(function () {
executeEnemyTurn();
}, 1000);
}
function executeEnemyTurn() {
var enemyIndex = 0;
function processNextEnemy() {
if (enemyIndex >= enemies.length) {
// All enemies processed, start new player turn
gameState = 'player_turn';
turnText.setText('Player Turn');
instructionText.setText('Select a unit to move');
turnPhase = 'select';
selectedUnit = null;
clearHighlights();
LK.getSound('trumpet').play();
return;
}
var enemy = enemies[enemyIndex];
if (!enemy || enemy.hp <= 0) {
enemyIndex++;
processNextEnemy();
return;
}
// Simple AI: move towards nearest player and attack if in range
var targets = [];
if (knight && knight.hp > 0) targets.push(knight);
if (archer && archer.hp > 0) targets.push(archer);
if (targets.length === 0) {
enemyIndex++;
processNextEnemy();
return;
}
// Find closest target
var closestTarget = targets[0];
var closestDistance = getDistance(enemy.gridX, enemy.gridY, closestTarget.gridX, closestTarget.gridY);
for (var i = 1; i < targets.length; i++) {
var distance = getDistance(enemy.gridX, enemy.gridY, targets[i].gridX, targets[i].gridY);
if (distance < closestDistance) {
closestTarget = targets[i];
closestDistance = distance;
}
}
// Try to attack first
if (closestDistance <= enemy.attackRange) {
if (enemy.attack && typeof enemy.attack === 'function') {
enemy.attack(closestTarget);
}
enemyIndex++;
LK.setTimeout(processNextEnemy, 500);
} else {
// Move towards target
var bestMove = findBestMove(enemy, closestTarget);
if (bestMove) {
// Play random movement sound
var moveSounds = ['move', 'move1', 'move3'];
var randomSound = moveSounds[Math.floor(Math.random() * moveSounds.length)];
LK.getSound(randomSound).play();
moveToPosition(enemy, bestMove.x, bestMove.y, function () {
enemyIndex++;
LK.setTimeout(processNextEnemy, 500);
});
} else {
enemyIndex++;
LK.setTimeout(processNextEnemy, 500);
}
}
}
processNextEnemy();
}
function moveToPosition(unit, targetX, targetY, callback) {
var startX = unit.gridX;
var startY = unit.gridY;
var path = [];
// Simple pathfinding - move one step at a time toward target
var currentX = startX;
var currentY = startY;
while (currentX !== targetX || currentY !== targetY) {
var nextX = currentX;
var nextY = currentY;
if (currentX < targetX) nextX++;else if (currentX > targetX) nextX--;else if (currentY < targetY) nextY++;else if (currentY > targetY) nextY--;
path.push({
x: nextX,
y: nextY
});
currentX = nextX;
currentY = nextY;
}
var stepIndex = 0;
function animateNextStep() {
if (stepIndex >= path.length) {
if (callback) callback();
return;
}
var step = path[stepIndex];
placeUnit(unit, step.x, step.y);
stepIndex++;
LK.setTimeout(animateNextStep, 300);
}
animateNextStep();
}
function findBestMove(enemy, target) {
var bestMove = null;
var bestDistance = getDistance(enemy.gridX, enemy.gridY, target.gridX, target.gridY);
for (var dx = -enemy.moveRange; dx <= enemy.moveRange; dx++) {
for (var dy = -enemy.moveRange; dy <= enemy.moveRange; dy++) {
if (Math.abs(dx) + Math.abs(dy) > enemy.moveRange) continue;
var newX = enemy.gridX + dx;
var newY = enemy.gridY + dy;
if (newX >= 0 && newX < gridWidth && newY >= 0 && newY < gridHeight) {
if (!grid[newX][newY].occupied || newX === enemy.gridX && newY === enemy.gridY) {
var distance = getDistance(newX, newY, target.gridX, target.gridY);
if (distance < bestDistance) {
bestDistance = distance;
bestMove = {
x: newX,
y: newY
};
}
}
}
}
}
return bestMove;
}
function spawnEnemies() {
var enemyTypes = [SkeletonSoldier, SkeletonArcher, SkeletonKnight];
var enemyCount = Math.min(3 + wave, 8);
for (var i = 0; i < enemyCount; i++) {
var enemyType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
var enemy = new enemyType();
// Find random empty position on right side of map
var placed = false;
var attempts = 0;
while (!placed && attempts < 50) {
var x = Math.floor(gridWidth * 0.6 + Math.random() * gridWidth * 0.4);
var y = Math.floor(Math.random() * gridHeight);
if (!grid[x][y].occupied) {
placeUnit(enemy, x, y);
enemies.push(enemy);
game.addChild(enemy);
placed = true;
}
attempts++;
}
}
}
function spawnBarrels() {
var barrelCount = Math.min(2 + Math.floor(wave / 2), 4);
for (var i = 0; i < barrelCount; i++) {
var barrel = new Barrel();
// Find random empty position in middle area of map
var placed = false;
var attempts = 0;
while (!placed && attempts < 50) {
var x = Math.floor(gridWidth * 0.3 + Math.random() * gridWidth * 0.4);
var y = Math.floor(Math.random() * gridHeight);
if (!grid[x][y].occupied) {
placeUnit(barrel, x, y);
barrels.push(barrel);
game.addChild(barrel);
placed = true;
}
attempts++;
}
}
}
function spawnObstacles() {
var obstacleTypes = ['stone', 'box', 'column'];
var obstacleCount = Math.min(3 + Math.floor(wave / 3), 6);
for (var i = 0; i < obstacleCount; i++) {
var obstacle = new Obstacle();
var type = obstacleTypes[Math.floor(Math.random() * obstacleTypes.length)];
obstacle.init(type);
// Find random empty position across the map
var placed = false;
var attempts = 0;
while (!placed && attempts < 50) {
var x = Math.floor(Math.random() * gridWidth);
var y = Math.floor(Math.random() * gridHeight);
// Avoid placing near starting positions and exit
if (x > 2 && x < gridWidth - 3 && !grid[x][y].occupied) {
placeUnit(obstacle, x, y);
obstacles.push(obstacle);
game.addChild(obstacle);
placed = true;
}
attempts++;
}
}
}
function checkVictory() {
if (enemies.length === 0) {
LK.getSound('trompeta').play();
LK.playMusic('Victoria');
LK.showYouWin();
return;
}
}
function showHealthInterface(unit) {
// Remove existing health interface if any
if (game.healthInterface) {
game.healthInterface.destroy();
game.healthInterface = null;
}
// Create health interface
var healthInterface = new Container();
var background = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.8,
tint: 0x000000
});
background.alpha = 0.8;
healthInterface.addChild(background);
// Health text
var healthText = new Text2('HP: ' + unit.hp + '/' + unit.maxHp, {
size: 40,
fill: 0xFFFFFF
});
healthText.anchor.set(0.5, 0.5);
healthInterface.addChild(healthText);
// Position interface next to end turn button, optimized for full screen
healthInterface.x = endTurnButton.x - 350;
healthInterface.y = endTurnButton.y;
game.addChild(healthInterface);
game.healthInterface = healthInterface;
// Auto-hide after 3 seconds
LK.setTimeout(function () {
if (game.healthInterface === healthInterface) {
healthInterface.destroy();
game.healthInterface = null;
}
}, 3000);
}
function checkGameOver() {
if ((!knight || knight.hp <= 0) && (!archer || archer.hp <= 0)) {
LK.showGameOver();
}
}
function showComicStory() {
if (currentComicPanel < comicStory.length) {
// Start comic music on first panel
if (currentComicPanel === 0) {
LK.playMusic('comicMusic');
}
var panel = new ComicPanel();
var storyData = comicStory[currentComicPanel];
panel.init(currentComicPanel + 1, storyData.title, storyData.story);
panel.x = 2048 / 2;
panel.y = 2732 / 2;
game.addChild(panel);
comicContainer = panel;
currentComicPanel++;
} else {
// Comic finished, start the game
startActualGame();
}
}
game.showNextComicPanel = function () {
if (comicContainer) {
comicContainer = null;
}
showComicStory();
};
function startActualGame() {
showingComic = false;
// Stop comic music and start battle music
LK.stopMusic();
LK.playMusic('battleMusic');
// Initialize the actual game
initializeGameplay();
}
function initializeGameplay() {
// Initialize grid
initializeGrid();
// Create and place heroes
knight = new Knight();
archer = new Archer();
game.addChild(knight);
game.addChild(archer);
placeUnit(knight, 1, 3);
placeUnit(archer, 1, 4);
// Create exit portal
exitPortal = LK.getAsset('exitPortal', {
anchorX: 0.5,
anchorY: 0.5
});
exitPortal.gridX = gridWidth - 2;
exitPortal.gridY = 3;
exitPortal.x = grid[exitPortal.gridX][exitPortal.gridY].x;
exitPortal.y = grid[exitPortal.gridX][exitPortal.gridY].y;
exitPortal.alpha = 0.7;
game.addChild(exitPortal);
// Spawn initial enemies
spawnEnemies();
// Spawn initial barrels
spawnBarrels();
// Spawn initial obstacles
spawnObstacles();
// Make End Turn button available from game start
endTurnButton.visible = true;
// Play trumpet sound to signal game start
LK.getSound('trumpet').play();
}
// Start with comic story
showComicStory();
game.update = function () {
// Show/hide UI based on comic state
if (showingComic) {
turnText.visible = false;
waveText.visible = false;
instructionText.visible = false;
endTurnButton.visible = false;
} else {
turnText.visible = true;
waveText.visible = true;
instructionText.visible = true;
// endTurnButton visibility handled by game logic
}
};
Un portal de color morado que tiene pinta de vortice. In-Game asset. 2d. High contrast. No shadows
caballero zorro, con escudo y lanza. In-Game asset. 2d. High contrast. No shadows
conejo con archo y flecha mirando hacia la derecha y capucha tiene. In-Game asset. 2d. High contrast. No shadows
arquero esqueleto. In-Game asset. High contrast. No shadows
un esqueleto con espada y escudo. In-Game asset. 2d. High contrast. No shadows
un esqueleto grande, con un hacha, un caso y escudo. In-Game asset. 2d. High contrast. No shadows
un botton que dice "Fin del Turno" con letras blancas y grandes. In-Game asset. 2d. High contrast. No shadows
cesped. In-Game asset. 2d. High contrast. No shadows
un barril rojo de era medieval. In-Game asset. 2d. High contrast. No shadows
una caja de madera de color marron. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
una columna de piedra. In-Game asset. 2d. High contrast. No shadows
rocas grande. In-Game asset. 2d. High contrast. No shadows
esqueletos guerreros que salen de la tierra por un brujo, miesntras un ballero zorro y un conejo arquero miran asombrado. In-Game asset. 2d. High contrast. No shadows
un hechizero esqueleto les mira de forma aterradora hacia adelante con ojos rojos y brillosos. In-Game asset. 2d. High contrast. No shadows
un caballero zorro y un arquero conejo caminan por el bosque tranquilamente, mientras en el fondo en la oscuridad del bosque se ven unos ojos rojos que resaltan. In-Game asset. 2d. High contrast. No shadows