User prompt
it doesnt let the player play after the first turn, from the second turn and on it shows only the next turn ui and doesnt let the player play
User prompt
Make it turn base, the Ai cant continuosly play
User prompt
Change the attack zone for the mage, it can only attack characters that is on tile around its attack tile
User prompt
The position of the characters in the blue team is inverted. THE line where tha king is should be below the line of the warriors and knights
User prompt
change the characters positioning, it should be Red: Archer, Mage, King, Mage, Archer Kinght, warrior, warrior, warrior, Knight Blue: Kinght, warrior, warrior, warrior, Knight Archer, Mage, King, Mage, Archer
User prompt
change the postition fo the characters for both teams. It should be like: blue team ex: Archer, Mage, King, Mage, Archer Knight, warrior, warrior, warrior, knight
User prompt
change the postition fo the characters for both teams. It should be like: blue team ex: Knight, warrior, warrior, warrior, knight Archer, Mage, King, Mage, Archer
User prompt
increase the size, make the whole arena fit to the screen
User prompt
Increase the size of everything, so that the board fits the screen ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Create game
Code edit (1 edits merged)
Please save this source code
User prompt
Tactical Kings: Battle of Champions
Initial prompt
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. Core Mechanics: Turn-Based Gameplay: Players take turns moving one character at a time, with automatic attacks occurring after movement. Pre-Calculated Attacks: Before moving, players see highlighted attack zones to plan the best positioning. Character Roles & Counters: Spear Knights → Strong melee attacks, move straight or sideways (1-3 tiles). Archers → Long-range (8 tiles forward), move sideways freely but only one tile forward if moving forward. Mages → Cast area spells 6 tiles ahead, damaging all surrounding enemies. Tank → Instant-kill attack (1 tile forward), slow movement (only 1 tile per turn). King → Moves freely like a chess queen, but only attacks 1 tile forward. AI Strategy: Simple pattern-following—AI moves towards optimal attack positions without complex decision trees. Minimal Animations: Basic sprite interpolation for movement, slight grow and shrink for idle, and a sprite change for attacks. Win Condition: Eliminate the opponent's King—no timers or alternative victory methods.
/**** * 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; }); // ArrowStreak: glowing arrow trail for archer attacks var ArrowStreak = Container.expand(function () { var self = Container.call(this); self.init = function (startX, startY, endX, endY, color) { self.x = startX; self.y = startY; self.alpha = 1; // Use a thin, long highlight_move asset as the streak var dx = endX - startX; var dy = endY - startY; var dist = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); var streak = self.attachAsset('highlight_move', { anchorX: 0, anchorY: 0.5, width: dist, height: 24, alpha: 0.7, tint: color || 0x44aaff }); streak.rotation = angle; // Animate fade out and shrink tween(streak, { alpha: 0, width: dist * 0.7 }, { duration: 350, easing: tween.easeOutQuad, onFinish: function onFinish() { if (self.parent) self.parent.removeChild(self); } }); return self; }; 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() { if (self.parent) { self.parent.removeChild(self); } } }); }, 1200); return self; }; return self; }); // ExplosionEffect: magical explosion with debris and sparkles for wizard area attacks var ExplosionEffect = Container.expand(function () { var self = Container.call(this); self.init = function (x, y, color) { self.x = x; self.y = y; self.alpha = 1; // Main explosion core var core = self.attachAsset('highlight_attack', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2, alpha: 0.7 }); // Animate core expansion and fade tween(core, { scaleX: 2.2, scaleY: 2.2, alpha: 0 }, { duration: 400, easing: tween.easeOutQuad }); // Debris particles for (var i = 0; i < 8; i++) { var angle = Math.PI * 2 / 8 * i; var debris = self.attachAsset('grid_cell', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.18 + Math.random() * 0.12, scaleY: 0.18 + Math.random() * 0.12, alpha: 0.7, tint: color || 0x99ccff }); debris.x = 0; debris.y = 0; var dist = 60 + Math.random() * 40; tween(debris, { x: Math.cos(angle) * dist, y: Math.sin(angle) * dist, alpha: 0 }, { duration: 500 + Math.random() * 200, easing: tween.easeOutQuad, onFinish: function (d) { return function () { if (d.parent) d.parent.removeChild(d); }; }(debris) }); } // Sparkles for (var j = 0; j < 6; j++) { var sparkle = self.attachAsset('highlight_move', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.12 + Math.random() * 0.08, scaleY: 0.12 + Math.random() * 0.08, alpha: 0.8, tint: 0xffffff }); sparkle.x = 0; sparkle.y = 0; var sAngle = Math.random() * Math.PI * 2; var sDist = 40 + Math.random() * 60; tween(sparkle, { x: Math.cos(sAngle) * sDist, y: Math.sin(sAngle) * sDist, alpha: 0 }, { duration: 350 + Math.random() * 200, easing: tween.easeOutQuad, onFinish: function (s) { return function () { if (s.parent) s.parent.removeChild(s); }; }(sparkle) }); } // Fade out container tween(self, { alpha: 0 }, { duration: 600, easing: tween.linear, onFinish: function onFinish() { if (self.parent) self.parent.removeChild(self); } }); return self; }; return self; }); // FloatingDamageText: shows animated floating damage numbers above units var FloatingDamageText = Container.expand(function () { var self = Container.call(this); self.init = function (damage, x, y, color) { // Create the damage text var dmgText = new Text2('-' + damage, { size: 80, fill: color || '#FFDD33' }); dmgText.anchor.set(0.5, 0.5); self.addChild(dmgText); self.x = x; self.y = y; self.alpha = 1; // Animate upwards and fade out tween(self, { y: y - 100, alpha: 0 }, { duration: 900, easing: tween.easeOutQuad, onFinish: function onFinish() { if (self.parent) self.parent.removeChild(self); } }); return self; }; 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) { console.log("[GridCell Debug] Cell clicked:", { row: self.row, col: self.col, gameState: game.gameState, occupied: self.occupied, occupiedByTeam: self.occupied ? self.occupiedBy.team : null, occupiedByType: self.occupied ? self.occupiedBy.type : null, highlighted: self.highlighted, highlightType: self.highlightType }); if (game.gameState === 'playerTurn' || game.gameState === 'player2Turn') { // If cell is highlighted for attack, directly perform the attack instead of trying to select the unit if (self.highlighted && self.highlightType === 'attack' && selectedUnit) { console.log("[GridCell Debug] Processing cell as attack target"); game.attackCell(self); return; // Prevent further processing } else { game.handleCellClick(self); } } }; return self; }); var PauseMenu = Container.expand(function () { var self = Container.call(this); // Create semi-transparent background overlay var overlay = self.attachAsset('grid_cell_dark', { anchorX: 0, anchorY: 0, width: 2048, height: 2732, alpha: 0.8 }); // Create pause menu title var titleText = new Text2('PAUSED', { size: 150, fill: '#FFFFFF' }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 800; self.addChild(titleText); // Create menu buttons var resumeButton = createButton('RESUME', 1200, 2048 / 2, 600, 150, 85); var menuButton = createButton('MAIN MENU', 1600, 2048 / 2, 600, 150, 85); // Add buttons to container self.addChild(resumeButton); self.addChild(menuButton); // Animate buttons with staggered delays animateButton(resumeButton, 0, 0.95); animateButton(menuButton, 150, 0.95); // Button interactions resumeButton.interactive = true; resumeButton.down = function () { game.togglePause(); }; menuButton.interactive = true; menuButton.down = function () { game.togglePause(); showMainMenu(); }; 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 can move freely to any unoccupied cell except its current position if (targetRow === self.row && targetCol === self.col) { return false; } return true; }; // 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; }); // UnitTrail: shows a fading afterimage or dust cloud at a given position var UnitTrail = Container.expand(function () { var self = Container.call(this); // type: "afterimage" or "dust" self.init = function (unit, type) { // Use the same asset as the unit, but faded and smaller var assetId = unit.type + '_' + unit.team; var trailGraphic = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8, alpha: 0.5 }); // Optionally tint for dust if (type === "dust") { trailGraphic.alpha = 0.35; } self.x = unit.x; self.y = unit.y; self.alpha = 0.7; // Fade out and shrink tween(self, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 500, easing: tween.easeOutQuad, onFinish: function onFinish() { if (self.parent) self.parent.removeChild(self); } }); return self; }; return self; }); // WarriorDustCloud: dust cloud for warrior movement/attack var WarriorDustCloud = Container.expand(function () { var self = Container.call(this); self.init = function (x, y) { self.x = x; self.y = y; self.alpha = 0.7; // Use grid_cell_dark as dust puff var dust = self.attachAsset('grid_cell_dark', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4 + Math.random() * 0.2, scaleY: 0.2 + Math.random() * 0.2, alpha: 0.5 }); // Animate dust expansion and fade tween(dust, { scaleX: dust.scaleX * 1.7, scaleY: dust.scaleY * 1.7, alpha: 0 }, { duration: 400 + Math.random() * 120, easing: tween.easeOutQuad, onFinish: function onFinish() { if (self.parent) self.parent.removeChild(self); } }); return self; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Background image asset // Game constants 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 = 'mainMenu'; // mainMenu, playerTurn, aiTurn, gameOver var gameMode; // PVP, PVA var ai = new AIPlayer(); var statusText = new Text2('Player Turn', { size: 120, // Significantly increased text size fill: 0xFFFFFF }); statusText.visible = false; // Hide status text from display // Create containers for different screens var mainMenuContainer = new Container(); var gameplayContainer = new Container(); var tutorialContainer = new Container(); var settingsContainer = new Container(); var gameModeContainer = new Container(); var pauseMenuContainer = new Container(); // Initial visibility mainMenuContainer.visible = true; gameplayContainer.visible = false; tutorialContainer.visible = false; settingsContainer.visible = false; gameModeContainer.visible = false; pauseMenuContainer.visible = false; // Initialize the game board function initializeGame() { // Play background music LK.playMusic('game_music', { volume: 0.3, fade: { start: 0, end: 0.5, duration: 1000 } }); // 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); // Add containers to game game.addChild(mainMenuContainer); game.addChild(gameplayContainer); game.addChild(tutorialContainer); game.addChild(settingsContainer); game.addChild(gameModeContainer); game.addChild(pauseMenuContainer); // Create pause button in gameplay var pauseButton = createButton('II', 100, 2048 - 100, 150, 150, 70); pauseButton.interactive = true; pauseButton.down = function () { game.togglePause(); }; gameplayContainer.addChild(pauseButton); // 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; gameplayContainer.addChild(cell); } } // Create units for both teams createUnits(); // Status text is handled internally by the game without UI display // Set up main menu createMainMenu(); createTutorialScreen(); createSettingsScreen(); createGameModeScreen(); createPauseMenu(); } // 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: 0x000000 // Black color for health text }); 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 gameplay container gameplayContainer.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 depending on game mode unit.down = function (x, y, obj) { console.log("[Unit Debug] Unit clicked:", { type: unit.type, team: team, row: unit.row, col: unit.col, gameMode: gameMode, gameState: game.gameState }); // Check if we're clicking on an enemy unit that's highlighted for attack var cell = grid[unit.row][unit.col]; if (cell.highlighted && cell.highlightType === 'attack' && selectedUnit) { console.log("[Unit Debug] Processing as attack target"); game.attackCell(cell); return; // Prevent further selection logic } if (gameMode === 'PVP') { // In PVP mode, blue units can be selected during playerTurn and red units during player2Turn if (team === 'blue' && game.gameState === 'playerTurn' || team === 'red' && game.gameState === 'player2Turn') { console.log("[Unit Debug] Unit eligible for selection"); selectUnit(unit); } else { console.log("[Unit Debug] Unit not eligible for selection - wrong team for current turn"); } } else { // In PVA mode, only blue units can be selected during player turn if (team === 'blue' && game.gameState === 'playerTurn') { console.log("[Unit Debug] Unit eligible for selection"); selectUnit(unit); } else { console.log("[Unit Debug] Unit not eligible for selection - PVA mode restrictions"); } } }; // 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) { console.log("[Click Debug] Cell clicked:", { row: cell.row, col: cell.col, highlighted: cell.highlighted, highlightType: cell.highlightType, hasSelectedUnit: !!selectedUnit, gameState: game.gameState }); if (!selectedUnit || game.gameState !== 'playerTurn' && game.gameState !== 'player2Turn') { console.log("[Click Debug] Click ignored: no unit selected or wrong game state"); return; } // If cell is highlighted for movement if (cell.highlighted && cell.highlightType === 'move') { console.log("[Click Debug] Processing as move"); moveSelectedUnit(cell.row, cell.col); } // If cell is highlighted for attack else if (cell.highlighted && cell.highlightType === 'attack') { console.log("[Click Debug] Processing as attack"); attackCell(cell); } else { console.log("[Click Debug] Cell not highlighted for move or attack"); } }; // Select a unit function selectUnit(unit) { console.log("[Select Debug] Unit selection attempt:", { unitType: unit.type, unitTeam: unit.team, gameMode: gameMode, gameState: game.gameState // Use game.gameState instead of gameState }); // Can only select your own units if (gameMode === 'PVP') { // In PVP mode, blue units during playerTurn and red units during player2Turn if (unit.team !== 'blue' && game.gameState === 'playerTurn' || unit.team !== 'red' && game.gameState === 'player2Turn') { console.log("[Select Debug] Selection rejected: wrong team for current turn"); return; } } else { // Original PVA mode logic if (unit.team !== 'blue' && game.gameState === 'playerTurn') { console.log("[Select Debug] Selection rejected: not a blue unit during player turn"); return; } if (unit.team !== 'red' && game.gameState === 'aiTurn') { console.log("[Select Debug] Selection rejected: not a red unit during AI turn"); return; } } console.log("[Select Debug] Unit successfully selected"); // 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 if (selectedUnit && selectedUnit.unitGraphic) { tween(selectedUnit.unitGraphic, { scaleX: 1.2, scaleY: 1.2, alpha: 1.0 }, { duration: 200, easing: tween.elasticOut, onFinish: function onFinish() { if (selectedUnit && selectedUnit.unitGraphic) { 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; // Play move sound effect only if sound is enabled if (game.soundEnabled) { LK.getSound('move_sound').play(); } // Move unit with animation // Leave a fading trail at the starting position var trail = new UnitTrail().init(selectedUnit, "afterimage"); gameplayContainer.addChild(trail); 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() { // Optionally, leave a dust cloud at the destination var dust = new UnitTrail().init(selectedUnit, "dust"); dust.x = targetX; dust.y = targetY; gameplayContainer.addChild(dust); // If warrior, add extra dust cloud for heavy step if (selectedUnit.type === 'warrior') { var warriorDust = new WarriorDustCloud().init(targetX, targetY); gameplayContainer.addChild(warriorDust); } // 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) { console.log("[Attack Debug] Attack started with params:", { hasSelectedUnit: !!selectedUnit, cellOccupied: cell.occupied, cellTeam: cell.occupied ? cell.occupiedBy.team : null, selectedUnitTeam: selectedUnit ? selectedUnit.team : null, gameMode: gameMode, gameState: game.gameState // Use game.gameState instead of gameState }); if (!selectedUnit || !cell.occupied || cell.occupiedBy.team === selectedUnit.team) { console.log("[Attack Debug] Attack aborted: basic validation failed"); return; } // Additional check for PVP mode to ensure correct team attacking in the right turn if (gameMode === 'PVP') { console.log("[Attack Debug] PVP check:", { isPlayerTurn: game.gameState === 'playerTurn', isPlayer2Turn: game.gameState === 'player2Turn', selectedUnitTeam: selectedUnit.team }); // Ensure only the correct team can attack during their turn if (game.gameState === 'playerTurn' && selectedUnit.team !== 'blue' || game.gameState === 'player2Turn' && selectedUnit.team !== 'red') { console.log("[Attack Debug] Attack aborted: wrong team for current turn"); return; } } console.log("[Attack Debug] Attack proceeding with target:", cell.occupiedBy.type); 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; // Play attack sound only if sound is enabled if (game.soundEnabled) { LK.getSound('attack_sound').play(); } // Enhanced attack animation sequence // Add a fast yellow screen flash at 0.5 alpha when a unit attacks an opponent unit, with 0.2s delay LK.setTimeout(function () { LK.effects.flashScreen(0xffff00, 120, 0.5); // 0.5 alpha, 120ms duration }, 200); // Add a small screen shake to the gameplay container with 0.2s delay LK.setTimeout(function () { tween(gameplayContainer, { x: gameplayContainer.x + (Math.random() > 0.5 ? 12 : -12), y: gameplayContainer.y + (Math.random() > 0.5 ? 8 : -8) }, { duration: 60, easing: tween.easeOutQuad, onFinish: function onFinish() { tween(gameplayContainer, { x: 0, y: 0 }, { duration: 80, easing: tween.easeOutQuad }); } }); }, 200); 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() { // If warrior, add dust cloud at lunge position if (selectedUnit.type === 'warrior') { var dustAttack = new WarriorDustCloud().init(selectedUnit.x, selectedUnit.y); gameplayContainer.addChild(dustAttack); } // 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()); // Show floating damage text var dmgText = new FloatingDamageText().init(actualDamage, targetUnit.x, targetUnit.y - 80, isCritical ? '#FF3333' : '#FFDD33'); gameplayContainer.addChild(dmgText); 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); // Show magical explosion effect at the center of the area attack var explosion = new ExplosionEffect().init(targetUnit.x, targetUnit.y, 0x99ccff); gameplayContainer.addChild(explosion); // 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); gameplayContainer.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()); // Show floating damage text for adjacent units var adjDmgText = new FloatingDamageText().init(adjDamage, adjTargetUnit.x, adjTargetUnit.y - 80, '#FFDD33'); gameplayContainer.addChild(adjDmgText); if (adjTargetUnit.health <= 0) { removeUnit(adjTargetUnit); } } } } else { // Normal attack // If archer, show arrow streak from attacker to target if (selectedUnit.type === 'archer') { var arrowStreak = new ArrowStreak().init(selectedUnit.x, selectedUnit.y, targetUnit.x, targetUnit.y, 0x44aaff); gameplayContainer.addChild(arrowStreak); } targetUnit.health -= actualDamage; targetUnit.healthText.setText(targetUnit.health.toString()); // Show floating damage text var dmgText = new FloatingDamageText().init(actualDamage, targetUnit.x, targetUnit.y - 80, isCritical ? '#FF3333' : '#FFDD33'); gameplayContainer.addChild(dmgText); // Show critical hit effect if applicable if (isCritical) { var critEffect = new CriticalDamageEffect().init(actualDamage, targetUnit.x, targetUnit.y - 50); gameplayContainer.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 gameplayContainer.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 (gameMode === 'PVP') { // In PVP mode, toggle between blue and red player turns if (game.gameState === 'playerTurn') { gameState = 'player2Turn'; game.gameState = 'player2Turn'; statusText.setText('Red Player Turn'); } else if (game.gameState === 'player2Turn') { gameState = 'playerTurn'; game.gameState = 'playerTurn'; statusText.setText('Blue Player Turn'); } } else { // Original PVA mode logic if (game.gameState === 'playerTurn') { gameState = 'aiTurn'; game.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 { gameState = 'playerTurn'; game.gameState = 'playerTurn'; statusText.setText('Player Turn'); } }, 500); // Small delay for better user experience } else if (game.gameState === 'aiTurn') { gameState = 'playerTurn'; game.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(); LK.setTimeout(function () { showMainMenu(); }, 3000); } else { statusText.setText('Game Over'); LK.showGameOver(); LK.setTimeout(function () { showMainMenu(); }, 3000); } } // 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; }; // 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; // Toggle pause functionality game.isPaused = false; game.togglePause = function () { game.isPaused = !game.isPaused; pauseMenuContainer.visible = game.isPaused; // Store previous game state when pausing if (game.isPaused) { game.previousGameState = game.gameState; } else { // Restore previous game state when unpausing if (game.previousGameState) { game.gameState = game.previousGameState; } } }; // 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; } }); // Create main menu function createMainMenu() { // Add game logo var logo = LK.getAsset('game_logo', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 800 }); mainMenuContainer.addChild(logo); // Add play button var playButton = createButton('PLAY GAME', 2732 / 2 + 100, 2048 / 2, 500, 150, 85); var tutorialButton = createButton('TUTORIAL', 2732 / 2 + 300, 2048 / 2, 500, 150, 85); var settingsButton = createButton('SETTINGS', 2732 / 2 + 500, 2048 / 2, 500, 150, 85); // Start button animations with staggered delays animateButton(playButton, 0, 0.95); animateButton(tutorialButton, 300); animateButton(settingsButton, 600); // Add button to main menu mainMenuContainer.addChild(playButton); mainMenuContainer.addChild(tutorialButton); mainMenuContainer.addChild(settingsButton); // Add button interaction playButton.interactive = true; playButton.down = function () { showGameMode(); }; tutorialButton.interactive = true; tutorialButton.down = function () { showTutorial(); }; settingsButton.interactive = true; settingsButton.down = function () { showSettings(); }; } //Create tutorial screen function createTutorialScreen() { // Add background overlay for better readability var bg = LK.getAsset('grid_cell_dark', { anchorX: 0, anchorY: 0, width: 2048, height: 2732, alpha: 0.2 }); tutorialContainer.addChild(bg); // Main container to allow scrolling var scrollContainer = new Container(); tutorialContainer.scrollContainer = scrollContainer; // Expose scrollContainer as a property tutorialContainer.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); // Define tutorial offset for positioning var tutorialOffset = { x: 0, y: 0 }; // 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 // Back button functionality var backButton = createButton('BACK', 2732 - 200, 200, 200, 100, 60); tutorialContainer.addChild(backButton); backButton.interactive = true; backButton.down = function () { showMainMenu(); }; animateButton(backButton, 0, 0.95); var scrollY = 0; tutorialContainer.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; tutorialContainer.down = function (x, y, obj) { lastY = y; lastTime = Date.now(); scrollVelocity = 0; isDragging = true; }; tutorialContainer.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; tutorialContainer.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; } }; tutorialContainer.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; tutorialContainer.scrollY = scrollY; // Update property scrollVelocity = 0; } else if (scrollY > maxScroll) { tween(scrollContainer, { y: -maxScroll }, { duration: 300, easing: tween.elasticOut }); scrollY = maxScroll; tutorialContainer.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; tutorialContainer.scrollY = scrollY; // Update property scrollVelocity = 0; } else if (scrollY > maxScroll) { scrollY = maxScroll; tutorialContainer.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 } } } //Create settings screen function createSettingsScreen() { // Ensure settingsContainer is properly initialized when settings screen is created settingsContainer.visible = false; // Title text var titleText = new Text2('Settings', { size: 150, fill: '#FFFFFF' }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 500; settingsContainer.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 }); }; // Expose elements for external access container.toggleBg = toggleBg; container.indicator = indicator; return container; }; // Create settings controls var soundToggle = createToggle('Sound Effects', 2732 / 2 + 400, true, 2048 / 2 - 600); var musicToggle = createToggle('Music', 2732 / 2 + 550, true, 2048 / 2 - 600); // Add music toggle functionality musicToggle.toggleBg.down = function (x, y, obj) { musicToggle.state = !musicToggle.state; tween(musicToggle.indicator, { x: musicToggle.state ? 950 : 850 }, { duration: 200, easing: tween.easeOutQuad }); // Toggle music based on the toggle state if (musicToggle.state) { LK.playMusic('game_music', { fade: { start: 0, end: 0.5, duration: 500 } }); } else { LK.stopMusic(); } }; // Add sound toggle functionality soundToggle.toggleBg.down = function (x, y, obj) { soundToggle.state = !soundToggle.state; game.soundEnabled = soundToggle.state; tween(soundToggle.indicator, { x: soundToggle.state ? 950 : 850 }, { duration: 200, easing: tween.easeOutQuad }); }; // Expose sound toggle state to the game object game.soundEnabled = soundToggle.state; // Add controls to container settingsContainer.addChild(soundToggle); settingsContainer.addChild(musicToggle); // Back button var backButton = createButton('BACK', 2732 - 200, 200, 200, 100, 60); settingsContainer.addChild(backButton); backButton.interactive = true; backButton.down = function () { showMainMenu(); }; animateButton(backButton, 0, 0.95); } //Create Game Mode screen function createGameModeScreen() { // Add game logo var logo = LK.getAsset('game_logo', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 800 }); gameModeContainer.addChild(logo); var titleText = new Text2('GAME MODE', { size: 150, fill: '#FFFFFF' }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 1350; gameModeContainer.addChild(titleText); // Add play button var pVpButton = createButton('PLAYER VS PLAYER', 2732 / 2 + 300, 2048 / 2, 800, 150, 85); var pvaButton = createButton('PLAYER VS AI', 2732 / 2 + 500, 2048 / 2, 600, 150, 85); // Start button animations with staggered delays animateButton(pVpButton, 0, 0.95); animateButton(pvaButton, 300, 0.95); // Add buttons to game mode container gameModeContainer.addChild(pVpButton); gameModeContainer.addChild(pvaButton); // Add button interaction pVpButton.interactive = true; pVpButton.down = function () { gameMode = 'PVP'; showGameplay(); }; pvaButton.interactive = true; pvaButton.down = function () { gameMode = 'PVA'; showGameplay(); }; // Back button var backButton = createButton('BACK', 2732 - 200, 200, 200, 100, 60); gameModeContainer.addChild(backButton); backButton.interactive = true; backButton.down = function () { showMainMenu(); }; animateButton(backButton, 0, 0.95); } // Show gameplay screen function showGameplay() { mainMenuContainer.visible = false; gameplayContainer.visible = true; gameModeContainer.visible = false; pauseMenuContainer.visible = false; game.isPaused = false; if (gameMode === 'PVP') { gameState = 'playerTurn'; game.gameState = 'playerTurn'; statusText.setText('Blue Player Turn'); } else { gameState = 'playerTurn'; game.gameState = 'playerTurn'; statusText.setText('Player Turn'); } } // Function to return to main menu function showMainMenu() { gameplayContainer.visible = false; mainMenuContainer.visible = true; tutorialContainer.visible = false; settingsContainer.visible = false; gameModeContainer.visible = false; pauseMenuContainer.visible = false; gameState = 'mainMenu'; // Reset the game state resetGame(); } // Function to show tutorial screen function showTutorial() { mainMenuContainer.visible = false; tutorialContainer.visible = true; gameState = 'tutorial'; } // Function to show settings screen function showSettings() { mainMenuContainer.visible = false; settingsContainer.visible = true; gameState = 'settings'; } function showGameMode() { mainMenuContainer.visible = false; gameModeContainer.visible = true; } // Create pause menu function createPauseMenu() { // Create pause menu var pauseMenu = new PauseMenu(); pauseMenuContainer.addChild(pauseMenu); } // Reset game state function resetGame() { // Clear the grid for (var row = 0; row < GRID_ROWS; row++) { for (var col = 0; col < GRID_COLS; col++) { if (grid[row] && grid[row][col]) { grid[row][col].occupied = false; grid[row][col].occupiedBy = null; grid[row][col].clearHighlight(); } } } // Remove all units for (var i = units.length - 1; i >= 0; i--) { if (units[i] && units[i].parent) { units[i].parent.removeChild(units[i]); } } units = []; // Reset selection and game state selectedUnit = null; // Recreate units createUnits(); } // Create buttons for different game modes var createButton = function createButton(text, yPos, xPos, width, height, textSize) { var button = new Container(); textSize = textSize || 100; // Button background var bg = LK.getAsset('grid_cell', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8, width: width, height: height }); button.addChild(bg); // Button text var buttonText = new Text2(text, { size: textSize, fill: '#000000' }); buttonText.anchor.set(0.5, 0.5); button.addChild(buttonText); // Position button button.y = yPos; button.x = xPos; // 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; }; // Animate buttons with grow and shrink effect function animateButton(button, delay, startScale, endScale) { // Provide default values for startScale and endScale if not specified startScale = startScale || 1.1; endScale = endScale || 1.0; LK.setTimeout(function () { tween(button, { scaleX: startScale, scaleY: startScale }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(button, { scaleX: endScale, scaleY: endScale }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { animateButton(button, 0); } }); } }); }, delay); } // Initialize game initializeGame(); ;
/****
* 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;
});
// ArrowStreak: glowing arrow trail for archer attacks
var ArrowStreak = Container.expand(function () {
var self = Container.call(this);
self.init = function (startX, startY, endX, endY, color) {
self.x = startX;
self.y = startY;
self.alpha = 1;
// Use a thin, long highlight_move asset as the streak
var dx = endX - startX;
var dy = endY - startY;
var dist = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
var streak = self.attachAsset('highlight_move', {
anchorX: 0,
anchorY: 0.5,
width: dist,
height: 24,
alpha: 0.7,
tint: color || 0x44aaff
});
streak.rotation = angle;
// Animate fade out and shrink
tween(streak, {
alpha: 0,
width: dist * 0.7
}, {
duration: 350,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (self.parent) self.parent.removeChild(self);
}
});
return self;
};
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() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
}, 1200);
return self;
};
return self;
});
// ExplosionEffect: magical explosion with debris and sparkles for wizard area attacks
var ExplosionEffect = Container.expand(function () {
var self = Container.call(this);
self.init = function (x, y, color) {
self.x = x;
self.y = y;
self.alpha = 1;
// Main explosion core
var core = self.attachAsset('highlight_attack', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.7
});
// Animate core expansion and fade
tween(core, {
scaleX: 2.2,
scaleY: 2.2,
alpha: 0
}, {
duration: 400,
easing: tween.easeOutQuad
});
// Debris particles
for (var i = 0; i < 8; i++) {
var angle = Math.PI * 2 / 8 * i;
var debris = self.attachAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.18 + Math.random() * 0.12,
scaleY: 0.18 + Math.random() * 0.12,
alpha: 0.7,
tint: color || 0x99ccff
});
debris.x = 0;
debris.y = 0;
var dist = 60 + Math.random() * 40;
tween(debris, {
x: Math.cos(angle) * dist,
y: Math.sin(angle) * dist,
alpha: 0
}, {
duration: 500 + Math.random() * 200,
easing: tween.easeOutQuad,
onFinish: function (d) {
return function () {
if (d.parent) d.parent.removeChild(d);
};
}(debris)
});
}
// Sparkles
for (var j = 0; j < 6; j++) {
var sparkle = self.attachAsset('highlight_move', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.12 + Math.random() * 0.08,
scaleY: 0.12 + Math.random() * 0.08,
alpha: 0.8,
tint: 0xffffff
});
sparkle.x = 0;
sparkle.y = 0;
var sAngle = Math.random() * Math.PI * 2;
var sDist = 40 + Math.random() * 60;
tween(sparkle, {
x: Math.cos(sAngle) * sDist,
y: Math.sin(sAngle) * sDist,
alpha: 0
}, {
duration: 350 + Math.random() * 200,
easing: tween.easeOutQuad,
onFinish: function (s) {
return function () {
if (s.parent) s.parent.removeChild(s);
};
}(sparkle)
});
}
// Fade out container
tween(self, {
alpha: 0
}, {
duration: 600,
easing: tween.linear,
onFinish: function onFinish() {
if (self.parent) self.parent.removeChild(self);
}
});
return self;
};
return self;
});
// FloatingDamageText: shows animated floating damage numbers above units
var FloatingDamageText = Container.expand(function () {
var self = Container.call(this);
self.init = function (damage, x, y, color) {
// Create the damage text
var dmgText = new Text2('-' + damage, {
size: 80,
fill: color || '#FFDD33'
});
dmgText.anchor.set(0.5, 0.5);
self.addChild(dmgText);
self.x = x;
self.y = y;
self.alpha = 1;
// Animate upwards and fade out
tween(self, {
y: y - 100,
alpha: 0
}, {
duration: 900,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (self.parent) self.parent.removeChild(self);
}
});
return self;
};
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) {
console.log("[GridCell Debug] Cell clicked:", {
row: self.row,
col: self.col,
gameState: game.gameState,
occupied: self.occupied,
occupiedByTeam: self.occupied ? self.occupiedBy.team : null,
occupiedByType: self.occupied ? self.occupiedBy.type : null,
highlighted: self.highlighted,
highlightType: self.highlightType
});
if (game.gameState === 'playerTurn' || game.gameState === 'player2Turn') {
// If cell is highlighted for attack, directly perform the attack instead of trying to select the unit
if (self.highlighted && self.highlightType === 'attack' && selectedUnit) {
console.log("[GridCell Debug] Processing cell as attack target");
game.attackCell(self);
return; // Prevent further processing
} else {
game.handleCellClick(self);
}
}
};
return self;
});
var PauseMenu = Container.expand(function () {
var self = Container.call(this);
// Create semi-transparent background overlay
var overlay = self.attachAsset('grid_cell_dark', {
anchorX: 0,
anchorY: 0,
width: 2048,
height: 2732,
alpha: 0.8
});
// Create pause menu title
var titleText = new Text2('PAUSED', {
size: 150,
fill: '#FFFFFF'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 800;
self.addChild(titleText);
// Create menu buttons
var resumeButton = createButton('RESUME', 1200, 2048 / 2, 600, 150, 85);
var menuButton = createButton('MAIN MENU', 1600, 2048 / 2, 600, 150, 85);
// Add buttons to container
self.addChild(resumeButton);
self.addChild(menuButton);
// Animate buttons with staggered delays
animateButton(resumeButton, 0, 0.95);
animateButton(menuButton, 150, 0.95);
// Button interactions
resumeButton.interactive = true;
resumeButton.down = function () {
game.togglePause();
};
menuButton.interactive = true;
menuButton.down = function () {
game.togglePause();
showMainMenu();
};
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 can move freely to any unoccupied cell except its current position
if (targetRow === self.row && targetCol === self.col) {
return false;
}
return true;
};
// 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;
});
// UnitTrail: shows a fading afterimage or dust cloud at a given position
var UnitTrail = Container.expand(function () {
var self = Container.call(this);
// type: "afterimage" or "dust"
self.init = function (unit, type) {
// Use the same asset as the unit, but faded and smaller
var assetId = unit.type + '_' + unit.team;
var trailGraphic = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.5
});
// Optionally tint for dust
if (type === "dust") {
trailGraphic.alpha = 0.35;
}
self.x = unit.x;
self.y = unit.y;
self.alpha = 0.7;
// Fade out and shrink
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (self.parent) self.parent.removeChild(self);
}
});
return self;
};
return self;
});
// WarriorDustCloud: dust cloud for warrior movement/attack
var WarriorDustCloud = Container.expand(function () {
var self = Container.call(this);
self.init = function (x, y) {
self.x = x;
self.y = y;
self.alpha = 0.7;
// Use grid_cell_dark as dust puff
var dust = self.attachAsset('grid_cell_dark', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4 + Math.random() * 0.2,
scaleY: 0.2 + Math.random() * 0.2,
alpha: 0.5
});
// Animate dust expansion and fade
tween(dust, {
scaleX: dust.scaleX * 1.7,
scaleY: dust.scaleY * 1.7,
alpha: 0
}, {
duration: 400 + Math.random() * 120,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
if (self.parent) self.parent.removeChild(self);
}
});
return self;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Background image asset
// Game constants
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 = 'mainMenu'; // mainMenu, playerTurn, aiTurn, gameOver
var gameMode; // PVP, PVA
var ai = new AIPlayer();
var statusText = new Text2('Player Turn', {
size: 120,
// Significantly increased text size
fill: 0xFFFFFF
});
statusText.visible = false; // Hide status text from display
// Create containers for different screens
var mainMenuContainer = new Container();
var gameplayContainer = new Container();
var tutorialContainer = new Container();
var settingsContainer = new Container();
var gameModeContainer = new Container();
var pauseMenuContainer = new Container();
// Initial visibility
mainMenuContainer.visible = true;
gameplayContainer.visible = false;
tutorialContainer.visible = false;
settingsContainer.visible = false;
gameModeContainer.visible = false;
pauseMenuContainer.visible = false;
// Initialize the game board
function initializeGame() {
// Play background music
LK.playMusic('game_music', {
volume: 0.3,
fade: {
start: 0,
end: 0.5,
duration: 1000
}
});
// 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);
// Add containers to game
game.addChild(mainMenuContainer);
game.addChild(gameplayContainer);
game.addChild(tutorialContainer);
game.addChild(settingsContainer);
game.addChild(gameModeContainer);
game.addChild(pauseMenuContainer);
// Create pause button in gameplay
var pauseButton = createButton('II', 100, 2048 - 100, 150, 150, 70);
pauseButton.interactive = true;
pauseButton.down = function () {
game.togglePause();
};
gameplayContainer.addChild(pauseButton);
// 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;
gameplayContainer.addChild(cell);
}
}
// Create units for both teams
createUnits();
// Status text is handled internally by the game without UI display
// Set up main menu
createMainMenu();
createTutorialScreen();
createSettingsScreen();
createGameModeScreen();
createPauseMenu();
}
// 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: 0x000000 // Black color for health text
});
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 gameplay container
gameplayContainer.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 depending on game mode
unit.down = function (x, y, obj) {
console.log("[Unit Debug] Unit clicked:", {
type: unit.type,
team: team,
row: unit.row,
col: unit.col,
gameMode: gameMode,
gameState: game.gameState
});
// Check if we're clicking on an enemy unit that's highlighted for attack
var cell = grid[unit.row][unit.col];
if (cell.highlighted && cell.highlightType === 'attack' && selectedUnit) {
console.log("[Unit Debug] Processing as attack target");
game.attackCell(cell);
return; // Prevent further selection logic
}
if (gameMode === 'PVP') {
// In PVP mode, blue units can be selected during playerTurn and red units during player2Turn
if (team === 'blue' && game.gameState === 'playerTurn' || team === 'red' && game.gameState === 'player2Turn') {
console.log("[Unit Debug] Unit eligible for selection");
selectUnit(unit);
} else {
console.log("[Unit Debug] Unit not eligible for selection - wrong team for current turn");
}
} else {
// In PVA mode, only blue units can be selected during player turn
if (team === 'blue' && game.gameState === 'playerTurn') {
console.log("[Unit Debug] Unit eligible for selection");
selectUnit(unit);
} else {
console.log("[Unit Debug] Unit not eligible for selection - PVA mode restrictions");
}
}
};
// 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) {
console.log("[Click Debug] Cell clicked:", {
row: cell.row,
col: cell.col,
highlighted: cell.highlighted,
highlightType: cell.highlightType,
hasSelectedUnit: !!selectedUnit,
gameState: game.gameState
});
if (!selectedUnit || game.gameState !== 'playerTurn' && game.gameState !== 'player2Turn') {
console.log("[Click Debug] Click ignored: no unit selected or wrong game state");
return;
}
// If cell is highlighted for movement
if (cell.highlighted && cell.highlightType === 'move') {
console.log("[Click Debug] Processing as move");
moveSelectedUnit(cell.row, cell.col);
}
// If cell is highlighted for attack
else if (cell.highlighted && cell.highlightType === 'attack') {
console.log("[Click Debug] Processing as attack");
attackCell(cell);
} else {
console.log("[Click Debug] Cell not highlighted for move or attack");
}
};
// Select a unit
function selectUnit(unit) {
console.log("[Select Debug] Unit selection attempt:", {
unitType: unit.type,
unitTeam: unit.team,
gameMode: gameMode,
gameState: game.gameState // Use game.gameState instead of gameState
});
// Can only select your own units
if (gameMode === 'PVP') {
// In PVP mode, blue units during playerTurn and red units during player2Turn
if (unit.team !== 'blue' && game.gameState === 'playerTurn' || unit.team !== 'red' && game.gameState === 'player2Turn') {
console.log("[Select Debug] Selection rejected: wrong team for current turn");
return;
}
} else {
// Original PVA mode logic
if (unit.team !== 'blue' && game.gameState === 'playerTurn') {
console.log("[Select Debug] Selection rejected: not a blue unit during player turn");
return;
}
if (unit.team !== 'red' && game.gameState === 'aiTurn') {
console.log("[Select Debug] Selection rejected: not a red unit during AI turn");
return;
}
}
console.log("[Select Debug] Unit successfully selected");
// 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
if (selectedUnit && selectedUnit.unitGraphic) {
tween(selectedUnit.unitGraphic, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1.0
}, {
duration: 200,
easing: tween.elasticOut,
onFinish: function onFinish() {
if (selectedUnit && selectedUnit.unitGraphic) {
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;
// Play move sound effect only if sound is enabled
if (game.soundEnabled) {
LK.getSound('move_sound').play();
}
// Move unit with animation
// Leave a fading trail at the starting position
var trail = new UnitTrail().init(selectedUnit, "afterimage");
gameplayContainer.addChild(trail);
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() {
// Optionally, leave a dust cloud at the destination
var dust = new UnitTrail().init(selectedUnit, "dust");
dust.x = targetX;
dust.y = targetY;
gameplayContainer.addChild(dust);
// If warrior, add extra dust cloud for heavy step
if (selectedUnit.type === 'warrior') {
var warriorDust = new WarriorDustCloud().init(targetX, targetY);
gameplayContainer.addChild(warriorDust);
}
// 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) {
console.log("[Attack Debug] Attack started with params:", {
hasSelectedUnit: !!selectedUnit,
cellOccupied: cell.occupied,
cellTeam: cell.occupied ? cell.occupiedBy.team : null,
selectedUnitTeam: selectedUnit ? selectedUnit.team : null,
gameMode: gameMode,
gameState: game.gameState // Use game.gameState instead of gameState
});
if (!selectedUnit || !cell.occupied || cell.occupiedBy.team === selectedUnit.team) {
console.log("[Attack Debug] Attack aborted: basic validation failed");
return;
}
// Additional check for PVP mode to ensure correct team attacking in the right turn
if (gameMode === 'PVP') {
console.log("[Attack Debug] PVP check:", {
isPlayerTurn: game.gameState === 'playerTurn',
isPlayer2Turn: game.gameState === 'player2Turn',
selectedUnitTeam: selectedUnit.team
});
// Ensure only the correct team can attack during their turn
if (game.gameState === 'playerTurn' && selectedUnit.team !== 'blue' || game.gameState === 'player2Turn' && selectedUnit.team !== 'red') {
console.log("[Attack Debug] Attack aborted: wrong team for current turn");
return;
}
}
console.log("[Attack Debug] Attack proceeding with target:", cell.occupiedBy.type);
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;
// Play attack sound only if sound is enabled
if (game.soundEnabled) {
LK.getSound('attack_sound').play();
}
// Enhanced attack animation sequence
// Add a fast yellow screen flash at 0.5 alpha when a unit attacks an opponent unit, with 0.2s delay
LK.setTimeout(function () {
LK.effects.flashScreen(0xffff00, 120, 0.5); // 0.5 alpha, 120ms duration
}, 200);
// Add a small screen shake to the gameplay container with 0.2s delay
LK.setTimeout(function () {
tween(gameplayContainer, {
x: gameplayContainer.x + (Math.random() > 0.5 ? 12 : -12),
y: gameplayContainer.y + (Math.random() > 0.5 ? 8 : -8)
}, {
duration: 60,
easing: tween.easeOutQuad,
onFinish: function onFinish() {
tween(gameplayContainer, {
x: 0,
y: 0
}, {
duration: 80,
easing: tween.easeOutQuad
});
}
});
}, 200);
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() {
// If warrior, add dust cloud at lunge position
if (selectedUnit.type === 'warrior') {
var dustAttack = new WarriorDustCloud().init(selectedUnit.x, selectedUnit.y);
gameplayContainer.addChild(dustAttack);
}
// 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());
// Show floating damage text
var dmgText = new FloatingDamageText().init(actualDamage, targetUnit.x, targetUnit.y - 80, isCritical ? '#FF3333' : '#FFDD33');
gameplayContainer.addChild(dmgText);
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);
// Show magical explosion effect at the center of the area attack
var explosion = new ExplosionEffect().init(targetUnit.x, targetUnit.y, 0x99ccff);
gameplayContainer.addChild(explosion);
// 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);
gameplayContainer.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());
// Show floating damage text for adjacent units
var adjDmgText = new FloatingDamageText().init(adjDamage, adjTargetUnit.x, adjTargetUnit.y - 80, '#FFDD33');
gameplayContainer.addChild(adjDmgText);
if (adjTargetUnit.health <= 0) {
removeUnit(adjTargetUnit);
}
}
}
} else {
// Normal attack
// If archer, show arrow streak from attacker to target
if (selectedUnit.type === 'archer') {
var arrowStreak = new ArrowStreak().init(selectedUnit.x, selectedUnit.y, targetUnit.x, targetUnit.y, 0x44aaff);
gameplayContainer.addChild(arrowStreak);
}
targetUnit.health -= actualDamage;
targetUnit.healthText.setText(targetUnit.health.toString());
// Show floating damage text
var dmgText = new FloatingDamageText().init(actualDamage, targetUnit.x, targetUnit.y - 80, isCritical ? '#FF3333' : '#FFDD33');
gameplayContainer.addChild(dmgText);
// Show critical hit effect if applicable
if (isCritical) {
var critEffect = new CriticalDamageEffect().init(actualDamage, targetUnit.x, targetUnit.y - 50);
gameplayContainer.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
gameplayContainer.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 (gameMode === 'PVP') {
// In PVP mode, toggle between blue and red player turns
if (game.gameState === 'playerTurn') {
gameState = 'player2Turn';
game.gameState = 'player2Turn';
statusText.setText('Red Player Turn');
} else if (game.gameState === 'player2Turn') {
gameState = 'playerTurn';
game.gameState = 'playerTurn';
statusText.setText('Blue Player Turn');
}
} else {
// Original PVA mode logic
if (game.gameState === 'playerTurn') {
gameState = 'aiTurn';
game.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 {
gameState = 'playerTurn';
game.gameState = 'playerTurn';
statusText.setText('Player Turn');
}
}, 500); // Small delay for better user experience
} else if (game.gameState === 'aiTurn') {
gameState = 'playerTurn';
game.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();
LK.setTimeout(function () {
showMainMenu();
}, 3000);
} else {
statusText.setText('Game Over');
LK.showGameOver();
LK.setTimeout(function () {
showMainMenu();
}, 3000);
}
}
// 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;
};
// 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;
// Toggle pause functionality
game.isPaused = false;
game.togglePause = function () {
game.isPaused = !game.isPaused;
pauseMenuContainer.visible = game.isPaused;
// Store previous game state when pausing
if (game.isPaused) {
game.previousGameState = game.gameState;
} else {
// Restore previous game state when unpausing
if (game.previousGameState) {
game.gameState = game.previousGameState;
}
}
};
// 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;
}
});
// Create main menu
function createMainMenu() {
// Add game logo
var logo = LK.getAsset('game_logo', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 800
});
mainMenuContainer.addChild(logo);
// Add play button
var playButton = createButton('PLAY GAME', 2732 / 2 + 100, 2048 / 2, 500, 150, 85);
var tutorialButton = createButton('TUTORIAL', 2732 / 2 + 300, 2048 / 2, 500, 150, 85);
var settingsButton = createButton('SETTINGS', 2732 / 2 + 500, 2048 / 2, 500, 150, 85);
// Start button animations with staggered delays
animateButton(playButton, 0, 0.95);
animateButton(tutorialButton, 300);
animateButton(settingsButton, 600);
// Add button to main menu
mainMenuContainer.addChild(playButton);
mainMenuContainer.addChild(tutorialButton);
mainMenuContainer.addChild(settingsButton);
// Add button interaction
playButton.interactive = true;
playButton.down = function () {
showGameMode();
};
tutorialButton.interactive = true;
tutorialButton.down = function () {
showTutorial();
};
settingsButton.interactive = true;
settingsButton.down = function () {
showSettings();
};
}
//Create tutorial screen
function createTutorialScreen() {
// Add background overlay for better readability
var bg = LK.getAsset('grid_cell_dark', {
anchorX: 0,
anchorY: 0,
width: 2048,
height: 2732,
alpha: 0.2
});
tutorialContainer.addChild(bg);
// Main container to allow scrolling
var scrollContainer = new Container();
tutorialContainer.scrollContainer = scrollContainer; // Expose scrollContainer as a property
tutorialContainer.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);
// Define tutorial offset for positioning
var tutorialOffset = {
x: 0,
y: 0
};
// 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
// Back button functionality
var backButton = createButton('BACK', 2732 - 200, 200, 200, 100, 60);
tutorialContainer.addChild(backButton);
backButton.interactive = true;
backButton.down = function () {
showMainMenu();
};
animateButton(backButton, 0, 0.95);
var scrollY = 0;
tutorialContainer.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;
tutorialContainer.down = function (x, y, obj) {
lastY = y;
lastTime = Date.now();
scrollVelocity = 0;
isDragging = true;
};
tutorialContainer.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;
tutorialContainer.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;
}
};
tutorialContainer.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;
tutorialContainer.scrollY = scrollY; // Update property
scrollVelocity = 0;
} else if (scrollY > maxScroll) {
tween(scrollContainer, {
y: -maxScroll
}, {
duration: 300,
easing: tween.elasticOut
});
scrollY = maxScroll;
tutorialContainer.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;
tutorialContainer.scrollY = scrollY; // Update property
scrollVelocity = 0;
} else if (scrollY > maxScroll) {
scrollY = maxScroll;
tutorialContainer.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
}
}
}
//Create settings screen
function createSettingsScreen() {
// Ensure settingsContainer is properly initialized when settings screen is created
settingsContainer.visible = false;
// Title text
var titleText = new Text2('Settings', {
size: 150,
fill: '#FFFFFF'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 500;
settingsContainer.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
});
};
// Expose elements for external access
container.toggleBg = toggleBg;
container.indicator = indicator;
return container;
};
// Create settings controls
var soundToggle = createToggle('Sound Effects', 2732 / 2 + 400, true, 2048 / 2 - 600);
var musicToggle = createToggle('Music', 2732 / 2 + 550, true, 2048 / 2 - 600);
// Add music toggle functionality
musicToggle.toggleBg.down = function (x, y, obj) {
musicToggle.state = !musicToggle.state;
tween(musicToggle.indicator, {
x: musicToggle.state ? 950 : 850
}, {
duration: 200,
easing: tween.easeOutQuad
});
// Toggle music based on the toggle state
if (musicToggle.state) {
LK.playMusic('game_music', {
fade: {
start: 0,
end: 0.5,
duration: 500
}
});
} else {
LK.stopMusic();
}
};
// Add sound toggle functionality
soundToggle.toggleBg.down = function (x, y, obj) {
soundToggle.state = !soundToggle.state;
game.soundEnabled = soundToggle.state;
tween(soundToggle.indicator, {
x: soundToggle.state ? 950 : 850
}, {
duration: 200,
easing: tween.easeOutQuad
});
};
// Expose sound toggle state to the game object
game.soundEnabled = soundToggle.state;
// Add controls to container
settingsContainer.addChild(soundToggle);
settingsContainer.addChild(musicToggle);
// Back button
var backButton = createButton('BACK', 2732 - 200, 200, 200, 100, 60);
settingsContainer.addChild(backButton);
backButton.interactive = true;
backButton.down = function () {
showMainMenu();
};
animateButton(backButton, 0, 0.95);
}
//Create Game Mode screen
function createGameModeScreen() {
// Add game logo
var logo = LK.getAsset('game_logo', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 800
});
gameModeContainer.addChild(logo);
var titleText = new Text2('GAME MODE', {
size: 150,
fill: '#FFFFFF'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 1350;
gameModeContainer.addChild(titleText);
// Add play button
var pVpButton = createButton('PLAYER VS PLAYER', 2732 / 2 + 300, 2048 / 2, 800, 150, 85);
var pvaButton = createButton('PLAYER VS AI', 2732 / 2 + 500, 2048 / 2, 600, 150, 85);
// Start button animations with staggered delays
animateButton(pVpButton, 0, 0.95);
animateButton(pvaButton, 300, 0.95);
// Add buttons to game mode container
gameModeContainer.addChild(pVpButton);
gameModeContainer.addChild(pvaButton);
// Add button interaction
pVpButton.interactive = true;
pVpButton.down = function () {
gameMode = 'PVP';
showGameplay();
};
pvaButton.interactive = true;
pvaButton.down = function () {
gameMode = 'PVA';
showGameplay();
};
// Back button
var backButton = createButton('BACK', 2732 - 200, 200, 200, 100, 60);
gameModeContainer.addChild(backButton);
backButton.interactive = true;
backButton.down = function () {
showMainMenu();
};
animateButton(backButton, 0, 0.95);
}
// Show gameplay screen
function showGameplay() {
mainMenuContainer.visible = false;
gameplayContainer.visible = true;
gameModeContainer.visible = false;
pauseMenuContainer.visible = false;
game.isPaused = false;
if (gameMode === 'PVP') {
gameState = 'playerTurn';
game.gameState = 'playerTurn';
statusText.setText('Blue Player Turn');
} else {
gameState = 'playerTurn';
game.gameState = 'playerTurn';
statusText.setText('Player Turn');
}
}
// Function to return to main menu
function showMainMenu() {
gameplayContainer.visible = false;
mainMenuContainer.visible = true;
tutorialContainer.visible = false;
settingsContainer.visible = false;
gameModeContainer.visible = false;
pauseMenuContainer.visible = false;
gameState = 'mainMenu';
// Reset the game state
resetGame();
}
// Function to show tutorial screen
function showTutorial() {
mainMenuContainer.visible = false;
tutorialContainer.visible = true;
gameState = 'tutorial';
}
// Function to show settings screen
function showSettings() {
mainMenuContainer.visible = false;
settingsContainer.visible = true;
gameState = 'settings';
}
function showGameMode() {
mainMenuContainer.visible = false;
gameModeContainer.visible = true;
}
// Create pause menu
function createPauseMenu() {
// Create pause menu
var pauseMenu = new PauseMenu();
pauseMenuContainer.addChild(pauseMenu);
}
// Reset game state
function resetGame() {
// Clear the grid
for (var row = 0; row < GRID_ROWS; row++) {
for (var col = 0; col < GRID_COLS; col++) {
if (grid[row] && grid[row][col]) {
grid[row][col].occupied = false;
grid[row][col].occupiedBy = null;
grid[row][col].clearHighlight();
}
}
}
// Remove all units
for (var i = units.length - 1; i >= 0; i--) {
if (units[i] && units[i].parent) {
units[i].parent.removeChild(units[i]);
}
}
units = [];
// Reset selection and game state
selectedUnit = null;
// Recreate units
createUnits();
}
// Create buttons for different game modes
var createButton = function createButton(text, yPos, xPos, width, height, textSize) {
var button = new Container();
textSize = textSize || 100;
// Button background
var bg = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
width: width,
height: height
});
button.addChild(bg);
// Button text
var buttonText = new Text2(text, {
size: textSize,
fill: '#000000'
});
buttonText.anchor.set(0.5, 0.5);
button.addChild(buttonText);
// Position button
button.y = yPos;
button.x = xPos;
// 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;
};
// Animate buttons with grow and shrink effect
function animateButton(button, delay, startScale, endScale) {
// Provide default values for startScale and endScale if not specified
startScale = startScale || 1.1;
endScale = endScale || 1.0;
LK.setTimeout(function () {
tween(button, {
scaleX: startScale,
scaleY: startScale
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(button, {
scaleX: endScale,
scaleY: endScale
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
animateButton(button, 0);
}
});
}
});
}, delay);
}
// Initialize game
initializeGame();
;
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