User prompt
Quita cuadros de río para hacerlos cuadros de puente (de 2 de horizontal y 3 de vertical) 2 puentes a lados opuestos del mapa.
User prompt
¿Y los 2 puentes? solo veo el río, así las unidades terrestres no van a pasar; y los indicadores de unidades deben estar abajo, ya que el botón de pausa no deja verlos
User prompt
De nuevo, te explicaré el puente ( en horizontal cuadro de río, cuadro de puente, cuadro de puente, de ahí 9 de río, cuadro de puente, cuadro de puente, y cuadro de río, y de ahí en vertical los copias hasta que sea de 3 en vertical), y que no haya árboles en frente de un puente, y mis unidades son en la esquina inferior derecha en vista de espejo a las unidades enemigas que estarán en la esquina superior derecha
User prompt
Ensancha algo más el río (3 de vertical) y pon los puentes en el río como dije (de 2 de horizontal por 3 de vertical), y quiero mis unidades aliadas al extremo derecho del mapa. Por cierto, el filtro de color que diferencia mis unidades de las enemigas (rojo enemigos y azul aliados) hazlo más opaco en el sprite y sin fondo, además de que una unidad aliada movida se vuelva gris, para así saber cuales ya se movieron, así como un contador de Unidades aliadas y otro de enemigas (8/8 rojo para enemigos y azul para aliados, reduciéndose en una cada que se pierda una unidad: 7/8, 6/8...) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Bien, lo siguiente es que quiero que ambos bandos estén en lados opuestos del mapa, que están muy cerca. De ahí sigue el terreno: Normal (no afecta la movilidad) Árbol (los Jinetes no lo cruzan, los Jinetes de Dragón los pasan de largo como si fuese terreno normal, y si una unidad lo cruza, no se mueve más hasta el siguiente turno), Agua (quiero un río en medio del mapa, el cual solo el jinete de dragón puede cruzar, pero los demás no), y 2 puentes (uno a cada extremo del río que funciona igual como el terreno normal, pero no tan pegados al borde del mapa, sino algo alejados). Por último, al mover todas tus unidades, el turno termina automáticamente. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Ahora, necesito un mapa más grande, que la opción de terminar turno no aparezca cuando salga la opción de atacar/curar o ignorar ni cuando sea el turno del oponente (para que no estorbe) y quiero que cuando salga la opción de atacar/curar o ignorar, al hacer clic en algo que no sea las opciones, la unidad vuelva a su posición original (para así moverla de nuevo a otra zona), ah, y que al clicar en una unidad enemiga, se vea su cuadrícula de movimiento y se quite al clicarla de nuevo. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
El Healer no cura, quiero que funcione igual que lo de atacar o ignorar, pero que sea Curar o Ignorar, además, quiero que se pueda cancelar la opción de mover una unidad seleccionada haciendo clic de nuevo en cualquier parte. Ah, y quiero que a cada unidad le demos su propio sprite por clase, no por bando. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Veamos, ahora quiero añadir más unidades: -Jinete (Avanza 3 casillas) -Jinete de dragón (Avanza 3 casillas) -Healer (No ataca, sino que cura 1 de HP al estar al lado de un aliado) -Acorazado (Solo avanza una casilla) Y dale a todos 2 de HP y 1 de daño para probar al Healer
User prompt
Please fix the bug: 'TypeError: Cannot use 'in' operator to search for 'x' in null' in or related to this line: 'tween(selectedUnit, {' Line Number: 435 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Bien, para diferenciar los equipos, pon un contorno rojo en los enemigos y azul en los jugables, además de una mini corona en la esquina inferior derecha del comandante de ambos, para saber cual es el comandante. Y añade un efecto al atacar (que el que ataca vibre y que el derrotado se desvanezca) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Todo bien, pero falta que, al derrotar al commander enemigo debería hacer ganar, y si te derrotan al tuyo pierdes
User prompt
Todo bien, pero el enemigo no se mueve solo en su turno, sino que lo controlo yo, corrígelo dándole una IA al enemigo, y también, el bow no puede atacar a una casilla de distancia, solo a 2, o sea, no ataca cuerpo a cuerpo como el espada y el mago ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'to')' in or related to this line: 'tween(unit).to({' Line Number: 321 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'to')' in or related to this line: 'tween(unit).to({' Line Number: 321
User prompt
No se ve nada
User prompt
Please fix the bug: 'Timeout.tick error: Cannot read properties of null (reading 'canAttack')' in or related to this line: 'if (selectedUnit.canAttack(tx, ty) && grid[ty][tx].unit && grid[ty][tx].unit.team !== selectedUnit.team && grid[ty][tx].unit.hp > 0) {' Line Number: 244
Code edit (1 edits merged)
Please save this source code
User prompt
Sigue sin funcionar, te daré una explicación simple del movimiento de una unidad 1.- Seleccionar unidad: -El jugador hace clic en una unidad propia. -En ese momento, se iluminan en la cuadrícula todas las casillas a las que puede moverse (según su rango de movimiento). 2.- Elegir destino: -El jugador hace clic en una casilla vacía dentro de ese rango. -La unidad se mueve automáticamente hasta esa casilla. 3.- Acción posterior al movimiento: -Si después del movimiento hay un enemigo dentro de su rango de ataque, aparece un pequeño menú con dos opciones: Atacar: el jugador hace clic y el combate ocurre. Ignorar: la unidad termina su turno sin atacar. -Si no hay enemigos cerca, el turno de la unidad simplemente termina al llegar a la casilla. Bien, corrige el código. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Sigo sin moverme como te dije, le hago clic a mi unidad y no me sale nada, que al hacer clic a mi unidad, aparezca una cuadrícula rojiza transparente para saber a donde se mueve cada uno al clicarlo
User prompt
No me puedo mover, lo que quiero es que al hacer clic en una unidad mía, me salga a donde lo puedo mover, siendo un máximo de 2 casillas, y de ahí se mueve a la casilla designada con otro clic, si hay una unidad cerca de su rango de ataque (1 casilla para las espadas, 2 para los arcos y 1 o 2 para los magos) me salga el botón atacar o ignorar, y quiero la cuadrícula más grande, de momento que las unidades mueran al primer daño.
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'toGlobal')' in or related to this line: 'var guiPos = LK.gui.toLocal(obj.parent.toGlobal(obj.position));' Line Number: 397
Code edit (1 edits merged)
Please save this source code
User prompt
Tactical Commander
Initial prompt
Bien, quiero un juego de estrategia por turnos en cuadrícula, donde controlas a un pequeño grupo de héroes con distintas habilidades y armas. Cada turno, eliges cómo mover a tus personajes por el mapa y a quién atacar, aprovechando el terreno para obtener ventajas defensivas o ofensivas. El enemigo también mueve sus tropas con la misma lógica, por lo que debes planear con cuidado, proteger a tus aliados más débiles y usar la posición a tu favor. El combate es sencillo: comparas el poder de ataque de una unidad contra la defensa del oponente, con distintos rangos de alcance según el arma (espadas de corto alcance, arcos a distancia, magia que atacan cerca y lejos, etc.). La partida se desarrolla en un único mapa con un objetivo claro, derrotar al líder enemigo. Si tu comandante cae, pierdes la batalla. Es un juego de estrategia táctica ligera, con personajes genéricos, reglas simples y un escenario único, diseñado para transmitir la tensión de pensar cada movimiento como si fuera decisivo.
/****
* 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
});
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;
self.showHighlight = function () {
highlightGraphics.alpha = 0.4;
attackGraphics.alpha = 0;
};
self.showAttackRange = function () {
attackGraphics.alpha = 0.5;
highlightGraphics.alpha = 0;
};
self.hideHighlights = function () {
highlightGraphics.alpha = 0;
attackGraphics.alpha = 0;
};
self.terrainType = 'normal'; // normal, tree, water, bridge
self.addTerrain = function (terrainType) {
if (!self.hasTerrain) {
self.terrainType = terrainType || 'normal';
var assetName = 'terrain';
if (terrainType === 'tree') assetName = 'tree';else if (terrainType === 'water') assetName = 'water';else if (terrainType === 'bridge') assetName = 'bridge';
var terrainGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
terrainGraphics.alpha = 0.8;
self.hasTerrain = true;
}
};
return self;
});
var Unit = Container.expand(function (type, team, weaponType) {
var self = Container.call(this);
self.type = type;
self.team = team;
self.weaponType = weaponType;
self.gridX = 0;
self.gridY = 0;
self.hp = 2;
self.maxHp = self.hp;
self.attack = 1;
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 {
// Use weapon type as sprite
if (weaponType === 'sword') {
unitGraphics = self.attachAsset('sword', {
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 {
unitGraphics = self.attachAsset('sword', {
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);
}
var weaponText = new Text2(weaponType === 'sword' ? 'S' : weaponType === 'bow' ? 'B' : weaponType === 'magic' ? 'M' : weaponType === 'rider' ? 'R' : weaponType === 'dragonRider' ? 'D' : weaponType === 'healer' ? 'H' : weaponType === 'armored' ? 'A' : '?', {
size: 30,
fill: 0xFFFFFF
});
weaponText.anchor.set(0.5, 0.5);
weaponText.y = -10;
self.addChild(weaponText);
var hpText = new Text2(self.hp.toString(), {
size: 20,
fill: 0xFFFFFF
});
hpText.anchor.set(0.5, 0.5);
hpText.y = 20;
self.addChild(hpText);
self.updateHpDisplay = function () {
hpText.setText(self.hp.toString());
if (self.hp <= 0) {
unitGraphics.alpha = 0.3;
}
};
self.takeDamage = function (damage) {
var actualDamage = Math.max(1, damage - 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 1;
return 1;
};
self.getMovementRange = function () {
if (self.weaponType === 'rider' || self.weaponType === 'dragonRider') return 3;
if (self.weaponType === 'armored') return 1;
return 2; // 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
}
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.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 = 'playing';
var turnPhase = 'select'; // 'select', 'move', 'chooseAction', 'attack'
// UI
var turnText = new Text2('Player Turn', {
size: 60,
fill: 0xFFFFFF
});
turnText.anchor.set(0.5, 0);
LK.gui.top.addChild(turnText);
var phaseText = new Text2('Select Unit', {
size: 40,
fill: 0xFFFFFF
});
phaseText.anchor.set(0.5, 0);
phaseText.y = 80;
LK.gui.top.addChild(phaseText);
// Unit counters
var playerCounter = new Text2('8/8', {
size: 35,
fill: 0x5555FF
});
playerCounter.anchor.set(0, 1);
playerCounter.x = 20;
playerCounter.y = -120;
LK.gui.bottomLeft.addChild(playerCounter);
var enemyCounter = new Text2('8/8', {
size: 35,
fill: 0xFF5555
});
enemyCounter.anchor.set(1, 1);
enemyCounter.x = -20;
enemyCounter.y = -120;
LK.gui.bottomRight.addChild(enemyCounter);
var endTurnBtn = new Text2('End Turn', {
size: 50,
fill: 0x00FF00
});
endTurnBtn.anchor.set(0.5, 1);
LK.gui.bottom.addChild(endTurnBtn);
// botones dinámicos de acción
var attackBtn = null;
var ignoreBtn = 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();
// 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();
}
// Initialize units
function initializeUnits() {
// Player units (bottom-right corner)
var playerUnit1 = new Unit('hero', 'player', 'sword');
placeUnit(playerUnit1, 13, 13);
playerUnits.push(playerUnit1);
var playerUnit2 = new Unit('hero', 'player', 'bow');
placeUnit(playerUnit2, 11, 13);
playerUnits.push(playerUnit2);
var playerUnit3 = new Unit('hero', 'player', 'magic');
placeUnit(playerUnit3, 9, 13);
playerUnits.push(playerUnit3);
var playerRider = new Unit('rider', 'player', 'rider');
placeUnit(playerRider, 14, 12);
playerUnits.push(playerRider);
var playerDragonRider = new Unit('dragonRider', 'player', 'dragonRider');
placeUnit(playerDragonRider, 8, 12);
playerUnits.push(playerDragonRider);
var playerHealer = new Unit('healer', 'player', 'healer');
placeUnit(playerHealer, 10, 14);
playerUnits.push(playerHealer);
var playerArmored = new Unit('armored', 'player', 'armored');
placeUnit(playerArmored, 14, 13);
playerUnits.push(playerArmored);
var playerCommander = new Unit('commander', 'player', 'sword');
placeUnit(playerCommander, 12, 14);
playerUnits.push(playerCommander);
// Enemy units (top-left corner)
var enemyUnit1 = new Unit('hero', 'enemy', 'sword');
placeUnit(enemyUnit1, 1, 1);
enemyUnits.push(enemyUnit1);
var enemyUnit2 = new Unit('hero', 'enemy', 'bow');
placeUnit(enemyUnit2, 3, 1);
enemyUnits.push(enemyUnit2);
var enemyUnit3 = new Unit('hero', 'enemy', 'magic');
placeUnit(enemyUnit3, 5, 1);
enemyUnits.push(enemyUnit3);
var enemyRider = new Unit('rider', 'enemy', 'rider');
placeUnit(enemyRider, 0, 2);
enemyUnits.push(enemyRider);
var enemyDragonRider = new Unit('dragonRider', 'enemy', 'dragonRider');
placeUnit(enemyDragonRider, 6, 2);
enemyUnits.push(enemyDragonRider);
var enemyHealer = new Unit('healer', 'enemy', 'healer');
placeUnit(enemyHealer, 4, 0);
enemyUnits.push(enemyHealer);
var enemyArmored = new Unit('armored', 'enemy', 'armored');
placeUnit(enemyArmored, 0, 1);
enemyUnits.push(enemyArmored);
var enemyCommander = new Unit('commander', 'enemy', 'sword');
placeUnit(enemyCommander, 2, 0);
enemyUnits.push(enemyCommander);
}
function generateTerrain() {
// 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');
}
}
// Add bridges: horizontal pattern (river-bridge-bridge then 9 river tiles then bridge-bridge-river)
// Bridge 1: columns 1-2, rows 6-8
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
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)
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
}];
for (var i = 0; i < treePositions.length; i++) {
var pos = treePositions[i];
grid[pos.y][pos.x].addTerrain('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 getReachableTiles(unit) {
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;
if (grid[newY][newX].unit) 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 - only dragon riders can cross
if (tile.terrainType === 'water' && unit.weaponType !== 'dragonRider') {
return -1; // Cannot move
}
// Tree - riders cannot cross, others get movement cost
if (tile.terrainType === 'tree') {
if (unit.weaponType === 'rider') {
return -1; // Cannot move
}
if (unit.weaponType === 'dragonRider') {
return 1; // Normal cost
}
return unit.getMovementRange(); // Ends movement for turn
}
// 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) {
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;
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 can cross water
if (unit.weaponType !== 'dragonRider') 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) {
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();
} else {
// Clicking different enemy - show movement
selectedEnemyUnit = clickedTile.unit;
clearHighlights();
showEnemyMovementOptions(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
};
turnPhase = 'move';
phaseText.setText('Choose destination');
showMovementOptions(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();
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
});
}
});
}
});
var isDead = target.takeDamage(selectedUnit.attack);
if (isDead) {
grid[target.gridY][target.gridX].unit = null;
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;
}
} 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.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 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 + '/8');
enemyCounter.setText(aliveEnemyUnits + '/8');
}
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
});
}
});
}
});
var isDead = target.takeDamage(enemy.attack);
if (isDead) {
grid[target.gridY][target.gridX].unit = null;
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);
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) {
moveUnit(enemy, bestMove.x, bestMove.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
});
}
});
}
});
var isDead = target.takeDamage(enemy.attack);
if (isDead) {
grid[target.gridY][target.gridX].unit = null;
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 (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();
};
// Initialize game
initializeGrid();
initializeUnits();
updateUnitCounters(); ===================================================================
--- original.js
+++ change.js
@@ -262,20 +262,20 @@
var playerCounter = new Text2('8/8', {
size: 35,
fill: 0x5555FF
});
-playerCounter.anchor.set(0, 0);
+playerCounter.anchor.set(0, 1);
playerCounter.x = 20;
-playerCounter.y = 20;
-LK.gui.topLeft.addChild(playerCounter);
+playerCounter.y = -120;
+LK.gui.bottomLeft.addChild(playerCounter);
var enemyCounter = new Text2('8/8', {
size: 35,
fill: 0xFF5555
});
-enemyCounter.anchor.set(1, 0);
+enemyCounter.anchor.set(1, 1);
enemyCounter.x = -20;
-enemyCounter.y = 20;
-LK.gui.topRight.addChild(enemyCounter);
+enemyCounter.y = -120;
+LK.gui.bottomRight.addChild(enemyCounter);
var endTurnBtn = new Text2('End Turn', {
size: 50,
fill: 0x00FF00
});
@@ -443,16 +443,16 @@
for (var x = 0; x < GRID_SIZE; x++) {
grid[y][x].addTerrain('water');
}
}
- // Add bridges: horizontal pattern (river-bridge-bridge-river...bridge-bridge-river)
+ // Add bridges: horizontal pattern (river-bridge-bridge then 9 river tiles then bridge-bridge-river)
// Bridge 1: columns 1-2, rows 6-8
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
+ // Bridge 2: columns 12-13, rows 6-8
for (var y = 6; y <= 8; y++) {
for (var x = 12; x <= 13; x++) {
grid[y][x].addTerrain('bridge');
}
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