User prompt
Cambia el Title del juego por Ice Emblem, no Tactical Commander
User prompt
Que suene la música Victory al ganar y YouLose si pierdes
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'destroy')' in or related to this line: 'selectedUnit.destroy();' Line Number: 1772
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of null (reading 'gridX')' in or related to this line: 'var distance = Math.abs(unit.gridX - x) + Math.abs(unit.gridY - y);' Line Number: 1113
User prompt
Cuando el Healer cure, que suene HealSound. Quiero que en la pantalla de inicio, en la esquina superior izquierda haya un sprite Logo y al lado diga: "FOLLOW ME: @Mugiwara_Shanks"
User prompt
Hay un problema, se queda puesta la opción de Move sin clicar una unidad antes, lo que provoca que pueda moverlas más de lo permitido. Y quiero que se pueda clicar una unidad que ya se movió para así ver sus stats. Y añade un asset para la animación de curación, que se comporte como el de ataque pero con su propio asset. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
La Healer cura 10 de HP a la unidad que cura, pero sin pasarse del máximo. Y quiero que al contraatacar una unidad se vea la misma animación de combate, de la unidad contraatacando, solo si puede contraatacar. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of null (reading 'gridX')' in or related to this line: 'var distance = Math.abs(unit.gridX - x) + Math.abs(unit.gridY - y);' Line Number: 1083
User prompt
Durante el combate, las unidades deben infligir exactamente el daño que aparece en su estadística de 'ATK' (por ejemplo, si un personaje tiene ATK = 15, sus ataques deben restar 15 HP al enemigo). El sistema de batalla no debe reemplazar este valor por un daño fijo (como 1), sino que siempre utilice la cifra indicada en los stats de cada unidad."
User prompt
Cuando estemos en el menú principal que suene MainMenu hasta que se haga clic y empiece el juego. Quiero que pongas un Asset para cuando una unidad es atacada, la cual aparece y desaparece rápido para dar efecto de golpe. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Quiero que lo de DERROTA AL COMANDANTE salga en inglés, y que sea encima del End Turn del Mapa, así quedará mejor que sobre el mapa ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
En el contador de unidades sale 20/10 en ambos bandos, se supone que es 10/10 en cada lado, ya que cada bando tiene 10 unidades
User prompt
Quiero que al hacer click la pantalla de inicio empiece el juego, que primero salga DERROTA AL COMANDANTE en grande sobre el mapa y luego se quite para empezar el juego ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Quita los sprites de las unidades moviéndose sobre la pantalla de inicio, y quita el título en la pantalla, así como el Start Game, le pondré otra cosa
User prompt
Ice Emblem - Pocket Edition en mayúsculas, y quiero un fondo de un campo, pon en assets algo para añadir el fondo
User prompt
Quiero que en el fondo de la pantalla de inicio del juego se vea de fondo a los sprites de las unidades luchando en un campo, y renombra al juego como Ice Emblem - Virtual Journey ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Quiero que al hacer clic al personaje, además de su clase y stats, salga su nombre, y cambia los stats de cada uno a como te señalo ahora: Ardan (Commander, HP 40, Daño 19), Kael (Sword1, HP 28, Daño 13), Hikari (Sword2, HP 32, Daño 14), Brutus (Acorazado, HP 52, Daño 15), Mizuki (Magic, HP 34, Daño 12), Lyra (Dragonrider, HP 35, Daño 16), Darius (Rider, HP 34, Daño 16), Eirynn (Healer, HP 26, Cura 10), Elian (Bow, HP 27, Daño 13) e Irios (Manakete, HP 30, Daño 17).
User prompt
Quiero una pantalla de inicio que diga el nombre del juego y un "START GAME" que al clicar inicie el juego
User prompt
Quiero que cada espadachín tenga su propio Sprite, divídelos en Sword1 y Sword2 para así dárselos. Lo mismo aplica a los 2 espadachines del enemigo (también serán Sword1 y Sword2)
User prompt
Quiero que durante la partida suene la música de fondo MedievalMusicFantasy1, al ganar suene Victory y al perder YouLose, que al atacar una unidad suene Impacts y cuando una unidad muere que suene Vanish al momento de desaparecer.
User prompt
Faltó darle otro espadachín al enemigo
User prompt
Elimina la unidad dancer, cámbiala por otro espadachín. Luego implementa la opción de contraataque, si una unidad ataca, la otra puede responder si se encuentra dentro de su campo de ataque, atacando, de ahí termina el combate. Por último, que el acorazado ataque de 1 a 2 casillas como el mago
User prompt
Sigue sin funcionar, usa la lógica del Healer en la Dancer para que interactúe con una unidad aliada en gris, saliendo la opción Danzar o Ignorar al estar al lado de uno, así dicha unidad reinicia su movimiento.
User prompt
La opción danzar se hace cuando Dancer está a la par de una unidad aliada que ya se movió, para que así esa unidad gane un movimiento extra.
User prompt
La unidad Bailarina puede moverse hasta 4 casillas y atacar a 1 de distancia, infligiendo el daño base de 1. Además, posee la habilidad especial Danzar: si al finalizar su turno hay una unidad aliada a exactamente 1 casilla de distancia (adyacente en cualquier dirección), aparece la opción Danzar/Ignorar sobre esa unidad. Al seleccionarla, la unidad aliada que ya se movió y/o atacó durante ese turno recupera la posibilidad de volver a moverse y/o atacar una vez más. Si está cerca de un enemigo sale Atacar o Ignorar como cualquier unidad normal. Y si hay un enemigo y un aliado a la par, sale Atacar/Danzar/Ignorar
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var GridTile = Container.expand(function (gridX, gridY) {
var self = Container.call(this);
self.gridX = gridX;
self.gridY = gridY;
self.unit = null;
self.hasTerrain = false;
var tileGraphics = self.attachAsset('gridTile', {
anchorX: 0.5,
anchorY: 0.5
});
tileGraphics.alpha = 0; // Make gridTile transparent so terrain is visible
self.terrainType = 'normal'; // normal, tree, water, bridge
self.terrainGraphics = null; // Store terrain graphics reference
self.addTerrain = function (terrainType) {
// Remove existing terrain graphics if present
if (self.terrainGraphics) {
self.removeChild(self.terrainGraphics);
self.terrainGraphics = null;
}
self.terrainType = terrainType || 'normal';
var assetName = 'terrain';
if (terrainType === 'tree') {
assetName = 'tree';
} else if (terrainType === 'water') {
assetName = 'water';
} else if (terrainType === 'bridge') {
assetName = 'bridge';
} else if (terrainType === 'normal') {
assetName = 'terrain';
}
self.terrainGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// For trees, make them fully opaque and visible, for other terrain make slightly transparent
if (terrainType === 'tree') {
self.terrainGraphics.alpha = 1.0;
} else {
self.terrainGraphics.alpha = 0.8;
}
// Move terrain graphics to bottom of display list so highlights show on top
self.setChildIndex(self.terrainGraphics, 0);
self.hasTerrain = true;
};
// Add highlight graphics AFTER terrain so they appear on top
var highlightGraphics = self.attachAsset('highlightTile', {
anchorX: 0.5,
anchorY: 0.5
});
highlightGraphics.alpha = 0;
var attackGraphics = self.attachAsset('attackTile', {
anchorX: 0.5,
anchorY: 0.5
});
attackGraphics.alpha = 0;
var attackRangeGraphics = self.attachAsset('attackRangeTile', {
anchorX: 0.5,
anchorY: 0.5
});
attackRangeGraphics.alpha = 0;
var healRangeGraphics = self.attachAsset('healRangeTile', {
anchorX: 0.5,
anchorY: 0.5
});
healRangeGraphics.alpha = 0;
self.showHighlight = function () {
highlightGraphics.alpha = 0.4;
attackGraphics.alpha = 0;
};
self.showAttackRange = function () {
attackGraphics.alpha = 0.5;
highlightGraphics.alpha = 0;
};
self.showAttackRangePreview = function () {
attackRangeGraphics.alpha = 0.3;
};
self.showHealRangePreview = function () {
healRangeGraphics.alpha = 0.3;
};
self.hideHighlights = function () {
highlightGraphics.alpha = 0;
attackGraphics.alpha = 0;
attackRangeGraphics.alpha = 0;
healRangeGraphics.alpha = 0;
};
return self;
});
var Unit = Container.expand(function (type, team, weaponType, swordVariant, unitName) {
var self = Container.call(this);
self.type = type;
self.team = team;
self.weaponType = weaponType;
self.swordVariant = swordVariant || 1; // Default to sword1 if not specified
self.name = unitName || 'Unknown';
self.gridX = 0;
self.gridY = 0;
// Set stats based on unit name and type
if (unitName === 'Ardan') {
self.hp = 40;
self.attack = 19;
} else if (unitName === 'Kael') {
self.hp = 28;
self.attack = 13;
} else if (unitName === 'Hikari') {
self.hp = 32;
self.attack = 14;
} else if (unitName === 'Brutus') {
self.hp = 52;
self.attack = 15;
} else if (unitName === 'Mizuki') {
self.hp = 34;
self.attack = 12;
} else if (unitName === 'Lyra') {
self.hp = 35;
self.attack = 16;
} else if (unitName === 'Darius') {
self.hp = 34;
self.attack = 16;
} else if (unitName === 'Eirynn') {
self.hp = 26;
self.attack = 10; // Healer uses attack stat for healing power
} else if (unitName === 'Elian') {
self.hp = 27;
self.attack = 13;
} else if (unitName === 'Irios') {
self.hp = 30;
self.attack = 17;
} else {
// Default stats for unnamed units
self.hp = 2;
self.attack = 1;
}
self.maxHp = self.hp;
self.defense = 1;
self.hasMoved = false;
self.hasAttacked = false;
var unitGraphics;
if (type === 'commander') {
unitGraphics = self.attachAsset('commander', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'rider') {
unitGraphics = self.attachAsset('rider', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'dragonRider') {
unitGraphics = self.attachAsset('dragonRider', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'healer') {
unitGraphics = self.attachAsset('healer', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'armored') {
unitGraphics = self.attachAsset('armored', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'manakete') {
unitGraphics = self.attachAsset('manakete', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
// Use weapon type as sprite
if (weaponType === 'sword') {
var swordAsset = 'sword' + self.swordVariant; // Use sword1 or sword2
unitGraphics = self.attachAsset(swordAsset, {
anchorX: 0.5,
anchorY: 0.5
});
} else if (weaponType === 'bow') {
unitGraphics = self.attachAsset('bow', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (weaponType === 'magic') {
unitGraphics = self.attachAsset('magic', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
var swordAsset = 'sword' + (self.swordVariant || 1); // Default to sword1
unitGraphics = self.attachAsset(swordAsset, {
anchorX: 0.5,
anchorY: 0.5
});
}
}
// Add team color filter directly to unit graphics (more opaque, no background)
if (team === 'player') {
unitGraphics.tint = 0x5555FF; // Blue tint for player units
} else {
unitGraphics.tint = 0xFF5555; // Red tint for enemy units
}
// Add commander crown
if (type === 'commander') {
var crown = new Text2('♛', {
size: 25,
fill: 0xFFD700
});
crown.anchor.set(1, 1);
crown.x = 50;
crown.y = 50;
self.addChild(crown);
}
self.updateHpDisplay = function () {
if (self.hp <= 0) {
unitGraphics.alpha = 0.3;
}
};
self.takeDamage = function (damage, attackerWeaponType) {
var baseDamage = 1; // Base damage is always 1
// Apply specific weapon advantage bonuses
if (attackerWeaponType === 'bow' && self.type === 'dragonRider') {
baseDamage = 2; // Bow does 2 total damage to Dragon Rider
} else if (attackerWeaponType === 'magic' && self.type === 'armored') {
baseDamage = 2; // Magic does 2 total damage to Armored
}
// All other combinations do 1 damage (no additional bonuses)
var actualDamage = Math.max(1, baseDamage - self.defense);
if (grid[self.gridY][self.gridX].hasTerrain) {
actualDamage = Math.max(1, actualDamage - 1);
}
self.hp -= actualDamage;
self.updateHpDisplay();
return self.hp <= 0;
};
self.getRange = function () {
if (self.weaponType === 'sword') {
return 1;
}
if (self.weaponType === 'bow') {
return 2;
}
if (self.weaponType === 'magic') {
return 2;
}
if (self.weaponType === 'rider') {
return 1;
}
if (self.weaponType === 'dragonRider') {
return 1;
}
if (self.weaponType === 'healer') {
return 1;
}
if (self.weaponType === 'armored') {
return 2;
}
return 1;
};
self.getMovementRange = function () {
if (self.weaponType === 'rider' || self.weaponType === 'dragonRider') {
return 5;
}
if (self.weaponType === 'manakete') {
return 4;
}
if (self.weaponType === 'armored') {
return 3;
}
return 4; // Default movement for sword, bow, magic, healer
};
self.canAttack = function (targetX, targetY) {
var distance = Math.abs(self.gridX - targetX) + Math.abs(self.gridY - targetY);
if (self.weaponType === 'healer') {
return false;
} // Healer cannot attack
if (self.weaponType === 'bow') {
return distance === 2; // Bow can only attack at exactly range 2, not adjacent
}
if (self.weaponType === 'manakete') {
return distance >= 1 && distance <= 2; // Manakete can attack at range 1-2 like magic
}
return distance <= self.getRange();
};
self.canHeal = function (targetX, targetY) {
if (self.weaponType !== 'healer') {
return false;
}
var distance = Math.abs(self.gridX - targetX) + Math.abs(self.gridY - targetY);
return distance === 1; // Healer can heal adjacent allies
};
self.heal = function (target) {
if (target.hp < target.maxHp) {
target.hp += 1;
if (target.hp > target.maxHp) {
target.hp = target.maxHp;
}
target.updateHpDisplay();
return true;
}
return false;
};
self.resetTurn = function () {
self.hasMoved = false;
self.hasAttacked = false;
unitGraphics.alpha = 1.0;
// Restore original team color
if (self.team === 'player') {
unitGraphics.tint = 0x5555FF; // Blue tint for player units
} else {
unitGraphics.tint = 0xFF5555; // Red tint for enemy units
}
};
self.counterAttack = function (attacker) {
if (self.hp <= 0) return false; // Can't counterattack if dead
if (!self.canAttack(attacker.gridX, attacker.gridY)) return false; // Must be in range
var isDead = attacker.takeDamage(self.attack, self.weaponType);
return isDead;
};
self.endTurn = function () {
self.hasMoved = true;
self.hasAttacked = true;
unitGraphics.alpha = 0.6;
// Add gray tint for moved allied units
if (self.team === 'player') {
unitGraphics.tint = 0x888888; // Gray tint for moved player units
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2d2d2d
});
/****
* Game Code
****/
var GRID_SIZE = 15;
var TILE_SIZE = 128;
var grid = [];
var playerUnits = [];
var enemyUnits = [];
var selectedUnit = null;
var originalUnitPosition = null; // Store original position when moving
var selectedEnemyUnit = null; // Track selected enemy for movement display
var currentTurn = 'player';
var gameState = 'menu'; // Start in menu state instead of playing
var turnPhase = 'select'; // 'select', 'move', 'chooseAction', 'attack'
// UI
// Start Menu UI
var gameTitle = new Text2('Ice Emblem - Virtual Journey', {
size: 60,
fill: 0xFFD700
});
gameTitle.anchor.set(0.5, 0.5);
LK.gui.center.addChild(gameTitle);
// Create animated background battle scene
var backgroundUnits = [];
var battleContainer = new Container();
game.addChild(battleContainer);
// Create several animated units fighting in the background
for (var i = 0; i < 8; i++) {
var unitType = ['sword1', 'sword2', 'commander', 'bow', 'magic', 'armored'][Math.floor(Math.random() * 6)];
var unit = LK.getAsset(unitType, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
});
// Random positions across the screen
unit.x = Math.random() * 2048;
unit.y = Math.random() * 2732;
// Color some units blue, some red to simulate teams
if (i % 2 === 0) {
unit.tint = 0x5555FF; // Blue team
} else {
unit.tint = 0xFF5555; // Red team
}
battleContainer.addChild(unit);
backgroundUnits.push(unit);
// Start random movement animation
animateBackgroundUnit(unit);
}
function animateBackgroundUnit(unit) {
var newX = Math.random() * 2048;
var newY = Math.random() * 2732;
var duration = 2000 + Math.random() * 3000;
tween(unit, {
x: newX,
y: newY
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
// Continue animation loop
animateBackgroundUnit(unit);
}
});
// Random rotation animation
var rotationTarget = unit.rotation + (Math.random() - 0.5) * Math.PI;
tween(unit, {
rotation: rotationTarget
}, {
duration: duration / 2,
easing: tween.easeInOut
});
}
var startButton = new Text2('START GAME', {
size: 60,
fill: 0x00FF00
});
startButton.anchor.set(0.5, 0.5);
startButton.y = 120;
startButton.interactive = true;
LK.gui.center.addChild(startButton);
var turnText = new Text2('Player Turn', {
size: 60,
fill: 0xFFFFFF
});
turnText.anchor.set(0.5, 0);
turnText.alpha = 0; // Hidden initially
LK.gui.top.addChild(turnText);
var phaseText = new Text2('Select Unit', {
size: 40,
fill: 0xFFFFFF
});
phaseText.anchor.set(0.5, 0);
phaseText.y = 80;
phaseText.alpha = 0; // Hidden initially
LK.gui.top.addChild(phaseText);
// Unit counters
var playerCounter = new Text2('10/10', {
size: 35,
fill: 0x5555FF
});
playerCounter.anchor.set(0, 1);
playerCounter.x = 20;
playerCounter.y = -120;
playerCounter.alpha = 0; // Hidden initially
LK.gui.bottomLeft.addChild(playerCounter);
var enemyCounter = new Text2('10/10', {
size: 35,
fill: 0xFF5555
});
enemyCounter.anchor.set(1, 1);
enemyCounter.x = -20;
enemyCounter.y = -120;
enemyCounter.alpha = 0; // Hidden initially
LK.gui.bottomRight.addChild(enemyCounter);
var endTurnBtn = new Text2('End Turn', {
size: 50,
fill: 0x00FF00
});
endTurnBtn.anchor.set(0.5, 1);
endTurnBtn.alpha = 0; // Hidden initially
endTurnBtn.interactive = false; // Disabled initially
LK.gui.bottom.addChild(endTurnBtn);
// Unit info panel
var unitInfoPanel = new Container();
unitInfoPanel.x = -20;
unitInfoPanel.y = 120;
LK.gui.topRight.addChild(unitInfoPanel);
var unitInfoBg = LK.getAsset('terrain', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.5,
scaleY: 2.2
});
unitInfoBg.tint = 0x444444;
unitInfoBg.alpha = 0.8;
unitInfoPanel.addChild(unitInfoBg);
var unitInfoImage = null;
var unitInfoName = new Text2('', {
size: 32,
fill: 0xFFD700
});
unitInfoName.anchor.set(1, 0);
unitInfoName.x = -20;
unitInfoName.y = -90;
unitInfoPanel.addChild(unitInfoName);
var unitInfoClass = new Text2('', {
size: 30,
fill: 0xFFFFFF
});
unitInfoClass.anchor.set(1, 0);
unitInfoClass.x = -20;
unitInfoClass.y = -60;
unitInfoPanel.addChild(unitInfoClass);
var unitInfoHP = new Text2('', {
size: 25,
fill: 0x00FF00
});
unitInfoHP.anchor.set(1, 0);
unitInfoHP.x = -20;
unitInfoHP.y = -25;
unitInfoPanel.addChild(unitInfoHP);
var unitInfoDamage = new Text2('', {
size: 25,
fill: 0xFF4444
});
unitInfoDamage.anchor.set(1, 0);
unitInfoDamage.x = -20;
unitInfoDamage.y = 10;
unitInfoPanel.addChild(unitInfoDamage);
unitInfoPanel.alpha = 0;
unitInfoPanel.visible = false; // Hide completely initially
function showUnitInfo(unit) {
if (unitInfoImage) {
unitInfoPanel.removeChild(unitInfoImage);
unitInfoImage = null;
}
var assetName = 'sword';
if (unit.type === 'commander') {
assetName = 'commander';
} else if (unit.type === 'rider') {
assetName = 'rider';
} else if (unit.type === 'dragonRider') {
assetName = 'dragonRider';
} else if (unit.type === 'healer') {
assetName = 'healer';
} else if (unit.type === 'armored') {
assetName = 'armored';
} else if (unit.type === 'manakete') {
assetName = 'manakete';
} else if (unit.weaponType === 'sword') {
assetName = 'sword' + unit.swordVariant; // Use sword1 or sword2
} else if (unit.weaponType === 'bow') {
assetName = 'bow';
} else if (unit.weaponType === 'magic') {
assetName = 'magic';
}
unitInfoImage = LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
var className = unit.type === 'commander' ? 'Commander' : unit.type === 'rider' ? 'Rider' : unit.type === 'dragonRider' ? 'Dragon Rider' : unit.type === 'healer' ? 'Healer' : unit.type === 'armored' ? 'Armored' : unit.type === 'manakete' ? 'Manakete' : unit.weaponType === 'sword' ? 'Sword' : unit.weaponType === 'bow' ? 'Bow' : unit.weaponType === 'magic' ? 'Magic' : 'Hero';
// Position image further left based on class name length to prevent overlap
var imageOffset = -220 - className.length * 8;
unitInfoImage.x = imageOffset;
unitInfoImage.y = -20;
// Remove tint from unit info image
unitInfoImage.tint = 0xFFFFFF;
unitInfoPanel.addChild(unitInfoImage);
unitInfoName.setText(unit.name);
unitInfoClass.setText(className);
unitInfoHP.setText('HP: ' + unit.hp + '/' + unit.maxHp);
if (unit.weaponType === 'healer') {
unitInfoDamage.setText('HEAL: ' + unit.attack);
} else {
unitInfoDamage.setText('ATK: ' + unit.attack);
}
unitInfoPanel.alpha = 1;
}
function hideUnitInfo() {
unitInfoPanel.alpha = 0;
}
// botones dinámicos de acción
var attackBtn = null;
var ignoreBtn = null;
// Action button variables
var danceBtn = null;
function showActionButtons() {
// Hide end turn button when showing action buttons
endTurnBtn.alpha = 0;
endTurnBtn.interactive = false;
if (selectedUnit.weaponType === 'healer') {
// Check if there are valid heal targets
var hasHealTargets = false;
for (var ty = 0; ty < GRID_SIZE; ty++) {
for (var tx = 0; tx < GRID_SIZE; tx++) {
if (selectedUnit.canHeal(tx, ty) && grid[ty][tx].unit && grid[ty][tx].unit.team === selectedUnit.team && grid[ty][tx].unit.hp < grid[ty][tx].unit.maxHp) {
hasHealTargets = true;
break;
}
}
if (hasHealTargets) {
break;
}
}
if (hasHealTargets) {
attackBtn = new Text2('Heal', {
size: 50,
fill: 0x00FF00
});
attackBtn.anchor.set(0.5, 1);
attackBtn.y = -60;
LK.gui.bottom.addChild(attackBtn);
attackBtn.interactive = true;
attackBtn.down = function () {
turnPhase = 'heal';
phaseText.setText('Choose ally to heal');
showHealOptions(selectedUnit);
removeActionButtons();
};
}
} else {
attackBtn = new Text2('Attack', {
size: 50,
fill: 0xFF0000
});
attackBtn.anchor.set(0.5, 1);
attackBtn.y = -60;
LK.gui.bottom.addChild(attackBtn);
attackBtn.interactive = true;
attackBtn.down = function () {
turnPhase = 'attack';
phaseText.setText('Choose enemy to attack');
showAttackOptions(selectedUnit);
removeActionButtons();
};
}
ignoreBtn = new Text2('Ignore', {
size: 50,
fill: 0xFFFF00
});
ignoreBtn.anchor.set(0.5, 1);
ignoreBtn.y = -10;
LK.gui.bottom.addChild(ignoreBtn);
ignoreBtn.interactive = true;
ignoreBtn.down = function () {
selectedUnit.endTurn();
turnPhase = 'select';
phaseText.setText('Select Unit');
selectedUnit = null;
originalUnitPosition = null;
clearHighlights();
removeActionButtons();
hideUnitInfo();
// Check if all player units have moved
checkAutoEndTurn();
};
}
function removeActionButtons() {
// Show end turn button again
if (currentTurn === 'player') {
endTurnBtn.alpha = 1;
endTurnBtn.interactive = true;
}
if (attackBtn) {
LK.gui.bottom.removeChild(attackBtn);
attackBtn = null;
}
if (ignoreBtn) {
LK.gui.bottom.removeChild(ignoreBtn);
ignoreBtn = null;
}
}
// Initialize grid
function initializeGrid() {
var startX = (2048 - GRID_SIZE * TILE_SIZE) / 2;
var startY = (2732 - GRID_SIZE * TILE_SIZE) / 2;
for (var y = 0; y < GRID_SIZE; y++) {
grid[y] = [];
for (var x = 0; x < GRID_SIZE; x++) {
var tile = new GridTile(x, y);
tile.x = startX + x * TILE_SIZE + TILE_SIZE / 2;
tile.y = startY + y * TILE_SIZE + TILE_SIZE / 2;
grid[y][x] = tile;
game.addChild(tile);
}
}
// Generate terrain
generateTerrain();
// Add border frame around the map
var borderThickness = 20;
var mapStartX = (2048 - GRID_SIZE * TILE_SIZE) / 2;
var mapStartY = (2732 - GRID_SIZE * TILE_SIZE) / 2;
var mapWidth = GRID_SIZE * TILE_SIZE;
var mapHeight = GRID_SIZE * TILE_SIZE;
// Create border graphics using terrain assets
// Top border
var topBorder = LK.getAsset('terrain', {
anchorX: 0,
anchorY: 0,
scaleX: (mapWidth + borderThickness * 2) / 132,
scaleY: borderThickness / 132
});
topBorder.x = mapStartX - borderThickness;
topBorder.y = mapStartY - borderThickness;
topBorder.tint = 0x8B4513;
game.addChild(topBorder);
// Bottom border
var bottomBorder = LK.getAsset('terrain', {
anchorX: 0,
anchorY: 0,
scaleX: (mapWidth + borderThickness * 2) / 132,
scaleY: borderThickness / 132
});
bottomBorder.x = mapStartX - borderThickness;
bottomBorder.y = mapStartY + mapHeight;
bottomBorder.tint = 0x8B4513;
game.addChild(bottomBorder);
// Left border
var leftBorder = LK.getAsset('terrain', {
anchorX: 0,
anchorY: 0,
scaleX: borderThickness / 132,
scaleY: mapHeight / 132
});
leftBorder.x = mapStartX - borderThickness;
leftBorder.y = mapStartY;
leftBorder.tint = 0x8B4513;
game.addChild(leftBorder);
// Right border
var rightBorder = LK.getAsset('terrain', {
anchorX: 0,
anchorY: 0,
scaleX: borderThickness / 132,
scaleY: mapHeight / 132
});
rightBorder.x = mapStartX + mapWidth;
rightBorder.y = mapStartY;
rightBorder.tint = 0x8B4513;
game.addChild(rightBorder);
}
// Initialize units
function initializeUnits() {
// Player units (bottom-right corner)
var playerCommander = new Unit('commander', 'player', 'sword', 1, 'Ardan');
placeUnit(playerCommander, 12, 14);
playerUnits.push(playerCommander);
var playerSword1 = new Unit('hero', 'player', 'sword', 1, 'Kael');
placeUnit(playerSword1, 13, 13);
playerUnits.push(playerSword1);
var playerSword2 = new Unit('hero', 'player', 'sword', 2, 'Hikari');
placeUnit(playerSword2, 9, 14);
playerUnits.push(playerSword2);
var playerArmored = new Unit('armored', 'player', 'armored', 1, 'Brutus');
placeUnit(playerArmored, 14, 13);
playerUnits.push(playerArmored);
var playerMagic = new Unit('hero', 'player', 'magic', 1, 'Mizuki');
placeUnit(playerMagic, 9, 13);
playerUnits.push(playerMagic);
var playerDragonRider = new Unit('dragonRider', 'player', 'dragonRider', 1, 'Lyra');
placeUnit(playerDragonRider, 8, 12);
playerUnits.push(playerDragonRider);
var playerRider = new Unit('rider', 'player', 'rider', 1, 'Darius');
placeUnit(playerRider, 14, 12);
playerUnits.push(playerRider);
var playerHealer = new Unit('healer', 'player', 'healer', 1, 'Eirynn');
placeUnit(playerHealer, 10, 14);
playerUnits.push(playerHealer);
var playerBow = new Unit('hero', 'player', 'bow', 1, 'Elian');
placeUnit(playerBow, 11, 13);
playerUnits.push(playerBow);
var playerManakete = new Unit('manakete', 'player', 'manakete', 1, 'Irios');
placeUnit(playerManakete, 11, 12);
playerUnits.push(playerManakete);
// Enemy units (top-left corner)
var enemyCommander = new Unit('commander', 'enemy', 'sword', 1, 'Ardan');
placeUnit(enemyCommander, 2, 0);
enemyUnits.push(enemyCommander);
var enemySword1 = new Unit('hero', 'enemy', 'sword', 1, 'Kael');
placeUnit(enemySword1, 1, 1);
enemyUnits.push(enemySword1);
var enemySword2 = new Unit('hero', 'enemy', 'sword', 2, 'Hikari');
placeUnit(enemySword2, 5, 0);
enemyUnits.push(enemySword2);
var enemyArmored = new Unit('armored', 'enemy', 'armored', 1, 'Brutus');
placeUnit(enemyArmored, 0, 1);
enemyUnits.push(enemyArmored);
var enemyMagic = new Unit('hero', 'enemy', 'magic', 1, 'Mizuki');
placeUnit(enemyMagic, 5, 1);
enemyUnits.push(enemyMagic);
var enemyDragonRider = new Unit('dragonRider', 'enemy', 'dragonRider', 1, 'Lyra');
placeUnit(enemyDragonRider, 6, 2);
enemyUnits.push(enemyDragonRider);
var enemyRider = new Unit('rider', 'enemy', 'rider', 1, 'Darius');
placeUnit(enemyRider, 0, 2);
enemyUnits.push(enemyRider);
var enemyHealer = new Unit('healer', 'enemy', 'healer', 1, 'Eirynn');
placeUnit(enemyHealer, 4, 0);
enemyUnits.push(enemyHealer);
var enemyBow = new Unit('hero', 'enemy', 'bow', 1, 'Elian');
placeUnit(enemyBow, 3, 1);
enemyUnits.push(enemyBow);
var enemyManakete = new Unit('manakete', 'enemy', 'manakete', 1, 'Irios');
placeUnit(enemyManakete, 3, 2);
enemyUnits.push(enemyManakete);
}
function generateTerrain() {
// First, add normal terrain to all tiles
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
grid[y][x].addTerrain('normal');
}
}
// Add water river in middle of map (rows 6, 7, 8 - 3 tiles wide)
for (var y = 6; y <= 8; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
grid[y][x].addTerrain('water');
}
}
// Replace water tiles with bridge tiles at specified positions
// Bridge 1: columns 1-2, rows 6-8 (2 horizontal x 3 vertical)
for (var y = 6; y <= 8; y++) {
for (var x = 1; x <= 2; x++) {
grid[y][x].addTerrain('bridge');
}
}
// Bridge 2: columns 12-13, rows 6-8 (2 horizontal x 3 vertical)
for (var y = 6; y <= 8; y++) {
for (var x = 12; x <= 13; x++) {
grid[y][x].addTerrain('bridge');
}
}
// Add some trees scattered around (avoiding bridge areas) - trees go on top of normal terrain
var treePositions = [{
x: 4,
y: 4
}, {
x: 6,
y: 5
}, {
x: 9,
y: 4
}, {
x: 11,
y: 5
}, {
x: 3,
y: 10
}, {
x: 5,
y: 11
}, {
x: 8,
y: 10
}, {
x: 10,
y: 11
}, {
x: 7,
y: 3
}, {
x: 14,
y: 10
}, {
x: 0,
y: 12
}, {
x: 14,
y: 4
}];
// Add trees after all base terrain is placed to ensure proper layering
for (var i = 0; i < treePositions.length; i++) {
var pos = treePositions[i];
// First ensure there's normal terrain, then add tree on top
if (grid[pos.y][pos.x].terrainType !== 'normal') {
grid[pos.y][pos.x].addTerrain('normal');
}
// Add tree terrain graphics on top of normal terrain
var treeGraphics = grid[pos.y][pos.x].attachAsset('tree', {
anchorX: 0.5,
anchorY: 0.5
});
treeGraphics.alpha = 1.0;
// Set terrain type but keep the normal terrain graphics underneath
grid[pos.y][pos.x].terrainType = 'tree';
}
}
function placeUnit(unit, x, y) {
unit.gridX = x;
unit.gridY = y;
unit.x = grid[y][x].x;
unit.y = grid[y][x].y;
grid[y][x].unit = unit;
game.addChild(unit);
}
function clearHighlights() {
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
grid[y][x].hideHighlights();
}
}
}
function showMovementOptions(unit) {
// Use pathfinding to check reachable tiles considering terrain
var reachableTiles = getReachableTiles(unit);
for (var i = 0; i < reachableTiles.length; i++) {
var tile = reachableTiles[i];
grid[tile.y][tile.x].showHighlight();
}
}
function showAttackOptions(unit) {
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (unit.canAttack(x, y) && grid[y][x].unit && grid[y][x].unit.team !== unit.team && grid[y][x].unit.hp > 0) {
grid[y][x].showAttackRange();
}
}
}
}
function showHealOptions(unit) {
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (unit.canHeal(x, y) && grid[y][x].unit && grid[y][x].unit.team === unit.team && grid[y][x].unit.hp < grid[y][x].unit.maxHp) {
grid[y][x].showAttackRange();
}
}
}
}
function showAttackRangePreview(unit) {
var reachableTiles = getReachableTiles(unit);
var attackRange = unit.getRange();
var reachablePositions = {};
var attackOutlineTiles = {};
// Mark all reachable positions (blue movement range)
for (var i = 0; i < reachableTiles.length; i++) {
var tile = reachableTiles[i];
reachablePositions[tile.x + ',' + tile.y] = true;
// Show blue movement range
grid[tile.y][tile.x].showHighlight();
}
// Mark current position as reachable too
reachablePositions[unit.gridX + ',' + unit.gridY] = true;
// Calculate attack outline around the perimeter of movement range
var allMovementPositions = reachableTiles.slice();
allMovementPositions.push({
x: unit.gridX,
y: unit.gridY
});
// For each movement position, check outward for attack range
for (var i = 0; i < allMovementPositions.length; i++) {
var pos = allMovementPositions[i];
// Check all possible attack positions from this movement position
for (var dy = -attackRange; dy <= attackRange; dy++) {
for (var dx = -attackRange; dx <= attackRange; dx++) {
var distance = Math.abs(dx) + Math.abs(dy);
// Check if this distance is valid for the unit's attack pattern
var isValidAttackDistance = false;
if (unit.weaponType === 'bow') {
isValidAttackDistance = distance === 2; // Bow attacks only at exactly range 2, not range 1
} else if (unit.weaponType === 'manakete') {
isValidAttackDistance = distance >= 1 && distance <= 2; // Manakete attacks at range 1-2 like magic
} else if (unit.weaponType === 'healer') {
isValidAttackDistance = distance === 1; // Healer heals at range 1
} else {
isValidAttackDistance = distance >= 1 && distance <= attackRange; // Regular units attack from 1 to max range
}
if (isValidAttackDistance) {
var ax = pos.x + dx;
var ay = pos.y + dy;
if (ax >= 0 && ax < GRID_SIZE && ay >= 0 && ay < GRID_SIZE) {
var key = ax + ',' + ay;
// Only show attack outline if it's NOT a reachable movement position
if (!reachablePositions[key]) {
attackOutlineTiles[key] = {
x: ax,
y: ay,
isHeal: unit.weaponType === 'healer'
};
}
}
}
}
}
}
// Show attack outline tiles
for (var key in attackOutlineTiles) {
var pos = attackOutlineTiles[key];
if (pos.isHeal) {
grid[pos.y][pos.x].showHealRangePreview();
} else {
grid[pos.y][pos.x].showAttackRangePreview();
}
}
}
function getReachableTiles(unit) {
if (!unit) {
return [];
}
var reachableTiles = [];
var maxRange = unit.getMovementRange();
var visited = {};
var queue = [{
x: unit.gridX,
y: unit.gridY,
cost: 0
}];
visited[unit.gridX + ',' + unit.gridY] = true;
while (queue.length > 0) {
var current = queue.shift();
// Check all 4 directions
var directions = [{
x: 0,
y: 1
}, {
x: 0,
y: -1
}, {
x: 1,
y: 0
}, {
x: -1,
y: 0
}];
for (var d = 0; d < directions.length; d++) {
var dir = directions[d];
var newX = current.x + dir.x;
var newY = current.y + dir.y;
var key = newX + ',' + newY;
if (newX < 0 || newX >= GRID_SIZE || newY < 0 || newY >= GRID_SIZE) {
continue;
}
if (visited[key]) {
continue;
}
// Allow passing through same-team units during pathfinding, but not ending on them
var tileUnit = grid[newY][newX].unit;
if (tileUnit && tileUnit.team === unit.team) {
// Can pass through same-team units but cannot end movement here
// Continue pathfinding but mark this as a pass-through tile
} else if (tileUnit && tileUnit.team !== unit.team) {
// Cannot pass through enemy units
continue;
}
var moveCost = getMoveCost(unit, newX, newY);
if (moveCost === -1) {
continue;
} // Cannot move to this tile
var newCost = current.cost + moveCost;
if (newCost <= maxRange) {
visited[key] = true;
queue.push({
x: newX,
y: newY,
cost: newCost
});
if (newX !== unit.gridX || newY !== unit.gridY) {
reachableTiles.push({
x: newX,
y: newY,
cost: newCost
});
}
}
}
}
return reachableTiles;
}
function getMoveCost(unit, x, y) {
var tile = grid[y][x];
// Water - dragon riders and manaketes can cross
if (tile.terrainType === 'water' && unit.weaponType !== 'dragonRider' && unit.weaponType !== 'manakete') {
return -1; // Cannot move
}
// Tree - only dragon riders and manaketes can cross, all others cannot
if (tile.terrainType === 'tree') {
if (unit.weaponType === 'dragonRider' || unit.weaponType === 'manakete') {
return 1; // Normal cost for dragonRider and manakete
}
return -1; // Cannot move through trees for all other units
}
// Normal terrain and bridges
return 1;
}
function showEnemyMovementOptions(unit) {
var reachableTiles = getReachableTiles(unit);
for (var i = 0; i < reachableTiles.length; i++) {
var tile = reachableTiles[i];
grid[tile.y][tile.x].showHighlight();
}
}
function moveUnit(unit, targetX, targetY) {
// Check if target tile is occupied by another unit
if (grid[targetY][targetX].unit) {
// Find nearest valid empty tile
var validTile = findNearestValidTile(unit, targetX, targetY);
targetX = validTile.x;
targetY = validTile.y;
}
grid[unit.gridY][unit.gridX].unit = null;
unit.gridX = targetX;
unit.gridY = targetY;
grid[targetY][targetX].unit = unit;
tween(unit, {
x: grid[targetY][targetX].x,
y: grid[targetY][targetX].y
}, {
duration: 300
});
clearHighlights();
}
function isValidMove(unit, x, y) {
if (x < 0 || x >= GRID_SIZE || y < 0 || y >= GRID_SIZE) {
return false;
}
// Cannot end movement on a tile with any unit (same team or enemy)
if (grid[y][x].unit) {
return false;
}
var distance = Math.abs(unit.gridX - x) + Math.abs(unit.gridY - y);
if (distance > unit.getMovementRange()) {
return false;
}
// Check terrain restrictions
var targetTile = grid[y][x];
if (targetTile.terrainType === 'water') {
// Only dragon riders and manaketes can cross water
if (unit.weaponType !== 'dragonRider' && unit.weaponType !== 'manakete') {
return false;
}
}
return true;
}
function getTileFromPosition(screenX, screenY) {
var startX = (2048 - GRID_SIZE * TILE_SIZE) / 2;
var startY = (2732 - GRID_SIZE * TILE_SIZE) / 2;
var gridX = Math.floor((screenX - startX) / TILE_SIZE);
var gridY = Math.floor((screenY - startY) / TILE_SIZE);
if (gridX >= 0 && gridX < GRID_SIZE && gridY >= 0 && gridY < GRID_SIZE) {
return {
x: gridX,
y: gridY
};
}
return null;
}
// Game click handler
game.down = function (x, y, obj) {
// Only handle game clicks when in playing state
if (gameState !== 'playing') {
return;
}
var tilePos = getTileFromPosition(x, y);
if (!tilePos) {
return;
}
var clickedTile = grid[tilePos.y][tilePos.x];
if (turnPhase === 'select') {
// Handle enemy unit selection for viewing movement
if (clickedTile.unit && clickedTile.unit.team !== currentTurn) {
if (selectedEnemyUnit === clickedTile.unit) {
// Clicking same enemy again - hide movement
selectedEnemyUnit = null;
clearHighlights();
hideUnitInfo();
} else {
// Clicking different enemy - show movement and attack range
selectedEnemyUnit = clickedTile.unit;
clearHighlights();
showEnemyMovementOptions(selectedEnemyUnit);
showAttackRangePreview(selectedEnemyUnit);
showUnitInfo(selectedEnemyUnit);
}
return;
}
// Clear enemy selection when clicking elsewhere
if (selectedEnemyUnit) {
selectedEnemyUnit = null;
clearHighlights();
}
// Handle player unit selection
if (clickedTile.unit && clickedTile.unit.team === currentTurn && !clickedTile.unit.hasMoved) {
selectedUnit = clickedTile.unit;
originalUnitPosition = {
x: selectedUnit.gridX,
y: selectedUnit.gridY
};
// Show unit info
showUnitInfo(selectedUnit);
// Check for attack targets from current position on first click
var hasAttackTargets = false;
for (var ty = 0; ty < GRID_SIZE; ty++) {
for (var tx = 0; tx < GRID_SIZE; tx++) {
if (selectedUnit.canAttack(tx, ty) && grid[ty][tx].unit && grid[ty][tx].unit.team !== selectedUnit.team && grid[ty][tx].unit.hp > 0) {
hasAttackTargets = true;
break;
}
}
if (hasAttackTargets) {
break;
}
}
if (hasAttackTargets) {
clearHighlights();
// Create move button
var moveBtn = new Text2('Move', {
size: 40,
fill: 0x00FF00
});
moveBtn.anchor.set(0.5, 1);
moveBtn.y = -120;
LK.gui.bottom.addChild(moveBtn);
moveBtn.interactive = true;
moveBtn.down = function () {
// Remove action buttons and continue with move
LK.gui.bottom.removeChild(moveBtn);
if (attackBtn) {
LK.gui.bottom.removeChild(attackBtn);
attackBtn = null;
}
if (ignoreBtn) {
LK.gui.bottom.removeChild(ignoreBtn);
ignoreBtn = null;
}
turnPhase = 'move';
phaseText.setText('Choose destination');
showMovementOptions(selectedUnit);
};
// Show attack and ignore buttons
turnPhase = 'chooseAction';
phaseText.setText('Attack, Ignore or Move');
showActionButtons();
} else {
turnPhase = 'move';
phaseText.setText('Choose destination');
showMovementOptions(selectedUnit);
showAttackRangePreview(selectedUnit);
}
}
} else if (turnPhase === 'move') {
// Cancel move selection by clicking anywhere
if (!isValidMove(selectedUnit, tilePos.x, tilePos.y)) {
selectedUnit = null;
originalUnitPosition = null;
turnPhase = 'select';
phaseText.setText('Select Unit');
clearHighlights();
hideUnitInfo();
return;
}
if (isValidMove(selectedUnit, tilePos.x, tilePos.y)) {
moveUnit(selectedUnit, tilePos.x, tilePos.y);
LK.setTimeout(function () {
if (!selectedUnit) {
return;
}
if (selectedUnit.weaponType === 'healer') {
var hasHealTargets = false;
for (var ty = 0; ty < GRID_SIZE; ty++) {
for (var tx = 0; tx < GRID_SIZE; tx++) {
if (selectedUnit.canHeal(tx, ty) && grid[ty][tx].unit && grid[ty][tx].unit.team === selectedUnit.team && grid[ty][tx].unit.hp < grid[ty][tx].unit.maxHp) {
hasHealTargets = true;
break;
}
}
if (hasHealTargets) {
break;
}
}
if (hasHealTargets) {
turnPhase = 'chooseAction';
phaseText.setText('Heal or Ignore');
showActionButtons();
} else {
selectedUnit.endTurn();
turnPhase = 'select';
phaseText.setText('Select Unit');
selectedUnit = null;
originalUnitPosition = null;
clearHighlights();
checkAutoEndTurn();
}
} else {
var hasAttackTargets = false;
for (var ty = 0; ty < GRID_SIZE; ty++) {
for (var tx = 0; tx < GRID_SIZE; tx++) {
if (selectedUnit.canAttack(tx, ty) && grid[ty][tx].unit && grid[ty][tx].unit.team !== selectedUnit.team && grid[ty][tx].unit.hp > 0) {
hasAttackTargets = true;
break;
}
}
if (hasAttackTargets) {
break;
}
}
if (hasAttackTargets) {
turnPhase = 'chooseAction';
phaseText.setText('Attack or Ignore');
showActionButtons();
} else {
selectedUnit.endTurn();
turnPhase = 'select';
phaseText.setText('Select Unit');
selectedUnit = null;
originalUnitPosition = null;
clearHighlights();
checkAutoEndTurn();
}
}
}, 350);
}
} else if (turnPhase === 'heal') {
if (clickedTile.unit && clickedTile.unit.team === selectedUnit.team && selectedUnit.canHeal(tilePos.x, tilePos.y)) {
var target = clickedTile.unit;
selectedUnit.heal(target);
selectedUnit.endTurn();
turnPhase = 'select';
phaseText.setText('Select Unit');
selectedUnit = null;
originalUnitPosition = null;
clearHighlights();
checkAutoEndTurn();
} else {
// Clicked elsewhere during heal - return to original position
if (originalUnitPosition && selectedUnit) {
moveUnit(selectedUnit, originalUnitPosition.x, originalUnitPosition.y);
LK.setTimeout(function () {
if (selectedUnit) {
turnPhase = 'move';
phaseText.setText('Choose destination');
showMovementOptions(selectedUnit);
}
}, 350);
}
}
} else if (turnPhase === 'attack') {
if (clickedTile.unit && clickedTile.unit.team !== selectedUnit.team && selectedUnit.canAttack(tilePos.x, tilePos.y)) {
var target = clickedTile.unit;
// Add attack shake animation
if (!selectedUnit) {
return;
}
var originalX = selectedUnit.x;
tween(selectedUnit, {
x: originalX + 10
}, {
duration: 100,
onFinish: function onFinish() {
if (!selectedUnit) {
return;
}
tween(selectedUnit, {
x: originalX - 10
}, {
duration: 100,
onFinish: function onFinish() {
if (!selectedUnit) {
return;
}
tween(selectedUnit, {
x: originalX
}, {
duration: 100
});
}
});
}
});
LK.getSound('Impacts').play();
var isDead = target.takeDamage(selectedUnit.attack, selectedUnit.weaponType);
// Check for counterattack if target survived
if (!isDead) {
var attackerDied = target.counterAttack(selectedUnit);
if (attackerDied) {
grid[selectedUnit.gridY][selectedUnit.gridX].unit = null;
LK.getSound('Vanish').play();
tween(selectedUnit, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
selectedUnit.destroy();
}
});
if (selectedUnit.team === 'player') {
var index = playerUnits.indexOf(selectedUnit);
if (index > -1) {
playerUnits.splice(index, 1);
}
updateUnitCounters();
if (selectedUnit.type === 'commander') {
LK.playMusic('YouLose');
LK.showGameOver();
return;
}
} else {
var index = enemyUnits.indexOf(selectedUnit);
if (index > -1) {
enemyUnits.splice(index, 1);
}
updateUnitCounters();
if (selectedUnit.type === 'commander') {
LK.playMusic('Victory');
LK.showYouWin();
return;
}
}
}
}
if (isDead) {
grid[target.gridY][target.gridX].unit = null;
LK.getSound('Vanish').play();
tween(target, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
target.destroy();
}
});
if (target.team === 'player') {
var index = playerUnits.indexOf(target);
if (index > -1) {
playerUnits.splice(index, 1);
}
updateUnitCounters();
// Check if player commander was defeated
if (target.type === 'commander') {
LK.playMusic('YouLose');
LK.showGameOver();
return;
}
} else {
var index = enemyUnits.indexOf(target);
if (index > -1) {
enemyUnits.splice(index, 1);
}
updateUnitCounters();
// Check if enemy commander was defeated
if (target.type === 'commander') {
LK.playMusic('Victory');
LK.showYouWin();
return;
}
}
}
selectedUnit.endTurn();
turnPhase = 'select';
phaseText.setText('Select Unit');
selectedUnit = null;
originalUnitPosition = null;
clearHighlights();
checkAutoEndTurn();
} else {
// Clicked elsewhere during attack - return to original position
if (originalUnitPosition && selectedUnit) {
moveUnit(selectedUnit, originalUnitPosition.x, originalUnitPosition.y);
LK.setTimeout(function () {
if (selectedUnit) {
turnPhase = 'move';
phaseText.setText('Choose destination');
showMovementOptions(selectedUnit);
}
}, 350);
}
}
}
};
// Enemy AI functions
function getValidMovesForUnit(unit) {
return getReachableTiles(unit);
}
function findNearestValidTile(unit, targetX, targetY) {
// If target tile is valid and empty, use it
if (targetX >= 0 && targetX < GRID_SIZE && targetY >= 0 && targetY < GRID_SIZE && !grid[targetY][targetX].unit && getMoveCost(unit, targetX, targetY) !== -1) {
return {
x: targetX,
y: targetY
};
}
// Search in expanding circles for nearest valid empty tile
for (var radius = 1; radius <= GRID_SIZE; radius++) {
for (var dy = -radius; dy <= radius; dy++) {
for (var dx = -radius; dx <= radius; dx++) {
// Only check tiles at current radius (perimeter of circle)
if (Math.abs(dx) !== radius && Math.abs(dy) !== radius) continue;
var newX = targetX + dx;
var newY = targetY + dy;
if (newX >= 0 && newX < GRID_SIZE && newY >= 0 && newY < GRID_SIZE && !grid[newY][newX].unit && getMoveCost(unit, newX, newY) !== -1) {
return {
x: newX,
y: newY
};
}
}
}
}
// If no valid tile found, return current position
return {
x: unit.gridX,
y: unit.gridY
};
}
function updateUnitCounters() {
var alivePlayerUnits = 0;
var aliveEnemyUnits = 0;
for (var i = 0; i < playerUnits.length; i++) {
if (playerUnits[i].hp > 0) {
alivePlayerUnits++;
}
}
for (var i = 0; i < enemyUnits.length; i++) {
if (enemyUnits[i].hp > 0) {
aliveEnemyUnits++;
}
}
playerCounter.setText(alivePlayerUnits + '/10');
enemyCounter.setText(aliveEnemyUnits + '/10');
}
function findAttackTargets(unit) {
var targets = [];
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (unit.canAttack(x, y) && grid[y][x].unit && grid[y][x].unit.team !== unit.team && grid[y][x].unit.hp > 0) {
targets.push({
unit: grid[y][x].unit,
x: x,
y: y
});
}
}
}
return targets;
}
function checkAutoEndTurn() {
if (currentTurn !== 'player') {
return;
}
var allMoved = true;
for (var i = 0; i < playerUnits.length; i++) {
if (!playerUnits[i].hasMoved && playerUnits[i].hp > 0) {
allMoved = false;
break;
}
}
if (allMoved) {
// Auto end turn
LK.setTimeout(function () {
for (var i = 0; i < playerUnits.length; i++) {
playerUnits[i].resetTurn();
}
currentTurn = 'enemy';
turnText.setText('Enemy Turn');
phaseText.setText('Enemy AI thinking...');
LK.setTimeout(executeEnemyAI, 1000);
}, 500);
}
}
function executeEnemyAI() {
if (currentTurn !== 'enemy') {
return;
}
// Hide end turn button during enemy turn
endTurnBtn.alpha = 0;
endTurnBtn.interactive = false;
var currentEnemyIndex = 0;
function processNextEnemy() {
if (currentEnemyIndex >= enemyUnits.length) {
// All enemies processed, end turn
for (var i = 0; i < enemyUnits.length; i++) {
enemyUnits[i].resetTurn();
}
currentTurn = 'player';
turnText.setText('Player Turn');
selectedUnit = null;
selectedEnemyUnit = null;
turnPhase = 'select';
phaseText.setText('Select Unit');
clearHighlights();
removeActionButtons();
// Show end turn button again for player turn
endTurnBtn.alpha = 1;
endTurnBtn.interactive = true;
return;
}
var enemy = enemyUnits[currentEnemyIndex];
currentEnemyIndex++;
if (enemy.hasMoved || enemy.hp <= 0) {
processNextEnemy();
return;
}
// Check if enemy can attack from current position
var currentTargets = findAttackTargets(enemy);
if (currentTargets.length > 0) {
// Attack the first available target
var target = currentTargets[0].unit;
// Add attack shake animation
var originalX = enemy.x;
tween(enemy, {
x: originalX + 10
}, {
duration: 100,
onFinish: function onFinish() {
tween(enemy, {
x: originalX - 10
}, {
duration: 100,
onFinish: function onFinish() {
tween(enemy, {
x: originalX
}, {
duration: 100
});
}
});
}
});
LK.getSound('Impacts').play();
var isDead = target.takeDamage(enemy.attack, enemy.weaponType);
// Check for counterattack if target survived
if (!isDead) {
var attackerDied = target.counterAttack(enemy);
if (attackerDied) {
grid[enemy.gridY][enemy.gridX].unit = null;
LK.getSound('Vanish').play();
tween(enemy, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
enemy.destroy();
}
});
var index = enemyUnits.indexOf(enemy);
if (index > -1) {
enemyUnits.splice(index, 1);
}
updateUnitCounters();
if (enemy.type === 'commander') {
LK.playMusic('Victory');
LK.showYouWin();
return;
}
enemy.endTurn();
LK.setTimeout(processNextEnemy, 500);
return;
}
}
if (isDead) {
grid[target.gridY][target.gridX].unit = null;
LK.getSound('Vanish').play();
tween(target, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
target.destroy();
}
});
if (target.team === 'player') {
var index = playerUnits.indexOf(target);
if (index > -1) {
playerUnits.splice(index, 1);
}
updateUnitCounters();
// Check if player commander was defeated
if (target.type === 'commander') {
LK.playMusic('YouLose');
LK.showGameOver();
return;
}
}
}
enemy.endTurn();
LK.setTimeout(processNextEnemy, 500);
return;
}
// Try to move closer to player units
var validMoves = getValidMovesForUnit(enemy);
if (validMoves.length > 0) {
// Find the move that gets closest to any player unit
var bestMove = null;
var bestScore = Infinity;
for (var m = 0; m < validMoves.length; m++) {
var move = validMoves[m];
var minDistanceToPlayer = Infinity;
for (var p = 0; p < playerUnits.length; p++) {
if (playerUnits[p].hp <= 0) {
continue;
}
var dist = Math.abs(move.x - playerUnits[p].gridX) + Math.abs(move.y - playerUnits[p].gridY);
minDistanceToPlayer = Math.min(minDistanceToPlayer, dist);
}
if (minDistanceToPlayer < bestScore) {
bestScore = minDistanceToPlayer;
bestMove = move;
}
}
if (bestMove) {
// Find nearest valid tile if bestMove position is occupied
var finalMove = findNearestValidTile(enemy, bestMove.x, bestMove.y);
moveUnit(enemy, finalMove.x, finalMove.y);
LK.setTimeout(function () {
// Check if can attack after moving
var targetsAfterMove = findAttackTargets(enemy);
if (targetsAfterMove.length > 0) {
var target = targetsAfterMove[0].unit;
// Add attack shake animation
var originalX = enemy.x;
tween(enemy, {
x: originalX + 10
}, {
duration: 100,
onFinish: function onFinish() {
tween(enemy, {
x: originalX - 10
}, {
duration: 100,
onFinish: function onFinish() {
tween(enemy, {
x: originalX
}, {
duration: 100
});
}
});
}
});
LK.getSound('Impacts').play();
var isDead = target.takeDamage(enemy.attack, enemy.weaponType);
// Check for counterattack if target survived
if (!isDead) {
var attackerDied = target.counterAttack(enemy);
if (attackerDied) {
grid[enemy.gridY][enemy.gridX].unit = null;
LK.getSound('Vanish').play();
tween(enemy, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
enemy.destroy();
}
});
var index = enemyUnits.indexOf(enemy);
if (index > -1) {
enemyUnits.splice(index, 1);
}
updateUnitCounters();
if (enemy.type === 'commander') {
LK.playMusic('Victory');
LK.showYouWin();
return;
}
enemy.endTurn();
LK.setTimeout(processNextEnemy, 500);
return;
}
}
if (isDead) {
grid[target.gridY][target.gridX].unit = null;
LK.getSound('Vanish').play();
tween(target, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
target.destroy();
}
});
if (target.team === 'player') {
var index = playerUnits.indexOf(target);
if (index > -1) {
playerUnits.splice(index, 1);
}
updateUnitCounters();
// Check if player commander was defeated
if (target.type === 'commander') {
LK.showGameOver();
return;
}
}
}
}
enemy.endTurn();
LK.setTimeout(processNextEnemy, 500);
}, 400);
return;
}
}
// No valid moves, end this unit's turn
enemy.endTurn();
LK.setTimeout(processNextEnemy, 300);
}
processNextEnemy();
}
// End turn button
endTurnBtn.interactive = true;
endTurnBtn.down = function () {
if (gameState !== 'playing') {
return;
}
if (currentTurn === 'player') {
for (var i = 0; i < playerUnits.length; i++) {
playerUnits[i].resetTurn();
}
currentTurn = 'enemy';
turnText.setText('Enemy Turn');
phaseText.setText('Enemy AI thinking...');
LK.setTimeout(executeEnemyAI, 1000);
} else {
for (var i = 0; i < enemyUnits.length; i++) {
enemyUnits[i].resetTurn();
}
currentTurn = 'player';
turnText.setText('Player Turn');
}
selectedUnit = null;
selectedEnemyUnit = null;
originalUnitPosition = null;
turnPhase = 'select';
phaseText.setText('Select Unit');
clearHighlights();
removeActionButtons();
};
// Start button click handler
startButton.down = function () {
startGame();
};
function startGame() {
// Hide menu UI
gameTitle.alpha = 0;
startButton.alpha = 0;
startButton.interactive = false;
// Hide background battle animation
battleContainer.alpha = 0;
// Show game UI
turnText.alpha = 1;
phaseText.alpha = 1;
playerCounter.alpha = 1;
enemyCounter.alpha = 1;
endTurnBtn.alpha = 1;
endTurnBtn.interactive = true;
unitInfoPanel.visible = true;
// Change game state
gameState = 'playing';
// Initialize and start game
initializeGrid();
initializeUnits();
updateUnitCounters();
// Play background music
LK.playMusic('MedievalMusicFantasy1');
} ===================================================================
--- original.js
+++ change.js
@@ -356,14 +356,64 @@
var gameState = 'menu'; // Start in menu state instead of playing
var turnPhase = 'select'; // 'select', 'move', 'chooseAction', 'attack'
// UI
// Start Menu UI
-var gameTitle = new Text2('TACTICAL WARFARE', {
- size: 80,
+var gameTitle = new Text2('Ice Emblem - Virtual Journey', {
+ size: 60,
fill: 0xFFD700
});
gameTitle.anchor.set(0.5, 0.5);
LK.gui.center.addChild(gameTitle);
+// Create animated background battle scene
+var backgroundUnits = [];
+var battleContainer = new Container();
+game.addChild(battleContainer);
+// Create several animated units fighting in the background
+for (var i = 0; i < 8; i++) {
+ var unitType = ['sword1', 'sword2', 'commander', 'bow', 'magic', 'armored'][Math.floor(Math.random() * 6)];
+ var unit = LK.getAsset(unitType, {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ alpha: 0.3
+ });
+ // Random positions across the screen
+ unit.x = Math.random() * 2048;
+ unit.y = Math.random() * 2732;
+ // Color some units blue, some red to simulate teams
+ if (i % 2 === 0) {
+ unit.tint = 0x5555FF; // Blue team
+ } else {
+ unit.tint = 0xFF5555; // Red team
+ }
+ battleContainer.addChild(unit);
+ backgroundUnits.push(unit);
+ // Start random movement animation
+ animateBackgroundUnit(unit);
+}
+function animateBackgroundUnit(unit) {
+ var newX = Math.random() * 2048;
+ var newY = Math.random() * 2732;
+ var duration = 2000 + Math.random() * 3000;
+ tween(unit, {
+ x: newX,
+ y: newY
+ }, {
+ duration: duration,
+ easing: tween.linear,
+ onFinish: function onFinish() {
+ // Continue animation loop
+ animateBackgroundUnit(unit);
+ }
+ });
+ // Random rotation animation
+ var rotationTarget = unit.rotation + (Math.random() - 0.5) * Math.PI;
+ tween(unit, {
+ rotation: rotationTarget
+ }, {
+ duration: duration / 2,
+ easing: tween.easeInOut
+ });
+}
var startButton = new Text2('START GAME', {
size: 60,
fill: 0x00FF00
});
@@ -1764,8 +1814,10 @@
// Hide menu UI
gameTitle.alpha = 0;
startButton.alpha = 0;
startButton.interactive = false;
+ // Hide background battle animation
+ battleContainer.alpha = 0;
// Show game UI
turnText.alpha = 1;
phaseText.alpha = 1;
playerCounter.alpha = 1;
Un arbusto frondoso estilo animado. In-Game asset. 2d. High contrast. No shadows
Un cuadrado perfecto, sin ningún contorno, gris y con textura de piedra. In-Game asset. 2d. High contrast. No shadows
Un joven comandante de porte noble, con cabello plateado profundo ligeramente desordenado y una diadema dorada. Sus ojos son grandes y dorados. Viste una armadura de placas negro adornada con bordes dorados, con hombreras firmes y un broche rojo brillante en el pecho. Lleva una capa roja que cae sobre su hombro izquierdo. Su expresión es seria y firme. Estilo chibi fullbody In-Game asset. 2d. High contrast. No shadows
Un guerrero acorazado en estilo chibi de cuerpo completo, de cuerpo bajo y ancho, con una gran armadura pesada de hierro gris oscuro que cubre casi todo su cuerpo. Un casco de hierro de aspecto rudo, dejando ver un bigote italiano chistoso y exagerado. La armadura es muy voluminosa, con hombreras redondeadas, brazales gruesos y botas metálicas, transmitiendo la idea de fortaleza e impenetrabilidad. Sostiene un enorme hacha de batalla de doble filo, desproporcionada en estilo chibi, que enfatiza su poder de ataque. Su expresión es ruda pero cómica gracias al bigote, dándole un aire intimidante y gracioso a la vez.. In-Game asset. 2d. High contrast. No shadows
Una sanadora en estilo chibi/cuerpo completo, una chica joven de físico algo marcado por el esfuerzo y la vida dura. Viste ropas tribales, va descalza. Su cabello es negro, largo y despeinado, cayendo con un aire descuidado pero natural. Porta un bastón improvisado hecho de una rama de árbol torcida, aún con algunas hojas pegadas, lo que resalta su carácter rústico y tribal. En su piel se notan tatuajes o marcas tribales, además de collares o pulseras hechos de hueso, madera o cuentas simples, que refuerzan su conexión espiritual con la naturaleza. Su expresión es serena y determinada, mostrando que a pesar de su apariencia austera y primitiva, es una guía y protectora para su gente.. In-Game asset. 2d. High contrast. No shadows
Una jinete de wyvern en estilo chibi/cuerpo completo. El wyvern es grande y de color morado, con alas extendidas y expresión fiera. La jinete es una joven guerrera con armadura ligera de torso en color verde con detalles aqua, con hombreras y guantes. Lleva pantalones blancos y botas verdes altas. Su cabello es corto estilo bob, de color aqua, y tiene ojos azules brillantes. Montada sobre el wyvern, sostiene con una mano las riendas y con la otra una lanza larga.. In-Game asset. 2d. High contrast. No shadows
Un manakete en estilo chibi/cuerpo completo. Se ve como un niño pequeño con cabello blanco desordenado. Sus alas de dragón, morado oscura y membranosas, están enrolladas alrededor de su cuerpo como si fueran una capucha, dándole un aire misterioso. Viste ropa casual y simple, algo holgada, que oculta sus verdaderos atributos. En sus brazos lleva vendas mal puestas, apenas cubriendo las escamas moradas de dragón que intentan asomarse bajo ellas, garras en las manos. En cada mejilla tiene tres marcas visibles como arañazos, y un colmillo visible. Su expresión es inocente pero enigmática, transmitiendo que no es un simple niño. El estilo es chibi pero con un aire ligeramente oscuro y mágico.. In-Game asset. 2d. High contrast. No shadows
Un caballero jinete en estilo chibi/cuerpo completo. Es un hombre delgado de aproximadamente 50 años, con barba de chivo bien marcada y expresión seria pero orgullosa. Lleva una armadura roja con detalles plateados brillantes, elegante y clásica. Porta una larga lanza de justas en una mano y un gran escudo ovalado en la otra, el cual está muy decorado con símbolos y adornos llamativos. Está montado sobre un caballo robusto y elegante, en pose firme y heroica. El estilo es chibi, pero mantiene la imponencia de un caballero veterano.. In-Game asset. 2d. High contrast. No shadows
Un arquero en estilo chibi/cuerpo completo. Es un joven de cabello castaño y tez blanca, con un aire humilde y campesino. Lleva ropa sencilla de campesino, con un parche en el ojo derecho que le da un toque rudo. Porta un arco de color dorado brillante y un carcaj lleno de flechas que lleva a modo de cinturón en la cintura. En la cabeza luce un sombrero vueltiao típico, ancho y llamativo. Está en pose de combate ligera, como apuntando o sosteniendo el arco listo para disparar, manteniendo el estilo simpático y expresivo de un chibi.. In-Game asset. 2d. High contrast. No shadows
Una maga en estilo chibi/cuerpo completo. Es una joven de unos 20 años, de cabello negro largo y suelto, con piel blanca pálida y ojeras marcadas que le dan un aire misterioso. Viste una capa morada que cubre su espalda y cae con elegancia, con guantes a juego del mismo color. Su atuendo consiste en una camiseta blanca sin mangas, pantalón negro ajustado y protecciones en los codos y rodillas para un toque práctico. En el cuello lleva un collar con un amuleto en forma de ojo dorado brillante, que resalta como símbolo mágico. Su expresión es seria y enigmática, transmitiendo el aire de una hechicera experimentada a pesar de su juventud.. In-Game asset. 2d. High contrast. No shadows
Un espadachín en estilo chibi/cuerpo completo. Es un joven de cabello negro algo desordenado, piel clara y expresión confiada, con una sonrisa desafiante. Viste un abrigo largo rojo hasta los tobillos, abierto al frente, con bordes dorados y un cinturón que ajusta a la cintura. Bajo el abrigo lleva una camiseta oscura, pantalones grises ajustados y botas rojas con detalles dorados. En su cintura lleva correas y fundas decorativas. Porta una gran espada de hoja plateada con brillo metálico y detalles en rojo, con la empuñadura dorada. Su pose transmite seguridad y carisma, como si estuviera listo para un combate pero también disfrutara de la situación.. In-Game asset. 2d. High contrast. No shadows
Una espadachina en estilo chibi/cuerpo completo. Es una mujer samurái de unos 30 años con cabello largo y marrón, atado ligeramente hacia atrás para que no estorbe en combate. Lleva una banda negra que cubre sus ojos. Su vestimenta es una túnica japonesa de batalla color azul cielo, de corte práctico y elegante, con detalles sutiles que insinúan resistencia. Viste unas botas japonesas de doble zanco (geta alta) que le dan un aire tradicional. Porta una katana brillante de hoja plateada y empuñadura simple, sostenida con firmeza en una mano, mientras la otra reposa relajada. Su expresión es calmada y disciplinada, transmitiendo seguridad y experiencia en combate.. In-Game asset. 2d. High contrast. No shadows
Efecto de Impacto sin letras. In-Game asset. 2d. High contrast. No shadows
Un destello verde blancuzco. In-Game asset. 2d. High contrast. No shadows