User prompt
Please fix the bug: 'tutorialOffset is not defined' in or related to this line: 'tween(button, {' Line Number: 855 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (2 edits merged)
Please save this source code
User prompt
no, the previous code the units actually dealt damage, not its just code writen but nothing happens, nothing updates on the screen or the game. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
its not attaking... you made it work in the begining of this project
User prompt
im clicking in the enemy to perform the attack but it does not animate or takes damage of the oponent.... Refactor the whole attack logic please, make it work
User prompt
the attack function is not doing anything, it should animate the player unit to an attack animation, and take damage on the attacked oponent health.
User prompt
self.canAttack = function (targetRow, targetCol) { // Base attack logic to be overridden by specific unit types return false; }; why does this function only returns false? its bugging the game, make it return true if theres an oponent unit in the highligh_attack cell/ attack range cell
User prompt
when the player clicks to attack it should make a small move animation to the attacking unit ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make it take damage to the oponent unit that is inside the highlight_attack cell and decrease its health accordingly to the player unit attackStrenght
User prompt
If the player clicks the highlight_attack cell, make it take damage in the oponent
User prompt
its not showing the highlight attack cell, the function must check which is the attack range of the selected unit
User prompt
The attack function must highlight the cell where it can attack using the highlight_attack image, just like the highlight_move is being used
User prompt
Create an attack logic, similar to chess
User prompt
rewrite the attack function so it highlights the unit it can attack and make it attackif the player click in the attack cell, the attack function must call the take damage function and take damage on the attacked oponent unit
User prompt
Add the attack function to be called from the players controllers, the player must first move, only after moving it can attack, for example, if a players unit has no oponent unit in the attack range cell, dont highlight that cell, but if there is, you can highlight before move. The player then has the choice to either attack and not move or move then attack, or not attack at all. If the oponent is not in the attack range cell before move, but end up being after moving, highlight the cell so that the player can attack. if even after moving theres no one in the attack range cell, it ends the player turn.
User prompt
Add the attack function to be called from the players controllers, the player must first move, only after moving it can attack, for example, if a players unit has no oponent unit in the attack range cell, dont highlight that cell, but if there is, you can highlight before move. The player then has the choice to either attack and not move or move then attack, or not attack at all. If the oponent is not in the attack range cell before move, but end up being after moving, highlight the cell so that the player can attack. if even after moving theres no one in the attack range cell, it ends the player turn.
User prompt
Create a function to attack the oponent unit that can be called from any controller. It should check if there's an oponent unit in the attack Range Cell of the selected unit
User prompt
just create a take damage function, that will be called to take tamage to the enemy team. This function must take damage to the oponent unit accordingly to the attackStrenght of the unit that is attacking.
User prompt
during play game, the game can have 3 states, player1 state, aiplayer state if is in player vs ai game mode, and player2 game state if is player vs player game mode
User prompt
✅ Add selectUnit function that selects a unit only if it belongs to the controller's team ✅ Update GridCell's down event handler to use the selectunits function ✅ Add reference to player controllers in the game initialization
User prompt
Please fix the bug: 'Uncaught ReferenceError: selectUnit is not defined' in or related to this line: 'selectUnit(unit);' Line Number: 1620
User prompt
Create a function called select character, that when you click in a cell that has character in it, it will select that character. But Keep in mind if the character is from the controller that is calling this function, if not, it does nothing, if it is, select that character. Add just this for now
User prompt
Create playercontroller1 with nothing in it
Code edit (1 edits merged)
Please save this source code
Code edit (3 edits merged)
Please save this source code
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var AIPlayer = Container.expand(function () {
var self = Container.call(this);
self.makeMove = function () {
// Only make a move if it's AI's turn
if (game.gameState !== 'aiTurn') {
return false;
}
var units = game.getTeamUnits('red');
var moveFound = false;
// First priority: attack if possible
for (var i = 0; i < units.length; i++) {
var unit = units[i];
var attacks = unit.getPossibleAttacks(game.grid);
if (attacks.length > 0) {
// Prioritize attacking king
var kingAttack = attacks.find(function (attack) {
return game.grid[attack.row][attack.col].occupiedBy && game.grid[attack.row][attack.col].occupiedBy.isKing;
});
if (kingAttack) {
game.selectUnit(unit);
game.attackCell(game.grid[kingAttack.row][kingAttack.col]);
return true;
}
// Look for advantageous critical attacks first
var criticalAttack = attacks.find(function (attack) {
var target = game.grid[attack.row][attack.col].occupiedBy;
return target && isCriticalDamage(unit.type, target.type);
});
if (criticalAttack) {
game.selectUnit(unit);
game.attackCell(game.grid[criticalAttack.row][criticalAttack.col]);
return true;
}
// For warriors, prioritize attacks to use their instant kill
if (unit.type === 'warrior' && attacks.length > 0) {
game.selectUnit(unit);
game.attackCell(game.grid[attacks[0].row][attacks[0].col]);
return true;
}
// For wizards, prioritize attack positions that can hit multiple enemies
if (unit.type === 'wizard' && attacks.length > 0) {
// Try to find attack that might hit multiple enemies due to area effect
var bestAttack = attacks[0];
var bestCount = 0;
for (var j = 0; j < attacks.length; j++) {
var attack = attacks[j];
var adjacentCells = getAdjacentCells(attack.row, attack.col);
var enemyCount = 0;
// Count adjacent enemy units
for (var k = 0; k < adjacentCells.length; k++) {
var cell = adjacentCells[k];
if (cell.occupied && cell.occupiedBy.team === 'blue') {
enemyCount++;
}
}
if (enemyCount > bestCount) {
bestCount = enemyCount;
bestAttack = attack;
}
}
game.selectUnit(unit);
game.attackCell(game.grid[bestAttack.row][bestAttack.col]);
return true;
}
// Otherwise attack first available target
game.selectUnit(unit);
game.attackCell(game.grid[attacks[0].row][attacks[0].col]);
return true;
}
}
// Second priority: move toward player king
var playerKing = game.getKing('blue');
if (playerKing) {
// Find unit closest to player king that can move
var closestUnit = null;
var closestDistance = Infinity;
for (var i = 0; i < units.length; i++) {
var unit = units[i];
var moves = unit.getPossibleMoves(game.grid);
if (moves.length > 0) {
var dist = Math.abs(unit.row - playerKing.row) + Math.abs(unit.col - playerKing.col);
// Give priority to certain unit types based on their strengths
if (unit.type === 'knight') {
// Knights are mobile, so give them priority
dist -= 2;
} else if (unit.type === 'archer') {
// Archers should try to get in position but not too close
if (dist > 4) {
dist -= 1;
} // Encourage moving closer if far away
else if (dist < 3) {
dist += 2;
} // Discourage getting too close
} else if (unit.type === 'warrior') {
// Warriors should get close for their instant kill
dist -= 3;
} else if (unit.type === 'wizard') {
// Wizards should stay at medium distance for area attacks
if (dist > 3 && dist < 6) {
dist -= 2;
}
}
if (dist < closestDistance) {
closestUnit = unit;
closestDistance = dist;
}
}
}
if (closestUnit) {
var bestMove = null;
var bestMoveScore = -Infinity;
var moves = closestUnit.getPossibleMoves(game.grid);
// Score each possible move
for (var i = 0; i < moves.length; i++) {
var move = moves[i];
var moveScore = closestDistance - (Math.abs(move.row - playerKing.row) + Math.abs(move.col - playerKing.col));
// Special movement strategy based on unit type
if (closestUnit.type === 'knight') {
// Knights should try to position for attack next turn
var potentialAttackRange = Math.abs(move.row - playerKing.row) + Math.abs(move.col - playerKing.col);
if (potentialAttackRange <= closestUnit.attackRange + 1) {
moveScore += 3;
}
} else if (closestUnit.type === 'archer') {
// Archers want to be at their optimal attack range
var distAfterMove = Math.abs(move.row - playerKing.row) + Math.abs(move.col - playerKing.col);
if (distAfterMove >= 3 && distAfterMove <= 5) {
moveScore += 4;
}
} else if (closestUnit.type === 'wizard') {
// Wizards prefer positions that might hit multiple units next turn
var adjacentCells = getAdjacentCells(move.row, move.col);
var playerUnitsNearby = 0;
for (var j = 0; j < adjacentCells.length; j++) {
var cell = adjacentCells[j];
if (cell.occupied && cell.occupiedBy.team === 'blue') {
playerUnitsNearby++;
}
}
moveScore += playerUnitsNearby * 2;
}
if (moveScore > bestMoveScore) {
bestMove = move;
bestMoveScore = moveScore;
}
}
if (bestMove) {
game.selectUnit(closestUnit);
game.moveSelectedUnit(bestMove.row, bestMove.col);
return true;
}
}
}
// Check if any unit is in danger and try to move it to safety
var unitsInDanger = units.filter(function (unit) {
// Check if any player unit could attack this unit in the next turn
var playerUnits = game.getTeamUnits('blue');
for (var i = 0; i < playerUnits.length; i++) {
var playerUnit = playerUnits[i];
// Calculate if the player unit could potentially attack this AI unit
var rowDiff = Math.abs(playerUnit.row - unit.row);
var colDiff = Math.abs(playerUnit.col - unit.col);
// Check if in potential attack range based on unit type
var inDanger = false;
if (playerUnit.type === 'warrior' && rowDiff + colDiff <= playerUnit.moveRange + playerUnit.attackRange) {
inDanger = true;
} else if (playerUnit.type === 'archer' && colDiff === 0 && rowDiff <= playerUnit.attackRange + 1) {
inDanger = true;
} else if (playerUnit.type === 'knight' && (rowDiff === 0 || colDiff === 0) && rowDiff + colDiff <= playerUnit.attackRange + 1) {
inDanger = true;
} else if (playerUnit.type === 'wizard' && rowDiff + colDiff <= playerUnit.moveRange + 2) {
inDanger = true;
}
// If king is in danger, prioritize its safety
if (inDanger && unit.isKing) {
return true;
} else if (inDanger && unit.health < 50) {
return true;
}
}
return false;
});
if (unitsInDanger.length > 0) {
// Prioritize king safety
var unitToSave = unitsInDanger.find(function (unit) {
return unit.isKing;
}) || unitsInDanger[0];
var moves = unitToSave.getPossibleMoves(game.grid);
if (moves.length > 0) {
// Find safest move (furthest from player units)
var playerUnits = game.getTeamUnits('blue');
var bestMove = null;
var bestSafetyScore = -Infinity;
for (var i = 0; i < moves.length; i++) {
var move = moves[i];
var safetyScore = 0;
// Calculate safety based on distance from all player units
for (var j = 0; j < playerUnits.length; j++) {
var playerUnit = playerUnits[j];
var dist = Math.abs(move.row - playerUnit.row) + Math.abs(move.col - playerUnit.col);
safetyScore += dist;
}
if (safetyScore > bestSafetyScore) {
bestSafetyScore = safetyScore;
bestMove = move;
}
}
if (bestMove) {
game.selectUnit(unitToSave);
game.moveSelectedUnit(bestMove.row, bestMove.col);
return true;
}
}
}
// Last resort: random move with random unit
var movableUnits = units.filter(function (unit) {
return unit.getPossibleMoves(game.grid).length > 0;
});
if (movableUnits.length > 0) {
var randomUnit = movableUnits[Math.floor(Math.random() * movableUnits.length)];
var possibleMoves = randomUnit.getPossibleMoves(game.grid);
var randomMove = possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
game.selectUnit(randomUnit);
game.moveSelectedUnit(randomMove.row, randomMove.col);
return true;
}
return false; // No move possible
};
return self;
});
var CriticalDamageEffect = Container.expand(function () {
var self = Container.call(this);
self.init = function (damage, x, y) {
// Create the critical hit text with animation
var critText = new Text2('CRITICAL HIT!', {
size: 80,
fill: '#FF0000'
});
critText.anchor.set(0.5, 0.5);
self.addChild(critText);
// Create the damage text
var damageText = new Text2('-' + damage, {
size: 100,
fill: '#FFFF00'
});
damageText.anchor.set(0.5, 0.5);
damageText.y = 80;
self.addChild(damageText);
// Position the effect
self.x = x;
self.y = y;
// Initial scale and alpha
self.scale.set(0.1);
self.alpha = 0;
// Animate in
tween(self, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1
}, {
duration: 300,
easing: tween.elasticOut
});
// Animate out after showing
LK.setTimeout(function () {
tween(self, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0,
y: self.y - 100
}, {
duration: 700,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
self.parent.removeChild(self);
}
});
}, 1200);
return self;
};
return self;
});
var DifficultySelectWindow = Container.expand(function () {
var self = Container.call(this);
// Semi-transparent background overlay to focus on the popup
var overlay = LK.getAsset('grid_cell_dark', {
anchorX: 0,
anchorY: 0,
width: 2048,
height: 2732,
alpha: 0.7
});
self.addChild(overlay);
// Main popup window
var popup = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
width: 1200,
height: 800,
alpha: 0.95
});
popup.x = 1024;
popup.y = 1366;
self.addChild(popup);
// Title for the popup
var titleText = new Text2('SELECT DIFFICULTY', {
size: 100,
fill: '#000000'
});
titleText.anchor.set(0.5, 0);
titleText.x = 1024;
titleText.y = 1066;
self.addChild(titleText);
// Create buttons for each difficulty
var createDifficultyButton = function createDifficultyButton(text, yOffset) {
var button = new Container();
// Button background
var bg = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
width: 700,
height: 120
});
button.addChild(bg);
// Button text
var buttonText = new Text2(text, {
size: 80,
fill: '#000000'
});
buttonText.anchor.set(0.5, 0.5);
button.addChild(buttonText);
// Position button
button.x = 1024;
button.y = yOffset;
// Button press animation
button.down = function (x, y, obj) {
tween(bg, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOutQuad
});
};
button.up = function (x, y, obj) {
tween(bg, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut
});
};
return button;
};
// Create difficulty buttons
var easyButton = createDifficultyButton('EASY', 1266);
var mediumButton = createDifficultyButton('MEDIUM', 1416);
var hardButton = createDifficultyButton('HARD', 1566);
// Add buttons to container
self.addChild(easyButton);
self.addChild(mediumButton);
self.addChild(hardButton);
// Close popup when clicking outside
overlay.down = function (x, y, obj) {
// Check if click is outside the popup area
var localPos = popup.toLocal({
x: x,
y: y
});
if (Math.abs(localPos.x) > popup.width / 2 || Math.abs(localPos.y) > popup.height / 2) {
game.closeDifficultyWindow();
}
};
// Button functionality - currently these don't do anything specific
// except close the window since difficulty isn't implemented yet
easyButton.up = function (x, y, obj) {
tween(easyButton.children[0], {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
game.closeDifficultyWindow();
// Set AI difficulty to easy (0.3 out of 1.0)
game.aiDifficulty = 0.3;
game.gameMode = 'pvai';
game.aiControlsRedTeam = true;
game.startGame();
}
});
};
mediumButton.up = function (x, y, obj) {
tween(mediumButton.children[0], {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
game.closeDifficultyWindow();
// Set AI difficulty to medium (0.6 out of 1.0)
game.aiDifficulty = 0.6;
game.gameMode = 'pvai';
game.aiControlsRedTeam = true;
game.startGame();
}
});
};
hardButton.up = function (x, y, obj) {
tween(hardButton.children[0], {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
game.closeDifficultyWindow();
// Set AI difficulty to hard (0.9 out of 1.0)
game.aiDifficulty = 0.9;
game.gameMode = 'pvai';
game.aiControlsRedTeam = true;
game.startGame();
}
});
};
return self;
});
var GameModeScreen = Container.expand(function () {
var self = Container.call(this);
// Logo image
var logo = LK.getAsset('game_logo', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -500
});
self.addChild(logo);
// Title text
var titleText = new Text2('SELECT GAME MODE', {
size: 150,
fill: '#FFFFFF'
});
titleText.anchor.set(0.5, 0);
titleText.y = 0;
self.addChild(titleText);
// Create a button for game mode selection
var createButton = function createButton(text, yOffset) {
var button = new Container();
// Button background
var bg = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
width: text === 'PLAYER VS PLAYER' ? 1000 : text === 'BACK' ? 500 : 800,
height: 150
});
button.addChild(bg);
// Button text
var buttonText = new Text2(text, {
size: 100,
fill: '#000000'
});
buttonText.anchor.set(0.5, 0.5);
button.addChild(buttonText);
// Position button
button.y = yOffset;
// Button press animation
button.down = function (x, y, obj) {
tween(bg, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOutQuad
});
};
button.up = function (x, y, obj) {
tween(bg, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut
});
};
return button;
};
// Create buttons for the two game modes
var pvpButton = createButton('PLAYER VS PLAYER', 300);
var pvaiButton = createButton('PLAYER VS AI', 500);
// Add back button
var backButton = createButton('BACK', 1200);
// Add buttons to container
self.addChild(pvpButton);
self.addChild(pvaiButton);
self.addChild(backButton);
// Animate buttons with grow and shrink effect
function animateButton(button, delay) {
LK.setTimeout(function () {
tween(button, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(button, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
animateButton(button, 0);
}
});
}
});
}, delay);
}
// Start button animations with staggered delays
animateButton(pvpButton, 0);
animateButton(pvaiButton, 300);
animateButton(backButton, 600);
// Button functionality
pvpButton.up = function (x, y, obj) {
tween(pvpButton.children[0], {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Player vs Player mode - start game directly, disable AI
game.aiControlsRedTeam = false;
game.gameMode = 'pvp';
game.startGame();
}
});
};
pvaiButton.up = function (x, y, obj) {
tween(pvaiButton.children[0], {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
// Show difficulty selection for AI mode
game.showDifficultyWindow();
}
});
};
backButton.up = function (x, y, obj) {
tween(backButton.children[0], {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
game.showTitleScreen();
}
});
};
return self;
});
var GridCell = Container.expand(function () {
var self = Container.call(this);
// Cell properties
self.row = 0;
self.col = 0;
self.occupied = false;
self.occupiedBy = null;
self.highlighted = false;
self.highlightType = null;
// Create cell graphic
var cellGraphic = self.attachAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
// Highlighting methods
self.highlight = function (type) {
if (self.highlightAsset) {
self.removeChild(self.highlightAsset);
}
self.highlighted = true;
self.highlightType = type;
if (type === 'move') {
self.highlightAsset = self.attachAsset('highlight_move', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
} else if (type === 'attack') {
self.highlightAsset = self.attachAsset('highlight_attack', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
} else if (type === 'selected') {
self.highlightAsset = self.attachAsset('highlight_selected', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
}
};
self.clearHighlight = function () {
if (self.highlightAsset) {
self.removeChild(self.highlightAsset);
self.highlightAsset = null;
}
self.highlighted = false;
self.highlightType = null;
};
// Event handlers
self.down = function (x, y, obj) {
if (game.gameState === 'playerTurn') {
game.handleCellClick(self);
}
};
return self;
});
var SettingsScreen = Container.expand(function () {
var self = Container.call(this);
// Title
var titleText = new Text2('SETTINGS', {
size: 150,
fill: '#FFFFFF'
});
titleText.anchor.set(0.5, 0);
titleText.y = -2732 / 3;
self.addChild(titleText);
// Create toggle button
var createToggle = function createToggle(text, yPos, initialState, xPos) {
var container = new Container();
container.y = yPos;
container.x = xPos;
// Label
var label = new Text2(text, {
size: 80,
fill: '#FFFFFF'
});
label.anchor.set(0, 0.5);
container.addChild(label);
// Toggle background
var toggleBg = LK.getAsset('grid_cell_dark', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 100,
alpha: 0.8
});
toggleBg.x = 900;
container.addChild(toggleBg);
// Toggle indicator
var indicator = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
indicator.x = initialState ? 950 : 850;
indicator.y = 0;
container.addChild(indicator);
// State
container.state = initialState;
// Toggle functionality
toggleBg.down = function (x, y, obj) {
container.state = !container.state;
tween(indicator, {
x: container.state ? 950 : 850
}, {
duration: 200,
easing: tween.easeOutQuad
});
};
return container;
};
// Create settings controls
var soundToggle = createToggle('Sound Effects', 400, true, -500);
var musicToggle = createToggle('Music', 550, true, -500);
// Add controls to container
self.addChild(soundToggle);
self.addChild(musicToggle);
// Back button
var backButton = new Container();
var backBg = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
width: 500,
height: 120
});
var backText = new Text2('BACK', {
size: 80,
fill: '#000000'
});
backText.anchor.set(0.5, 0.5);
backButton.addChild(backBg);
backButton.addChild(backText);
backButton.x = 0;
backButton.y = 800;
self.addChild(backButton);
// Back button functionality
backButton.down = function (x, y, obj) {
tween(backBg, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOutQuad
});
};
// Add grow and shrink animation to back button
function animateBackButton() {
tween(backBg, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(backBg, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: animateBackButton
});
}
});
}
// Start the animation
animateBackButton();
backButton.up = function (x, y, obj) {
tween(backBg, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
game.showTitleScreen();
}
});
};
return self;
});
var TitleScreen = Container.expand(function () {
var self = Container.call(this);
// Logo image
var logo = LK.getAsset('game_logo', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -200
});
self.addChild(logo);
// Title text placeholder to maintain layout structure
var titleText = new Text2('', {
size: 200,
fill: '#FFFFFF'
});
titleText.anchor.set(0.5, 0.5);
self.addChild(titleText);
// Create buttons for different game modes
var createButton = function createButton(text, yOffset) {
var button = new Container();
// Button background
var bg = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
width: 800,
height: 150
});
button.addChild(bg);
// Button text
var buttonText = new Text2(text, {
size: 100,
fill: '#000000'
});
buttonText.anchor.set(0.5, 0.5);
button.addChild(buttonText);
// Position button
button.y = yOffset;
// Button hover/press effects
button.down = function (x, y, obj) {
tween(bg, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOutQuad
});
};
button.up = function (x, y, obj) {
tween(bg, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut
});
};
return button;
};
// Create menu buttons
var playButton = createButton('PLAY GAME', 100);
var tutorialButton = createButton('TUTORIAL', 300);
var settingsButton = createButton('SETTINGS', 500);
// Add buttons to container
self.addChild(playButton);
self.addChild(tutorialButton);
self.addChild(settingsButton);
// Animate buttons with grow and shrink effect
function animateButton(button, delay) {
LK.setTimeout(function () {
tween(button, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(button, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
animateButton(button, 0);
}
});
}
});
}, delay);
}
// Start button animations with staggered delays
animateButton(playButton, 0);
animateButton(tutorialButton, 300);
animateButton(settingsButton, 600);
// Button functionality
playButton.down = function (x, y, obj) {
tween(playButton.children[0], {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOutQuad
});
};
playButton.up = function (x, y, obj) {
tween(playButton.children[0], {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
game.showGameModeScreen();
}
});
};
tutorialButton.down = function (x, y, obj) {
tween(tutorialButton.children[0], {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOutQuad
});
};
tutorialButton.up = function (x, y, obj) {
tween(tutorialButton.children[0], {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
game.showTutorial();
}
});
};
settingsButton.down = function (x, y, obj) {
tween(settingsButton.children[0], {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOutQuad
});
};
settingsButton.up = function (x, y, obj) {
tween(settingsButton.children[0], {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
game.showSettings();
}
});
};
return self;
});
var TutorialScreen = Container.expand(function () {
var self = Container.call(this);
// Add background overlay for better readability
var bg = LK.getAsset('grid_cell_dark', {
anchorX: 0,
anchorY: 0,
width: 2048,
height: 2732,
alpha: 0.2
});
self.addChild(bg);
// Main container to allow scrolling
var scrollContainer = new Container();
self.scrollContainer = scrollContainer; // Expose scrollContainer as a property
self.addChild(scrollContainer);
// Title container with fancier styling
var titleContainer = new Container();
titleContainer.y = 80;
scrollContainer.addChild(titleContainer);
// Title background for emphasis
var titleBg = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
width: 1800,
height: 180,
alpha: 0.0
});
titleContainer.addChild(titleBg);
// Main title text
var titleText = new Text2('Tactical Kings: Tutorials', {
size: 120,
fill: '#FFFFFF'
});
titleText.anchor.set(0.5, 0.5);
titleContainer.addChild(titleText);
// Content creation helper function with visual aids
var createTutorialSection = function createTutorialSection(title, content, yPos, visualType) {
var section = new Container();
section.y = yPos;
// Section header background
var headerBg = LK.getAsset('highlight_move', {
anchorX: 0.5,
anchorY: 0.5,
width: 1600,
height: 120,
alpha: 0.6
});
headerBg.y = 50;
section.addChild(headerBg);
// Section title with better styling
var sectionTitle = new Text2(title, {
size: 90,
fill: '#FFFFFF'
});
sectionTitle.anchor.set(0.5, 0.5);
sectionTitle.y = 50;
section.addChild(sectionTitle);
// Content background for better readability
var contentBg = LK.getAsset('grid_cell_dark', {
anchorX: 0.5,
anchorY: 0,
width: 1800,
height: content.split('\n').length * 70 + 160,
// Dynamic height based on content
alpha: 0.0
});
contentBg.y = 120;
section.addChild(contentBg);
// Section content with improved formatting
var sectionContent = new Text2(content, {
size: 60,
fill: '#FFFFFF',
wordWrap: true,
wordWrapWidth: 1600
});
sectionContent.anchor.set(0.5, 0);
sectionContent.y = 140;
section.addChild(sectionContent);
// Visual aid based on section type
if (visualType === 'core') {
// Game objective visual - chessboard with highlighted king
var boardVisual = new Container();
boardVisual.y = contentBg.height + 750;
// Mini grid visual
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
var cell = LK.getAsset((i + j) % 2 === 0 ? 'grid_cell' : 'grid_cell_dark', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120,
alpha: 0.7
});
cell.x = (j - 1) * 130;
cell.y = (i - 1) * 130;
boardVisual.addChild(cell);
// Add highlight to center to show objective
if (i === 1 && j === 1) {
var highlight = LK.getAsset('highlight_selected', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120,
alpha: 0.7
});
cell.addChild(highlight);
// Add king piece
var king = LK.getAsset('king_red', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
cell.addChild(king);
}
}
}
section.addChild(boardVisual);
// Adjust content background height to include visual
contentBg.height += 400;
} else if (visualType === 'characters') {
// Character roster visual
var charactersVisual = new Container();
charactersVisual.y = contentBg.height + 1000;
// Create visual for each character type
var characters = [{
type: 'knight_blue',
name: 'Knight',
x: -600
}, {
type: 'archer_blue',
name: 'Archer',
x: -300
}, {
type: 'wizard_blue',
name: 'Mage',
x: 0
}, {
type: 'warrior_blue',
name: 'Tank',
x: 300
}, {
type: 'king_blue',
name: 'King',
x: 600
}];
characters.forEach(function (_char) {
var charContainer = new Container();
charContainer.x = _char.x;
// Background circle
var bg = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
width: 180,
height: 180,
alpha: 0.5
});
charContainer.addChild(bg);
// Character icon
var icon = LK.getAsset(_char.type, {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 150
});
charContainer.addChild(icon);
// Character name
var name = new Text2(_char.name, {
size: 40,
fill: '#FFFFFF'
});
name.anchor.set(0.5, 0);
name.y = 100;
charContainer.addChild(name);
charactersVisual.addChild(charContainer);
});
section.addChild(charactersVisual);
// Adjust content background height to include visual
contentBg.height += 350;
} else if (visualType === 'tactics') {
// Tactics visual - counter relationship diagram
var tacticsVisual = new Container();
tacticsVisual.y = contentBg.height + 1000;
// Create counter relationship triangle
var counterPositions = [{
type: 'knight_blue',
name: 'Knight',
x: -300,
y: 0
}, {
type: 'archer_blue',
name: 'Archer',
x: 300,
y: 0
}, {
type: 'wizard_blue',
name: 'Mage',
x: 0,
y: -260
}];
// Draw relationship lines first (so they appear behind icons)
var relationshipLines = new Container();
// Draw triangle connecting the three unit types
var p1 = counterPositions[0];
var p2 = counterPositions[1];
var p3 = counterPositions[2];
// Create line assets as rectangles
var line1 = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
width: Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)),
height: 10,
alpha: 0.6
});
line1.x = (p1.x + p2.x) / 2;
line1.y = (p1.y + p2.y) / 2;
line1.rotation = Math.atan2(p2.y - p1.y, p2.x - p1.x);
relationshipLines.addChild(line1);
var line2 = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
width: Math.sqrt(Math.pow(p3.x - p2.x, 2) + Math.pow(p3.y - p2.y, 2)),
height: 10,
alpha: 0.6
});
line2.x = (p2.x + p3.x) / 2;
line2.y = (p2.y + p3.y) / 2;
line2.rotation = Math.atan2(p3.y - p2.y, p3.x - p2.x);
relationshipLines.addChild(line2);
var line3 = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
width: Math.sqrt(Math.pow(p1.x - p3.x, 2) + Math.pow(p1.y - p3.y, 2)),
height: 10,
alpha: 0.6
});
line3.x = (p3.x + p1.x) / 2;
line3.y = (p3.y + p1.y) / 2;
line3.rotation = Math.atan2(p1.y - p3.y, p1.x - p3.x);
relationshipLines.addChild(line3);
tacticsVisual.addChild(relationshipLines);
// Add character icons with labels
counterPositions.forEach(function (pos) {
var unitContainer = new Container();
unitContainer.x = pos.x;
unitContainer.y = pos.y;
// Background circle
var bg = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
width: 180,
height: 180,
alpha: 0.5
});
unitContainer.addChild(bg);
// Unit icon
var icon = LK.getAsset(pos.type, {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 150
});
unitContainer.addChild(icon);
// Unit name
var name = new Text2(pos.name, {
size: 40,
fill: '#FFFFFF'
});
name.anchor.set(0.5, 0);
name.y = 100;
unitContainer.addChild(name);
tacticsVisual.addChild(unitContainer);
});
section.addChild(tacticsVisual);
// Adjust content background height to include visual
contentBg.height += 500;
}
return section;
};
// Define content with better formatting and line breaks for readability
var coreContent = "The objective of Tactical Kings is to capture the opponent's King while protecting your own.\n\n" + "• Turn-Based Gameplay: Players take turns moving units and attacking enemies\n\n" + "• Attack Zones: Each unit type has unique movement patterns and attack ranges\n\n" + "• Strategic Planning: Control territory and set up favorable exchanges\n\n" + "• Win Condition: Eliminate the enemy King to win the match";
var characterContent = "• Knight: Moves horizontally and vertically up to 3 tiles. Attacks along movement paths. Strong against Archers.\n\n" + "• Archer: Moves sideways freely and 1 tile forward. Attacks up to 4 tiles ahead in a straight line. Counters Mages.\n\n" + "• Mage: Moves 2 tiles in any direction. Has area-effect attacks up to 3 tiles away, damaging adjacent enemies. Effective against Knights.\n\n" + "• Tank: Slow movement but high health. Can instantly defeat non-King units in front of it.\n\n" + "• King: Can teleport freely to any unoccupied cell on the board. Attacks 1 tile forward. Must be protected at all costs - losing it means defeat.";
var tacticsContent = "• Unit Counters: Use the right units against enemies:\n - Knights beat Archers\n - Archers beat Mages\n - Mages beat Knights\n\n" + "• Territory Control: Position units to control key areas of the board\n\n" + "• King Protection: Always keep defensive units near your King\n\n" + "• Tactical Retreats: Sometimes moving away from danger is better than attacking\n\n" + "• Sacrifice Strategies: Trading pieces can be advantageous if it creates a winning position";
// Calculate vertical spacing between sections
var ySpacing = 340;
// Add tutorial sections with improved spacing and visual aids - adjusted positioning
var coreConceptsSection = createTutorialSection("Core Concepts", coreContent, ySpacing, 'core');
var characterGuidesSection = createTutorialSection("Character Guides", characterContent, ySpacing + 1800, 'characters');
var advancedTacticsSection = createTutorialSection("Advanced Tactics", tacticsContent, ySpacing + 3800, 'tactics');
// Add sections to scroll container
scrollContainer.addChild(coreConceptsSection);
scrollContainer.addChild(characterGuidesSection);
scrollContainer.addChild(advancedTacticsSection);
// Center the scroll container horizontally and apply offset
scrollContainer.x = 1024 + tutorialOffset.x;
// Position the content vertically to avoid overlapping with offset
scrollContainer.y = 0 + tutorialOffset.y;
// Update content positioning
titleContainer.y = 150;
// Adjust section spacing to prevent overlapping
var ySpacing = 450; // Increased spacing between sections
// Back button with improved visibility
var backButton = new Container();
var backBg = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
width: 300,
height: 120
});
var backText = new Text2('BACK', {
size: 70,
fill: '#000000'
});
backText.anchor.set(0.5, 0.5);
backButton.addChild(backBg);
backButton.addChild(backText);
backButton.x = 200 + tutorialOffset.x; // Position in bottom-left with offset
backButton.y = 2500 + tutorialOffset.y; // Adjusted position with offset
self.addChild(backButton);
// Back button functionality
backButton.down = function (x, y, obj) {
tween(backBg, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOutQuad
});
};
// Add grow and shrink animation to back button
function animateBackButton() {
tween(backBg, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(backBg, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: animateBackButton
});
}
});
}
// Start the animation
animateBackButton();
backButton.up = function (x, y, obj) {
tween(backBg, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
game.showTitleScreen();
}
});
};
// Add scroll indicator to show users the screen is scrollable
var scrollIndicator = new Container();
var arrowUp = LK.getAsset('highlight_attack', {
anchorX: 0.5,
anchorY: 0,
width: 50,
height: 30,
alpha: 0.8
});
arrowUp.y = -35;
scrollIndicator.addChild(arrowUp);
var arrowDown = LK.getAsset('highlight_attack', {
anchorX: 0.5,
anchorY: 1,
width: 50,
height: 30,
alpha: 0.8
});
arrowDown.y = 35;
arrowDown.rotation = Math.PI;
scrollIndicator.addChild(arrowDown);
var indicatorText = new Text2("Scroll", {
size: 40,
fill: '#FFFFFF'
});
indicatorText.anchor.set(0.5, 0.5);
scrollIndicator.addChild(indicatorText);
scrollIndicator.x = 1950;
scrollIndicator.y = 1366;
self.addChild(scrollIndicator);
// Animate scroll indicator to attract attention
function animateScrollIndicator() {
tween(scrollIndicator, {
alpha: 0.3
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(scrollIndicator, {
alpha: 1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: animateScrollIndicator
});
}
});
}
animateScrollIndicator();
// Enhanced scroll functionality with momentum
var scrollY = 0;
self.scrollY = scrollY; // Expose scrollY as a property
var scrollVelocity = 0;
var maxScroll = 4000; // Reduced to prevent scrolling past content
var isDragging = false;
var lastY = 0;
var lastTime = 0;
self.down = function (x, y, obj) {
lastY = y;
lastTime = Date.now();
scrollVelocity = 0;
isDragging = true;
};
self.move = function (x, y, obj) {
if (isDragging) {
var currentTime = Date.now();
var deltaTime = currentTime - lastTime;
var deltaY = y - lastY;
// Calculate velocity (pixels per millisecond)
if (deltaTime > 0) {
scrollVelocity = deltaY / deltaTime * 7.5; // Scale factor for better feel
}
scrollY -= deltaY;
self.scrollY = scrollY; // Update property
// Clamp scrolling with elastic effect
if (scrollY < 0) {
scrollY = scrollY * 0.5; // Resistance when pulling past top
} else if (scrollY > maxScroll) {
scrollY = maxScroll + (scrollY - maxScroll) * 0.5; // Resistance when pulling past bottom
}
// Apply scroll with offset vector
scrollContainer.y = -scrollY + tutorialOffset.y;
lastY = y;
lastTime = currentTime;
}
};
self.up = function (x, y, obj) {
isDragging = false;
// Apply elastic snapback if pulled past boundaries
if (scrollY < 0) {
tween(scrollContainer, {
y: 0
}, {
duration: 300,
easing: tween.elasticOut
});
scrollY = 0;
self.scrollY = scrollY; // Update property
scrollVelocity = 0;
} else if (scrollY > maxScroll) {
tween(scrollContainer, {
y: -maxScroll
}, {
duration: 300,
easing: tween.elasticOut
});
scrollY = maxScroll;
self.scrollY = scrollY; // Update property
scrollVelocity = 0;
}
// Apply momentum scrolling
if (Math.abs(scrollVelocity) > 0.1) {
applyScrollMomentum();
}
};
// Handle momentum scrolling
function applyScrollMomentum() {
// Apply velocity with decay
scrollY -= scrollVelocity * 10;
scrollVelocity *= 0.95; // Decay factor
// Boundary checking
if (scrollY < 0) {
scrollY = 0;
self.scrollY = scrollY; // Update property
scrollVelocity = 0;
} else if (scrollY > maxScroll) {
scrollY = maxScroll;
self.scrollY = scrollY; // Update property
scrollVelocity = 0;
}
// Apply scroll with offset vector
scrollContainer.y = -scrollY + tutorialOffset.y;
// Continue animation if velocity is significant
if (Math.abs(scrollVelocity) > 0.1) {
LK.setTimeout(applyScrollMomentum, 16); // ~60fps
}
}
return self;
});
var Unit = Container.expand(function () {
var self = Container.call(this);
// Unit properties
self.type = "";
self.team = "";
self.row = 0;
self.col = 0;
self.isKing = false;
self.alive = true;
self.selected = false;
self.health = 100; // Base health for all units
self.attackStrength = 20; // Base attack strength for all units
// Initialize with type and team
self.init = function (type, team, row, col) {
self.type = type;
self.team = team;
self.row = row;
self.col = col;
self.isKing = type === 'king';
// Set different health and attack values based on unit type
switch (type) {
case 'king':
self.health = 250;
self.attackStrength = 35;
self.moveRange = 1; // Can move 1 tile in any direction
self.attackRange = 1; // Can attack 1 tile forward
break;
case 'warrior':
self.health = 280;
self.attackStrength = 45;
self.moveRange = 1; // Can move 1 tile in any direction
self.attackRange = 1; // Can attack 1 tile forward
break;
case 'knight':
self.health = 150;
self.attackStrength = 40;
self.moveRange = 3; // Can move up to 3 tiles horizontally or vertically
self.attackRange = 2; // Can attack in movement range
break;
case 'wizard':
self.health = 90;
self.attackStrength = 60;
self.moveRange = 2; // Can move up to 2 tiles in any direction
self.attackRange = 3; // Attack 6 tiles ahead and surrounding
break;
case 'archer':
self.health = 100;
self.attackStrength = 50;
self.moveRange = 1; // Can move sideways freely, 1 tile forward
self.attackRange = 4; // Can attack up to 8 tiles ahead
break;
}
var assetId = type + '_' + team;
self.unitGraphic = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
return self;
};
// Movement validation
self.canMoveTo = function (targetRow, targetCol) {
// Base movement logic to be overridden by specific unit types
return false;
};
// Attack validation
self.canAttack = function (targetRow, targetCol) {
// Base attack logic to be overridden by specific unit types
return false;
};
// Get possible move cells
self.getPossibleMoves = function (grid) {
var moves = [];
for (var r = 0; r < grid.length; r++) {
for (var c = 0; c < grid[r].length; c++) {
if (this.canMoveTo(r, c) && !grid[r][c].occupied) {
moves.push({
row: r,
col: c
});
}
}
}
return moves;
};
// Get possible attack cells
self.getPossibleAttacks = function (grid) {
var attacks = [];
for (var r = 0; r < grid.length; r++) {
for (var c = 0; c < grid[r].length; c++) {
if (this.canAttack(r, c) && grid[r][c].occupied && grid[r][c].occupiedBy.team !== this.team) {
attacks.push({
row: r,
col: c
});
}
}
}
return attacks;
};
// Move unit to new position
self.moveTo = function (targetRow, targetCol) {
self.row = targetRow;
self.col = targetCol;
};
// Select unit
self.select = function () {
self.selected = true;
self.unitGraphic.alpha = 1.0;
};
// Deselect unit
self.deselect = function () {
self.selected = false;
self.unitGraphic.alpha = 0.8;
};
return self;
});
var Wizard = Unit.expand(function () {
var self = Unit.call(this);
// Override movement validation
self.canMoveTo = function (targetRow, targetCol) {
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
// Wizard can move diagonally or in straight lines
return rowDiff <= 2 && colDiff <= 2;
};
// Override attack validation
self.canAttack = function (targetRow, targetCol) {
// Wizard can attack up to 3 tiles away in any direction
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
// Total distance (Manhattan distance) should be less than or equal to attack range
return rowDiff + colDiff <= self.attackRange && !(rowDiff === 0 && colDiff === 0);
};
// Wizards do area damage
self.isAreaAttacker = true;
return self;
});
var Warrior = Unit.expand(function () {
var self = Unit.call(this);
// Override movement validation
self.canMoveTo = function (targetRow, targetCol) {
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
// Warrior can only move up to moveRange tile in any direction
return rowDiff <= self.moveRange && colDiff <= self.moveRange && !(rowDiff === 0 && colDiff === 0);
};
// Override attack validation
self.canAttack = function (targetRow, targetCol) {
// Instant-kill attack (1 tile forward)
if (self.team === 'blue') {
return targetRow === self.row - self.attackRange && targetCol === self.col;
} else {
return targetRow === self.row + self.attackRange && targetCol === self.col;
}
};
// Warriors have instant kill ability
self.hasInstantKill = true;
return self;
});
var Knight = Unit.expand(function () {
var self = Unit.call(this);
// Override movement validation
self.canMoveTo = function (targetRow, targetCol) {
// Knight moves straight or sideways (1-3 tiles)
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
// Can move horizontally or vertically up to moveRange tiles
return rowDiff === 0 && colDiff > 0 && colDiff <= self.moveRange || colDiff === 0 && rowDiff > 0 && rowDiff <= self.moveRange;
};
// Override attack validation
self.canAttack = function (targetRow, targetCol) {
// Knight can attack along the same paths as movement
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
return rowDiff === 0 && colDiff > 0 && colDiff <= self.attackRange || colDiff === 0 && rowDiff > 0 && rowDiff <= self.attackRange;
};
return self;
});
var King = Unit.expand(function () {
var self = Unit.call(this);
// Override movement validation
self.canMoveTo = function (targetRow, targetCol) {
// King moves like a chess queen but limited distance
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
// Can move in any direction but maximum moveRange spaces
return rowDiff <= self.moveRange && colDiff <= self.moveRange && !(rowDiff === 0 && colDiff === 0);
};
// Override attack validation
self.canAttack = function (targetRow, targetCol) {
// King only attacks forward based on team direction and attack range
var forwardRow = self.team === 'blue' ? self.row - self.attackRange : self.row + self.attackRange;
return targetRow === forwardRow && targetCol === self.col;
};
return self;
});
var Archer = Unit.expand(function () {
var self = Unit.call(this);
// Override movement validation
self.canMoveTo = function (targetRow, targetCol) {
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
// Can move sideways freely, but only moveRange tiles forward
if (self.team === 'blue') {
return colDiff > 0 && rowDiff === 0 ||
// sideways
targetRow === self.row - self.moveRange && colDiff === 0; // forward
} else {
return colDiff > 0 && rowDiff === 0 ||
// sideways
targetRow === self.row + self.moveRange && colDiff === 0; // forward
}
};
// Override attack validation
self.canAttack = function (targetRow, targetCol) {
// Can attack any enemy in the line attackRange tiles forward
if (self.team === 'blue') {
// For blue team, attack any cell in the same column forward (up) within attack range
return targetCol === self.col && targetRow < self.row && self.row - targetRow <= self.attackRange;
} else {
// For red team, attack any cell in the same column forward (down) within attack range
return targetCol === self.col && targetRow > self.row && targetRow - self.row <= self.attackRange;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Import tween plugin
// Game constants
// Background image asset
var GRID_ROWS = 10;
var GRID_COLS = 5;
var CELL_SIZE = 250; // Significantly increased cell size
var GRID_PADDING_X = (2048 - GRID_COLS * CELL_SIZE) / 2;
var GRID_PADDING_Y = (2732 - GRID_ROWS * CELL_SIZE) / 2;
// Game variables
var grid = [];
var units = [];
var selectedUnit = null;
var gameState = 'playerTurn'; // playerTurn, aiTurn, gameOver
var ai = new AIPlayer();
var statusText = new Text2('Player Turn', {
size: 120,
// Significantly increased text size
fill: 0xFFFFFF
});
var gameState = 'mainMenu'; // mainMenu, tutorial, settings, player1Turn, player2Turn, aiTurn, gameOver
var gameMode = null; // 'pvp' for player vs player, 'pvai' for player vs AI
// Screen instances
var titleScreen = new TitleScreen();
var tutorialScreen = new TutorialScreen();
var settingsScreen = new SettingsScreen();
var gameModeScreen = new GameModeScreen();
var difficultyWindow = new DifficultySelectWindow();
// Make settings screen accessible to the game object
game.settingsScreen = settingsScreen;
// Position screens at center
titleScreen.x = 1024;
titleScreen.y = 1366;
tutorialScreen.x = 0;
tutorialScreen.y = 0;
settingsScreen.x = 1024;
settingsScreen.y = 1366;
game.showTitleScreen();
// Initialize the game board
function initializeGame() {
// Add background image first so it's behind other elements
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChild(background);
// Create grid
for (var row = 0; row < GRID_ROWS; row++) {
grid[row] = [];
for (var col = 0; col < GRID_COLS; col++) {
var cell = new GridCell();
cell.row = row;
cell.col = col;
cell.x = GRID_PADDING_X + col * CELL_SIZE + CELL_SIZE / 2;
cell.y = GRID_PADDING_Y + row * CELL_SIZE + CELL_SIZE / 2;
// Alternate cell colors
if ((row + col) % 2 === 0) {
cell.removeChild(cell.children[0]); // Remove default graphic
cell.attachAsset('grid_cell_dark', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
}
grid[row][col] = cell;
game.addChild(cell);
}
}
// Create units for both teams
createUnits();
// Add status text
statusText.anchor.set(0.5, 0);
statusText.x = 2048 / 2;
statusText.y = 50;
LK.gui.top.addChild(statusText);
}
// Create all units for both teams
function createUnits() {
// Blue team (player) - bottom of the board
// Formation: Knight, Warrior, Warrior, Warrior, Knight (row 8)
// Formation: Archer, Mage, King, Mage, Archer (row 9)
createUnit('knight', 'blue', 8, 0);
createUnit('warrior', 'blue', 8, 1);
createUnit('warrior', 'blue', 8, 2);
createUnit('warrior', 'blue', 8, 3);
createUnit('knight', 'blue', 8, 4);
createUnit('archer', 'blue', 9, 0);
createUnit('wizard', 'blue', 9, 1);
createUnit('king', 'blue', 9, 2);
createUnit('wizard', 'blue', 9, 3);
createUnit('archer', 'blue', 9, 4);
// Red team (AI) - top of the board
// Formation: Knight, Warrior, Warrior, Warrior, Knight (row 1)
// Formation: Archer, Mage, King, Mage, Archer (row 0)
createUnit('knight', 'red', 1, 0);
createUnit('warrior', 'red', 1, 1);
createUnit('warrior', 'red', 1, 2);
createUnit('warrior', 'red', 1, 3);
createUnit('knight', 'red', 1, 4);
createUnit('archer', 'red', 0, 0);
createUnit('wizard', 'red', 0, 1);
createUnit('king', 'red', 0, 2);
createUnit('wizard', 'red', 0, 3);
createUnit('archer', 'red', 0, 4);
}
// Create a single unit
function createUnit(type, team, row, col) {
var unit;
// Create the appropriate unit type
switch (type) {
case 'king':
unit = new King().init(type, team, row, col);
break;
case 'knight':
unit = new Knight().init(type, team, row, col);
break;
case 'archer':
unit = new Archer().init(type, team, row, col);
break;
case 'wizard':
unit = new Wizard().init(type, team, row, col);
break;
case 'warrior':
unit = new Warrior().init(type, team, row, col);
break;
}
// Position unit
unit.x = grid[row][col].x;
unit.y = grid[row][col].y;
unit.unitGraphic.alpha = 0.8;
// Add health display
unit.healthText = new Text2(unit.health.toString(), {
size: 50,
fill: 0xFFD700 // Golden yellow color for better visibility
});
unit.healthText.anchor.set(0.5, 0.5);
unit.healthText.y = unit.unitGraphic.height / 2 + 10;
unit.addChild(unit.healthText);
// Add subtle entry animation
unit.unitGraphic.scale.set(0.1);
unit.unitGraphic.alpha = 0.3;
// Add unit to game
game.addChild(unit);
units.push(unit);
// Mark cell as occupied
grid[row][col].occupied = true;
grid[row][col].occupiedBy = unit;
// Add click handler for player units
if (team === 'blue') {
unit.down = function (x, y, obj) {
if (game.gameState === 'playerTurn') {
selectUnit(unit);
}
};
}
// Animate unit entry with enhanced effect
tween(unit.unitGraphic, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.8
}, {
duration: 600,
easing: tween.elasticOut,
delay: 100 * (row + col) % 5 // Staggered appearance for visual interest
});
return unit;
}
// Handle cell click
game.handleCellClick = function (cell) {
if (!selectedUnit || game.gameState !== 'playerTurn') {
return;
}
// If cell is highlighted for movement
if (cell.highlighted && cell.highlightType === 'move') {
moveSelectedUnit(cell.row, cell.col);
}
// If cell is highlighted for attack
else if (cell.highlighted && cell.highlightType === 'attack') {
attackCell(cell);
}
};
// Select a unit
function selectUnit(unit) {
// Can only select your own units
if (unit.team !== 'blue' && gameState === 'playerTurn') {
return;
}
if (unit.team !== 'red' && gameState === 'aiTurn') {
return;
}
// Deselect previous unit if there was one
if (selectedUnit) {
selectedUnit.deselect();
clearAllHighlights();
}
// Select new unit
selectedUnit = unit;
selectedUnit.select();
// Enhanced selection animation with glow effect
tween(selectedUnit.unitGraphic, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1.0
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
tween(selectedUnit.unitGraphic, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.9
}, {
duration: 200,
easing: tween.easeOutQuad
});
}
});
// Highlight cell under selected unit
grid[unit.row][unit.col].highlight('selected');
// Highlight possible moves with delay for better visual feedback
var possibleMoves = unit.getPossibleMoves(grid);
for (var i = 0; i < possibleMoves.length; i++) {
(function (index) {
LK.setTimeout(function () {
if (selectedUnit === unit) {
// Only highlight if still selected
var move = possibleMoves[index];
grid[move.row][move.col].highlight('move');
}
}, index * 30);
})(i);
}
// Highlight possible attacks with delay
var possibleAttacks = unit.getPossibleAttacks(grid);
for (var i = 0; i < possibleAttacks.length; i++) {
(function (index) {
LK.setTimeout(function () {
if (selectedUnit === unit) {
// Only highlight if still selected
var attack = possibleAttacks[index];
grid[attack.row][attack.col].highlight('attack');
}
}, (possibleMoves.length + index) * 30);
})(i);
}
}
// Move selected unit to new position
function moveSelectedUnit(targetRow, targetCol) {
if (!selectedUnit) {
return;
}
// Update grid occupation
grid[selectedUnit.row][selectedUnit.col].occupied = false;
grid[selectedUnit.row][selectedUnit.col].occupiedBy = null;
// Move unit with animation
selectedUnit.moveTo(targetRow, targetCol);
// Animate movement with tween
var targetX = grid[targetRow][targetCol].x;
var targetY = grid[targetRow][targetCol].y;
tween(selectedUnit, {
x: targetX,
y: targetY
}, {
duration: 300,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
// Update grid occupation at new position
grid[targetRow][targetCol].occupied = true;
grid[targetRow][targetCol].occupiedBy = selectedUnit;
// Deselect unit and clear highlights
selectedUnit.deselect();
clearAllHighlights();
selectedUnit = null;
// Check if any attacks are possible after movement
checkForAutoAttack();
// Switch turns
endTurn();
}
});
}
// Attack a cell
function attackCell(cell) {
if (!selectedUnit || !cell.occupied || cell.occupiedBy.team === selectedUnit.team) {
return;
}
var targetUnit = cell.occupiedBy;
// Animate the attacker - "lunge" toward target
var originalX = selectedUnit.x;
var originalY = selectedUnit.y;
var directionX = targetUnit.x - selectedUnit.x;
var directionY = targetUnit.y - selectedUnit.y;
var distance = Math.sqrt(directionX * directionX + directionY * directionY);
var normalizedX = directionX / distance * 30; // small lunge
var normalizedY = directionY / distance * 30;
// Enhanced attack animation sequence
tween(selectedUnit, {
x: selectedUnit.x + normalizedX * 1.5,
// More pronounced lunge
y: selectedUnit.y + normalizedY * 1.5
}, {
duration: 200,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
// More dramatic visual flash on target
LK.effects.flashObject(targetUnit, 0xff0000, 400);
// Return attacker to original position
tween(selectedUnit, {
x: originalX,
y: originalY
}, {
duration: 200,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
// Check for critical damage
var isCritical = isCriticalDamage(selectedUnit.type, targetUnit.type);
var damageMultiplier = isCritical ? getCriticalDamageMultiplier() : 1.0;
var actualDamage = Math.floor(selectedUnit.attackStrength * damageMultiplier);
// Perform attack after animation
if (selectedUnit.hasInstantKill && !targetUnit.isKing) {
// Warrior's instant kill works on all enemies except kings
removeUnit(targetUnit);
} else if (targetUnit.isKing) {
// King only dies in one hit if attacked by a warrior
if (selectedUnit.type === 'warrior') {
removeUnit(targetUnit);
} else {
// Apply normal damage to king
var isCritical = isCriticalDamage(selectedUnit.type, targetUnit.type);
var damageMultiplier = isCritical ? getCriticalDamageMultiplier() : 1.0;
var actualDamage = Math.floor(selectedUnit.attackStrength * damageMultiplier);
targetUnit.health -= actualDamage;
targetUnit.healthText.setText(targetUnit.health.toString());
if (targetUnit.health <= 0) {
removeUnit(targetUnit);
}
}
} else if (selectedUnit.isAreaAttacker) {
// Area attack affects target and adjacent units of opposite team
var adjacentCells = getAdjacentCells(cell.row, cell.col);
// Apply damage instead of instant kill
targetUnit.health -= actualDamage;
targetUnit.healthText.setText(targetUnit.health.toString());
// Show critical hit effect if applicable
if (isCritical) {
var critEffect = new CriticalDamageEffect().init(actualDamage, targetUnit.x, targetUnit.y - 50);
game.addChild(critEffect);
// Add stronger visual feedback
tween(targetUnit, {
scaleX: 0.8,
scaleY: 0.8,
rotation: 0.2
}, {
duration: 100,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
tween(targetUnit, {
scaleX: 1.0,
scaleY: 1.0,
rotation: 0
}, {
duration: 200,
easing: tween.elasticOut
});
}
});
}
if (targetUnit.health <= 0) {
removeUnit(targetUnit);
}
for (var i = 0; i < adjacentCells.length; i++) {
var adjCell = adjacentCells[i];
if (adjCell.occupied && adjCell.occupiedBy.team !== selectedUnit.team) {
var adjTargetUnit = adjCell.occupiedBy;
var adjDamage = Math.floor(actualDamage / 2); // Half damage for adjacent units
adjTargetUnit.health -= adjDamage;
adjTargetUnit.healthText.setText(adjTargetUnit.health.toString());
if (adjTargetUnit.health <= 0) {
removeUnit(adjTargetUnit);
}
}
}
} else {
// Normal attack
targetUnit.health -= actualDamage;
targetUnit.healthText.setText(targetUnit.health.toString());
// Show critical hit effect if applicable
if (isCritical) {
var critEffect = new CriticalDamageEffect().init(actualDamage, targetUnit.x, targetUnit.y - 50);
game.addChild(critEffect);
// Add stronger visual feedback for critical hit
tween(targetUnit, {
scaleX: 0.7,
scaleY: 0.7,
rotation: 0.3
}, {
duration: 100,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
tween(targetUnit, {
scaleX: 1.0,
scaleY: 1.0,
rotation: 0
}, {
duration: 300,
easing: tween.elasticOut
});
}
});
}
if (targetUnit.health <= 0) {
removeUnit(targetUnit);
}
}
// Deselect unit and clear highlights
selectedUnit.deselect();
clearAllHighlights();
selectedUnit = null;
// Switch turns
endTurn();
}
});
}
});
}
// Check if auto-attack is possible (for units that moved next to enemies)
function checkForAutoAttack() {
// Not implemented in this version - all attacks are chosen by player
}
// Get adjacent cells
function getAdjacentCells(row, col) {
var adjacent = [];
var directions = [{
r: -1,
c: 0
}, {
r: 1,
c: 0
}, {
r: 0,
c: -1
}, {
r: 0,
c: 1
}, {
r: -1,
c: -1
}, {
r: -1,
c: 1
}, {
r: 1,
c: -1
}, {
r: 1,
c: 1
}];
for (var i = 0; i < directions.length; i++) {
var newRow = row + directions[i].r;
var newCol = col + directions[i].c;
if (newRow >= 0 && newRow < GRID_ROWS && newCol >= 0 && newCol < GRID_COLS) {
adjacent.push(grid[newRow][newCol]);
}
}
return adjacent;
}
// Remove a unit from the game
function removeUnit(unit) {
// Clear grid cell
grid[unit.row][unit.col].occupied = false;
grid[unit.row][unit.col].occupiedBy = null;
// Remove from units array
var index = units.indexOf(unit);
if (index > -1) {
units.splice(index, 1);
}
// Remove from display
game.removeChild(unit);
// Check for game over if a king was killed
if (unit.isKing) {
if (unit.team === 'blue') {
endGame('red');
} else {
endGame('blue');
}
}
}
// Clear all highlights from the grid
function clearAllHighlights() {
for (var row = 0; row < GRID_ROWS; row++) {
for (var col = 0; col < GRID_COLS; col++) {
grid[row][col].clearHighlight();
}
}
}
// End the current turn
function endTurn() {
if (gameState === 'playerTurn') {
gameState = 'aiTurn';
statusText.setText('AI Turn');
// Execute AI turn with delay for better visibility
LK.setTimeout(function () {
if (ai.makeMove()) {
// AI successfully made a move, turn will be ended in the attack/move handlers
} else {
// AI couldn't make a move, switch back to player
gameState = 'playerTurn';
statusText.setText('Player Turn');
}
}, 500); // Small delay for better user experience
} else if (gameState === 'aiTurn') {
gameState = 'playerTurn';
statusText.setText('Player Turn');
}
}
// End the game with a winner
function endGame(winner) {
gameState = 'gameOver';
if (winner === 'blue') {
statusText.setText('You Win!');
LK.showYouWin();
} else {
statusText.setText('Game Over');
LK.showGameOver();
}
}
// Helper functions to determine critical damage relationships
function isCriticalDamage(attackerType, targetType) {
// Knights are strong against archers
if (attackerType === 'knight' && targetType === 'archer') {
return true;
}
// Archers are strong against mages/wizards
if (attackerType === 'archer' && targetType === 'wizard') {
return true;
}
// Mages/wizards are strong against knights
if (attackerType === 'wizard' && targetType === 'knight') {
return true;
}
return false;
}
// Make isCriticalDamage available to the game object
game.isCriticalDamage = isCriticalDamage;
function getCriticalDamageMultiplier() {
// Critical damage is 2x normal damage
return 2.0;
}
// Helper functions
game.getTeamUnits = function (team) {
return units.filter(function (unit) {
return unit.team === team;
});
};
game.getKing = function (team) {
for (var i = 0; i < units.length; i++) {
if (units[i].isKing && units[i].team === team) {
return units[i];
}
}
return null;
};
// Function to update tutorial position
game.updateTutorialPosition = function (x, y) {
// Update the offset vector
tutorialOffset.x = x || tutorialOffset.x;
tutorialOffset.y = y || tutorialOffset.y;
// Update positions if tutorial is active
if (tutorialScreen && tutorialScreen.parent) {
// Update main container position
tutorialScreen.scrollContainer.x = 1024 + tutorialOffset.x;
tutorialScreen.scrollContainer.y = -tutorialScreen.scrollY + tutorialOffset.y;
// Update back button position
var backButton = tutorialScreen.children[tutorialScreen.children.length - 2]; // Back button is second to last child
backButton.x = 200 + tutorialOffset.x;
backButton.y = 2500 + tutorialOffset.y;
// Update scroll indicator position
var scrollIndicator = tutorialScreen.children[tutorialScreen.children.length - 1]; // Scroll indicator is last child
scrollIndicator.x = 1950 + tutorialOffset.x;
scrollIndicator.y = 1366 + tutorialOffset.y;
}
};
// Show difficulty selection window
game.showDifficultyWindow = function () {
game.addChild(difficultyWindow);
};
// Close difficulty selection window
game.closeDifficultyWindow = function () {
game.removeChild(difficultyWindow);
};
// Screen management functions
game.showTitleScreen = function () {
// Clear any existing game elements
game.removeAllGameElements();
// Show title screen
game.addChild(titleScreen);
game.removeChild(tutorialScreen);
game.removeChild(settingsScreen);
game.removeChild(gameModeScreen);
// Hide status text during main menu
statusText.visible = false;
// Set game state
gameState = 'mainMenu';
// Animate title to top third of screen
var logo = titleScreen.children[0];
var titleText = titleScreen.children[1];
// Animate logo
tween(logo, {
y: -500
}, {
duration: 800,
easing: tween.elasticOut
});
// Animate title text
tween(titleText, {
y: 800
}, {
duration: 800,
easing: tween.elasticOut
});
};
game.showTutorial = function () {
// Show tutorial screen
game.removeChild(titleScreen);
game.addChild(tutorialScreen);
game.removeChild(settingsScreen);
game.removeChild(gameModeScreen);
// Set game state
gameState = 'tutorial';
};
game.showSettings = function () {
// Show settings screen
game.removeChild(titleScreen);
game.removeChild(tutorialScreen);
game.removeChild(gameModeScreen);
game.addChild(settingsScreen);
// Set game state
gameState = 'settings';
};
game.showGameModeScreen = function () {
// Show game mode selection screen
game.removeChild(titleScreen);
game.removeChild(tutorialScreen);
game.removeChild(settingsScreen);
game.addChild(gameModeScreen);
// Center the screen
gameModeScreen.x = 1024;
gameModeScreen.y = 1366;
// Set game state
gameState = 'gameMode';
};
game.startGame = function () {
// Clear screens
game.removeChild(titleScreen);
game.removeChild(tutorialScreen);
game.removeChild(settingsScreen);
game.removeChild(gameModeScreen);
// Set default game mode if not already set
if (game.gameMode === undefined || game.gameMode === null) {
game.gameMode = 'pvai'; // Default to Player vs AI
}
// Set default AI difficulty if not already set
if (game.aiDifficulty === undefined) {
game.aiDifficulty = 0.3; // Default to easy
}
// Ensure aiControlsRedTeam is set properly
if (game.aiControlsRedTeam === undefined) {
game.aiControlsRedTeam = game.gameMode === 'pvai'; // Only let AI control red team in PvAI mode
}
// Initialize player controllers
game.player1Controller = new PlayerController1();
game.player1Controller.team = 'blue';
if (game.gameMode === 'pvp') {
game.player2Controller = new PlayerController1();
game.player2Controller.team = 'red';
}
// Initialize the game grid
for (var row = 0; row < GRID_ROWS; row++) {
grid[row] = [];
for (var col = 0; col < GRID_COLS; col++) {
var cell = new GridCell();
cell.row = row;
cell.col = col;
cell.x = GRID_PADDING_X + col * CELL_SIZE + CELL_SIZE / 2;
cell.y = GRID_PADDING_Y + row * CELL_SIZE + CELL_SIZE / 2;
// Alternate cell colors
if ((row + col) % 2 === 0) {
cell.removeChild(cell.children[0]); // Remove default graphic
cell.attachAsset('grid_cell_dark', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
}
grid[row][col] = cell;
game.addChild(cell);
}
}
// Create units
createUnits();
// Show status text during gameplay
statusText.visible = true;
// Set game state to player 1's turn
gameState = 'player1Turn';
statusText.setText('Player 1 Turn');
// Deselect any selected unit
selectedUnit = null;
// Clear all highlights
clearAllHighlights();
};
// Remove all game elements (grid cells and units)
game.removeAllGameElements = function () {
// Remove grid cells if they exist
for (var row = 0; row < grid.length; row++) {
for (var col = 0; col < grid[row].length; col++) {
if (grid[row][col]) {
game.removeChild(grid[row][col]);
}
}
}
// Remove units
for (var i = 0; i < units.length; i++) {
game.removeChild(units[i]);
}
// Reset game variables and re-initialize grid as empty objects to avoid undefined references
grid = [];
for (var row = 0; row < GRID_ROWS; row++) {
grid[row] = [];
for (var col = 0; col < GRID_COLS; col++) {
grid[row][col] = {};
}
}
units = [];
selectedUnit = null;
};
// Expose functions to game object for access from other classes
game.selectUnit = selectUnit;
game.moveSelectedUnit = moveSelectedUnit;
game.attackCell = attackCell;
game.grid = grid;
game.gameState = gameState;
game.getAdjacentCells = getAdjacentCells;
// Create a getter/setter for game state to ensure consistency
Object.defineProperty(game, 'gameState', {
get: function get() {
return gameState;
},
set: function set(value) {
gameState = value;
}
});
// Initialize game
initializeGame(); ===================================================================
--- original.js
+++ change.js
@@ -5,37 +5,286 @@
/****
* Classes
****/
-var DamageEffect = Container.expand(function () {
+var AIPlayer = Container.expand(function () {
var self = Container.call(this);
- self.init = function (amount, x, y, isCritical) {
- // Set position
- self.x = x;
- self.y = y;
- // Create text with appropriate styling
- var color = isCritical ? '#FF0000' : '#FF6600';
- var prefix = "-";
- var size = isCritical ? 90 : 70;
- var damageText = new Text2(prefix + amount, {
- size: size,
- fill: color
+ self.makeMove = function () {
+ // Only make a move if it's AI's turn
+ if (game.gameState !== 'aiTurn') {
+ return false;
+ }
+ var units = game.getTeamUnits('red');
+ var moveFound = false;
+ // First priority: attack if possible
+ for (var i = 0; i < units.length; i++) {
+ var unit = units[i];
+ var attacks = unit.getPossibleAttacks(game.grid);
+ if (attacks.length > 0) {
+ // Prioritize attacking king
+ var kingAttack = attacks.find(function (attack) {
+ return game.grid[attack.row][attack.col].occupiedBy && game.grid[attack.row][attack.col].occupiedBy.isKing;
+ });
+ if (kingAttack) {
+ game.selectUnit(unit);
+ game.attackCell(game.grid[kingAttack.row][kingAttack.col]);
+ return true;
+ }
+ // Look for advantageous critical attacks first
+ var criticalAttack = attacks.find(function (attack) {
+ var target = game.grid[attack.row][attack.col].occupiedBy;
+ return target && isCriticalDamage(unit.type, target.type);
+ });
+ if (criticalAttack) {
+ game.selectUnit(unit);
+ game.attackCell(game.grid[criticalAttack.row][criticalAttack.col]);
+ return true;
+ }
+ // For warriors, prioritize attacks to use their instant kill
+ if (unit.type === 'warrior' && attacks.length > 0) {
+ game.selectUnit(unit);
+ game.attackCell(game.grid[attacks[0].row][attacks[0].col]);
+ return true;
+ }
+ // For wizards, prioritize attack positions that can hit multiple enemies
+ if (unit.type === 'wizard' && attacks.length > 0) {
+ // Try to find attack that might hit multiple enemies due to area effect
+ var bestAttack = attacks[0];
+ var bestCount = 0;
+ for (var j = 0; j < attacks.length; j++) {
+ var attack = attacks[j];
+ var adjacentCells = getAdjacentCells(attack.row, attack.col);
+ var enemyCount = 0;
+ // Count adjacent enemy units
+ for (var k = 0; k < adjacentCells.length; k++) {
+ var cell = adjacentCells[k];
+ if (cell.occupied && cell.occupiedBy.team === 'blue') {
+ enemyCount++;
+ }
+ }
+ if (enemyCount > bestCount) {
+ bestCount = enemyCount;
+ bestAttack = attack;
+ }
+ }
+ game.selectUnit(unit);
+ game.attackCell(game.grid[bestAttack.row][bestAttack.col]);
+ return true;
+ }
+ // Otherwise attack first available target
+ game.selectUnit(unit);
+ game.attackCell(game.grid[attacks[0].row][attacks[0].col]);
+ return true;
+ }
+ }
+ // Second priority: move toward player king
+ var playerKing = game.getKing('blue');
+ if (playerKing) {
+ // Find unit closest to player king that can move
+ var closestUnit = null;
+ var closestDistance = Infinity;
+ for (var i = 0; i < units.length; i++) {
+ var unit = units[i];
+ var moves = unit.getPossibleMoves(game.grid);
+ if (moves.length > 0) {
+ var dist = Math.abs(unit.row - playerKing.row) + Math.abs(unit.col - playerKing.col);
+ // Give priority to certain unit types based on their strengths
+ if (unit.type === 'knight') {
+ // Knights are mobile, so give them priority
+ dist -= 2;
+ } else if (unit.type === 'archer') {
+ // Archers should try to get in position but not too close
+ if (dist > 4) {
+ dist -= 1;
+ } // Encourage moving closer if far away
+ else if (dist < 3) {
+ dist += 2;
+ } // Discourage getting too close
+ } else if (unit.type === 'warrior') {
+ // Warriors should get close for their instant kill
+ dist -= 3;
+ } else if (unit.type === 'wizard') {
+ // Wizards should stay at medium distance for area attacks
+ if (dist > 3 && dist < 6) {
+ dist -= 2;
+ }
+ }
+ if (dist < closestDistance) {
+ closestUnit = unit;
+ closestDistance = dist;
+ }
+ }
+ }
+ if (closestUnit) {
+ var bestMove = null;
+ var bestMoveScore = -Infinity;
+ var moves = closestUnit.getPossibleMoves(game.grid);
+ // Score each possible move
+ for (var i = 0; i < moves.length; i++) {
+ var move = moves[i];
+ var moveScore = closestDistance - (Math.abs(move.row - playerKing.row) + Math.abs(move.col - playerKing.col));
+ // Special movement strategy based on unit type
+ if (closestUnit.type === 'knight') {
+ // Knights should try to position for attack next turn
+ var potentialAttackRange = Math.abs(move.row - playerKing.row) + Math.abs(move.col - playerKing.col);
+ if (potentialAttackRange <= closestUnit.attackRange + 1) {
+ moveScore += 3;
+ }
+ } else if (closestUnit.type === 'archer') {
+ // Archers want to be at their optimal attack range
+ var distAfterMove = Math.abs(move.row - playerKing.row) + Math.abs(move.col - playerKing.col);
+ if (distAfterMove >= 3 && distAfterMove <= 5) {
+ moveScore += 4;
+ }
+ } else if (closestUnit.type === 'wizard') {
+ // Wizards prefer positions that might hit multiple units next turn
+ var adjacentCells = getAdjacentCells(move.row, move.col);
+ var playerUnitsNearby = 0;
+ for (var j = 0; j < adjacentCells.length; j++) {
+ var cell = adjacentCells[j];
+ if (cell.occupied && cell.occupiedBy.team === 'blue') {
+ playerUnitsNearby++;
+ }
+ }
+ moveScore += playerUnitsNearby * 2;
+ }
+ if (moveScore > bestMoveScore) {
+ bestMove = move;
+ bestMoveScore = moveScore;
+ }
+ }
+ if (bestMove) {
+ game.selectUnit(closestUnit);
+ game.moveSelectedUnit(bestMove.row, bestMove.col);
+ return true;
+ }
+ }
+ }
+ // Check if any unit is in danger and try to move it to safety
+ var unitsInDanger = units.filter(function (unit) {
+ // Check if any player unit could attack this unit in the next turn
+ var playerUnits = game.getTeamUnits('blue');
+ for (var i = 0; i < playerUnits.length; i++) {
+ var playerUnit = playerUnits[i];
+ // Calculate if the player unit could potentially attack this AI unit
+ var rowDiff = Math.abs(playerUnit.row - unit.row);
+ var colDiff = Math.abs(playerUnit.col - unit.col);
+ // Check if in potential attack range based on unit type
+ var inDanger = false;
+ if (playerUnit.type === 'warrior' && rowDiff + colDiff <= playerUnit.moveRange + playerUnit.attackRange) {
+ inDanger = true;
+ } else if (playerUnit.type === 'archer' && colDiff === 0 && rowDiff <= playerUnit.attackRange + 1) {
+ inDanger = true;
+ } else if (playerUnit.type === 'knight' && (rowDiff === 0 || colDiff === 0) && rowDiff + colDiff <= playerUnit.attackRange + 1) {
+ inDanger = true;
+ } else if (playerUnit.type === 'wizard' && rowDiff + colDiff <= playerUnit.moveRange + 2) {
+ inDanger = true;
+ }
+ // If king is in danger, prioritize its safety
+ if (inDanger && unit.isKing) {
+ return true;
+ } else if (inDanger && unit.health < 50) {
+ return true;
+ }
+ }
+ return false;
});
+ if (unitsInDanger.length > 0) {
+ // Prioritize king safety
+ var unitToSave = unitsInDanger.find(function (unit) {
+ return unit.isKing;
+ }) || unitsInDanger[0];
+ var moves = unitToSave.getPossibleMoves(game.grid);
+ if (moves.length > 0) {
+ // Find safest move (furthest from player units)
+ var playerUnits = game.getTeamUnits('blue');
+ var bestMove = null;
+ var bestSafetyScore = -Infinity;
+ for (var i = 0; i < moves.length; i++) {
+ var move = moves[i];
+ var safetyScore = 0;
+ // Calculate safety based on distance from all player units
+ for (var j = 0; j < playerUnits.length; j++) {
+ var playerUnit = playerUnits[j];
+ var dist = Math.abs(move.row - playerUnit.row) + Math.abs(move.col - playerUnit.col);
+ safetyScore += dist;
+ }
+ if (safetyScore > bestSafetyScore) {
+ bestSafetyScore = safetyScore;
+ bestMove = move;
+ }
+ }
+ if (bestMove) {
+ game.selectUnit(unitToSave);
+ game.moveSelectedUnit(bestMove.row, bestMove.col);
+ return true;
+ }
+ }
+ }
+ // Last resort: random move with random unit
+ var movableUnits = units.filter(function (unit) {
+ return unit.getPossibleMoves(game.grid).length > 0;
+ });
+ if (movableUnits.length > 0) {
+ var randomUnit = movableUnits[Math.floor(Math.random() * movableUnits.length)];
+ var possibleMoves = randomUnit.getPossibleMoves(game.grid);
+ var randomMove = possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
+ game.selectUnit(randomUnit);
+ game.moveSelectedUnit(randomMove.row, randomMove.col);
+ return true;
+ }
+ return false; // No move possible
+ };
+ return self;
+});
+var CriticalDamageEffect = Container.expand(function () {
+ var self = Container.call(this);
+ self.init = function (damage, x, y) {
+ // Create the critical hit text with animation
+ var critText = new Text2('CRITICAL HIT!', {
+ size: 80,
+ fill: '#FF0000'
+ });
+ critText.anchor.set(0.5, 0.5);
+ self.addChild(critText);
+ // Create the damage text
+ var damageText = new Text2('-' + damage, {
+ size: 100,
+ fill: '#FFFF00'
+ });
damageText.anchor.set(0.5, 0.5);
+ damageText.y = 80;
self.addChild(damageText);
- // Animate the damage text
- tween(damageText, {
- y: damageText.y - 100,
- alpha: 0,
- scaleX: 1.5,
- scaleY: 1.5
+ // Position the effect
+ self.x = x;
+ self.y = y;
+ // Initial scale and alpha
+ self.scale.set(0.1);
+ self.alpha = 0;
+ // Animate in
+ tween(self, {
+ scaleX: 1.2,
+ scaleY: 1.2,
+ alpha: 1
}, {
- duration: 800,
- easing: tween.easeOutQuad,
- onFinish: function onFinish() {
- self.destroy();
- }
+ duration: 300,
+ easing: tween.elasticOut
});
+ // Animate out after showing
+ LK.setTimeout(function () {
+ tween(self, {
+ scaleX: 1.5,
+ scaleY: 1.5,
+ alpha: 0,
+ y: self.y - 100
+ }, {
+ duration: 700,
+ easing: tween.easeOutQuad,
+ onFinish: function onFinish() {
+ self.parent.removeChild(self);
+ }
+ });
+ }, 1200);
return self;
};
return self;
});
@@ -354,21 +603,19 @@
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
- } else if (type === 'selected') {
- self.highlightAsset = self.attachAsset('highlight_selected', {
+ } else if (type === 'attack') {
+ self.highlightAsset = self.attachAsset('highlight_attack', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
- } else if (type === 'attack') {
- self.highlightAsset = self.attachAsset('highlight_attack', {
+ } else if (type === 'selected') {
+ self.highlightAsset = self.attachAsset('highlight_selected', {
anchorX: 0.5,
anchorY: 0.5,
- alpha: 0.6,
- width: 250,
- height: 250
+ alpha: 0.5
});
}
};
self.clearHighlight = function () {
@@ -380,33 +627,14 @@
self.highlightType = null;
};
// Event handlers
self.down = function (x, y, obj) {
- if ((game.gameState === 'player1Turn' || game.gameState === 'player2Turn' || game.gameState === 'aiTurn') && self.occupied) {
- var controller;
- if (game.gameState === 'player1Turn') {
- controller = game.player1Controller;
- } else if (game.gameState === 'player2Turn') {
- controller = game.player2Controller;
- } else if (game.gameState === 'aiTurn') {
- controller = game.player2Controller;
- }
- selectCharacter(self.occupiedBy, controller);
- } else if (game.gameState === 'player1Turn' && selectedUnit && selectedUnit.team === 'blue' || game.gameState === 'player2Turn' && selectedUnit && selectedUnit.team === 'red' || game.gameState === 'aiTurn' && selectedUnit && selectedUnit.team === 'red') {
+ if (game.gameState === 'playerTurn') {
game.handleCellClick(self);
}
};
return self;
});
-var PlayerController1 = Container.expand(function () {
- var self = Container.call(this);
- return self;
-});
-var PlayerController2 = Container.expand(function () {
- var self = Container.call(this);
- return self;
-});
-// Handle cell click to move units and manage turns
var SettingsScreen = Container.expand(function () {
var self = Container.call(this);
// Title
var titleText = new Text2('SETTINGS', {
@@ -1220,9 +1448,9 @@
switch (type) {
case 'king':
self.health = 250;
self.attackStrength = 35;
- self.moveRange = Infinity; // Can move anywhere on the board
+ self.moveRange = 1; // Can move 1 tile in any direction
self.attackRange = 1; // Can attack 1 tile forward
break;
case 'warrior':
self.health = 280;
@@ -1251,38 +1479,27 @@
}
var assetId = type + '_' + team;
self.unitGraphic = self.attachAsset(assetId, {
anchorX: 0.5,
- anchorY: 0.5,
- alpha: 1.0
+ anchorY: 0.5
});
return self;
};
// Movement validation
self.canMoveTo = function (targetRow, targetCol) {
- if (grid[targetRow][targetCol].occupied) {
- return false;
- }
// Base movement logic to be overridden by specific unit types
return false;
};
// Attack validation
self.canAttack = function (targetRow, targetCol) {
// Base attack logic to be overridden by specific unit types
- // This is just a base method that should be overridden by specific unit types
- // Each unit type implements its own attack pattern logic in its subclass
- // The implementation here just checks if the target contains an opponent
- if (grid[targetRow][targetCol].occupied && grid[targetRow][targetCol].occupiedBy.team !== self.team) {
- return true;
- }
return false;
};
// Get possible move cells
self.getPossibleMoves = function (grid) {
var moves = [];
for (var r = 0; r < grid.length; r++) {
for (var c = 0; c < grid[r].length; c++) {
- // Add occupation check here
if (this.canMoveTo(r, c) && !grid[r][c].occupied) {
moves.push({
row: r,
col: c
@@ -1325,147 +1542,116 @@
return self;
});
var Wizard = Unit.expand(function () {
var self = Unit.call(this);
+ // Override movement validation
self.canMoveTo = function (targetRow, targetCol) {
- if (grid[targetRow][targetCol].occupied) {
- return false;
- }
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
+ // Wizard can move diagonally or in straight lines
return rowDiff <= 2 && colDiff <= 2;
};
// Override attack validation
self.canAttack = function (targetRow, targetCol) {
// Wizard can attack up to 3 tiles away in any direction
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
- // Check if there's an opponent unit at the target position
- var hasOpponent = grid[targetRow][targetCol].occupied && grid[targetRow][targetCol].occupiedBy.team !== self.team;
// Total distance (Manhattan distance) should be less than or equal to attack range
- return hasOpponent && rowDiff + colDiff <= self.attackRange && !(rowDiff === 0 && colDiff === 0);
+ return rowDiff + colDiff <= self.attackRange && !(rowDiff === 0 && colDiff === 0);
};
// Wizards do area damage
self.isAreaAttacker = true;
return self;
});
var Warrior = Unit.expand(function () {
var self = Unit.call(this);
+ // Override movement validation
self.canMoveTo = function (targetRow, targetCol) {
- if (grid[targetRow][targetCol].occupied) {
- return false;
- }
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
+ // Warrior can only move up to moveRange tile in any direction
return rowDiff <= self.moveRange && colDiff <= self.moveRange && !(rowDiff === 0 && colDiff === 0);
};
// Override attack validation
self.canAttack = function (targetRow, targetCol) {
- // Check if target position has an opponent unit
- var hasOpponent = grid[targetRow][targetCol].occupied && grid[targetRow][targetCol].occupiedBy.team !== self.team;
// Instant-kill attack (1 tile forward)
if (self.team === 'blue') {
- return hasOpponent && targetRow === self.row - self.attackRange && targetCol === self.col;
+ return targetRow === self.row - self.attackRange && targetCol === self.col;
} else {
- return hasOpponent && targetRow === self.row + self.attackRange && targetCol === self.col;
+ return targetRow === self.row + self.attackRange && targetCol === self.col;
}
};
// Warriors have instant kill ability
self.hasInstantKill = true;
return self;
});
var Knight = Unit.expand(function () {
var self = Unit.call(this);
+ // Override movement validation
self.canMoveTo = function (targetRow, targetCol) {
- if (grid[targetRow][targetCol].occupied) {
- return false;
- }
+ // Knight moves straight or sideways (1-3 tiles)
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
+ // Can move horizontally or vertically up to moveRange tiles
return rowDiff === 0 && colDiff > 0 && colDiff <= self.moveRange || colDiff === 0 && rowDiff > 0 && rowDiff <= self.moveRange;
};
// Override attack validation
self.canAttack = function (targetRow, targetCol) {
- // Check if target position has an opponent unit
- var hasOpponent = grid[targetRow][targetCol].occupied && grid[targetRow][targetCol].occupiedBy.team !== self.team;
// Knight can attack along the same paths as movement
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
- return hasOpponent && (rowDiff === 0 && colDiff > 0 && colDiff <= self.attackRange || colDiff === 0 && rowDiff > 0 && rowDiff <= self.attackRange);
+ return rowDiff === 0 && colDiff > 0 && colDiff <= self.attackRange || colDiff === 0 && rowDiff > 0 && rowDiff <= self.attackRange;
};
return self;
});
var King = Unit.expand(function () {
var self = Unit.call(this);
+ // Override movement validation
self.canMoveTo = function (targetRow, targetCol) {
- if (grid[targetRow][targetCol].occupied) {
- return false;
- }
+ // King moves like a chess queen but limited distance
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
- return rowDiff > 0 || colDiff > 0;
+ // Can move in any direction but maximum moveRange spaces
+ return rowDiff <= self.moveRange && colDiff <= self.moveRange && !(rowDiff === 0 && colDiff === 0);
};
// Override attack validation
self.canAttack = function (targetRow, targetCol) {
- // Check if target position has an opponent unit
- var hasOpponent = grid[targetRow][targetCol].occupied && grid[targetRow][targetCol].occupiedBy.team !== self.team;
// King only attacks forward based on team direction and attack range
var forwardRow = self.team === 'blue' ? self.row - self.attackRange : self.row + self.attackRange;
- return hasOpponent && targetRow === forwardRow && targetCol === self.col;
+ return targetRow === forwardRow && targetCol === self.col;
};
return self;
});
var Archer = Unit.expand(function () {
var self = Unit.call(this);
+ // Override movement validation
self.canMoveTo = function (targetRow, targetCol) {
- if (grid[targetRow][targetCol].occupied) {
- return false;
- }
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
+ // Can move sideways freely, but only moveRange tiles forward
if (self.team === 'blue') {
- return colDiff > 0 && rowDiff === 0 || targetRow === self.row - self.moveRange && colDiff === 0;
+ return colDiff > 0 && rowDiff === 0 ||
+ // sideways
+ targetRow === self.row - self.moveRange && colDiff === 0; // forward
} else {
- return colDiff > 0 && rowDiff === 0 || targetRow === self.row + self.moveRange && colDiff === 0;
+ return colDiff > 0 && rowDiff === 0 ||
+ // sideways
+ targetRow === self.row + self.moveRange && colDiff === 0; // forward
}
};
// Override attack validation
self.canAttack = function (targetRow, targetCol) {
- // Check if target position has an opponent unit
- var hasOpponent = grid[targetRow][targetCol].occupied && grid[targetRow][targetCol].occupiedBy.team !== self.team;
// Can attack any enemy in the line attackRange tiles forward
if (self.team === 'blue') {
// For blue team, attack any cell in the same column forward (up) within attack range
- return hasOpponent && targetCol === self.col && targetRow < self.row && self.row - targetRow <= self.attackRange;
+ return targetCol === self.col && targetRow < self.row && self.row - targetRow <= self.attackRange;
} else {
// For red team, attack any cell in the same column forward (down) within attack range
- return hasOpponent && targetCol === self.col && targetRow > self.row && targetRow - self.row <= self.attackRange;
+ return targetCol === self.col && targetRow > self.row && targetRow - self.row <= self.attackRange;
}
};
return self;
});
-// Vector2 class is now defined in the Classes section
-var Vector2 = Container.expand(function () {
- var self = Container.call(this);
- self.init = function (x, y) {
- self.x = x || 0;
- self.y = y || 0;
- return self;
- };
- self.add = function (v) {
- return new Vector2().init(self.x + v.x, self.y + v.y);
- };
- self.subtract = function (v) {
- return new Vector2().init(self.x - v.x, self.y - v.y);
- };
- self.multiply = function (scalar) {
- return new Vector2().init(self.x * scalar, self.y * scalar);
- };
- self.clone = function () {
- return new Vector2().init(self.x, self.y);
- };
- return self;
-});
/****
* Initialize Game
****/
@@ -1475,98 +1661,45 @@
/****
* Game Code
****/
-// Attack highlight asset removed
-// Add global vector for tutorial content positioning
// Import tween plugin
// Game constants
// Background image asset
-var tutorialOffset = new Vector2().init(0, 0);
-var dragNode = null; // Initialize dragNode for drag functionality
var GRID_ROWS = 10;
var GRID_COLS = 5;
var CELL_SIZE = 250; // Significantly increased cell size
var GRID_PADDING_X = (2048 - GRID_COLS * CELL_SIZE) / 2;
var GRID_PADDING_Y = (2732 - GRID_ROWS * CELL_SIZE) / 2;
// Game variables
-// Initialize grid as a 2D array of empty objects to avoid undefined references
var grid = [];
-for (var row = 0; row < GRID_ROWS; row++) {
- grid[row] = [];
- for (var col = 0; col < GRID_COLS; col++) {
- grid[row][col] = {};
- }
-}
var units = [];
var selectedUnit = null;
-var gameState = 'mainMenu'; // mainMenu, tutorial, settings, player1Turn, player2Turn, aiTurn, gameOver
-var gameMode = null; // 'pvp' for player vs player, 'pvai' for player vs AI
+var gameState = 'playerTurn'; // playerTurn, aiTurn, gameOver
+var ai = new AIPlayer();
var statusText = new Text2('Player Turn', {
size: 120,
// Significantly increased text size
fill: 0xFFFFFF
});
+var gameState = 'mainMenu'; // mainMenu, tutorial, settings, player1Turn, player2Turn, aiTurn, gameOver
+var gameMode = null; // 'pvp' for player vs player, 'pvai' for player vs AI
// Screen instances
var titleScreen = new TitleScreen();
var tutorialScreen = new TutorialScreen();
var settingsScreen = new SettingsScreen();
var gameModeScreen = new GameModeScreen();
var difficultyWindow = new DifficultySelectWindow();
// Make settings screen accessible to the game object
game.settingsScreen = settingsScreen;
-// Fix the global move handler to properly handle slider dragging
-game.move = function (x, y, obj) {
- // If we have a dragNode (for character dragging), handle that
- if (dragNode) {
- dragNode.x = x;
- dragNode.y = y;
- }
- // If we're in settings screen, handle movement
- if (settingsScreen && settingsScreen.parent && obj) {
- // No sliders to handle after removing AI difficulty slider
- }
-};
-// Create handleMove function
-function handleMove(x, y, obj) {
- if (dragNode) {
- dragNode.x = x;
- dragNode.y = y;
- }
-}
-// Handle touch/mouse down state for sliders
-game.down = function (x, y, obj) {
- // Only set dragNode if character exists and is defined
- if (typeof character !== 'undefined') {
- dragNode = character;
- } else {
- dragNode = null;
- }
- // Mark objects as being touched down
- if (obj && obj.down) {
- obj.__isTouchDown = true;
- }
- // Also call move handler right away to make effect instant
- if (handleMove) {
- handleMove(x, y, obj);
- }
-};
-// Handle touch/mouse up for sliders
-game.up = function (x, y, obj) {
- // Original logic
- dragNode = null;
- // Reset touch down states
- if (settingsScreen && settingsScreen.parent) {
- // No sliders to reset after removing AI difficulty slider
- }
-};
// Position screens at center
titleScreen.x = 1024;
titleScreen.y = 1366;
tutorialScreen.x = 0;
tutorialScreen.y = 0;
settingsScreen.x = 1024;
settingsScreen.y = 1366;
+game.showTitleScreen();
// Initialize the game board
function initializeGame() {
// Add background image first so it's behind other elements
var background = LK.getAsset('background', {
@@ -1577,15 +1710,36 @@
width: 2048,
height: 2732
});
game.addChild(background);
- // First show the title screen instead of starting the game directly
- game.showTitleScreen();
- // Add status text (hidden initially, shown during gameplay)
+ // Create grid
+ for (var row = 0; row < GRID_ROWS; row++) {
+ grid[row] = [];
+ for (var col = 0; col < GRID_COLS; col++) {
+ var cell = new GridCell();
+ cell.row = row;
+ cell.col = col;
+ cell.x = GRID_PADDING_X + col * CELL_SIZE + CELL_SIZE / 2;
+ cell.y = GRID_PADDING_Y + row * CELL_SIZE + CELL_SIZE / 2;
+ // Alternate cell colors
+ if ((row + col) % 2 === 0) {
+ cell.removeChild(cell.children[0]); // Remove default graphic
+ cell.attachAsset('grid_cell_dark', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ alpha: 0.5
+ });
+ }
+ grid[row][col] = cell;
+ game.addChild(cell);
+ }
+ }
+ // Create units for both teams
+ createUnits();
+ // Add status text
statusText.anchor.set(0.5, 0);
statusText.x = 2048 / 2;
statusText.y = 50;
- statusText.visible = false;
LK.gui.top.addChild(statusText);
}
// Create all units for both teams
function createUnits() {
@@ -1657,23 +1811,15 @@
units.push(unit);
// Mark cell as occupied
grid[row][col].occupied = true;
grid[row][col].occupiedBy = unit;
- // Add click handler for units
- // For blue team: clickable during player 1's turn
- // For red team: clickable during player 2's turn if PvP mode or AI is disabled
+ // Add click handler for player units
if (team === 'blue') {
unit.down = function (x, y, obj) {
- if (game.gameState === 'player1Turn') {
+ if (game.gameState === 'playerTurn') {
selectUnit(unit);
}
};
- } else if (team === 'red') {
- unit.down = function (x, y, obj) {
- if (game.gameState === 'player2Turn' && game.gameMode === 'pvp' || game.gameState === 'aiTurn' && !game.aiControlsRedTeam) {
- selectUnit(unit);
- }
- };
}
// Animate unit entry with enhanced effect
tween(unit.unitGraphic, {
scaleX: 1.0,
@@ -1685,8 +1831,269 @@
delay: 100 * (row + col) % 5 // Staggered appearance for visual interest
});
return unit;
}
+// Handle cell click
+game.handleCellClick = function (cell) {
+ if (!selectedUnit || game.gameState !== 'playerTurn') {
+ return;
+ }
+ // If cell is highlighted for movement
+ if (cell.highlighted && cell.highlightType === 'move') {
+ moveSelectedUnit(cell.row, cell.col);
+ }
+ // If cell is highlighted for attack
+ else if (cell.highlighted && cell.highlightType === 'attack') {
+ attackCell(cell);
+ }
+};
+// Select a unit
+function selectUnit(unit) {
+ // Can only select your own units
+ if (unit.team !== 'blue' && gameState === 'playerTurn') {
+ return;
+ }
+ if (unit.team !== 'red' && gameState === 'aiTurn') {
+ return;
+ }
+ // Deselect previous unit if there was one
+ if (selectedUnit) {
+ selectedUnit.deselect();
+ clearAllHighlights();
+ }
+ // Select new unit
+ selectedUnit = unit;
+ selectedUnit.select();
+ // Enhanced selection animation with glow effect
+ tween(selectedUnit.unitGraphic, {
+ scaleX: 1.2,
+ scaleY: 1.2,
+ alpha: 1.0
+ }, {
+ duration: 200,
+ easing: tween.elasticOut,
+ onFinish: function onFinish() {
+ tween(selectedUnit.unitGraphic, {
+ scaleX: 1.0,
+ scaleY: 1.0,
+ alpha: 0.9
+ }, {
+ duration: 200,
+ easing: tween.easeOutQuad
+ });
+ }
+ });
+ // Highlight cell under selected unit
+ grid[unit.row][unit.col].highlight('selected');
+ // Highlight possible moves with delay for better visual feedback
+ var possibleMoves = unit.getPossibleMoves(grid);
+ for (var i = 0; i < possibleMoves.length; i++) {
+ (function (index) {
+ LK.setTimeout(function () {
+ if (selectedUnit === unit) {
+ // Only highlight if still selected
+ var move = possibleMoves[index];
+ grid[move.row][move.col].highlight('move');
+ }
+ }, index * 30);
+ })(i);
+ }
+ // Highlight possible attacks with delay
+ var possibleAttacks = unit.getPossibleAttacks(grid);
+ for (var i = 0; i < possibleAttacks.length; i++) {
+ (function (index) {
+ LK.setTimeout(function () {
+ if (selectedUnit === unit) {
+ // Only highlight if still selected
+ var attack = possibleAttacks[index];
+ grid[attack.row][attack.col].highlight('attack');
+ }
+ }, (possibleMoves.length + index) * 30);
+ })(i);
+ }
+}
+// Move selected unit to new position
+function moveSelectedUnit(targetRow, targetCol) {
+ if (!selectedUnit) {
+ return;
+ }
+ // Update grid occupation
+ grid[selectedUnit.row][selectedUnit.col].occupied = false;
+ grid[selectedUnit.row][selectedUnit.col].occupiedBy = null;
+ // Move unit with animation
+ selectedUnit.moveTo(targetRow, targetCol);
+ // Animate movement with tween
+ var targetX = grid[targetRow][targetCol].x;
+ var targetY = grid[targetRow][targetCol].y;
+ tween(selectedUnit, {
+ x: targetX,
+ y: targetY
+ }, {
+ duration: 300,
+ easing: tween.easeOutQuad,
+ onFinish: function onFinish() {
+ // Update grid occupation at new position
+ grid[targetRow][targetCol].occupied = true;
+ grid[targetRow][targetCol].occupiedBy = selectedUnit;
+ // Deselect unit and clear highlights
+ selectedUnit.deselect();
+ clearAllHighlights();
+ selectedUnit = null;
+ // Check if any attacks are possible after movement
+ checkForAutoAttack();
+ // Switch turns
+ endTurn();
+ }
+ });
+}
+// Attack a cell
+function attackCell(cell) {
+ if (!selectedUnit || !cell.occupied || cell.occupiedBy.team === selectedUnit.team) {
+ return;
+ }
+ var targetUnit = cell.occupiedBy;
+ // Animate the attacker - "lunge" toward target
+ var originalX = selectedUnit.x;
+ var originalY = selectedUnit.y;
+ var directionX = targetUnit.x - selectedUnit.x;
+ var directionY = targetUnit.y - selectedUnit.y;
+ var distance = Math.sqrt(directionX * directionX + directionY * directionY);
+ var normalizedX = directionX / distance * 30; // small lunge
+ var normalizedY = directionY / distance * 30;
+ // Enhanced attack animation sequence
+ tween(selectedUnit, {
+ x: selectedUnit.x + normalizedX * 1.5,
+ // More pronounced lunge
+ y: selectedUnit.y + normalizedY * 1.5
+ }, {
+ duration: 200,
+ easing: tween.easeOutQuad,
+ onFinish: function onFinish() {
+ // More dramatic visual flash on target
+ LK.effects.flashObject(targetUnit, 0xff0000, 400);
+ // Return attacker to original position
+ tween(selectedUnit, {
+ x: originalX,
+ y: originalY
+ }, {
+ duration: 200,
+ easing: tween.easeOutQuad,
+ onFinish: function onFinish() {
+ // Check for critical damage
+ var isCritical = isCriticalDamage(selectedUnit.type, targetUnit.type);
+ var damageMultiplier = isCritical ? getCriticalDamageMultiplier() : 1.0;
+ var actualDamage = Math.floor(selectedUnit.attackStrength * damageMultiplier);
+ // Perform attack after animation
+ if (selectedUnit.hasInstantKill && !targetUnit.isKing) {
+ // Warrior's instant kill works on all enemies except kings
+ removeUnit(targetUnit);
+ } else if (targetUnit.isKing) {
+ // King only dies in one hit if attacked by a warrior
+ if (selectedUnit.type === 'warrior') {
+ removeUnit(targetUnit);
+ } else {
+ // Apply normal damage to king
+ var isCritical = isCriticalDamage(selectedUnit.type, targetUnit.type);
+ var damageMultiplier = isCritical ? getCriticalDamageMultiplier() : 1.0;
+ var actualDamage = Math.floor(selectedUnit.attackStrength * damageMultiplier);
+ targetUnit.health -= actualDamage;
+ targetUnit.healthText.setText(targetUnit.health.toString());
+ if (targetUnit.health <= 0) {
+ removeUnit(targetUnit);
+ }
+ }
+ } else if (selectedUnit.isAreaAttacker) {
+ // Area attack affects target and adjacent units of opposite team
+ var adjacentCells = getAdjacentCells(cell.row, cell.col);
+ // Apply damage instead of instant kill
+ targetUnit.health -= actualDamage;
+ targetUnit.healthText.setText(targetUnit.health.toString());
+ // Show critical hit effect if applicable
+ if (isCritical) {
+ var critEffect = new CriticalDamageEffect().init(actualDamage, targetUnit.x, targetUnit.y - 50);
+ game.addChild(critEffect);
+ // Add stronger visual feedback
+ tween(targetUnit, {
+ scaleX: 0.8,
+ scaleY: 0.8,
+ rotation: 0.2
+ }, {
+ duration: 100,
+ easing: tween.easeOutQuad,
+ onFinish: function onFinish() {
+ tween(targetUnit, {
+ scaleX: 1.0,
+ scaleY: 1.0,
+ rotation: 0
+ }, {
+ duration: 200,
+ easing: tween.elasticOut
+ });
+ }
+ });
+ }
+ if (targetUnit.health <= 0) {
+ removeUnit(targetUnit);
+ }
+ for (var i = 0; i < adjacentCells.length; i++) {
+ var adjCell = adjacentCells[i];
+ if (adjCell.occupied && adjCell.occupiedBy.team !== selectedUnit.team) {
+ var adjTargetUnit = adjCell.occupiedBy;
+ var adjDamage = Math.floor(actualDamage / 2); // Half damage for adjacent units
+ adjTargetUnit.health -= adjDamage;
+ adjTargetUnit.healthText.setText(adjTargetUnit.health.toString());
+ if (adjTargetUnit.health <= 0) {
+ removeUnit(adjTargetUnit);
+ }
+ }
+ }
+ } else {
+ // Normal attack
+ targetUnit.health -= actualDamage;
+ targetUnit.healthText.setText(targetUnit.health.toString());
+ // Show critical hit effect if applicable
+ if (isCritical) {
+ var critEffect = new CriticalDamageEffect().init(actualDamage, targetUnit.x, targetUnit.y - 50);
+ game.addChild(critEffect);
+ // Add stronger visual feedback for critical hit
+ tween(targetUnit, {
+ scaleX: 0.7,
+ scaleY: 0.7,
+ rotation: 0.3
+ }, {
+ duration: 100,
+ easing: tween.easeOutQuad,
+ onFinish: function onFinish() {
+ tween(targetUnit, {
+ scaleX: 1.0,
+ scaleY: 1.0,
+ rotation: 0
+ }, {
+ duration: 300,
+ easing: tween.elasticOut
+ });
+ }
+ });
+ }
+ if (targetUnit.health <= 0) {
+ removeUnit(targetUnit);
+ }
+ }
+ // Deselect unit and clear highlights
+ selectedUnit.deselect();
+ clearAllHighlights();
+ selectedUnit = null;
+ // Switch turns
+ endTurn();
+ }
+ });
+ }
+ });
+}
+// Check if auto-attack is possible (for units that moved next to enemies)
+function checkForAutoAttack() {
+ // Not implemented in this version - all attacks are chosen by player
+}
// Get adjacent cells
function getAdjacentCells(row, col) {
var adjacent = [];
var directions = [{
@@ -1722,123 +2129,8 @@
}
}
return adjacent;
}
-// Function to deal damage to a unit
-function takeDamage(targetUnit, attackingUnit) {
- // Check if units exist
- if (!targetUnit || !attackingUnit) return;
- // Apply damage based on attacking unit's strength
- var damageAmount = attackingUnit.attackStrength;
- targetUnit.health -= damageAmount;
- // Ensure health doesn't go below 0
- if (targetUnit.health < 0) targetUnit.health = 0;
- // Update health display
- targetUnit.healthText.setText(targetUnit.health.toString());
- // Visual feedback for damage - more intense flash for higher damage
- var flashDuration = damageAmount > 40 ? 500 : 300;
- LK.effects.flashObject(targetUnit, 0xff0000, flashDuration);
- // Add damage number indicator with better visibility
- var damageText = new Text2("-" + damageAmount, {
- size: 70,
- fill: '#FF0000'
- });
- damageText.anchor.set(0.5, 0.5);
- damageText.x = targetUnit.x;
- damageText.y = targetUnit.y - 50;
- game.addChild(damageText);
- // Animate damage text with improved animation
- tween(damageText, {
- y: damageText.y - 120,
- alpha: 0,
- scaleX: 1.5,
- scaleY: 1.5
- }, {
- duration: 800,
- easing: tween.easeOutQuad,
- onFinish: function onFinish() {
- game.removeChild(damageText);
- // Check if unit is defeated before switching turns
- if (targetUnit.health <= 0) {
- removeUnit(targetUnit);
- }
- // After damage is applied and animation completes, switch turns
- if (gameState === 'player1Turn' || gameState === 'player2Turn' || gameState === 'aiTurn') {
- switchTurns();
- }
- }
- });
-}
-// Function to attack an opponent unit
-function attackUnit(targetRow, targetCol) {
- if (!selectedUnit) return false;
- // Check if there's a unit at the target position
- if (!grid[targetRow][targetCol].occupied) return false;
- // Get the target unit
- var targetUnit = grid[targetRow][targetCol].occupiedBy;
- // Check if target is an enemy unit
- if (targetUnit.team === selectedUnit.team) return false;
- // Check if the target is within attack range
- if (!selectedUnit.canAttack(targetRow, targetCol)) return false;
- // Store original position
- var originalX = selectedUnit.x;
- var originalY = selectedUnit.y;
- // Calculate position 60% of the way toward target for attack animation
- var targetX = targetUnit.x;
- var targetY = targetUnit.y;
- var moveToX = originalX + (targetX - originalX) * 0.6;
- var moveToY = originalY + (targetY - originalY) * 0.6;
- // Flash attacking unit to indicate action started
- LK.effects.flashObject(selectedUnit, 0x00ff00, 150);
- // Animate unit moving toward target
- tween(selectedUnit, {
- x: moveToX,
- y: moveToY,
- scaleX: 1.2,
- scaleY: 1.2
- }, {
- duration: 200,
- easing: tween.easeOutQuad,
- onFinish: function onFinish() {
- // After moving toward target, animate back to original position
- tween(selectedUnit, {
- x: originalX,
- y: originalY,
- scaleX: 1.0,
- scaleY: 1.0
- }, {
- duration: 200,
- easing: tween.elasticOut,
- onFinish: function onFinish() {
- // Apply damage to the target based on attacking unit's attackStrength
- takeDamage(targetUnit, selectedUnit);
- // Handle area attacks for wizards
- if (selectedUnit.isAreaAttacker) {
- // Get adjacent cells and damage units in them
- var adjacentCells = getAdjacentCells(targetRow, targetCol);
- for (var i = 0; i < adjacentCells.length; i++) {
- var cell = adjacentCells[i];
- if (cell.occupied && cell.occupiedBy.team !== selectedUnit.team) {
- takeDamage(cell.occupiedBy, selectedUnit);
- }
- }
- }
- // Handle instant kill for warriors
- if (selectedUnit.hasInstantKill && !targetUnit.isKing) {
- removeUnit(targetUnit);
- }
- // Visual feedback for attack
- LK.effects.flashObject(selectedUnit, 0x00ff00, 300);
- // Show critical hit effect for attacks that deal significant damage
- if (selectedUnit.attackStrength > 40) {
- LK.effects.flashScreen(0xff0000, 200);
- }
- }
- });
- }
- });
- return true;
-}
// Remove a unit from the game
function removeUnit(unit) {
// Clear grid cell
grid[unit.row][unit.col].occupied = false;
@@ -1866,23 +2158,61 @@
grid[row][col].clearHighlight();
}
}
}
-// Get all cells that the selected unit can attack
-function getSelectedUnitAttacks() {
- if (!selectedUnit) return [];
- var attackableCells = [];
- for (var row = 0; row < GRID_ROWS; row++) {
- for (var col = 0; col < GRID_COLS; col++) {
- if (selectedUnit.canAttack(row, col)) {
- if (grid[row][col].occupied && grid[row][col].occupiedBy.team !== selectedUnit.team) {
- attackableCells.push(grid[row][col]);
- }
+// End the current turn
+function endTurn() {
+ if (gameState === 'playerTurn') {
+ gameState = 'aiTurn';
+ statusText.setText('AI Turn');
+ // Execute AI turn with delay for better visibility
+ LK.setTimeout(function () {
+ if (ai.makeMove()) {
+ // AI successfully made a move, turn will be ended in the attack/move handlers
+ } else {
+ // AI couldn't make a move, switch back to player
+ gameState = 'playerTurn';
+ statusText.setText('Player Turn');
}
- }
+ }, 500); // Small delay for better user experience
+ } else if (gameState === 'aiTurn') {
+ gameState = 'playerTurn';
+ statusText.setText('Player Turn');
}
- return attackableCells;
}
+// End the game with a winner
+function endGame(winner) {
+ gameState = 'gameOver';
+ if (winner === 'blue') {
+ statusText.setText('You Win!');
+ LK.showYouWin();
+ } else {
+ statusText.setText('Game Over');
+ LK.showGameOver();
+ }
+}
+// Helper functions to determine critical damage relationships
+function isCriticalDamage(attackerType, targetType) {
+ // Knights are strong against archers
+ if (attackerType === 'knight' && targetType === 'archer') {
+ return true;
+ }
+ // Archers are strong against mages/wizards
+ if (attackerType === 'archer' && targetType === 'wizard') {
+ return true;
+ }
+ // Mages/wizards are strong against knights
+ if (attackerType === 'wizard' && targetType === 'knight') {
+ return true;
+ }
+ return false;
+}
+// Make isCriticalDamage available to the game object
+game.isCriticalDamage = isCriticalDamage;
+function getCriticalDamageMultiplier() {
+ // Critical damage is 2x normal damage
+ return 2.0;
+}
// Helper functions
game.getTeamUnits = function (team) {
return units.filter(function (unit) {
return unit.team === team;
@@ -1895,82 +2225,8 @@
}
}
return null;
};
-game.grid = grid;
-game.gameState = gameState;
-game.getAdjacentCells = getAdjacentCells;
-// Create a getter/setter for game state to ensure consistency
-Object.defineProperty(game, 'gameState', {
- get: function get() {
- return gameState;
- },
- set: function set(value) {
- gameState = value;
- }
-});
-// Function to select a unit
-function selectUnit(unit) {
- // Check if unit exists
- if (!unit) return;
- // Verify unit belongs to current controller's team based on game state
- var currentTeam;
- if (game.gameState === 'player1Turn') {
- currentTeam = 'blue';
- } else if (game.gameState === 'player2Turn' || game.gameState === 'aiTurn') {
- currentTeam = 'red';
- } else {
- return; // Not in a valid turn state
- }
- if (unit.team !== currentTeam) return;
- // Deselect previously selected unit if there is one
- if (selectedUnit) {
- selectedUnit.deselect();
- }
- // Select the new unit
- selectedUnit = unit;
- selectedUnit.select();
- // Clear all previous highlights
- clearAllHighlights();
- // Show possible moves
- var possibleMoves = selectedUnit.getPossibleMoves(grid);
- for (var i = 0; i < possibleMoves.length; i++) {
- var move = possibleMoves[i];
- grid[move.row][move.col].highlight('move');
- }
- // Show attackable cells
- var attackableCells = getSelectedUnitAttacks();
- for (var i = 0; i < attackableCells.length; i++) {
- attackableCells[i].highlight('attack');
- }
-}
-// Function to select a character if it belongs to the current controller's team
-function selectCharacter(character, controller) {
- // Verify the character belongs to the controller's team
- if (character && controller && character.team === controller.team) {
- // Select the character
- if (selectedUnit) {
- selectedUnit.deselect();
- }
- selectedUnit = character;
- selectedUnit.select();
- // Clear all previous highlights
- clearAllHighlights();
- // Show possible moves
- var possibleMoves = selectedUnit.getPossibleMoves(grid);
- for (var i = 0; i < possibleMoves.length; i++) {
- var move = possibleMoves[i];
- grid[move.row][move.col].highlight('move');
- }
- // Show attackable cells
- var attackableCells = getSelectedUnitAttacks();
- for (var i = 0; i < attackableCells.length; i++) {
- attackableCells[i].highlight('attack');
- }
- return true; // Successfully selected
- }
- return false; // Could not select (wrong team)
-}
// Function to update tutorial position
game.updateTutorialPosition = function (x, y) {
// Update the offset vector
tutorialOffset.x = x || tutorialOffset.x;
@@ -2142,83 +2398,22 @@
}
units = [];
selectedUnit = null;
};
-// Initialize game
-initializeGame();
-// Handle cell click to move units and manage turns
-game.handleCellClick = function (cell) {
- if (!selectedUnit) return;
- // Check if this is a valid attack
- if (cell.highlighted && cell.highlightType === 'attack') {
- // Get the target unit
- var targetUnit = grid[cell.row][cell.col].occupiedBy;
- if (targetUnit && targetUnit.team !== selectedUnit.team) {
- // Attack the unit in this cell
- var attacked = attackUnit(cell.row, cell.col);
- if (attacked) {
- // After attack completes, the turn will switch in the takeDamage animation callback
- // Clear selection and highlights now as the attack is in progress
- selectedUnit.deselect();
- selectedUnit = null;
- clearAllHighlights();
- return;
- }
- }
+// Expose functions to game object for access from other classes
+game.selectUnit = selectUnit;
+game.moveSelectedUnit = moveSelectedUnit;
+game.attackCell = attackCell;
+game.grid = grid;
+game.gameState = gameState;
+game.getAdjacentCells = getAdjacentCells;
+// Create a getter/setter for game state to ensure consistency
+Object.defineProperty(game, 'gameState', {
+ get: function get() {
+ return gameState;
+ },
+ set: function set(value) {
+ gameState = value;
}
- // Check if this is a valid move
- if (cell.highlighted && cell.highlightType === 'move') {
- // Move the selected unit
- moveSelectedUnit(cell.row, cell.col);
- // After move, switch turns
- switchTurns();
- }
-};
-// Function to switch turns based on game mode
-function switchTurns() {
- if (game.gameState === 'player1Turn') {
- if (game.gameMode === 'pvp') {
- // Switch to player 2's turn
- gameState = 'player2Turn';
- statusText.setText('Player 2 Turn');
- } else {
- // Switch to AI turn
- gameState = 'aiTurn';
- statusText.setText('AI Turn');
- }
- } else if (game.gameState === 'player2Turn') {
- // Switch to player 1's turn
- gameState = 'player1Turn';
- statusText.setText('Player 1 Turn');
- } else if (game.gameState === 'aiTurn') {
- // Switch to player 1's turn
- gameState = 'player1Turn';
- statusText.setText('Player 1 Turn');
- }
- // Clear selection and highlights after turn switch
- if (selectedUnit) {
- selectedUnit.deselect();
- selectedUnit = null;
- }
- clearAllHighlights();
-}
-// Function to move selected unit to a new position
-function moveSelectedUnit(targetRow, targetCol) {
- if (!selectedUnit) return;
- // Update grid occupancy
- grid[selectedUnit.row][selectedUnit.col].occupied = false;
- grid[selectedUnit.row][selectedUnit.col].occupiedBy = null;
- // Update unit position
- selectedUnit.row = targetRow;
- selectedUnit.col = targetCol;
- // Update grid with new position
- grid[targetRow][targetCol].occupied = true;
- grid[targetRow][targetCol].occupiedBy = selectedUnit;
- // Move unit visually with animation
- tween(selectedUnit, {
- x: grid[targetRow][targetCol].x,
- y: grid[targetRow][targetCol].y
- }, {
- duration: 300,
- easing: tween.easeOutQuad
- });
-}
\ No newline at end of file
+});
+// Initialize game
+initializeGame();
\ No newline at end of file
top view
make it have red-ish clothes
Make it seen from behind top view
make it seen from behind top view
make it just a grass field, and make it from 3 times far away
Create a logo for this game based on this description: Title: "Tactical Kings: Battle of Champions" A fantasy-themed, chess-inspired strategy game where players control unique units with predefined attack zones. Victory comes from eliminating the opponent’s King by strategically positioning characters on a 5x10 grid.. In-Game asset. 2d. High contrast. No shadows
make it wear more blue ish colors
make it from top view
paint the word silver
top view, seeing the back of the character
top down view, do not cut any elements off screen
Make it seen from his back, do not cut any elements off screen