/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var ChessPiece = Container.expand(function (type, color, row, col) {
var self = Container.call(this);
self.pieceType = type;
self.pieceColor = color;
self.row = row;
self.col = col;
self.hasMoved = false;
self.poisonTurns = 0;
self.poisonEffect = null;
self.poisonCounterText = null;
self.snakeMovesThisTurn = 0;
self.maxSnakeMoves = 3;
self.snakeLastMoveDirection = null; // Track last move direction for straight ahead requirement
self.snakeOnCooldown = false; // Prevent consecutive snake usage
self.princessEliminationMode = false; // Track if princess is in elimination mode
self.princessTargetsSelected = 0; // Track how many targets princess has selected
self.princessSelectedTargets = []; // Store selected targets for elimination
self.summonerMoveCount = 0; // Track moves for summoner ability
self.hasSummoned = false; // Track if summoner has summoned this cycle
var assetName = color + type.charAt(0).toUpperCase() + type.slice(1);
var pieceGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.updatePosition = function (animate) {
var targetX = boardStartX + self.col * squareSize + squareSize / 2;
var targetY = boardStartY + self.row * squareSize + squareSize / 2;
if (animate && (self.x !== targetX || self.y !== targetY)) {
// Add subtle bounce animation for piece movement
tween(self, {
x: targetX,
y: targetY,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Bounce back to normal size
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.bounceOut
});
}
});
} else {
self.x = targetX;
self.y = targetY;
}
};
self.canMoveTo = function (targetRow, targetCol) {
// Poisoned pieces cannot move
if (self.poisonTurns > 0) {
return false;
}
if (targetRow < 0 || targetRow > 7 || targetCol < 0 || targetCol > 15) {
return false;
}
var targetPiece = getPieceAt(targetRow, targetCol);
if (targetPiece && targetPiece.pieceColor === self.pieceColor) {
return false;
}
switch (self.pieceType) {
case 'pawn':
return self.canPawnMove(targetRow, targetCol);
case 'rook':
return self.canRookMove(targetRow, targetCol);
case 'knight':
return self.canKnightMove(targetRow, targetCol);
case 'bishop':
return self.canBishopMove(targetRow, targetCol);
case 'queen':
// Check stamina for queen moves
if (self.pieceColor === 'white' && staminaPoints <= 0) {
return false;
}
if (self.pieceColor === 'black' && aiStaminaPoints <= 0) {
return false;
}
return self.canQueenMove(targetRow, targetCol);
case 'princess':
return self.canPrincessMove(targetRow, targetCol);
case 'king':
return self.canKingMove(targetRow, targetCol);
case 'snake':
// Check stamina for snake moves
if (self.pieceColor === 'white' && staminaPoints <= 0) {
return false;
}
if (self.pieceColor === 'black' && aiStaminaPoints <= 0) {
return false;
}
return self.canSnakeMove(targetRow, targetCol);
case 'jester':
return self.canJesterMove(targetRow, targetCol);
case 'summoner':
return self.canSummonerMove(targetRow, targetCol);
}
return false;
};
self.canPawnMove = function (targetRow, targetCol) {
var direction = self.pieceColor === 'white' ? -1 : 1;
var startRow = self.pieceColor === 'white' ? 6 : 1;
if (targetCol === self.col) {
if (targetRow === self.row + direction && !getPieceAt(targetRow, targetCol)) {
return true;
}
if (self.row === startRow && targetRow === self.row + 2 * direction && !getPieceAt(targetRow, targetCol)) {
// Check if path is clear for 2-square pawn move
return isPathClear(self.row, self.col, targetRow, targetCol);
}
} else if (Math.abs(targetCol - self.col) === 1 && targetRow === self.row + direction) {
var targetPiece = getPieceAt(targetRow, targetCol);
if (targetPiece && targetPiece.pieceColor !== self.pieceColor) {
return true;
}
}
return false;
};
self.canRookMove = function (targetRow, targetCol) {
if (self.row !== targetRow && self.col !== targetCol) {
return false;
}
return isPathClear(self.row, self.col, targetRow, targetCol);
};
self.canKnightMove = function (targetRow, targetCol) {
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
return rowDiff === 2 && colDiff === 1 || rowDiff === 1 && colDiff === 2;
};
self.canBishopMove = function (targetRow, targetCol) {
if (Math.abs(targetRow - self.row) !== Math.abs(targetCol - self.col)) {
return false;
}
return isPathClear(self.row, self.col, targetRow, targetCol);
};
self.canPrincessMove = function (targetRow, targetCol) {
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
// Princess can move exactly 2 squares straight (horizontal or vertical only)
if (rowDiff === 2 && colDiff === 0 || rowDiff === 0 && colDiff === 2) {
// Cannot jump over pieces - path must be clear
return isPathClear(self.row, self.col, targetRow, targetCol);
}
// Princess can move 1 square straight for positioning
if (rowDiff === 1 && colDiff === 0 || rowDiff === 0 && colDiff === 1) {
return true;
}
return false;
};
self.canQueenMove = function (targetRow, targetCol) {
// Queen moves like both rook and bishop (any distance in straight lines or diagonals)
if (self.row === targetRow || self.col === targetCol) {
// Rook-like movement (horizontal or vertical)
return isPathClear(self.row, self.col, targetRow, targetCol);
}
if (Math.abs(targetRow - self.row) === Math.abs(targetCol - self.col)) {
// Bishop-like movement (diagonal)
return isPathClear(self.row, self.col, targetRow, targetCol);
}
return false;
};
self.canKingMove = function (targetRow, targetCol) {
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
// Normal king move (one square in any direction)
if (rowDiff <= 1 && colDiff <= 1) {
// Check if king would be safe at target position
return isKingSafeAt(self, targetRow, targetCol);
}
// Castling logic (king moves 2 squares horizontally)
if (!self.hasMoved && rowDiff === 0 && colDiff === 2) {
// Check if castling is possible
if (targetCol > self.col) {
// King-side castling (right)
var rook = getPieceAt(self.row, 15); // Rightmost rook
if (rook && rook.pieceType === 'rook' && !rook.hasMoved && rook.pieceColor === self.pieceColor) {
// Check if path is clear and king doesn't move through check
for (var col = self.col + 1; col < 15; col++) {
if (getPieceAt(self.row, col) || !isKingSafeAt(self, self.row, col)) {
return false;
}
}
return isKingSafeAt(self, targetRow, targetCol);
}
} else {
// Queen-side castling (left)
var rook = getPieceAt(self.row, 0); // Leftmost rook
if (rook && rook.pieceType === 'rook' && !rook.hasMoved && rook.pieceColor === self.pieceColor) {
// Check if path is clear and king doesn't move through check
for (var col = self.col - 1; col > 0; col--) {
if (getPieceAt(self.row, col) || !isKingSafeAt(self, self.row, col)) {
return false;
}
}
return isKingSafeAt(self, targetRow, targetCol);
}
}
}
return false;
};
self.applyPoison = function (turns) {
self.poisonTurns = turns;
self.showPoisonEffect();
};
self.showPoisonEffect = function () {
if (self.poisonEffect) {
self.poisonEffect.destroy();
self.poisonEffect = null;
}
if (self.poisonCounterText) {
self.poisonCounterText.destroy();
self.poisonCounterText = null;
}
if (self.poisonTurns > 0) {
self.poisonEffect = self.attachAsset('poisonEffect', {
anchorX: 0.5,
anchorY: 0.5
});
self.poisonEffect.x = 30;
self.poisonEffect.y = -30;
self.poisonCounterText = new Text2(self.poisonTurns.toString(), {
size: 24,
fill: 0xFFFFFF
});
self.poisonCounterText.anchor.set(0.5, 0.5);
self.poisonCounterText.x = 30;
self.poisonCounterText.y = -30;
self.addChild(self.poisonCounterText);
// Tint piece red when poisoned
if (pieceGraphics && !pieceGraphics.destroyed) {
tween(pieceGraphics, {
tint: 0xff6666
}, {
duration: 300,
easing: tween.easeOut
});
}
} else {
// Remove red tint when poison wears off
if (pieceGraphics && !pieceGraphics.destroyed) {
tween(pieceGraphics, {
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeOut
});
}
}
};
self.reducePoisonTurns = function () {
if (self.poisonTurns > 0) {
self.poisonTurns--;
self.showPoisonEffect();
}
};
self.canJesterMove = function (targetRow, targetCol) {
// Jester moves exactly 2 squares in any direction (like checker piece)
var rowDiff = targetRow - self.row;
var colDiff = targetCol - self.col;
var absRowDiff = Math.abs(rowDiff);
var absColDiff = Math.abs(colDiff);
// Must move exactly 2 squares in any direction
if (Math.max(absRowDiff, absColDiff) !== 2) {
return false;
}
// Check if move is in valid direction (straight or diagonal, 2 squares)
var isValidDirection = false;
if (absRowDiff === 2 && absColDiff === 0 ||
// Vertical 2 squares
absRowDiff === 0 && absColDiff === 2 ||
// Horizontal 2 squares
absRowDiff === 2 && absColDiff === 2) {
// Diagonal 2 squares
isValidDirection = true;
}
if (!isValidDirection) {
return false;
}
// Calculate middle square position
var middleRow = self.row + (rowDiff > 0 ? 1 : rowDiff < 0 ? -1 : 0);
var middleCol = self.col + (colDiff > 0 ? 1 : colDiff < 0 ? -1 : 0);
// Check if middle square is within bounds
if (middleRow < 0 || middleRow > 7 || middleCol < 0 || middleCol > 15) {
return false;
}
var middlePiece = getPieceAt(middleRow, middleCol);
var targetPiece = getPieceAt(targetRow, targetCol);
// Jester cannot land on an occupied square - target square must always be empty
// Also jester cannot capture the king
if (targetPiece) {
if (targetPiece.pieceType === 'king') {
return false; // Jester cannot capture the king
}
return false; // Cannot capture pieces on destination square
}
// Normal move: middle square empty, target square empty
if (!middlePiece) {
return true; // Can move to empty square through empty middle
}
// Jump capture move: middle square has enemy piece, target square must be empty (already checked above)
if (middlePiece.pieceColor !== self.pieceColor) {
return true; // Can jump over enemy piece to empty square
}
// Can jump over own pawns
if (middlePiece.pieceColor === self.pieceColor && middlePiece.pieceType === 'pawn') {
return true; // Can jump over own pawns to empty square
}
return false; // Can't jump over other own pieces
};
self.canSummonerMove = function (targetRow, targetCol) {
// Summoner can only move 1 square in any direction (like a king)
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
// Summoner can move exactly 1 square in any direction (horizontal, vertical, or diagonal)
if (rowDiff <= 1 && colDiff <= 1 && (rowDiff > 0 || colDiff > 0)) {
return true;
}
return false;
};
self.canSnakeMove = function (targetRow, targetCol) {
// Check if snake is on cooldown
if (self.snakeOnCooldown) {
return false;
}
var rowDiff = targetRow - self.row;
var colDiff = targetCol - self.col;
var distance = Math.max(Math.abs(rowDiff), Math.abs(colDiff));
// Check if poisoning will occur
var willPoison = false;
var poisonPositions = [{
row: self.row - 1,
col: self.col - 1
}, {
row: self.row - 1,
col: self.col
}, {
row: self.row - 1,
col: self.col + 1
}, {
row: self.row,
col: self.col - 1
}, {
row: self.row,
col: self.col + 1
}, {
row: self.row + 1,
col: self.col - 1
}, {
row: self.row + 1,
col: self.col
}, {
row: self.row + 1,
col: self.col + 1
}];
for (var i = 0; i < poisonPositions.length; i++) {
var pos = poisonPositions[i];
if (pos.row >= 0 && pos.row < 8 && pos.col >= 0 && pos.col < 16) {
var pieceAtPos = getPieceAt(pos.row, pos.col);
if (pieceAtPos && pieceAtPos.pieceColor !== self.pieceColor) {
willPoison = true;
break;
}
}
}
// Snake can only move exactly 1 square
if (distance !== 1) {
return false;
}
// Check if the path is clear (no jumping over pieces)
if (!isPathClear(self.row, self.col, targetRow, targetCol)) {
return false;
}
// First move: 1 square in any direction (straight lines only)
if (self.snakeMovesThisTurn === 0) {
// Must be straight line movement (horizontal, vertical, or diagonal)
if (rowDiff === 0 || colDiff === 0 || Math.abs(rowDiff) === Math.abs(colDiff)) {
return true;
}
return false;
}
// Second move: 1 square diagonally in any direction
if (self.snakeMovesThisTurn === 1) {
// Must be diagonal movement
if (Math.abs(rowDiff) === Math.abs(colDiff) && Math.abs(rowDiff) === 1) {
return true;
}
return false;
}
// Third move: 1 square in any direction - only if not poisoning
if (self.snakeMovesThisTurn === 2 && !willPoison) {
// Can move to any adjacent square (any direction)
return true;
}
return false;
};
self.executeSummon = function () {
// Check if summoner can summon (moved 3 times and not already summoned)
if (self.summonerMoveCount < 3 || self.hasSummoned) return;
var summonPositions = [
// Orthogonal squares first: up, right, down, left
{
row: self.row - 1,
col: self.col
},
// up
{
row: self.row,
col: self.col + 1
},
// right
{
row: self.row + 1,
col: self.col
},
// down
{
row: self.row,
col: self.col - 1
},
// left
// Diagonal squares: up-right, down-right, down-left, up-left
{
row: self.row - 1,
col: self.col + 1
},
// up-right
{
row: self.row + 1,
col: self.col + 1
},
// down-right
{
row: self.row + 1,
col: self.col - 1
},
// down-left
{
row: self.row - 1,
col: self.col - 1
} // up-left
];
var summonedCount = 0;
var maxSummons = 2;
for (var i = 0; i < summonPositions.length && summonedCount < maxSummons; i++) {
var pos = summonPositions[i];
// Check if position is valid and empty
if (pos.row >= 0 && pos.row < 8 && pos.col >= 0 && pos.col < 16) {
if (!getPieceAt(pos.row, pos.col)) {
// Summon pawn at this position
var newPawn = new ChessPiece('pawn', self.pieceColor, pos.row, pos.col);
pieces.push(newPawn);
gameBoard[pos.row][pos.col] = newPawn;
game.addChild(newPawn);
newPawn.updatePosition(true);
summonedCount++;
// Create summoning effect
tween(newPawn, {
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.5
}, {
duration: 0
});
tween(newPawn, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 600,
easing: tween.bounceOut
});
}
}
}
if (summonedCount > 0) {
// Reset summoner state
self.summonerMoveCount = 0;
self.hasSummoned = true;
// Show summoning message
var summonText = new Text2('Summoner conjured ' + summonedCount + ' pawn' + (summonedCount > 1 ? 's' : '') + '!', {
size: 50,
fill: self.pieceColor === 'white' ? 0xFFFFFF : 0xFF0000
});
summonText.anchor.set(0.5, 0.5);
summonText.x = 1024;
summonText.y = 1200;
summonText.alpha = 0;
game.addChild(summonText);
tween(summonText, {
alpha: 1.0,
y: summonText.y - 50
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(summonText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
summonText.destroy();
}
});
}, 1500);
}
});
}
};
// Add hover effects for better interactivity
self.startHover = function () {
tween(self, {
scaleX: 1.05,
scaleY: 1.05,
y: self.y - 5
}, {
duration: 200,
easing: tween.easeOut
});
};
self.endHover = function () {
tween(self, {
scaleX: 1.0,
scaleY: 1.0,
y: boardStartY + self.row * squareSize + squareSize / 2
}, {
duration: 200,
easing: tween.easeOut
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2D1810
});
/****
* Game Code
****/
// Game dimensions and board setup
// Chess board squares
// White pieces
// Black pieces
// Sound effects
var squareSize = 128;
var boardStartX = (2048 - squareSize * 16) / 2;
var boardStartY = (2732 - squareSize * 8) / 2 + 100;
// Game state
var currentPlayer = 'white';
var selectedPiece = null;
var validMoves = [];
var pieces = [];
var gameBoard = [];
var highlightSquares = [];
var validMoveSquares = [];
var escapeSquares = [];
var kingSquare = null;
var blackKingSquare = null;
var dragNode = null;
var gameState = 'playing'; // playing, check, checkmate, stalemate
var whiteKingLives = 4;
var blackKingLives = 4;
var currentSnakePiece = null;
var snakeMovesRemaining = 0;
var activeSnakePiece = null; // Track which specific snake is currently active
// Stamina system variables
var staminaPoints = 1; // Start with 1 point
var maxStaminaPoints = 3;
var staminaContainer = null;
var staminaBars = [];
var aiStaminaBars = [];
// Pink square overlays for Queen and Snake pieces when stamina is depleted
var queenSnakePinkSquares = [];
// AI stamina system variables
var aiStaminaPoints = 1; // AI starts with 1 point
var aiMaxStaminaPoints = 3;
// Timer variables
var whiteTimeRemaining = 60000; // 60 seconds in milliseconds
var blackTimeRemaining = 60000; // 60 seconds in milliseconds
var timerInterval = null;
var gameStartTime = null;
var currentTurnStartTime = null;
// Initialize empty board
var _loop = function _loop() {
gameBoard[row] = [];
for (col = 0; col < 16; col++) {
gameBoard[row][col] = null;
}
// Missing function definitions for AI evaluation
function calculatePerfectActivity(move) {
// Simple activity calculation based on piece mobility
return simulateMove(move, function () {
var mobility = 0;
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
if (move.piece.canMoveTo(row, col)) {
mobility++;
}
}
}
return mobility;
});
}
function evaluatePerfectPawnStructure(move) {
// Simple pawn structure evaluation
var score = 0;
if (move.piece.pieceType === 'pawn') {
// Bonus for pawn advancement
var advancement = move.piece.pieceColor === 'black' ? move.toRow - move.piece.row : move.piece.row - move.toRow;
if (advancement > 0) score += advancement * 2;
}
return score;
}
function evaluateEndgameTechnique(move) {
// Simple endgame evaluation
var totalPieces = pieces.length;
if (totalPieces < 12) {
// In endgame, centralize king and activate pieces
var score = 0;
if (move.piece.pieceType === 'king') {
var centerDistance = Math.abs(move.toRow - 3.5) + Math.abs(move.toCol - 7.5);
score += (16 - centerDistance) * 3;
}
return score;
}
return 0;
}
function evaluateGrandmasterPositional(move) {
// Simple positional evaluation
return evaluatePositionalFactors(move) + evaluatePieceCoordination(move);
}
},
col;
for (var row = 0; row < 8; row++) {
_loop();
}
// Create visual board
var boardContainer = game.addChild(new Container());
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
var isLight = (row + col) % 2 === 0;
var square = LK.getAsset(isLight ? 'lightSquare' : 'darkSquare', {
anchorX: 0,
anchorY: 0
});
square.x = boardStartX + col * squareSize;
square.y = boardStartY + row * squareSize;
boardContainer.addChild(square);
}
}
// Add coordinate system labels
var coordinateContainer = game.addChild(new Container());
// Column labels (a-p for 16 columns)
var columnLabels = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'];
for (var col = 0; col < 16; col++) {
// Bottom row labels
var bottomLabel = new Text2(columnLabels[col], {
size: 32,
fill: 0xFFFFFF
});
bottomLabel.anchor.set(0.5, 0.5);
bottomLabel.x = boardStartX + col * squareSize + squareSize / 2;
bottomLabel.y = boardStartY + 8 * squareSize + 25;
coordinateContainer.addChild(bottomLabel);
// Top row labels
var topLabel = new Text2(columnLabels[col], {
size: 32,
fill: 0xFFFFFF
});
topLabel.anchor.set(0.5, 0.5);
topLabel.x = boardStartX + col * squareSize + squareSize / 2;
topLabel.y = boardStartY - 25;
coordinateContainer.addChild(topLabel);
}
// Row labels (8-1 from top to bottom)
for (var row = 0; row < 8; row++) {
var rowNumber = (8 - row).toString();
// Left side labels
var leftLabel = new Text2(rowNumber, {
size: 32,
fill: 0xFFFFFF
});
leftLabel.anchor.set(0.5, 0.5);
leftLabel.x = boardStartX - 25;
leftLabel.y = boardStartY + row * squareSize + squareSize / 2;
coordinateContainer.addChild(leftLabel);
// Right side labels
var rightLabel = new Text2(rowNumber, {
size: 32,
fill: 0xFFFFFF
});
rightLabel.anchor.set(0.5, 0.5);
rightLabel.x = boardStartX + 16 * squareSize + 25;
rightLabel.y = boardStartY + row * squareSize + squareSize / 2;
coordinateContainer.addChild(rightLabel);
}
// Initialize pieces with mixed layout - includes quenns, rooks, knights, 2 princesses, and 2 jesters
var basePieceSetup = [['summoner', 'knight', 'bishop', 'queen', 'king', 'queen', 'bishop', 'knight', 'rook', 'jester', 'knight', 'bishop', 'princess', 'princess', 'snake', 'jester'], ['pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn']];
var pieceSetup = shufflePiecesForNewRound(basePieceSetup);
// Create black pieces
for (var row = 0; row < 2; row++) {
for (var col = 0; col < 16; col++) {
var piece = new ChessPiece(pieceSetup[row][col], 'black', row, col);
pieces.push(piece);
gameBoard[row][col] = piece;
game.addChild(piece);
piece.updatePosition();
}
}
// Create white pieces
for (var row = 6; row < 8; row++) {
for (var col = 0; col < 16; col++) {
var pieceType = row === 6 ? 'pawn' : pieceSetup.whiteSetup[col];
var piece = new ChessPiece(pieceType, 'white', row, col);
pieces.push(piece);
gameBoard[row][col] = piece;
game.addChild(piece);
piece.updatePosition();
}
}
// Initialize king square
updateKingSquare();
// UI Elements
var turnText = new Text2('Your Turn', {
size: 80,
fill: 0xFFFFFF
});
turnText.anchor.set(0.5, 0);
LK.gui.top.addChild(turnText);
// Lives display - positioned behind chess pieces on the board
var whiteLivesContainer = new Container();
game.addChild(whiteLivesContainer);
var blackLivesContainer = new Container();
game.addChild(blackLivesContainer);
var whiteLivesText = new Text2('Your Lives: 4', {
size: 40,
fill: 0xFFFFFF
});
whiteLivesText.anchor.set(0.5, 0.5);
whiteLivesText.x = boardStartX + 16 * squareSize / 2;
whiteLivesText.y = boardStartY + 8 * squareSize + 50;
whiteLivesContainer.addChild(whiteLivesText);
var blackLivesText = new Text2('Enemy Lives: 4', {
size: 40,
fill: 0xFF0000
});
blackLivesText.anchor.set(0.5, 0.5);
blackLivesText.x = boardStartX + 16 * squareSize / 2;
blackLivesText.y = boardStartY - 50;
blackLivesContainer.addChild(blackLivesText);
// Lives visual indicators
var whiteLivesHearts = [];
var blackLivesHearts = [];
for (var i = 0; i < 4; i++) {
var whiteHeart = LK.getAsset('playerLifeBar', {
anchorX: 0.5,
anchorY: 0.5
});
whiteHeart.x = boardStartX + 16 * squareSize / 2 - 75 + i * 50;
whiteHeart.y = boardStartY + 8 * squareSize + 100;
whiteLivesContainer.addChild(whiteHeart);
whiteLivesHearts.push(whiteHeart);
var blackHeart = LK.getAsset('aiLifeBar', {
anchorX: 0.5,
anchorY: 0.5
});
blackHeart.x = boardStartX + 16 * squareSize / 2 - 75 + i * 50;
blackHeart.y = boardStartY - 100;
blackLivesContainer.addChild(blackHeart);
blackLivesHearts.push(blackHeart);
}
// Create stamina bar display
staminaContainer = new Container();
game.addChild(staminaContainer);
var staminaText = new Text2('Stamina', {
size: 40,
fill: 0xff69b4
});
staminaText.anchor.set(0.5, 0.5);
staminaText.x = boardStartX + 16 * squareSize - 100;
staminaText.y = boardStartY + 8 * squareSize + 150;
staminaContainer.addChild(staminaText);
for (var i = 0; i < maxStaminaPoints; i++) {
var staminaBar = LK.getAsset('staminaBar', {
anchorX: 0.5,
anchorY: 0.5
});
staminaBar.x = boardStartX + 16 * squareSize - 200 + i * 50;
staminaBar.y = boardStartY + 8 * squareSize + 200;
staminaContainer.addChild(staminaBar);
staminaBars.push(staminaBar);
}
// Create AI stamina bar display
var aiStaminaContainer = new Container();
game.addChild(aiStaminaContainer);
var aiStaminaText = new Text2('Enemy Stamina', {
size: 40,
fill: 0xff69b4
});
aiStaminaText.anchor.set(0.5, 0.5);
aiStaminaText.x = boardStartX + 16 * squareSize - 100;
aiStaminaText.y = boardStartY - 150;
aiStaminaContainer.addChild(aiStaminaText);
var aiStaminaBars = [];
// Hover tooltip system variables
var hoveredPiece = null;
var hoverStartTime = null;
var hoverTooltip = null;
var hoverTimer = null;
var HOVER_DELAY = 2000; // 2 seconds in milliseconds
for (var i = 0; i < aiMaxStaminaPoints; i++) {
var aiStaminaBar = LK.getAsset('staminaBar', {
anchorX: 0.5,
anchorY: 0.5
});
aiStaminaBar.x = boardStartX + 16 * squareSize - 200 + i * 50;
aiStaminaBar.y = boardStartY - 100;
aiStaminaContainer.addChild(aiStaminaBar);
aiStaminaBars.push(aiStaminaBar);
}
updateStaminaDisplay();
// Timer display elements
var whiteTimerText = new Text2('1:00', {
size: 60,
fill: 0xFFFFFF
});
whiteTimerText.anchor.set(0.5, 0.5);
whiteTimerText.x = boardStartX + 0 * squareSize + squareSize / 2; // Align with "a" column
whiteTimerText.y = boardStartY + 8 * squareSize + 75; // Below the bottom "a" label
game.addChild(whiteTimerText);
var blackTimerText = new Text2('1:00', {
size: 60,
fill: 0xFF0000
});
blackTimerText.anchor.set(0.5, 0.5);
blackTimerText.x = boardStartX + 0 * squareSize + squareSize / 2; // Align with "a" column
blackTimerText.y = boardStartY - 75; // Above the top "a" label
game.addChild(blackTimerText);
// Initialize and start timer after UI elements are created
updateTimerDisplay();
startTimer();
function getPieceAt(row, col) {
if (row === undefined || col === undefined || row < 0 || row > 7 || col < 0 || col > 15) {
return null;
}
return gameBoard[row][col];
}
function isPathClear(fromRow, fromCol, toRow, toCol) {
var rowStep = toRow > fromRow ? 1 : toRow < fromRow ? -1 : 0;
var colStep = toCol > fromCol ? 1 : toCol < fromCol ? -1 : 0;
var currentRow = fromRow + rowStep;
var currentCol = fromCol + colStep;
while (currentRow !== toRow || currentCol !== toCol) {
if (getPieceAt(currentRow, currentCol)) {
return false;
}
currentRow += rowStep;
currentCol += colStep;
}
return true;
}
function getSquareFromPosition(x, y) {
var col = Math.floor((x - boardStartX) / squareSize);
var row = Math.floor((y - boardStartY) / squareSize);
if (row >= 0 && row < 8 && col >= 0 && col < 16) {
return {
row: row,
col: col
};
}
return null;
}
function clearHighlights() {
// Smooth fade-out for highlights
for (var i = 0; i < highlightSquares.length; i++) {
var highlight = highlightSquares[i];
tween(highlight, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (!highlight.destroyed) highlight.destroy();
}
});
}
highlightSquares = [];
for (var i = 0; i < validMoveSquares.length; i++) {
var highlight = validMoveSquares[i];
// Immediately destroy flashing green squares instead of animating them out
if (!highlight.destroyed) highlight.destroy();
}
validMoveSquares = [];
for (var i = 0; i < escapeSquares.length; i++) {
var highlight = escapeSquares[i];
tween(highlight, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (!highlight.destroyed) highlight.destroy();
}
});
}
escapeSquares = [];
}
function createCaptureParticles(x, y, color) {
// Create sparkling particle effect for captures
for (var i = 0; i < 8; i++) {
var particle = LK.getAsset('poisonEffect', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: color || 0xFFD700,
alpha: 1.0
});
particle.x = x;
particle.y = y;
game.addChild(particle);
// Animate particles spreading out
var angle = i / 8 * Math.PI * 2;
var distance = 60 + Math.random() * 40;
var targetX = x + Math.cos(angle) * distance;
var targetY = y + Math.sin(angle) * distance;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1,
rotation: Math.random() * Math.PI * 2
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (!particle.destroyed) particle.destroy();
}
});
}
}
function createExplosionEffect(centerRow, centerCol) {
// Create explosion effect in 3x3 area around the center position
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
var explosionRow = centerRow + dr;
var explosionCol = centerCol + dc;
// Check bounds
if (explosionRow >= 0 && explosionRow < 8 && explosionCol >= 0 && explosionCol < 16) {
var explosionX = boardStartX + explosionCol * squareSize + squareSize / 2;
var explosionY = boardStartY + explosionRow * squareSize + squareSize / 2;
// Create multiple explosion particles for each square
for (var i = 0; i < 12; i++) {
var explosionParticle = LK.getAsset('poisonEffect', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6 + Math.random() * 0.4,
scaleY: 0.6 + Math.random() * 0.4,
tint: 0xFF6600 + Math.floor(Math.random() * 0x9900),
// Orange to red explosion colors
alpha: 0.9
});
explosionParticle.x = explosionX + (Math.random() - 0.5) * 60;
explosionParticle.y = explosionY + (Math.random() - 0.5) * 60;
game.addChild(explosionParticle);
// Animate explosion particles
var explosionAngle = Math.random() * Math.PI * 2;
var explosionDistance = 80 + Math.random() * 60;
var explosionTargetX = explosionX + Math.cos(explosionAngle) * explosionDistance;
var explosionTargetY = explosionY + Math.sin(explosionAngle) * explosionDistance;
tween(explosionParticle, {
x: explosionTargetX,
y: explosionTargetY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1,
rotation: Math.random() * Math.PI * 4
}, {
duration: 600 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (!explosionParticle.destroyed) explosionParticle.destroy();
}
});
}
// Create bright flash effect in center of explosion
if (dr === 0 && dc === 0) {
var flash = LK.getAsset('highlightSquare', {
anchorX: 0,
anchorY: 0,
alpha: 0.8,
tint: 0xFF3300,
scaleX: 1.5,
scaleY: 1.5
});
flash.x = boardStartX + explosionCol * squareSize - squareSize * 0.25;
flash.y = boardStartY + explosionRow * squareSize - squareSize * 0.25;
game.addChild(flash);
tween(flash, {
alpha: 0,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (!flash.destroyed) flash.destroy();
}
});
}
}
}
}
}
function updateKingSquare() {
// Remove existing king squares
if (kingSquare) {
kingSquare.destroy();
kingSquare = null;
}
if (blackKingSquare) {
blackKingSquare.destroy();
blackKingSquare = null;
}
// Find the white king and create purple square under it
var whiteKing = findKing('white');
if (whiteKing) {
kingSquare = LK.getAsset('kingSquare', {
anchorX: 0,
anchorY: 0,
alpha: 0.8
});
kingSquare.x = boardStartX + whiteKing.col * squareSize;
kingSquare.y = boardStartY + whiteKing.row * squareSize;
boardContainer.addChild(kingSquare);
}
// Find the black king and create purple square under it
var blackKing = findKing('black');
if (blackKing) {
blackKingSquare = LK.getAsset('kingSquare', {
anchorX: 0,
anchorY: 0,
alpha: 0.8
});
blackKingSquare.x = boardStartX + blackKing.col * squareSize;
blackKingSquare.y = boardStartY + blackKing.row * squareSize;
boardContainer.addChild(blackKingSquare);
}
}
function highlightSquare(row, col, color) {
var assetName = 'validMoveSquare';
if (color === 'blue') {
assetName = 'highlightSquare';
} else if (color === 'yellow') {
assetName = 'escapeSquare';
}
var highlight = LK.getAsset(assetName, {
anchorX: 0,
anchorY: 0,
alpha: 0.0,
scaleX: 0.8,
scaleY: 0.8
});
highlight.x = boardStartX + col * squareSize;
highlight.y = boardStartY + row * squareSize;
game.addChild(highlight);
// Smooth fade-in and scale animation for highlights
tween(highlight, {
alpha: 0.7,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 250,
easing: tween.easeOut
});
// Add subtle pulsing animation for valid moves
if (color === 'green') {
var _pulseTween = function pulseTween() {
tween(highlight, {
alpha: 0.4
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(highlight, {
alpha: 0.7
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: _pulseTween
});
}
});
};
_pulseTween();
}
if (color === 'blue') {
highlightSquares.push(highlight);
} else if (color === 'yellow') {
escapeSquares.push(highlight);
} else {
validMoveSquares.push(highlight);
}
}
function showValidMoves(piece) {
validMoves = [];
var playerInCheck = isInCheck(piece.pieceColor);
// Always highlight the selected piece position in blue first
highlightSquare(piece.row, piece.col, 'blue');
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
if (piece.canMoveTo(row, col)) {
// If in check, only allow moves that escape check
if (playerInCheck) {
if (canEscapeCheck(piece, row, col)) {
validMoves.push({
row: row,
col: col
});
// Highlight escape move in yellow when in check
highlightSquare(row, col, 'yellow');
}
} else {
validMoves.push({
row: row,
col: col
});
highlightSquare(row, col, 'green');
}
}
}
}
}
function movePiece(piece, newRow, newCol) {
var capturedPiece = getPieceAt(newRow, newCol);
var isCapture = capturedPiece !== null;
// Record player move for AI imitation
recordPlayerMove(piece, piece.row, piece.col, newRow, newCol);
// Remove piece from old position
gameBoard[piece.row][piece.col] = null;
// Check for king auto-capture of adjacent snake before normal capture handling
if (piece.pieceType === 'snake') {
// Check if any enemy king is adjacent to the snake's new position
var enemyColor = piece.pieceColor === 'white' ? 'black' : 'white';
var enemyKing = findKing(enemyColor);
if (enemyKing) {
var kingRowDiff = Math.abs(enemyKing.row - newRow);
var kingColDiff = Math.abs(enemyKing.col - newCol);
// If king is adjacent (including diagonal), king automatically captures snake
if (kingRowDiff <= 1 && kingColDiff <= 1) {
// King auto-captures the snake
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === piece) {
pieces[i].destroy();
pieces.splice(i, 1);
break;
}
}
// Move king to snake position
gameBoard[enemyKing.row][enemyKing.col] = null;
enemyKing.row = newRow;
enemyKing.col = newCol;
gameBoard[enemyKing.row][enemyKing.col] = enemyKing;
enemyKing.updatePosition(true);
updateKingSquare();
LK.getSound('capture').play();
turnText.setText(enemyKing.pieceColor.charAt(0).toUpperCase() + enemyKing.pieceColor.slice(1) + ' King Auto-Captures Snake!');
// Reset snake state
currentSnakePiece = null;
snakeMovesRemaining = 0;
activeSnakePiece = null;
// Switch turns
currentPlayer = currentPlayer === 'white' ? 'black' : 'white';
return;
}
}
}
// Handle jester jump captures
if (piece.pieceType === 'jester') {
var rowDiff = newRow - piece.row;
var colDiff = newCol - piece.col;
var absRowDiff = Math.abs(rowDiff);
var absColDiff = Math.abs(colDiff);
// Check if this is a 2-square move (potential jump)
if (Math.max(absRowDiff, absColDiff) === 2) {
// Calculate middle square position
var middleRow = piece.row + (rowDiff > 0 ? 1 : rowDiff < 0 ? -1 : 0);
var middleCol = piece.col + (colDiff > 0 ? 1 : colDiff < 0 ? -1 : 0);
var jumpedPiece = getPieceAt(middleRow, middleCol);
// If there's an enemy piece in the middle, capture it (jump capture)
if (jumpedPiece && jumpedPiece.pieceColor !== piece.pieceColor) {
// Remove jumped piece with effects
var jumpCaptureX = boardStartX + middleCol * squareSize + squareSize / 2;
var jumpCaptureY = boardStartY + middleRow * squareSize + squareSize / 2;
var jumpParticleColor = jumpedPiece.pieceColor === 'white' ? 0xFFD700 : 0xFF4444;
createCaptureParticles(jumpCaptureX, jumpCaptureY, jumpParticleColor);
// Animate jumped piece removal
tween(jumpedPiece, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5,
rotation: Math.PI
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (!jumpedPiece.destroyed) {
jumpedPiece.destroy();
}
}
});
// Play special jester capture sound
LK.getSound('jesterCapture').play();
// Remove jumped piece from game state
gameBoard[middleRow][middleCol] = null;
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === jumpedPiece) {
pieces.splice(i, 1);
break;
}
}
}
}
}
// Remove captured piece with visual effects
if (capturedPiece) {
// Create particle effects at capture position
var captureX = boardStartX + newCol * squareSize + squareSize / 2;
var captureY = boardStartY + newRow * squareSize + squareSize / 2;
var particleColor = capturedPiece.pieceColor === 'white' ? 0xFFD700 : 0xFF4444;
createCaptureParticles(captureX, captureY, particleColor);
// Animate captured piece before removal
tween(capturedPiece, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5,
rotation: Math.PI
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (!capturedPiece.destroyed) {
capturedPiece.destroy();
}
}
});
// Check if captured piece is a snake in the middle of its multi-move sequence
if (capturedPiece.pieceType === 'snake' && capturedPiece.snakeMovesThisTurn > 0 && capturedPiece === activeSnakePiece) {
// Snake was captured before completing its moves - put on cooldown and reset sequence
capturedPiece.snakeOnCooldown = true;
capturedPiece.snakeMovesThisTurn = 0;
capturedPiece.snakeLastMoveDirection = null;
// Reset global snake state
currentSnakePiece = null;
snakeMovesRemaining = 0;
activeSnakePiece = null;
}
// Handle king capture - check if king can counter-capture
if (capturedPiece.pieceType === 'king') {
// Check if captured king can counter-capture the attacking piece
var canCounterCapture = capturedPiece && piece && capturedPiece.canMoveTo(piece.row, piece.col) && isKingSafeAt(capturedPiece, piece.row, piece.col);
if (canCounterCapture) {
// Auto counter-capture: king takes the attacking piece
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === piece) {
pieces[i].destroy();
pieces.splice(i, 1);
break;
}
}
// Move king to attacking piece position
gameBoard[capturedPiece.row][capturedPiece.col] = null;
capturedPiece.row = piece.row;
capturedPiece.col = piece.col;
gameBoard[capturedPiece.row][capturedPiece.col] = capturedPiece;
capturedPiece.updatePosition(true);
updateKingSquare();
LK.getSound('capture').play();
// Apply 1 damage to the king's side for escaping
if (capturedPiece.pieceColor === 'white') {
whiteKingLives--;
activateAbility('white', whiteKingLives);
} else {
blackKingLives--;
activateAbility('black', blackKingLives);
}
updateLivesDisplay();
turnText.setText(capturedPiece.pieceColor.charAt(0).toUpperCase() + capturedPiece.pieceColor.slice(1) + ' King Counter-Attacks! 1 Life Lost for Escaping!');
return;
} else {
// Reduce king lives
if (capturedPiece.pieceColor === 'white') {
whiteKingLives--;
activateAbility('white', whiteKingLives);
} else {
blackKingLives--;
activateAbility('black', blackKingLives);
}
// Update lives display
updateLivesDisplay();
// Check if king is out of lives (restart game when lives reach 0)
if (whiteKingLives <= 0 || blackKingLives <= 0) {
var winner = whiteKingLives <= 0 ? 'Black' : 'White';
turnText.setText(winner + (winner === 'Black' ? ' side won.' : ' Wins! King Defeated! New round begins with shuffled pieces!'));
LK.setTimeout(function () {
restartGame();
}, 2000);
return;
}
}
} else {
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === capturedPiece) {
pieces[i].destroy();
pieces.splice(i, 1);
break;
}
}
// Add stamina point for capturing (exclude Queen and Snake captures)
if (capturedPiece.pieceType !== 'queen' && capturedPiece.pieceType !== 'snake') {
if (piece.pieceColor === 'white' && staminaPoints < maxStaminaPoints) {
staminaPoints++;
updateStaminaDisplay();
} else if (piece.pieceColor === 'black' && aiStaminaPoints < aiMaxStaminaPoints) {
aiStaminaPoints++;
}
}
}
}
// Handle castling before moving the king
if (piece.pieceType === 'king' && Math.abs(newCol - piece.col) === 2) {
var rookCol, rookNewCol;
if (newCol > piece.col) {
// King-side castling
rookCol = 15;
rookNewCol = newCol - 1;
} else {
// Queen-side castling
rookCol = 0;
rookNewCol = newCol + 1;
}
var rook = getPieceAt(piece.row, rookCol);
if (rook) {
// Move rook
gameBoard[rook.row][rook.col] = null;
rook.col = rookNewCol;
rook.hasMoved = true;
gameBoard[rook.row][rook.col] = rook;
rook.updatePosition(true);
}
}
// Move piece to new position
piece.row = newRow;
piece.col = newCol;
piece.hasMoved = true;
gameBoard[newRow][newCol] = piece;
piece.updatePosition(true);
// Handle stamina cost for Queen moves (immediate) and Snake moves (on third move)
if (piece.pieceType === 'queen') {
if (piece.pieceColor === 'white' && staminaPoints > 0) {
staminaPoints--;
// Store reference to piece that consumed stamina for pink square tracking
if (!window.whiteStaminaConsumers) window.whiteStaminaConsumers = [];
window.whiteStaminaConsumers.push({
type: piece.pieceType,
row: piece.row,
col: piece.col,
id: piece
});
updateStaminaDisplay();
} else if (piece.pieceColor === 'black' && aiStaminaPoints > 0) {
aiStaminaPoints--;
// Store reference to piece that consumed stamina for pink square tracking
if (!window.blackStaminaConsumers) window.blackStaminaConsumers = [];
window.blackStaminaConsumers.push({
type: piece.pieceType,
row: piece.row,
col: piece.col,
id: piece
});
}
}
// Update king square if a king moved
if (piece.pieceType === 'king') {
updateKingSquare();
}
// Play sound
if (isCapture) {
if (piece.pieceType === 'jester') {
LK.getSound('jesterCapture').play();
} else {
LK.getSound('capture').play();
}
} else {
LK.getSound('move').play();
}
// Check for pawn promotion
if (piece.pieceType === 'pawn' && (newRow === 0 || newRow === 7)) {
promotePawn(piece);
}
// Handle summoner move counting and summoning
if (piece.pieceType === 'summoner') {
piece.summonerMoveCount++;
piece.hasSummoned = false; // Reset summoned flag when moving
if (piece.summonerMoveCount >= 3) {
// Execute summoning after move animation completes
LK.setTimeout(function () {
piece.executeSummon();
}, 400);
}
}
// Handle snake multi-move mechanics
if (piece.pieceType === 'snake') {
// Set this snake as the active snake if starting its turn
if (piece.snakeMovesThisTurn === 0) {
activeSnakePiece = piece;
var rowDiff = newRow - piece.row;
var colDiff = newCol - piece.col;
piece.snakeLastMoveDirection = {
row: rowDiff === 0 ? 0 : rowDiff > 0 ? 1 : -1,
col: colDiff === 0 ? 0 : colDiff > 0 ? 1 : -1
};
}
// Check if poisoning will occur at the new position after the move
var willPoison = false;
var poisonPositions = [{
row: newRow - 1,
col: newCol - 1
}, {
row: newRow - 1,
col: newCol
}, {
row: newRow - 1,
col: newCol + 1
}, {
row: newRow,
col: newCol - 1
}, {
row: newRow,
col: newCol + 1
}, {
row: newRow + 1,
col: newCol - 1
}, {
row: newRow + 1,
col: newCol
}, {
row: newRow + 1,
col: newCol + 1
}];
for (var i = 0; i < poisonPositions.length; i++) {
var pos = poisonPositions[i];
if (pos.row >= 0 && pos.row < 8 && pos.col >= 0 && pos.col < 16) {
var pieceAtPos = getPieceAt(pos.row, pos.col);
if (pieceAtPos && pieceAtPos.pieceColor !== piece.pieceColor) {
willPoison = true;
break;
}
}
}
piece.snakeMovesThisTurn++;
var maxMoves = willPoison ? 2 : piece.maxSnakeMoves;
snakeMovesRemaining = maxMoves - piece.snakeMovesThisTurn;
currentSnakePiece = piece;
// Apply poison to pieces around the snake's new position
applyPoisonBehindSnake(piece, newRow, newCol);
// Handle additional stamina cost for poisoning
if (willPoison) {
// Deduct additional stamina for poisoning
if (piece.pieceColor === 'white' && staminaPoints > 0) {
staminaPoints--;
updateStaminaDisplay();
} else if (piece.pieceColor === 'black' && aiStaminaPoints > 0) {
aiStaminaPoints--;
}
}
// If poisoning occurred, immediately switch turns after 2 moves
if (willPoison) {
// Snake completed poisoning moves - put on cooldown and reset
piece.snakeMovesThisTurn = 0;
piece.snakeOnCooldown = true;
piece.snakeLastMoveDirection = null;
currentSnakePiece = null;
snakeMovesRemaining = 0;
activeSnakePiece = null; // Reset active snake
turnText.setText(piece.pieceColor.charAt(0).toUpperCase() + piece.pieceColor.slice(1) + ' Snake poisoned enemies! Turn passes to opponent.');
} else {
// Snake eats piece at each stop - already handled by capture logic above
// If snake has more moves, don't switch turns yet
if (piece.snakeMovesThisTurn < maxMoves) {
var moveNames = ['', 'First', 'Second', 'Third'];
turnText.setText(piece.pieceColor.charAt(0).toUpperCase() + piece.pieceColor.slice(1) + ' Snake - ' + moveNames[piece.snakeMovesThisTurn] + ' move complete, ' + snakeMovesRemaining + ' moves remaining');
// Clear current selection to allow new move
clearHighlights();
selectedPiece = null;
return; // Don't switch turns
}
// Snake completed all 3 moves - deduct stamina now and put on cooldown
if (piece.pieceColor === 'white' && staminaPoints > 0) {
staminaPoints--;
// Store reference to piece that consumed stamina for pink square tracking
if (!window.whiteStaminaConsumers) window.whiteStaminaConsumers = [];
window.whiteStaminaConsumers.push({
type: piece.pieceType,
row: piece.row,
col: piece.col,
id: piece
});
updateStaminaDisplay();
} else if (piece.pieceColor === 'black' && aiStaminaPoints > 0) {
aiStaminaPoints--;
// Store reference to piece that consumed stamina for pink square tracking
if (!window.blackStaminaConsumers) window.blackStaminaConsumers = [];
window.blackStaminaConsumers.push({
type: piece.pieceType,
row: piece.row,
col: piece.col,
id: piece
});
}
piece.snakeMovesThisTurn = 0;
piece.snakeOnCooldown = true;
piece.snakeLastMoveDirection = null;
currentSnakePiece = null;
snakeMovesRemaining = 0;
activeSnakePiece = null; // Reset active snake
}
}
// Switch turns
currentPlayer = currentPlayer === 'white' ? 'black' : 'white';
// Reset timer for new turn
currentTurnStartTime = Date.now();
// Reduce poison turns and remove snake cooldown for all pieces of the new current player
for (var i = 0; i < pieces.length; i++) {
if (pieces[i].pieceColor === currentPlayer) {
pieces[i].reducePoisonTurns();
// Remove snake cooldown when it becomes this player's turn
if (pieces[i].pieceType === 'snake') {
pieces[i].snakeOnCooldown = false;
}
}
}
turnText.setText(currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + '\'s Turn');
// Check for check/checkmate
if (isInCheck(currentPlayer)) {
// In check situations, look for defensive moves: capturing the threatening piece, blocking, or king escape
var king = findKing(currentPlayer);
var threateningPiece = findThreateningPiece(king);
var canKingCounterCapture = false;
var canDefend = false;
var defendingPiece = null;
var defenseRow = -1;
var defenseCol = -1;
// Check for defensive options when in check
if (threateningPiece) {
// Priority 1: King can directly counter-capture the threatening piece
if (king && king.canMoveTo(threateningPiece.row, threateningPiece.col) && isKingSafeAt(king, threateningPiece.row, threateningPiece.col)) {
canKingCounterCapture = true;
canDefend = true;
defendingPiece = king;
defenseRow = threateningPiece.row;
defenseCol = threateningPiece.col;
} else {
// Priority 2: Any other piece can capture the threatening piece to protect the king
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === currentPlayer && piece !== king && piece.canMoveTo(threateningPiece.row, threateningPiece.col)) {
// Test if this capture would resolve the check
if (canEscapeCheck(piece, threateningPiece.row, threateningPiece.col)) {
canDefend = true;
defendingPiece = piece;
defenseRow = threateningPiece.row;
defenseCol = threateningPiece.col;
break;
}
}
}
// Priority 3: If no capture is possible, look for blocking moves or king escape moves
if (!canDefend) {
// Check if any piece can block the attack path or if king can escape
var allDefensiveMoves = getAllValidMoves(currentPlayer);
canDefend = allDefensiveMoves.length > 0;
}
}
}
// If defensive action is available, execute it
if (canDefend && defendingPiece) {
// Auto-defense: piece protects king by capturing the threatening piece
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === threateningPiece) {
pieces[i].destroy();
pieces.splice(i, 1);
break;
}
}
// Move defending piece to capture position
gameBoard[defendingPiece.row][defendingPiece.col] = null;
defendingPiece.row = defenseRow;
defendingPiece.col = defenseCol;
gameBoard[defendingPiece.row][defendingPiece.col] = defendingPiece;
defendingPiece.updatePosition(true);
if (defendingPiece.pieceType === 'king') {
updateKingSquare();
// King counter-capture - game continues without penalty when king directly captures threat
if (canKingCounterCapture) {
turnText.setText(defendingPiece.pieceColor.charAt(0).toUpperCase() + defendingPiece.pieceColor.slice(1) + ' King Counter-Captures Threatening Piece!');
} else {
// Apply 1 damage to king's side for escaping by counter-capture in defensive situations
if (defendingPiece.pieceColor === 'white') {
whiteKingLives--;
activateAbility('white', whiteKingLives);
} else {
blackKingLives--;
activateAbility('black', blackKingLives);
}
updateLivesDisplay();
turnText.setText(defendingPiece.pieceColor.charAt(0).toUpperCase() + defendingPiece.pieceColor.slice(1) + ' King Defends! 1 Life Lost for Escaping!');
}
} else {
var pieceTitle = defendingPiece.pieceType.charAt(0).toUpperCase() + defendingPiece.pieceType.slice(1);
turnText.setText(defendingPiece.pieceColor.charAt(0).toUpperCase() + defendingPiece.pieceColor.slice(1) + ' ' + pieceTitle + ' Protects King by Capturing Threat!');
}
LK.getSound('capture').play();
// Continue game - switch turns
currentPlayer = currentPlayer === 'white' ? 'black' : 'white';
turnText.setText(currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + '\'s Turn');
gameState = 'playing';
// Trigger AI move if it's black's turn
if (currentPlayer === 'black') {
makeAIMove();
}
return;
}
// Check if player has no defensive options (no escape moves) - comprehensive defense check
if (!hasValidEscapeMoves(currentPlayer)) {
// Look for all possible defensive options: king counter-capture, piece protection, blocking
var king = findKing(currentPlayer);
var threateningPiece = findThreateningPiece(king);
var canDefendKing = false;
var protectingPiece = null;
if (threateningPiece) {
// Option 1: King can directly counter-capture the threatening piece
if (king && king.canMoveTo(threateningPiece.row, threateningPiece.col) && isKingSafeAt(king, threateningPiece.row, threateningPiece.col)) {
canDefendKing = true;
protectingPiece = king;
} else {
// Option 2: Any other piece can protect the king by capturing the threatening piece
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === currentPlayer && piece !== king && piece.canMoveTo(threateningPiece.row, threateningPiece.col)) {
if (canEscapeCheck(piece, threateningPiece.row, threateningPiece.col)) {
canDefendKing = true;
protectingPiece = piece;
break;
}
}
}
// Option 3: Any piece can block the attack path (if attack is not adjacent)
if (!canDefendKing && king && threateningPiece) {
var blockingMoves = getAllValidMoves(currentPlayer);
for (var i = 0; i < blockingMoves.length; i++) {
if (canEscapeCheck(blockingMoves[i].piece, blockingMoves[i].toRow, blockingMoves[i].toCol)) {
canDefendKing = true;
protectingPiece = blockingMoves[i].piece;
break;
}
}
}
}
}
if (canDefendKing && protectingPiece && threateningPiece) {
// Execute king protection with explosion effect
var threateningPieceRow = threateningPiece.row;
var threateningPieceCol = threateningPiece.col;
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === threateningPiece) {
pieces[i].destroy();
pieces.splice(i, 1);
break;
}
}
// Create explosion effect where threatening piece was destroyed
createExplosionEffect(threateningPieceRow, threateningPieceCol);
// Move protecting piece to capture position
gameBoard[protectingPiece.row][protectingPiece.col] = null;
protectingPiece.row = threateningPiece.row;
protectingPiece.col = threateningPiece.col;
gameBoard[protectingPiece.row][protectingPiece.col] = protectingPiece;
protectingPiece.updatePosition(true);
if (protectingPiece.pieceType === 'king') {
updateKingSquare();
}
LK.getSound('capture').play();
var pieceTitle = protectingPiece.pieceType.charAt(0).toUpperCase() + protectingPiece.pieceType.slice(1);
turnText.setText(protectingPiece.pieceColor.charAt(0).toUpperCase() + protectingPiece.pieceColor.slice(1) + ' ' + pieceTitle + ' Protects King! Game Continues!');
// Switch turns and continue game
currentPlayer = currentPlayer === 'white' ? 'black' : 'white';
turnText.setText(currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + '\'s Turn');
gameState = 'playing';
// Trigger AI move if it's black's turn
if (currentPlayer === 'black') {
makeAIMove();
}
return;
}
// Checkmate situation - king is trapped and cannot escape, lose 2 lives and remove only the checking piece
if (currentPlayer === 'white') {
whiteKingLives -= 2;
if (whiteKingLives < 0) whiteKingLives = 0;
} else {
blackKingLives -= 2;
if (blackKingLives < 0) blackKingLives = 0;
}
// Remove only the piece that is currently checking the king (the threatening piece)
var king = findKing(currentPlayer);
var checkingPiece = findThreateningPiece(king);
if (checkingPiece) {
var checkingPieceRow = checkingPiece.row;
var checkingPieceCol = checkingPiece.col;
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === checkingPiece) {
gameBoard[checkingPiece.row][checkingPiece.col] = null;
checkingPiece.destroy();
pieces.splice(i, 1);
break;
}
}
// Create explosion effect where checking piece was destroyed
createExplosionEffect(checkingPieceRow, checkingPieceCol);
}
updateLivesDisplay();
// Check if king is out of lives (restart game when lives reach 0)
if (whiteKingLives <= 0 || blackKingLives <= 0) {
var winner = whiteKingLives <= 0 ? 'Black' : 'White';
turnText.setText(winner + (winner === 'Black' ? ' side won.' : ' Wins! King Out of Lives!'));
LK.setTimeout(function () {
restartGame();
}, 2000);
return;
}
turnText.setText('Checkmate! ' + currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + ' King loses 2 lives! Threatening piece destroyed!');
// Switch turns and continue game
currentPlayer = currentPlayer === 'white' ? 'black' : 'white';
LK.setTimeout(function () {
turnText.setText(currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + '\'s Turn');
gameState = 'playing';
// Trigger AI move if it's black's turn
if (currentPlayer === 'black') {
makeAIMove();
}
}, 2000);
return;
}
// Reduce king lives when in check but not checkmated
if (currentPlayer === 'white') {
whiteKingLives--;
activateAbility('white', whiteKingLives);
} else {
blackKingLives--;
activateAbility('black', blackKingLives);
}
updateLivesDisplay();
// Check if king is out of lives (restart game when lives reach 0)
if (whiteKingLives <= 0 || blackKingLives <= 0) {
var winner = whiteKingLives <= 0 ? 'Black' : 'White';
turnText.setText(winner + (winner === 'Black' ? ' side won.' : ' Wins! King Out of Lives! New round begins with shuffled pieces!'));
LK.setTimeout(function () {
restartGame();
}, 2000);
return;
}
gameState = 'check';
LK.getSound('check').play();
// Add dramatic check visual effect
LK.effects.flashScreen(0xFF0000, 800);
var kingInCheck = findKing(currentPlayer);
if (kingInCheck) {
// Make the king flash red
tween(kingInCheck, {
tint: 0xFF6666
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(kingInCheck, {
tint: 0xFFFFFF
}, {
duration: 300,
easing: tween.easeOut
});
}
});
}
var currentLives = currentPlayer === 'white' ? whiteKingLives : blackKingLives;
if (currentLives === 0) {
turnText.setText(currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + ' in Check! FINAL LIFE - Must Escape or Lose!');
} else {
turnText.setText(currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + ' in Check! Lives: ' + currentLives + ' - Must Escape!');
}
} else if (isStalemate(currentPlayer)) {
gameState = 'stalemate';
turnText.setText('Stalemate!');
LK.setTimeout(function () {
restartGame();
}, 2000);
} else {
gameState = 'playing';
}
// Trigger AI move if it's black's turn
if (currentPlayer === 'black' && (gameState === 'playing' || gameState === 'check')) {
makeAIMove();
}
}
function getPieceValue(pieceType) {
switch (pieceType) {
case 'pawn':
return 1;
case 'bishop':
return 3;
case 'knight':
return 3;
case 'rook':
return 5;
case 'queen':
return 9;
case 'snake':
return 6;
// Higher value due to special abilities
case 'jester':
return 4;
// Moderate value for unique movement
case 'princess':
return 8;
case 'summoner':
return 8;
// Same value as princess
case 'king':
return 0;
default:
return 0;
}
}
function promotePawn(pawn) {
// Promote pawn to quenn (most powerful standard piece available)
var originalRow = pawn.row;
var originalCol = pawn.col;
var originalColor = pawn.pieceColor;
// Remove old pawn
pawn.destroy();
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === pawn) {
pieces.splice(i, 1);
break;
}
}
gameBoard[originalRow][originalCol] = null;
// Create new queen
var newQueen = new ChessPiece('queen', originalColor, originalRow, originalCol);
pieces.push(newQueen);
gameBoard[originalRow][originalCol] = newQueen;
game.addChild(newQueen);
newQueen.updatePosition();
}
function findKing(color) {
for (var i = 0; i < pieces.length; i++) {
if (pieces[i].pieceType === 'king' && pieces[i].pieceColor === color) {
return pieces[i];
}
}
return null;
}
function isInCheck(color) {
var king = findKing(color);
if (!king) return false;
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor !== color && piece.canMoveTo(king.row, king.col)) {
return true;
}
}
return false;
}
function isCheckmate(color) {
if (!isInCheck(color)) return false;
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === color) {
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
if (piece.canMoveTo(row, col)) {
// Simulate move
var originalRow = piece.row;
var originalCol = piece.col;
var capturedPiece = getPieceAt(row, col);
gameBoard[originalRow][originalCol] = null;
gameBoard[row][col] = piece;
piece.row = row;
piece.col = col;
var stillInCheck = isInCheck(color);
// Restore position
piece.row = originalRow;
piece.col = originalCol;
gameBoard[originalRow][originalCol] = piece;
gameBoard[row][col] = capturedPiece;
if (!stillInCheck) {
return false;
}
}
}
}
}
}
return true;
}
function isStalemate(color) {
if (isInCheck(color)) return false;
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === color) {
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
if (piece.canMoveTo(row, col)) {
return false;
}
}
}
}
}
return true;
}
function getAllValidMoves(color) {
var allMoves = [];
var playerInCheck = isInCheck(color);
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === color) {
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
if (piece.canMoveTo(row, col)) {
// If in check, only allow moves that escape check
if (playerInCheck) {
if (canEscapeCheck(piece, row, col)) {
allMoves.push({
piece: piece,
toRow: row,
toCol: col
});
}
} else {
allMoves.push({
piece: piece,
toRow: row,
toCol: col
});
}
}
}
}
}
}
return allMoves;
}
function makeAIMove() {
if (currentPlayer !== 'black' || gameState !== 'playing' && gameState !== 'check') return;
// If there's an active snake continuing its multi-move sequence, prioritize it
if (activeSnakePiece && activeSnakePiece.pieceColor === 'black' && snakeMovesRemaining > 0) {
var snakeMoves = [];
// Get valid moves only for the active snake
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
if (activeSnakePiece.canMoveTo(row, col)) {
snakeMoves.push({
piece: activeSnakePiece,
toRow: row,
toCol: col
});
}
}
}
if (snakeMoves.length > 0) {
var bestSnakeMove = chooseBestAIMove(snakeMoves);
if (bestSnakeMove) {
LK.setTimeout(function () {
movePiece(bestSnakeMove.piece, bestSnakeMove.toRow, bestSnakeMove.toCol);
// Continue snake moves if still the AI's turn and snake has moves left
if (currentSnakePiece && currentSnakePiece.pieceColor === 'black' && snakeMovesRemaining > 0 && currentPlayer === 'black') {
LK.setTimeout(function () {
makeAIMove(); // Recursive call for next snake move
}, 800);
}
}, 800);
return;
}
}
}
var validAIMoves = getAllValidMoves('black');
// Check if AI has no valid moves but can defend king
if (validAIMoves.length === 0 && isInCheck('black')) {
var blackKing = findKing('black');
var threateningPiece = findThreateningPiece(blackKing);
var canDefendAI = false;
var defendingAIPiece = null;
var defenseAIRow = -1;
var defenseAICol = -1;
// Look for defensive options for AI king
if (threateningPiece) {
// Priority 1: AI king can directly counter-capture the threatening piece
if (blackKing && blackKing.canMoveTo(threateningPiece.row, threateningPiece.col) && isKingSafeAt(blackKing, threateningPiece.row, threateningPiece.col)) {
canDefendAI = true;
defendingAIPiece = blackKing;
defenseAIRow = threateningPiece.row;
defenseAICol = threateningPiece.col;
} else {
// Priority 2: Any other AI piece can protect the king by capturing the threatening piece
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'black' && piece !== blackKing && piece.canMoveTo(threateningPiece.row, threateningPiece.col)) {
// Test if this capture would protect the AI king
if (canEscapeCheck(piece, threateningPiece.row, threateningPiece.col)) {
canDefendAI = true;
defendingAIPiece = piece;
defenseAIRow = threateningPiece.row;
defenseAICol = threateningPiece.col;
break;
}
}
}
}
}
if (canDefendAI) {
// AI defense: piece protects king by capturing the threatening piece
LK.setTimeout(function () {
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === threateningPiece) {
pieces[i].destroy();
pieces.splice(i, 1);
break;
}
}
// Move AI defending piece to capture position
gameBoard[defendingAIPiece.row][defendingAIPiece.col] = null;
defendingAIPiece.row = defenseAIRow;
defendingAIPiece.col = defenseAICol;
gameBoard[defendingAIPiece.row][defendingAIPiece.col] = defendingAIPiece;
defendingAIPiece.updatePosition(true);
if (defendingAIPiece.pieceType === 'king') {
updateKingSquare();
// AI King counter-capture - game continues normally
if (defendingAIPiece === blackKing) {
turnText.setText('Black King Protects Itself! Game Continues!');
} else {
// Apply 1 damage to AI king for defensive action in extreme situations
blackKingLives--;
activateAbility('black', blackKingLives);
updateLivesDisplay();
turnText.setText('Black King Defends! 1 Life Lost for Escaping!');
}
} else {
var pieceTitle = defendingAIPiece.pieceType.charAt(0).toUpperCase() + defendingAIPiece.pieceType.slice(1);
turnText.setText('Black ' + pieceTitle + ' Protects King by Capturing Threat!');
}
LK.getSound('capture').play();
// Continue game - switch turns
currentPlayer = 'white';
turnText.setText('Your Turn');
gameState = 'playing';
}, 800);
return;
} else {
// No auto-capture possible and no valid escape moves - checkmate, lose 2 lives and remove only the checking piece
blackKingLives -= 2;
if (blackKingLives < 0) blackKingLives = 0;
// Remove only the piece that is currently checking the AI king (the threatening piece)
var blackKing = findKing('black');
var checkingPiece = findThreateningPiece(blackKing);
if (checkingPiece) {
var checkingPieceRow = checkingPiece.row;
var checkingPieceCol = checkingPiece.col;
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === checkingPiece) {
gameBoard[checkingPiece.row][checkingPiece.col] = null;
checkingPiece.destroy();
pieces.splice(i, 1);
break;
}
}
// Create explosion effect where checking piece was destroyed
createExplosionEffect(checkingPieceRow, checkingPieceCol);
}
updateLivesDisplay();
// Check if AI king is out of lives
if (blackKingLives <= 0) {
turnText.setText('White Wins! AI King Out of Lives! New round begins with shuffled pieces!');
LK.setTimeout(function () {
restartGame();
}, 2000);
return;
}
turnText.setText('AI Checkmate! Black King loses 2 lives! Threatening piece destroyed!');
// Switch turns and continue game
currentPlayer = 'white';
LK.setTimeout(function () {
turnText.setText('Your Turn');
gameState = 'playing';
}, 2000);
return;
}
}
if (validAIMoves.length === 0) return;
// Advanced AI Strategy Implementation
var bestMove = chooseBestAIMove(validAIMoves);
if (bestMove) {
// Check if this is a snake move and handle multi-move sequence
if (bestMove.piece.pieceType === 'snake') {
// Execute the move after a short delay
LK.setTimeout(function () {
movePiece(bestMove.piece, bestMove.toRow, bestMove.toCol);
// If snake has more moves remaining and it's still the AI's turn, continue with AI moves
if (currentSnakePiece && currentSnakePiece.pieceColor === 'black' && snakeMovesRemaining > 0 && currentPlayer === 'black') {
// Delay before next snake move
LK.setTimeout(function () {
makeAIMove(); // Recursive call for next snake move
}, 800);
}
}, 800);
} else {
// Execute regular move after a short delay
LK.setTimeout(function () {
movePiece(bestMove.piece, bestMove.toRow, bestMove.toCol);
}, 800);
}
}
}
function isMoveThreatening(fromRow, fromCol, toRow, toCol, targetRow, targetCol) {
// Simple check if move gets closer to target (used for king protection)
var currentDistance = Math.abs(fromRow - targetRow) + Math.abs(fromCol - targetCol);
var newDistance = Math.abs(toRow - targetRow) + Math.abs(toCol - targetCol);
return newDistance < currentDistance;
}
function chooseBestAIMove(validMoves) {
// AI opponent behavior with 19% chance for special behaviors
var randomValue = Math.random();
// 19% chance for special AI behaviors
if (randomValue < 0.19) {
var behaviorType = Math.floor(Math.random() * 3); // 0, 1, or 2
if (behaviorType === 0) {
// Wrong move - choose a suboptimal move
var wrongMove = findWrongMove(validMoves);
if (wrongMove) {
return wrongMove;
}
} else if (behaviorType === 1) {
// Think for 2 seconds before making normal move
var normalMove = findNormalAIMove(validMoves);
if (normalMove) {
// Delay the move by 2 seconds
LK.setTimeout(function () {
// The actual move execution happens in the calling function
}, 2000);
return normalMove;
}
} else if (behaviorType === 2) {
// Imitate player's last move if possible
var imitationMove = findPlayerImitationMove(validMoves);
if (imitationMove) {
return imitationMove;
}
}
}
// Super Grandmaster AI - Perfect calculation depth and evaluation
var bestMove = null;
var bestScore = -100000;
var calculationDepth = 5; // Deep calculation
// Priority 1: Immediate checkmate (highest priority)
var checkmateMove = findCheckmateInOne(validMoves);
if (checkmateMove) {
return checkmateMove;
}
// Priority 2: Prevent opponent checkmate threats
var preventCheckmateMove = preventOpponentCheckmate(validMoves);
if (preventCheckmateMove) {
return preventCheckmateMove;
}
// Priority 3: Emergency king defense when in check
if (isInCheck('black')) {
var defensiveMove = findOptimalDefensiveMove(validMoves);
if (defensiveMove) {
return defensiveMove;
}
}
// Priority 4: Princess elimination for maximum material gain
var princessEliminationMove = findBestPrincessElimination();
if (princessEliminationMove) {
return princessEliminationMove;
}
// Priority 5: Deep tactical combinations
var tacticalMove = findBestTacticalCombination(validMoves);
if (tacticalMove) {
return tacticalMove;
}
// Priority 6: Advanced positional play with perfect evaluation
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var score = evaluateMovePerfectly(move, calculationDepth);
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
}
return bestMove || validMoves[0];
}
function findCheckmateMove(validMoves) {
// Look for moves that can checkmate the opponent
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (simulateMove(move, function () {
return isCheckmate('white');
})) {
return move;
}
}
return null;
}
function findBestCapture(validMoves) {
var bestCapture = null;
var bestValue = 0;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
var captureValue = getPieceValue(targetPiece.pieceType);
// Prioritize king captures highly
if (targetPiece.pieceType === 'king') {
captureValue = 1000;
}
if (captureValue > bestValue) {
bestValue = captureValue;
bestCapture = move;
}
}
}
return bestCapture;
}
function shouldTakeCapture(captureMove) {
// Analyze if capture is safe by looking ahead 2 moves
var isSafe = simulateMove(captureMove, function () {
// Check if the capturing piece can be counter-captured
var capturedPosition = {
row: captureMove.toRow,
col: captureMove.toCol
};
var opponentMoves = getAllValidMoves('white');
for (var i = 0; i < opponentMoves.length; i++) {
var counterMove = opponentMoves[i];
if (counterMove.toRow === capturedPosition.row && counterMove.toCol === capturedPosition.col) {
var counterValue = getPieceValue(counterMove.piece.pieceType);
var captureValue = getPieceValue(getPieceAt(captureMove.toRow, captureMove.toCol).pieceType);
// Only take if gain is worth the risk
return captureValue > counterValue;
}
}
return true; // Safe capture
});
return isSafe;
}
function findTacticalMove(validMoves) {
// Look for moves that create pressure or tactical advantages
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
// Check if move puts opponent in check
if (simulateMove(move, function () {
return isInCheck('white');
})) {
return move;
}
// Check if move creates fork or pin opportunities
if (createsFork(move) || createsPin(move)) {
return move;
}
}
return null;
}
function findWithdrawMove(validMoves) {
// Sometimes withdraw valuable pieces when under threat
var valuablePieces = [];
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'black' && (piece.pieceType === 'princess' || piece.pieceType === 'queen' || piece.pieceType === 'snake' || piece.pieceType === 'rook')) {
if (isPieceUnderThreat(piece)) {
valuablePieces.push(piece);
}
}
}
for (var i = 0; i < valuablePieces.length; i++) {
var piece = valuablePieces[i];
for (var j = 0; j < validMoves.length; j++) {
var move = validMoves[j];
if (move.piece === piece && !isPieceUnderThreatAt(piece, move.toRow, move.toCol)) {
// Use knight to create pressure occasionally
if (Math.random() < 0.3 && piece.pieceType === 'knight') {
if (createsPressure(move)) {
return move;
}
}
return move; // Safe withdrawal
}
}
}
return null;
}
function findSafePositionalMove(validMoves) {
// Choose a move that improves position without risk
var safeMoves = [];
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (!isPieceUnderThreatAt(move.piece, move.toRow, move.toCol)) {
safeMoves.push(move);
}
}
if (safeMoves.length > 0) {
return safeMoves[Math.floor(Math.random() * safeMoves.length)];
}
return validMoves[Math.floor(Math.random() * validMoves.length)];
}
function simulateMove(move, testFunction) {
// Temporarily execute move and test condition
var originalRow = move.piece.row;
var originalCol = move.piece.col;
var capturedPiece = getPieceAt(move.toRow, move.toCol);
// Make temporary move
gameBoard[originalRow][originalCol] = null;
gameBoard[move.toRow][move.toCol] = move.piece;
move.piece.row = move.toRow;
move.piece.col = move.toCol;
var result = testFunction();
// Restore position
move.piece.row = originalRow;
move.piece.col = originalCol;
gameBoard[originalRow][originalCol] = move.piece;
gameBoard[move.toRow][move.toCol] = capturedPiece;
return result;
}
function isPieceUnderThreat(piece) {
var whitePieces = pieces.filter(function (p) {
return p.pieceColor === 'white';
});
for (var i = 0; i < whitePieces.length; i++) {
if (whitePieces[i].canMoveTo(piece.row, piece.col)) {
return true;
}
}
return false;
}
function isPieceUnderThreatAt(piece, row, col) {
var whitePieces = pieces.filter(function (p) {
return p.pieceColor === 'white';
});
for (var i = 0; i < whitePieces.length; i++) {
if (whitePieces[i].canMoveTo(row, col)) {
return true;
}
}
return false;
}
function createsFork(move) {
// Check if move attacks two or more enemy pieces
var attackCount = 0;
var whitePieces = pieces.filter(function (p) {
return p.pieceColor === 'white';
});
return simulateMove(move, function () {
for (var i = 0; i < whitePieces.length; i++) {
if (move.piece.canMoveTo(whitePieces[i].row, whitePieces[i].col)) {
attackCount++;
}
}
return attackCount >= 2;
});
}
function createsPin(move) {
// Simplified pin detection - attacks piece that's protecting the king
var whiteKing = findKing('white');
if (!whiteKing) return false;
return simulateMove(move, function () {
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
// Check if removing this piece would expose king
var originalRow = targetPiece.row;
var originalCol = targetPiece.col;
gameBoard[originalRow][originalCol] = null;
var exposesKing = move.piece.canMoveTo(whiteKing.row, whiteKing.col);
gameBoard[originalRow][originalCol] = targetPiece;
return exposesKing;
}
return false;
});
}
function createsPressure(move) {
// Check if move creates pressure on opponent's position
var pressureScore = 0;
var whitePieces = pieces.filter(function (p) {
return p.pieceColor === 'white';
});
return simulateMove(move, function () {
for (var i = 0; i < whitePieces.length; i++) {
var piece = whitePieces[i];
if (move.piece.canMoveTo(piece.row, piece.col)) {
pressureScore += getPieceValue(piece.pieceType);
}
}
return pressureScore > 3; // Creates meaningful pressure
});
}
function findHighValueCapture(validMoves) {
// AI prioritizes capturing high value pieces with any piece
var bestCapture = null;
var bestPriority = 0;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
// Prioritize capturing high value pieces normally
var priority = getPieceValue(targetPiece.pieceType);
if (priority > bestPriority) {
bestPriority = priority;
bestCapture = move;
}
}
}
return bestCapture;
}
function findPoisonOpportunity(validMoves) {
// AI uses snake to move closer to enemy pieces protecting the king to poison them
var whiteKing = findKing('white');
if (!whiteKing) return null;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (move.piece.pieceType === 'snake') {
// Check if this snake move would poison pieces near the white king
var kingProtectors = getKingProtectors('white');
if (kingProtectors.length > 0) {
// Check if moving snake to this position would poison any king protectors
var wouldPoisonProtector = simulateMove(move, function () {
var poisonPositions = [{
row: move.toRow - 1,
col: move.toCol - 1
}, {
row: move.toRow - 1,
col: move.toCol
}, {
row: move.toRow - 1,
col: move.toCol + 1
}, {
row: move.toRow,
col: move.toCol - 1
}, {
row: move.toRow,
col: move.toCol + 1
}, {
row: move.toRow + 1,
col: move.toCol - 1
}, {
row: move.toRow + 1,
col: move.toCol
}, {
row: move.toRow + 1,
col: move.toCol + 1
}];
for (var j = 0; j < poisonPositions.length; j++) {
var pos = poisonPositions[j];
if (pos.row >= 0 && pos.row < 8 && pos.col >= 0 && pos.col < 16) {
var pieceAtPos = getPieceAt(pos.row, pos.col);
if (pieceAtPos && pieceAtPos.pieceColor === 'white') {
for (var k = 0; k < kingProtectors.length; k++) {
if (kingProtectors[k] === pieceAtPos) {
return true;
}
}
}
}
}
return false;
});
if (wouldPoisonProtector) {
return move;
}
}
}
}
return null;
}
function getKingProtectors(color) {
// Find pieces that are protecting the king
var king = findKing(color);
if (!king) return [];
var protectors = [];
var enemyColor = color === 'white' ? 'black' : 'white';
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === color && piece !== king) {
// Check if removing this piece would expose king to attack
var originalRow = piece.row;
var originalCol = piece.col;
gameBoard[originalRow][originalCol] = null;
var exposesKing = false;
for (var j = 0; j < pieces.length; j++) {
var enemyPiece = pieces[j];
if (enemyPiece.pieceColor === enemyColor && enemyPiece.canMoveTo(king.row, king.col)) {
exposesKing = true;
break;
}
}
gameBoard[originalRow][originalCol] = piece;
if (exposesKing) {
protectors.push(piece);
}
}
}
return protectors;
}
function canEscapeCheck(piece, newRow, newCol) {
// Simulate the move to see if it gets the king out of check
var originalRow = piece.row;
var originalCol = piece.col;
var capturedPiece = getPieceAt(newRow, newCol);
// Make temporary move
gameBoard[originalRow][originalCol] = null;
gameBoard[newRow][newCol] = piece;
piece.row = newRow;
piece.col = newCol;
// Check if still in check after move
var stillInCheck = isInCheck(piece.pieceColor);
// Restore original position
piece.row = originalRow;
piece.col = originalCol;
gameBoard[originalRow][originalCol] = piece;
gameBoard[newRow][newCol] = capturedPiece;
return !stillInCheck;
}
function isKingSafeAt(king, newRow, newCol) {
// Check if king would be safe at this position
var originalRow = king.row;
var originalCol = king.col;
var capturedPiece = getPieceAt(newRow, newCol);
// Temporarily move king to test position
gameBoard[originalRow][originalCol] = null;
gameBoard[newRow][newCol] = king;
king.row = newRow;
king.col = newCol;
// Check if king would be in check at new position (including snake threats)
var wouldBeInCheck = isInCheck(king.pieceColor);
// Additional check specifically for snake multi-move threats
if (!wouldBeInCheck) {
wouldBeInCheck = isKingThreatenedBySnake(king, newRow, newCol);
}
// Restore original position
king.row = originalRow;
king.col = originalCol;
gameBoard[originalRow][originalCol] = king;
gameBoard[newRow][newCol] = capturedPiece;
return !wouldBeInCheck;
}
function hasValidEscapeMoves(color) {
// Check if color has any valid moves when in check, considering snake threats
var king = findKing(color);
if (!king) return false;
// Check all possible king moves
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
var newRow = king.row + dr;
var newCol = king.col + dc;
if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 16) {
var targetPiece = getPieceAt(newRow, newCol);
if (!targetPiece || targetPiece.pieceColor !== color) {
if (isKingSafeAt(king, newRow, newCol)) {
return true; // Found a safe escape square
}
}
}
}
}
// Check if any other piece can block or capture to protect king
var allMoves = getAllValidMoves(color);
return allMoves.length > 0;
}
function findThreateningPiece(king) {
// Find the piece that is threatening the king, including snake multi-move threats
if (!king) return null;
// Check immediate threats first
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor !== king.pieceColor && piece.canMoveTo(king.row, king.col)) {
return piece;
}
}
// Check for snake multi-move threats
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceType === 'snake' && piece.pieceColor !== king.pieceColor && !piece.snakeOnCooldown) {
if (canSnakeReachPosition(piece, king.row, king.col)) {
return piece; // Snake can reach king position
}
}
}
return null;
}
function formatTime(milliseconds) {
var totalSeconds = Math.max(0, Math.floor(milliseconds / 1000));
var minutes = Math.floor(totalSeconds / 60);
var seconds = totalSeconds % 60;
return minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
}
function updateTimerDisplay() {
whiteTimerText.setText(formatTime(whiteTimeRemaining));
blackTimerText.setText(formatTime(blackTimeRemaining));
}
function startTimer() {
if (timerInterval) {
LK.clearInterval(timerInterval);
}
currentTurnStartTime = Date.now();
gameStartTime = gameStartTime || currentTurnStartTime;
timerInterval = LK.setInterval(function () {
if (gameState === 'playing' || gameState === 'check') {
var now = Date.now();
var elapsed = now - currentTurnStartTime;
if (currentPlayer === 'white') {
whiteTimeRemaining -= elapsed;
if (whiteTimeRemaining <= 0) {
whiteTimeRemaining = 0;
handleTimeOut('white');
return;
}
} else {
blackTimeRemaining -= elapsed;
if (blackTimeRemaining <= 0) {
blackTimeRemaining = 0;
handleTimeOut('black');
return;
}
}
updateTimerDisplay();
currentTurnStartTime = now;
}
}, 100);
}
function stopTimer() {
if (timerInterval) {
LK.clearInterval(timerInterval);
timerInterval = null;
}
}
function handleTimeOut(player) {
stopTimer();
var winner = player === 'white' ? 'Black' : 'White';
if (player === 'white') {
turnText.setText('Black side won! White ran out of time!');
} else {
turnText.setText('White Wins! Black ran out of time!');
}
gameState = 'gameover';
LK.setTimeout(function () {
restartGame();
}, 2000);
}
function updateLivesDisplay() {
// Update text
whiteLivesText.setText('Your Lives: ' + whiteKingLives);
blackLivesText.setText('Enemy Lives: ' + blackKingLives);
// Update visual hearts with smooth animation
for (var i = 0; i < 4; i++) {
if (i < whiteKingLives) {
tween(whiteLivesHearts[i], {
alpha: 1.0,
tint: 0xFFFFFF
}, {
duration: 300,
easing: tween.easeOut
});
} else {
tween(whiteLivesHearts[i], {
alpha: 0.3,
tint: 0x666666
}, {
duration: 300,
easing: tween.easeOut
});
}
if (i < blackKingLives) {
tween(blackLivesHearts[i], {
alpha: 1.0,
tint: 0xFF0000
}, {
duration: 300,
easing: tween.easeOut
});
} else {
tween(blackLivesHearts[i], {
alpha: 0.3,
tint: 0x666666
}, {
duration: 300,
easing: tween.easeOut
});
}
}
}
function updateStaminaDisplay() {
// Update stamina bar visual indicators
for (var i = 0; i < maxStaminaPoints; i++) {
if (i < staminaPoints) {
tween(staminaBars[i], {
alpha: 1.0,
tint: 0xff69b4
}, {
duration: 300,
easing: tween.easeOut
});
} else {
tween(staminaBars[i], {
alpha: 0.3,
tint: 0x666666
}, {
duration: 300,
easing: tween.easeOut
});
}
}
// Update AI stamina bar visual indicators
for (var i = 0; i < aiMaxStaminaPoints; i++) {
if (i < aiStaminaPoints) {
tween(aiStaminaBars[i], {
alpha: 1.0,
tint: 0xff69b4
}, {
duration: 300,
easing: tween.easeOut
});
} else {
tween(aiStaminaBars[i], {
alpha: 0.3,
tint: 0x666666
}, {
duration: 300,
easing: tween.easeOut
});
}
}
// Update Queen and Snake pink square highlighting
updateQueenSnakePinkSquares();
}
function activateAbility(color, remainingLives) {
var abilityName = '';
var abilityEffect = '';
switch (remainingLives) {
case 3:
abilityName = 'Shield Boost';
abilityEffect = 'All pieces gain temporary protection';
// Flash all pieces of that color green to show protection
flashPiecesOfColor(color, 0x00FF00, 1000);
break;
case 2:
abilityName = 'Speed Rush';
abilityEffect = 'Pieces move faster for next 3 turns';
// Make pieces glow blue to show speed boost
flashPiecesOfColor(color, 0x0099FF, 1500);
break;
case 1:
abilityName = 'Rage Mode';
abilityEffect = 'Double damage on next capture';
// Make pieces glow red to show rage
flashPiecesOfColor(color, 0xFF3333, 2000);
break;
case 0:
abilityName = 'Final Stand';
abilityEffect = 'Last desperate attempt';
break;
}
// Show ability activation message
var message = color.charAt(0).toUpperCase() + color.slice(1) + ' Ability: ' + abilityName;
var abilityText = new Text2(message, {
size: 60,
fill: color === 'white' ? 0xFFFFFF : 0xFF0000
});
abilityText.anchor.set(0.5, 0.5);
abilityText.x = 1024;
abilityText.y = 1200;
game.addChild(abilityText);
// Start with small scale and animate in
abilityText.scaleX = 0.5;
abilityText.scaleY = 0.5;
abilityText.alpha = 0;
// Animate ability text entrance
tween(abilityText, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 300,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Hold for a moment then fade out
LK.setTimeout(function () {
tween(abilityText, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0,
y: abilityText.y - 50
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
abilityText.destroy();
}
});
}, 1000);
}
});
}
function flashPiecesOfColor(color, flashColor, duration) {
for (var i = 0; i < pieces.length; i++) {
if (pieces[i].pieceColor === color) {
LK.effects.flashObject(pieces[i], flashColor, duration);
}
}
}
function applyPoisonBehindSnake(snake, oldRow, oldCol) {
// Apply poison to pieces around the snake's previous position (behind it)
var poisonPositions = [{
row: oldRow - 1,
col: oldCol - 1
}, {
row: oldRow - 1,
col: oldCol
}, {
row: oldRow - 1,
col: oldCol + 1
}, {
row: oldRow,
col: oldCol - 1
}, {
row: oldRow,
col: oldCol + 1
}, {
row: oldRow + 1,
col: oldCol - 1
}, {
row: oldRow + 1,
col: oldCol
}, {
row: oldRow + 1,
col: oldCol + 1
}];
for (var i = 0; i < poisonPositions.length; i++) {
var pos = poisonPositions[i];
if (pos.row >= 0 && pos.row < 8 && pos.col >= 0 && pos.col < 16) {
var pieceAtPos = getPieceAt(pos.row, pos.col);
if (pieceAtPos && pieceAtPos.pieceColor !== snake.pieceColor) {
// Kings are immune to poison - skip poisoning kings completely
if (pieceAtPos.pieceType === 'king') {
continue; // Skip poisoning kings
}
// Snake cannot poison the king
if (pieceAtPos.pieceType === 'king') {
continue; // Skip poisoning kings
}
pieceAtPos.applyPoison(3); // Poison for 3 turns
// Flash piece red to show poison application with proper closure
var targetPiece = pieceAtPos; // Capture the piece in closure
tween(targetPiece, {
tint: 0xff0000
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(targetPiece, {
tint: 0xff6666
}, {
duration: 200,
easing: tween.easeOut
});
}
});
}
}
}
}
function restartGame() {
// Stop timer when game ends
stopTimer();
// Determine winner before clearing state
var winner = whiteKingLives <= 0 ? 'Black' : 'White';
// Show win screen with team name
showWinScreen(winner);
}
function showWinScreen(winningTeam) {
// Create win screen overlay
var winScreen = new Container();
game.addChild(winScreen);
// Semi-transparent background
var background = LK.getAsset('darkSquare', {
anchorX: 0,
anchorY: 0,
alpha: 0.8,
scaleX: 20,
scaleY: 25
});
background.x = 0;
background.y = 0;
winScreen.addChild(background);
// Win text
var winText = new Text2(winningTeam + (winningTeam === 'Black' ? ' side won.' : ' Team Wins!'), {
size: 120,
fill: winningTeam === 'White' ? 0xFFFFFF : 0xFF0000
});
winText.anchor.set(0.5, 0.5);
winText.x = 1024;
winText.y = 1366;
winScreen.addChild(winText);
// Animate win text
tween(winText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeOut
});
// Auto-restart after 1 second
LK.setTimeout(function () {
winScreen.destroy();
actualRestartGame();
}, 1000);
}
function shufflePiecesForNewRound(pieceSetup) {
// Create array of non-pawn, non-king pieces for swapping
var swappablePieces = ['summoner', 'knight', 'bishop', 'queen', 'queen', 'knight', 'rook', 'jester', 'knight', 'bishop', 'princess', 'princess', 'snake', 'jester'];
// Create shuffled version of swappable pieces for each side
var shuffledPiecesBlack = swappablePieces.slice(); // Copy array for black
var shuffledPiecesWhite = swappablePieces.slice(); // Copy array for white
// Shuffle black pieces
for (var i = shuffledPiecesBlack.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = shuffledPiecesBlack[i];
shuffledPiecesBlack[i] = shuffledPiecesBlack[j];
shuffledPiecesBlack[j] = temp;
}
// Shuffle white pieces separately
for (var i = shuffledPiecesWhite.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = shuffledPiecesWhite[i];
shuffledPiecesWhite[i] = shuffledPiecesWhite[j];
shuffledPiecesWhite[j] = temp;
}
// Replace pieces in setup while keeping king in center
var blackPieceIndex = 0;
var whitePieceIndex = 0;
// Shuffle black pieces (top row)
for (var col = 0; col < 16; col++) {
if (pieceSetup[0][col] !== 'king' && pieceSetup[0][col] !== 'pawn') {
pieceSetup[0][col] = shuffledPiecesBlack[blackPieceIndex % shuffledPiecesBlack.length];
blackPieceIndex++;
}
}
// Create white setup by shuffling pieces for bottom row (this will be used in piece creation)
var whiteSetup = pieceSetup[0].slice(); // Copy the structure
for (var col = 0; col < 16; col++) {
if (whiteSetup[col] !== 'king' && whiteSetup[col] !== 'pawn') {
whiteSetup[col] = shuffledPiecesWhite[whitePieceIndex % shuffledPiecesWhite.length];
whitePieceIndex++;
}
}
// Store white setup for use in piece creation
pieceSetup.whiteSetup = whiteSetup;
return pieceSetup;
}
function randomlySwapNonPawnPieces(pieceSetup) {
// Keep the original function for backward compatibility
return shufflePiecesForNewRound(pieceSetup);
}
function actualRestartGame() {
// Clear all pieces
for (var i = 0; i < pieces.length; i++) {
pieces[i].destroy();
}
pieces = [];
// Clear highlights
clearHighlights();
// Clear pink squares
for (var i = 0; i < queenSnakePinkSquares.length; i++) {
if (!queenSnakePinkSquares[i].destroyed) {
queenSnakePinkSquares[i].destroy();
}
}
queenSnakePinkSquares = [];
// Clear king squares
if (kingSquare) {
kingSquare.destroy();
kingSquare = null;
}
if (blackKingSquare) {
blackKingSquare.destroy();
blackKingSquare = null;
}
// Reset game state
currentPlayer = 'white';
selectedPiece = null;
validMoves = [];
dragNode = null;
gameState = 'playing';
whiteKingLives = 4;
blackKingLives = 4;
currentSnakePiece = null;
snakeMovesRemaining = 0;
activeSnakePiece = null; // Reset active snake
// Reset stamina system
staminaPoints = 1;
aiStaminaPoints = 1;
// Clear stamina consumer tracking
window.whiteStaminaConsumers = [];
window.blackStaminaConsumers = [];
updateStaminaDisplay();
// Reset timer variables
whiteTimeRemaining = 60000;
blackTimeRemaining = 60000;
gameStartTime = null;
currentTurnStartTime = null;
// Clear hover tooltip system
if (hoverTimer) {
LK.clearTimeout(hoverTimer);
hoverTimer = null;
}
hidePieceTooltip();
hoveredPiece = null;
hoverStartTime = null;
// Snake cooldowns will be reset when new pieces are created - no need to access destroyed pieces
updateLivesDisplay();
turnText.setText('Your Turn');
// Clear board
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
gameBoard[row][col] = null;
}
}
// Create base piece setup and shuffle non-pawn pieces for new round
var basePieceSetup = [['summoner', 'knight', 'bishop', 'queen', 'king', 'queen', 'bishop', 'knight', 'rook', 'jester', 'knight', 'bishop', 'princess', 'princess', 'snake', 'jester'], ['pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn']];
var pieceSetup = shufflePiecesForNewRound(basePieceSetup);
// Create black pieces
for (var row = 0; row < 2; row++) {
for (var col = 0; col < 16; col++) {
var piece = new ChessPiece(pieceSetup[row][col], 'black', row, col);
pieces.push(piece);
gameBoard[row][col] = piece;
game.addChild(piece);
piece.updatePosition();
}
}
// Create white pieces
for (var row = 6; row < 8; row++) {
for (var col = 0; col < 16; col++) {
var pieceType = row === 6 ? 'pawn' : pieceSetup.whiteSetup[col];
var piece = new ChessPiece(pieceType, 'white', row, col);
pieces.push(piece);
gameBoard[row][col] = piece;
game.addChild(piece);
piece.updatePosition();
}
}
// Initialize king square after restart
updateKingSquare();
// Restart timer
updateTimerDisplay();
startTimer();
}
game.down = function (x, y, obj) {
if (gameState !== 'playing' && gameState !== 'check') return;
if (currentPlayer !== 'white') return; // Only allow white player to move pieces
// Clear hover tooltip when clicking
if (hoverTimer) {
LK.clearTimeout(hoverTimer);
hoverTimer = null;
}
hidePieceTooltip();
hoveredPiece = null;
var square = getSquareFromPosition(x, y);
if (!square) return;
var clickedPiece = getPieceAt(square.row, square.col);
// If we have a selected piece, try to move it
if (selectedPiece) {
// Check if clicked on valid move
var isValidMove = false;
for (var i = 0; i < validMoves.length; i++) {
if (validMoves[i].row === square.row && validMoves[i].col === square.col) {
isValidMove = true;
break;
}
}
if (isValidMove) {
movePiece(selectedPiece, square.row, square.col);
clearHighlights();
selectedPiece = null;
return;
} else {
// Invalid move or clicking elsewhere - clear selection
clearHighlights();
selectedPiece = null;
}
}
// Handle princess elimination mode
if (selectedPiece && selectedPiece.pieceType === 'princess' && selectedPiece.princessEliminationMode) {
if (clickedPiece && clickedPiece.pieceColor !== selectedPiece.pieceColor) {
// Check if target is within princess's elimination range (2 squares in any direction)
var rowDiff = Math.abs(clickedPiece.row - selectedPiece.row);
var colDiff = Math.abs(clickedPiece.col - selectedPiece.col);
var maxRange = Math.max(rowDiff, colDiff);
if (maxRange <= 2 && selectedPiece.princessSelectedTargets.indexOf(clickedPiece) === -1) {
selectedPiece.princessSelectedTargets.push(clickedPiece);
selectedPiece.princessTargetsSelected++;
highlightSquare(clickedPiece.row, clickedPiece.col, 'blue');
turnText.setText('Princess selected target ' + selectedPiece.princessTargetsSelected + '/2');
if (selectedPiece.princessTargetsSelected === 2) {
// Execute elimination
for (var i = 0; i < selectedPiece.princessSelectedTargets.length; i++) {
var target = selectedPiece.princessSelectedTargets[i];
for (var j = 0; j < pieces.length; j++) {
if (pieces[j] === target) {
gameBoard[target.row][target.col] = null;
target.destroy();
pieces.splice(j, 1);
break;
}
}
}
LK.getSound('capture').play();
// Reset princess state and switch turns
selectedPiece.princessEliminationMode = false;
selectedPiece.princessTargetsSelected = 0;
selectedPiece.princessSelectedTargets = [];
clearHighlights();
selectedPiece = null;
currentPlayer = currentPlayer === 'white' ? 'black' : 'white';
turnText.setText(currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + '\'s Turn');
if (currentPlayer === 'black') {
makeAIMove();
}
return;
}
}
}
return;
}
// If there's an active snake continuing its moves, only allow that snake to be selected
if (activeSnakePiece && activeSnakePiece.pieceColor === 'white' && snakeMovesRemaining > 0) {
if (clickedPiece === activeSnakePiece) {
selectedPiece = activeSnakePiece;
dragNode = activeSnakePiece;
showValidMoves(activeSnakePiece);
}
return;
}
// Normal piece selection - only allow white pieces to be selected
if (clickedPiece && clickedPiece.pieceColor === 'white') {
// Add selection animation
tween(clickedPiece, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(clickedPiece, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
}
});
// Check for princess double-click to enter elimination mode
if (clickedPiece.pieceType === 'princess' && selectedPiece === clickedPiece) {
clickedPiece.princessEliminationMode = true;
clickedPiece.princessTargetsSelected = 0;
clickedPiece.princessSelectedTargets = [];
clearHighlights();
highlightSquare(clickedPiece.row, clickedPiece.col, 'blue');
// Show elimination range (2 squares in all directions)
for (var dr = -2; dr <= 2; dr++) {
for (var dc = -2; dc <= 2; dc++) {
if (dr === 0 && dc === 0) continue; // Skip princess's own square
var targetRow = clickedPiece.row + dr;
var targetCol = clickedPiece.col + dc;
if (targetRow >= 0 && targetRow < 8 && targetCol >= 0 && targetCol < 16) {
var targetPiece = getPieceAt(targetRow, targetCol);
if (targetPiece && targetPiece.pieceColor !== clickedPiece.pieceColor) {
highlightSquare(targetRow, targetCol, 'green'); // Show valid targets in green
} else if (!targetPiece) {
highlightSquare(targetRow, targetCol, 'yellow'); // Show empty squares in elimination range
}
}
}
}
turnText.setText('Princess Elimination Mode: Select 2 enemies within 2 squares');
return;
}
// Select the piece and show valid moves
selectedPiece = clickedPiece;
dragNode = clickedPiece;
showValidMoves(clickedPiece);
}
};
game.move = function (x, y, obj) {
if (dragNode) {
dragNode.x = x;
dragNode.y = y;
}
// Handle piece hover for tooltips
var square = getSquareFromPosition(x, y);
var currentPiece = null;
if (square) {
currentPiece = getPieceAt(square.row, square.col);
}
// Check if we're hovering over a different piece or no piece
if (currentPiece !== hoveredPiece) {
// Clear existing hover state
if (hoverTimer) {
LK.clearTimeout(hoverTimer);
hoverTimer = null;
}
hidePieceTooltip();
// Set new hover state
hoveredPiece = currentPiece;
if (hoveredPiece) {
hoverStartTime = Date.now();
hoverTimer = LK.setTimeout(function () {
if (hoveredPiece && !hoverTooltip) {
showPieceTooltip(hoveredPiece, x, y);
}
}, HOVER_DELAY);
}
}
};
game.up = function (x, y, obj) {
if (dragNode && selectedPiece) {
var square = getSquareFromPosition(x, y);
if (square) {
var isValidMove = false;
for (var i = 0; i < validMoves.length; i++) {
if (validMoves[i].row === square.row && validMoves[i].col === square.col) {
isValidMove = true;
break;
}
}
if (isValidMove) {
movePiece(selectedPiece, square.row, square.col);
clearHighlights();
selectedPiece = null;
} else {
selectedPiece.updatePosition(true);
}
} else {
selectedPiece.updatePosition(true);
}
}
dragNode = null;
};
game.update = function () {
// Game logic runs automatically through event handlers
};
// Track the last player move for imitation
var lastPlayerMove = null;
function recordPlayerMove(piece, fromRow, fromCol, toRow, toCol) {
if (piece.pieceColor === 'white') {
lastPlayerMove = {
pieceType: piece.pieceType,
fromRow: fromRow,
fromCol: fromCol,
toRow: toRow,
toCol: toCol,
rowDiff: toRow - fromRow,
colDiff: toCol - fromCol
};
}
}
function findWrongMove(validMoves) {
// Choose a move that's clearly suboptimal
var wrongMoves = [];
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var moveValue = evaluateMovePerfectly(move, 2);
// Consider moves that are clearly bad (negative value or very low)
if (moveValue < 10) {
wrongMoves.push(move);
}
// Also consider moves that put pieces in danger
if (isPieceUnderThreatAt(move.piece, move.toRow, move.toCol)) {
wrongMoves.push(move);
}
}
// If no clearly wrong moves, pick a random low-value move
if (wrongMoves.length === 0) {
var sortedMoves = validMoves.slice().sort(function (a, b) {
return evaluateMovePerfectly(a, 2) - evaluateMovePerfectly(b, 2);
});
wrongMoves = sortedMoves.slice(0, Math.min(3, sortedMoves.length));
}
return wrongMoves.length > 0 ? wrongMoves[Math.floor(Math.random() * wrongMoves.length)] : null;
}
function findNormalAIMove(validMoves) {
// Find a decent move using simplified evaluation
var bestMove = null;
var bestScore = -1000;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var score = 0;
// Basic capture evaluation
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
score += getPieceValue(targetPiece.pieceType) * 10;
}
// Basic safety check
if (!isPieceUnderThreatAt(move.piece, move.toRow, move.toCol)) {
score += 5;
}
// Basic center control
var centerDistance = Math.abs(move.toRow - 3.5) + Math.abs(move.toCol - 7.5);
score += (16 - centerDistance) * 0.5;
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
}
return bestMove;
}
function findPlayerImitationMove(validMoves) {
if (!lastPlayerMove) return null;
// Try to find a similar move pattern
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
// Check if piece type matches
if (move.piece.pieceType === lastPlayerMove.pieceType) {
// Check if move pattern is similar (same direction/distance)
var rowDiff = move.toRow - move.piece.row;
var colDiff = move.toCol - move.piece.col;
if (rowDiff === lastPlayerMove.rowDiff && colDiff === lastPlayerMove.colDiff) {
return move; // Exact pattern match
}
// Check for similar pattern (same direction, different distance)
var playerDirection = {
row: lastPlayerMove.rowDiff === 0 ? 0 : lastPlayerMove.rowDiff > 0 ? 1 : -1,
col: lastPlayerMove.colDiff === 0 ? 0 : lastPlayerMove.colDiff > 0 ? 1 : -1
};
var moveDirection = {
row: rowDiff === 0 ? 0 : rowDiff > 0 ? 1 : -1,
col: colDiff === 0 ? 0 : colDiff > 0 ? 1 : -1
};
if (playerDirection.row === moveDirection.row && playerDirection.col === moveDirection.col) {
return move; // Same direction
}
}
}
// If no exact imitation possible, try similar piece types
var similarPieceTypes = {
'queen': ['princess', 'rook', 'bishop'],
'princess': ['queen', 'rook'],
'rook': ['queen', 'princess'],
'bishop': ['queen'],
'knight': ['jester'],
'jester': ['knight'],
'snake': ['queen', 'princess']
};
if (similarPieceTypes[lastPlayerMove.pieceType]) {
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (similarPieceTypes[lastPlayerMove.pieceType].indexOf(move.piece.pieceType) !== -1) {
return move; // Similar piece type
}
}
}
return null;
}
function findAggressiveSnakeMove(validMoves) {
// AI uses snake aggressively like a queen for attacks and board control
var bestSnakeMove = null;
var bestValue = 0;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (move.piece.pieceType === 'snake') {
var moveValue = 0;
// Prioritize captures with snake
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
moveValue += getPieceValue(targetPiece.pieceType) * 2; // Double weight for snake captures
}
// Prioritize moves that put enemy king in check
if (simulateMove(move, function () {
return isInCheck('white');
})) {
moveValue += 15; // High priority for check moves
}
// Prioritize moves that control center squares
var centerDistance = Math.abs(move.toRow - 3.5) + Math.abs(move.toCol - 7.5);
moveValue += (16 - centerDistance) * 0.5; // Favor central positions
// Prioritize moves that attack multiple pieces
var attackCount = 0;
var whitePieces = pieces.filter(function (p) {
return p.pieceColor === 'white';
});
simulateMove(move, function () {
for (var j = 0; j < whitePieces.length; j++) {
if (move.piece.canMoveTo(whitePieces[j].row, whitePieces[j].col)) {
attackCount++;
}
}
return false;
});
moveValue += attackCount * 3; // Bonus for forking
// Avoid moves that put snake in immediate danger unless high value
if (!isPieceUnderThreatAt(move.piece, move.toRow, move.toCol) || moveValue > 10) {
if (moveValue > bestValue) {
bestValue = moveValue;
bestSnakeMove = move;
}
}
}
}
return bestSnakeMove;
}
function findSnakeTacticalMove(validMoves) {
// Find tactical moves specifically for snake pieces - aggressive positioning
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (move.piece.pieceType === 'snake') {
// Snake moves to attack enemy pieces near their king
var whiteKing = findKing('white');
if (whiteKing) {
var distanceToKing = Math.abs(move.toRow - whiteKing.row) + Math.abs(move.toCol - whiteKing.col);
// Prioritize moves that get snake closer to enemy king area
if (distanceToKing <= 3) {
var currentDistance = Math.abs(move.piece.row - whiteKing.row) + Math.abs(move.piece.col - whiteKing.col);
if (distanceToKing < currentDistance) {
return move; // Move closer to king
}
}
}
// Snake moves to create pressure on enemy back rank
if (move.toRow <= 2) {
// Near enemy back rank
return move;
}
// Snake moves to control key squares
var keySquares = [{
row: 3,
col: 7
}, {
row: 3,
col: 8
}, {
row: 4,
col: 7
}, {
row: 4,
col: 8
}]; // Center control
for (var j = 0; j < keySquares.length; j++) {
if (move.toRow === keySquares[j].row && move.toCol === keySquares[j].col) {
return move;
}
}
}
}
return null;
}
function findDefensivePrincessMove(validMoves) {
// AI uses princess and queen more timidly for solid compression and defensive checkmate setups
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (move.piece.pieceType === 'princess' || move.piece.pieceType === 'queen') {
// Queen stays back and supports other pieces
var backRankSafety = move.toRow <= 2; // Stay in back ranks
if (backRankSafety) {
// Quenn supports checkmate patterns by controlling escape squares
var whiteKing = findKing('white');
if (whiteKing) {
// Check if queen move controls king's escape squares
var controlsEscape = false;
var escapeSquares = [{
row: whiteKing.row - 1,
col: whiteKing.col - 1
}, {
row: whiteKing.row - 1,
col: whiteKing.col
}, {
row: whiteKing.row - 1,
col: whiteKing.col + 1
}, {
row: whiteKing.row,
col: whiteKing.col - 1
}, {
row: whiteKing.row,
col: whiteKing.col + 1
}, {
row: whiteKing.row + 1,
col: whiteKing.col - 1
}, {
row: whiteKing.row + 1,
col: whiteKing.col
}, {
row: whiteKing.row + 1,
col: whiteKing.col + 1
}];
simulateMove(move, function () {
for (var j = 0; j < escapeSquares.length; j++) {
var escSquare = escapeSquares[j];
if (escSquare.row >= 0 && escSquare.row < 8 && escSquare.col >= 0 && escSquare.col < 16) {
if (move.piece.canMoveTo(escSquare.row, escSquare.col)) {
controlsEscape = true;
break;
}
}
}
return false;
});
if (controlsEscape) {
return move; // Queen controls escape squares for checkmate
}
}
// Queen defends other pieces
var defendsAllies = false;
simulateMove(move, function () {
for (var j = 0; j < pieces.length; j++) {
var ally = pieces[j];
if (ally.pieceColor === 'black' && ally !== move.piece) {
if (isPieceUnderThreat(ally)) {
// Check if queen can defend this piece
if (move.piece.canMoveTo(ally.row, ally.col)) {
defendsAllies = true;
break;
}
}
}
}
return false;
});
if (defendsAllies) {
return move; // Queen defends allies
}
}
// Only make aggressive queen moves if very safe and high value
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
var captureValue = getPieceValue(targetPiece.pieceType);
if (captureValue >= 5 && !isPieceUnderThreatAt(move.piece, move.toRow, move.toCol)) {
return move; // Safe high-value capture only
}
}
}
}
return null;
}
function isKingThreatenedBySnake(king, kingRow, kingCol) {
// Check if any enemy snake can reach the king position through its multi-move capability
var enemyColor = king.pieceColor === 'white' ? 'black' : 'white';
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceType === 'snake' && piece.pieceColor === enemyColor && !piece.snakeOnCooldown) {
// Check if snake can reach king in 1-3 moves
if (canSnakeReachPosition(piece, kingRow, kingCol)) {
return true;
}
}
}
return false;
}
function canSnakeReachPosition(snake, targetRow, targetCol) {
// Check if snake can reach target position within its 3-move sequence
// This simulates the snake's complex movement pattern
var visited = {};
function canReachRecursive(currentRow, currentCol, movesLeft, moveType) {
if (movesLeft === 0) return false;
if (currentRow === targetRow && currentCol === targetCol) return true;
var key = currentRow + ',' + currentCol + ',' + movesLeft + ',' + moveType;
if (visited[key]) return false;
visited[key] = true;
// Generate possible moves based on snake move type
var possibleMoves = [];
if (moveType === 0) {
// First move: straight lines only
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
// Must be straight line movement
if (dr === 0 || dc === 0 || Math.abs(dr) === Math.abs(dc)) {
var newRow = currentRow + dr;
var newCol = currentCol + dc;
if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 16) {
var piece = getPieceAt(newRow, newCol);
if (!piece || piece.pieceColor !== snake.pieceColor) {
possibleMoves.push({
row: newRow,
col: newCol,
nextMoveType: 1
});
}
}
}
}
}
} else if (moveType === 1) {
// Second move: diagonal only
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (Math.abs(dr) === Math.abs(dc) && Math.abs(dr) === 1) {
var newRow = currentRow + dr;
var newCol = currentCol + dc;
if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 16) {
var piece = getPieceAt(newRow, newCol);
if (!piece || piece.pieceColor !== snake.pieceColor) {
possibleMoves.push({
row: newRow,
col: newCol,
nextMoveType: 2
});
}
}
}
}
}
} else if (moveType === 2) {
// Third move: any direction
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
var newRow = currentRow + dr;
var newCol = currentCol + dc;
if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 16) {
var piece = getPieceAt(newRow, newCol);
if (!piece || piece.pieceColor !== snake.pieceColor) {
possibleMoves.push({
row: newRow,
col: newCol,
nextMoveType: 3
});
}
}
}
}
}
// Try each possible move
for (var j = 0; j < possibleMoves.length; j++) {
var move = possibleMoves[j];
if (canReachRecursive(move.row, move.col, movesLeft - 1, move.nextMoveType)) {
return true;
}
}
return false;
}
// Start with snake's current position and first move type
return canReachRecursive(snake.row, snake.col, snake.maxSnakeMoves, 0);
}
function evaluateMovePerfectly(move, depth) {
// Perfect evaluation with grandmaster-level understanding
var score = 0;
var piece = move.piece;
var targetPiece = getPieceAt(move.toRow, move.toCol);
// 1. Checkmate detection (perfect priority)
if (simulateMove(move, function () {
return isCheckmate('white');
})) {
return 50000; // Mate is always best
}
// 2. Prevent our own checkmate (critical)
var opponentThreats = getAllValidMoves('white');
var preventsCheckmate = false;
for (var i = 0; i < opponentThreats.length; i++) {
if (simulateMove(move, function () {
return !simulateOpponentMove(opponentThreats[i], function () {
return isCheckmate('black');
});
})) {
preventsCheckmate = true;
break;
}
}
if (preventsCheckmate) score += 10000;
// 3. Perfect material evaluation
if (targetPiece && targetPiece.pieceColor === 'white') {
var captureValue = getPieceValue(targetPiece.pieceType);
var exchangeValue = calculatePerfectExchange(move, depth);
score += exchangeValue * 100;
// Grandmaster piece priorities
if (targetPiece.pieceType === 'queen') score += 900;
if (targetPiece.pieceType === 'princess') score += 800;
if (targetPiece.pieceType === 'snake') score += 600;
if (targetPiece.pieceType === 'rook') score += 500;
}
// 4. Perfect tactical pattern recognition
score += evaluatePerfectTactics(move, depth);
// 5. Strategic piece coordination
score += evaluatePerfectStrategy(move);
// 6. King attack precision
score += evaluatePerfectKingAttack(move);
// 7. Basic positional evaluation
score += evaluatePositionalFactors(move);
return score;
}
function calculatePerfectCoordination(move) {
// Calculate how well pieces coordinate after this move
var coordinationScore = 0;
return simulateMove(move, function () {
// Count pieces that support each other
var supportingPairs = 0;
var totalSupport = 0;
for (var i = 0; i < pieces.length; i++) {
var piece1 = pieces[i];
if (piece1.pieceColor === 'black') {
var supportCount = 0;
for (var j = 0; j < pieces.length; j++) {
var piece2 = pieces[j];
if (piece2.pieceColor === 'black' && piece1 !== piece2) {
// Check if piece2 defends piece1
if (piece2.canMoveTo(piece1.row, piece1.col)) {
supportCount++;
totalSupport++;
}
}
}
if (supportCount > 0) {
supportingPairs++;
}
}
}
// Calculate coordination bonuses
coordinationScore += supportingPairs * 3; // Bonus for each supported piece
coordinationScore += totalSupport * 2; // Bonus for total defensive connections
// Special coordination patterns
// Queen and rook coordination
var blackQueens = pieces.filter(function (p) {
return p.pieceColor === 'black' && p.pieceType === 'queen';
});
var blackRooks = pieces.filter(function (p) {
return p.pieceColor === 'black' && p.pieceType === 'rook';
});
for (var i = 0; i < blackQueens.length; i++) {
for (var j = 0; j < blackRooks.length; j++) {
var rowDiff = Math.abs(blackQueens[i].row - blackRooks[j].row);
var colDiff = Math.abs(blackQueens[i].col - blackRooks[j].col);
if (rowDiff === 0 || colDiff === 0) {
coordinationScore += 5; // Same rank/file coordination
}
}
}
// Bishop pair coordination
var blackBishops = pieces.filter(function (p) {
return p.pieceColor === 'black' && p.pieceType === 'bishop';
});
if (blackBishops.length >= 2) {
coordinationScore += 8; // Bishop pair bonus
}
return coordinationScore;
});
}
function calculatePerfectExchange(move, depth) {
// Calculate exact exchange value with perfect accuracy
var ourPiece = move.piece;
var enemyPiece = getPieceAt(move.toRow, move.toCol);
if (!enemyPiece) return 0;
var exchangeSequence = [];
var currentPos = {
row: move.toRow,
col: move.toCol
};
// Calculate all pieces that can recapture
var recapturers = [];
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'white' && piece.canMoveTo(currentPos.row, currentPos.col)) {
recapturers.push({
piece: piece,
value: getPieceValue(piece.pieceType)
});
}
}
// Sort by piece value (sacrifice lowest first)
recapturers.sort(function (a, b) {
return a.value - b.value;
});
var balance = getPieceValue(enemyPiece.pieceType);
var attackerValue = getPieceValue(ourPiece.pieceType);
// Perfect exchange calculation
for (var i = 0; i < Math.min(recapturers.length, depth); i++) {
if (i % 2 === 0) {
balance -= attackerValue;
} else {
balance += recapturers[Math.floor(i / 2)].value;
}
attackerValue = i < recapturers.length ? recapturers[i].value : 0;
}
return balance;
}
function evaluatePerfectTactics(move, depth) {
var score = 0;
// Perfect fork detection
var forkValue = calculatePerfectFork(move);
score += forkValue * 50;
// Perfect pin detection
if (createsPerfectPin(move)) score += 200;
// Perfect discovered attack
if (createsPerfectDiscovery(move)) score += 250;
// Perfect deflection
if (createsPerfectDeflection(move)) score += 180;
// Perfect skewer
if (createsPerfectSkewer(move)) score += 220;
return score;
}
function evaluatePerfectStrategy(move) {
var score = 0;
// Perfect piece coordination
var coordinationValue = calculatePerfectCoordination(move);
score += coordinationValue * 15;
// Basic piece activity
score += evaluatePositionalFactors(move) * 0.5;
// Basic tactical patterns
score += evaluateTacticalPatterns(move) * 0.8;
return score;
}
function evaluatePerfectKingAttack(move) {
var score = 0;
var enemyKing = findKing('white');
if (!enemyKing) return 0;
return simulateMove(move, function () {
var attackingPieces = 0;
var controlledSquares = 0;
// Count pieces attacking king area
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'black') {
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
var kingArea = {
row: enemyKing.row + dr,
col: enemyKing.col + dc
};
if (kingArea.row >= 0 && kingArea.row < 8 && kingArea.col >= 0 && kingArea.col < 16) {
if (piece.canMoveTo(kingArea.row, kingArea.col)) {
controlledSquares++;
if (dr === 0 && dc === 0) attackingPieces++;
}
}
}
}
}
}
return attackingPieces * 30 + controlledSquares * 10;
});
}
function countThreatsToKing(color) {
var king = findKing(color);
if (!king) return 0;
var threats = 0;
var enemyColor = color === 'white' ? 'black' : 'white';
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === enemyColor && piece.canMoveTo(king.row, king.col)) {
threats++;
}
}
return threats;
}
function calculateExchangeValue(move) {
var attackerValue = getPieceValue(move.piece.pieceType);
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (!targetPiece) return 0;
var defenderValue = getPieceValue(targetPiece.pieceType);
// Simulate the capture and see if opponent can recapture
return simulateMove(move, function () {
var opponentMoves = getAllValidMoves('white');
var canRecapture = false;
var recaptureValue = 0;
for (var i = 0; i < opponentMoves.length; i++) {
var opMove = opponentMoves[i];
if (opMove.toRow === move.toRow && opMove.toCol === move.toCol) {
canRecapture = true;
recaptureValue = Math.min(recaptureValue || attackerValue, getPieceValue(opMove.piece.pieceType));
}
}
if (canRecapture) {
return defenderValue - recaptureValue;
} else {
return defenderValue;
}
});
}
function evaluateTacticalPatterns(move) {
var score = 0;
// Check gives reduced bonus (more defensive approach)
if (simulateMove(move, function () {
return isInCheck('white');
})) {
score += 15; // Reduced from 30
}
// Fork pattern - attacking multiple pieces (less aggressive)
var forkTargets = 0;
simulateMove(move, function () {
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'white' && move.piece.canMoveTo(piece.row, piece.col)) {
forkTargets++;
}
}
return false;
});
if (forkTargets >= 2) {
score += 20; // Reduced from 40
} else if (forkTargets >= 1) {
score += 5; // Reduced from 10
}
// Pin detection (reduced)
if (createsPinPattern(move)) {
score += 12; // Reduced from 25
}
// Discovered attack (reduced)
if (createsDiscoveredAttack(move)) {
score += 17; // Reduced from 35
}
return score;
}
function evaluatePieceSpecificMove(move) {
var score = 0;
var piece = move.piece;
switch (piece.pieceType) {
case 'snake':
// Snake special abilities
score += evaluateSnakeMove(move);
break;
case 'princess':
// Princess elimination potential
score += evaluatePrincessMove(move);
break;
case 'summoner':
// Summoner positioning and summoning potential
score += evaluateSummonerMove(move);
break;
case 'queen':
// Queen positioning and power
score += evaluateQueenMove(move);
break;
case 'knight':
// Knight outpost evaluation
score += evaluateKnightMove(move);
break;
case 'rook':
// Rook file and rank control
score += evaluateRookMove(move);
break;
case 'bishop':
// Bishop diagonal control
score += evaluateBishopMove(move);
break;
case 'pawn':
// Pawn advancement and promotion
score += evaluatePawnMove(move);
break;
}
return score;
}
function evaluateSnakeMove(move) {
var score = 0;
// Reduced poison aggression - only target when safe
var poisonTargets = 0;
simulateMove(move, function () {
var poisonPositions = [{
row: move.toRow - 1,
col: move.toCol - 1
}, {
row: move.toRow - 1,
col: move.toCol
}, {
row: move.toRow - 1,
col: move.toCol + 1
}, {
row: move.toRow,
col: move.toCol - 1
}, {
row: move.toRow,
col: move.toCol + 1
}, {
row: move.toRow + 1,
col: move.toCol - 1
}, {
row: move.toRow + 1,
col: move.toCol
}, {
row: move.toRow + 1,
col: move.toCol + 1
}];
for (var i = 0; i < poisonPositions.length; i++) {
var pos = poisonPositions[i];
if (pos.row >= 0 && pos.row < 8 && pos.col >= 0 && pos.col < 16) {
var targetPiece = getPieceAt(pos.row, pos.col);
if (targetPiece && targetPiece.pieceColor === 'white' && targetPiece.pieceType !== 'king' && targetPiece.pieceType !== 'queen') {
poisonTargets++;
score += getPieceValue(targetPiece.pieceType) * 2; // Reduced from 5
}
}
}
return false;
});
// Reduced multi-move tactical advantage
if (move.piece.snakeMovesThisTurn === 0) {
score += 8; // Reduced from 15
}
// Favor defensive positioning
var distanceFromOwnKing = Math.abs(move.toRow - findKing('black').row) + Math.abs(move.toCol - findKing('black').col);
if (distanceFromOwnKing <= 4) {
score += 10; // Bonus for staying near own king
}
return score;
}
function evaluateSummonerMove(move) {
var score = 0;
// Bonus for positioning summoner to summon in useful positions
if (move.piece.summonerMoveCount >= 2) {
// About to summon, evaluate summoning positions
var summonPositions = [{
row: move.toRow - 1,
col: move.toCol
},
// up
{
row: move.toRow,
col: move.toCol + 1
},
// right
{
row: move.toRow + 1,
col: move.toCol
},
// down
{
row: move.toRow,
col: move.toCol - 1
},
// left
{
row: move.toRow - 1,
col: move.toCol + 1
},
// up-right
{
row: move.toRow + 1,
col: move.toCol + 1
},
// down-right
{
row: move.toRow + 1,
col: move.toCol - 1
},
// down-left
{
row: move.toRow - 1,
col: move.toCol - 1
} // up-left
];
var goodSummonSpots = 0;
for (var i = 0; i < summonPositions.length && goodSummonSpots < 2; i++) {
var pos = summonPositions[i];
if (pos.row >= 0 && pos.row < 8 && pos.col >= 0 && pos.col < 16) {
if (!getPieceAt(pos.row, pos.col)) {
goodSummonSpots++;
// Bonus for summoning in aggressive positions
if (move.piece.pieceColor === 'black' && pos.row >= 4 || move.piece.pieceColor === 'white' && pos.row <= 3) {
score += 15;
} else {
score += 8;
}
}
}
}
if (goodSummonSpots >= 2) {
score += 25; // Bonus for full summoning potential
}
}
// Strategic positioning like princess
var centralControl = 0;
var centerSquares = [{
row: 3,
col: 7
}, {
row: 3,
col: 8
}, {
row: 4,
col: 7
}, {
row: 4,
col: 8
}];
for (var i = 0; i < centerSquares.length; i++) {
var square = centerSquares[i];
if (Math.abs(move.toRow - square.row) <= 2 && Math.abs(move.toCol - square.col) <= 2) {
centralControl++;
}
}
score += centralControl * 3;
return score;
}
;
function evaluatePrincessMove(move) {
var score = 0;
// Evaluate elimination potential within 2 squares
var enemiesInRange = [];
for (var dr = -2; dr <= 2; dr++) {
for (var dc = -2; dc <= 2; dc++) {
if (dr === 0 && dc === 0) continue;
var checkRow = move.toRow + dr;
var checkCol = move.toCol + dc;
if (checkRow >= 0 && checkRow < 8 && checkCol >= 0 && checkCol < 16) {
var piece = getPieceAt(checkRow, checkCol);
if (piece && piece.pieceColor === 'white') {
enemiesInRange.push(piece);
score += getPieceValue(piece.pieceType) * 2;
}
}
}
}
// Bonus for positioning where elimination can capture 2+ high-value pieces
if (enemiesInRange.length >= 2) {
// Sort enemies by value and consider best 2
enemiesInRange.sort(function (a, b) {
return getPieceValue(b.pieceType) - getPieceValue(a.pieceType);
});
var topTwoValue = getPieceValue(enemiesInRange[0].pieceType) + getPieceValue(enemiesInRange[1].pieceType);
score += topTwoValue * 5; // High bonus for double elimination potential
}
return score;
}
function evaluateQueenMove(move) {
var score = 0;
// Queens should control center and support attacks
var centralControl = 0;
var centerSquares = [{
row: 3,
col: 7
}, {
row: 3,
col: 8
}, {
row: 4,
col: 7
}, {
row: 4,
col: 8
}];
simulateMove(move, function () {
for (var i = 0; i < centerSquares.length; i++) {
var square = centerSquares[i];
if (move.piece.canMoveTo(square.row, square.col)) {
centralControl++;
}
}
return false;
});
score += centralControl * 5;
// Bonus for long-range queen moves
var rowDiff = Math.abs(move.toRow - move.piece.row);
var colDiff = Math.abs(move.toCol - move.piece.col);
var moveDistance = Math.max(rowDiff, colDiff);
if (moveDistance > 2) {
score += 5; // Bonus for using queen's long-range capability
}
// Support other pieces
var supportedAllies = countSupportedAllies(move);
score += supportedAllies * 8;
return score;
}
function evaluatePositionalFactors(move) {
var score = 0;
// Center control
var centerDistance = Math.abs(move.toRow - 3.5) + Math.abs(move.toCol - 7.5);
score += (16 - centerDistance) * 2;
// Piece mobility after move
var mobilityAfter = simulateMove(move, function () {
var validMovesAfter = 0;
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
if (move.piece.canMoveTo(row, col)) {
validMovesAfter++;
}
}
}
return validMovesAfter;
});
score += mobilityAfter * 1;
// Safety evaluation
if (isPieceUnderThreatAt(move.piece, move.toRow, move.toCol)) {
score -= getPieceValue(move.piece.pieceType) * 3;
}
return score;
}
function evaluateKingAttack(move) {
var score = 0;
var whiteKing = findKing('white');
if (!whiteKing) return 0;
// Reduced emphasis on approaching enemy king
var distanceToKing = Math.abs(move.toRow - whiteKing.row) + Math.abs(move.toCol - whiteKing.col);
var currentDistance = Math.abs(move.piece.row - whiteKing.row) + Math.abs(move.piece.col - whiteKing.col);
if (distanceToKing < currentDistance) {
score += (currentDistance - distanceToKing) * 1; // Reduced from 3
}
// Reduced control emphasis around enemy king
var controlledKingSquares = 0;
simulateMove(move, function () {
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
var kingAdjacentRow = whiteKing.row + dr;
var kingAdjacentCol = whiteKing.col + dc;
if (kingAdjacentRow >= 0 && kingAdjacentRow < 8 && kingAdjacentCol >= 0 && kingAdjacentCol < 16) {
if (move.piece.canMoveTo(kingAdjacentRow, kingAdjacentCol)) {
controlledKingSquares++;
}
}
}
}
return false;
});
score += controlledKingSquares * 3; // Reduced from 8
return score;
}
function evaluatePieceCoordination(move) {
var score = 0;
// Count how many allied pieces this move supports
var supportedAllies = countSupportedAllies(move);
score += supportedAllies * 5;
// Count how many allied pieces support this piece at new position
var supportingAllies = countSupportingAllies(move);
score += supportingAllies * 3;
return score;
}
function countSupportedAllies(move) {
var supported = 0;
return simulateMove(move, function () {
for (var i = 0; i < pieces.length; i++) {
var ally = pieces[i];
if (ally.pieceColor === 'black' && ally !== move.piece) {
if (move.piece.canMoveTo(ally.row, ally.col)) {
supported++;
}
}
}
return supported;
});
}
function countSupportingAllies(move) {
var supporting = 0;
return simulateMove(move, function () {
for (var i = 0; i < pieces.length; i++) {
var ally = pieces[i];
if (ally.pieceColor === 'black' && ally !== move.piece) {
if (ally.canMoveTo(move.toRow, move.toCol)) {
supporting++;
}
}
}
return supporting;
});
}
function createsPinPattern(move) {
var whiteKing = findKing('white');
if (!whiteKing) return false;
return simulateMove(move, function () {
// Check if this move creates a pin on enemy pieces
for (var i = 0; i < pieces.length; i++) {
var enemyPiece = pieces[i];
if (enemyPiece.pieceColor === 'white' && enemyPiece !== whiteKing) {
// Temporarily remove the piece and see if king becomes attackable
var originalRow = enemyPiece.row;
var originalCol = enemyPiece.col;
gameBoard[originalRow][originalCol] = null;
var pinExists = move.piece.canMoveTo(whiteKing.row, whiteKing.col);
gameBoard[originalRow][originalCol] = enemyPiece;
if (pinExists) {
return true;
}
}
}
return false;
});
}
function createsDiscoveredAttack(move) {
// Check if moving this piece uncovers an attack from another piece
var originalRow = move.piece.row;
var originalCol = move.piece.col;
// Temporarily move piece and check if any ally can now attack
return simulateMove(move, function () {
for (var i = 0; i < pieces.length; i++) {
var ally = pieces[i];
if (ally.pieceColor === 'black' && ally !== move.piece) {
for (var j = 0; j < pieces.length; j++) {
var enemy = pieces[j];
if (enemy.pieceColor === 'white') {
// Check if ally can now attack enemy after piece moved
var couldAttackBefore = false;
// Temporarily put piece back at original position
gameBoard[move.toRow][move.toCol] = null;
gameBoard[originalRow][originalCol] = move.piece;
move.piece.row = originalRow;
move.piece.col = originalCol;
couldAttackBefore = ally.canMoveTo(enemy.row, enemy.col);
// Put piece back at new position
gameBoard[originalRow][originalCol] = null;
gameBoard[move.toRow][move.toCol] = move.piece;
move.piece.row = move.toRow;
move.piece.col = move.toCol;
var canAttackAfter = ally.canMoveTo(enemy.row, enemy.col);
if (!couldAttackBefore && canAttackAfter) {
return true;
}
}
}
}
}
return false;
});
}
function findBestPrincessElimination() {
// Find AI princess pieces that can use elimination ability
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceType === 'princess' && piece.pieceColor === 'black') {
// Find enemies within 2 squares of princess
var targetsInRange = [];
for (var dr = -2; dr <= 2; dr++) {
for (var dc = -2; dc <= 2; dc++) {
if (dr === 0 && dc === 0) continue;
var checkRow = piece.row + dr;
var checkCol = piece.col + dc;
if (checkRow >= 0 && checkRow < 8 && checkCol >= 0 && checkCol < 16) {
var targetPiece = getPieceAt(checkRow, checkCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
targetsInRange.push(targetPiece);
}
}
}
}
// If we can eliminate 2+ pieces, prioritize highest value targets
if (targetsInRange.length >= 2) {
// Sort by piece value, highest first
targetsInRange.sort(function (a, b) {
return getPieceValue(b.pieceType) - getPieceValue(a.pieceType);
});
// Check if the top 2 targets are worth eliminating
var topTwoValue = getPieceValue(targetsInRange[0].pieceType) + getPieceValue(targetsInRange[1].pieceType);
if (topTwoValue >= 6) {
// Worth at least 6 points total
// Execute princess elimination
LK.setTimeout(function () {
// Remove the two highest value targets
for (var j = 0; j < 2; j++) {
var target = targetsInRange[j];
for (var k = 0; k < pieces.length; k++) {
if (pieces[k] === target) {
gameBoard[target.row][target.col] = null;
target.destroy();
pieces.splice(k, 1);
break;
}
}
}
LK.getSound('capture').play();
turnText.setText('Black Princess eliminates 2 pieces!');
// Switch turns
currentPlayer = 'white';
LK.setTimeout(function () {
turnText.setText('Your Turn');
}, 1000);
}, 800);
return {
piece: piece,
isElimination: true
}; // Special marker for elimination
}
}
}
}
return null;
}
function findBestDefensiveMove(validMoves) {
var bestDefense = null;
var bestScore = -1000;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var score = 0;
// Check if this move gets us out of check
if (simulateMove(move, function () {
return !isInCheck('black');
})) {
score += 100;
// Prefer counter-attacks when escaping check
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
score += getPieceValue(targetPiece.pieceType) * 10;
}
if (score > bestScore) {
bestScore = score;
bestDefense = move;
}
}
}
return bestDefense;
}
// Enhanced piece-specific evaluation functions
function evaluateKnightMove(move) {
var score = 0;
// Knights are effective in closed positions and as outposts
var distanceFromCenter = Math.abs(move.toRow - 3.5) + Math.abs(move.toCol - 7.5);
if (distanceFromCenter < 4) {
score += 10; // Favor central knight positions
}
return score;
}
function evaluateRookMove(move) {
var score = 0;
// Rooks benefit from open files and ranks
var openFile = true;
var openRank = true;
// Check if file is open
for (var row = 0; row < 8; row++) {
if (row !== move.toRow) {
var piece = getPieceAt(row, move.toCol);
if (piece && piece.pieceColor === move.piece.pieceColor) {
openFile = false;
break;
}
}
}
// Check if rank is open
for (var col = 0; col < 16; col++) {
if (col !== move.toCol) {
var piece = getPieceAt(move.toRow, col);
if (piece && piece.pieceColor === move.piece.pieceColor) {
openRank = false;
break;
}
}
}
if (openFile) score += 15;
if (openRank) score += 10;
return score;
}
function evaluateBishopMove(move) {
var score = 0;
// Bishops benefit from long diagonals
var diagonalLength = Math.min(Math.min(move.toRow, 7 - move.toRow) + Math.min(move.toCol, 15 - move.toCol), Math.min(move.toRow, 7 - move.toRow) + Math.min(15 - move.toCol, move.toCol));
score += diagonalLength * 2;
return score;
}
function evaluatePawnMove(move) {
var score = 0;
// Pawns should advance toward promotion
var advancement = move.piece.pieceColor === 'black' ? move.toRow - move.piece.row : move.piece.row - move.toRow;
if (advancement > 0) {
score += advancement * 5;
}
// Near promotion gets huge bonus
if (move.piece.pieceColor === 'black' && move.toRow >= 6 || move.piece.pieceColor === 'white' && move.toRow <= 1) {
score += 50;
}
return score;
}
function findCheckmateInOne(validMoves) {
// Find immediate checkmate moves - highest priority
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (simulateMove(move, function () {
if (isInCheck('white')) {
// Check if white has any escape moves
var whiteMoves = getAllValidMoves('white');
for (var j = 0; j < whiteMoves.length; j++) {
if (canEscapeCheck(whiteMoves[j].piece, whiteMoves[j].toRow, whiteMoves[j].toCol)) {
return false; // Not checkmate - escape exists
}
}
return true; // Checkmate confirmed
}
return false;
})) {
return move; // Found checkmate in one
}
}
return null;
}
function preventOpponentCheckmate(validMoves) {
// Prevent opponent from achieving checkmate next move
var opponentThreats = getAllValidMoves('white');
for (var i = 0; i < opponentThreats.length; i++) {
var threatMove = opponentThreats[i];
// Check if opponent threat leads to our checkmate
var wouldBeCheckmate = simulateOpponentMove(threatMove, function () {
if (isInCheck('black')) {
var blackMoves = getAllValidMoves('black');
for (var j = 0; j < blackMoves.length; j++) {
if (canEscapeCheck(blackMoves[j].piece, blackMoves[j].toRow, blackMoves[j].toCol)) {
return false; // We can escape
}
}
return true; // We would be checkmated
}
return false;
});
if (wouldBeCheckmate) {
// Find move that prevents this checkmate
for (var k = 0; k < validMoves.length; k++) {
var preventMove = validMoves[k];
if (simulateMove(preventMove, function () {
return !simulateOpponentMove(threatMove, function () {
return isInCheck('black');
});
})) {
return preventMove; // This move prevents checkmate
}
}
}
}
return null;
}
function simulateOpponentMove(opponentMove, testFunction) {
// Temporarily simulate opponent's move
var originalRow = opponentMove.piece.row;
var originalCol = opponentMove.piece.col;
var capturedPiece = getPieceAt(opponentMove.toRow, opponentMove.toCol);
gameBoard[originalRow][originalCol] = null;
gameBoard[opponentMove.toRow][opponentMove.toCol] = opponentMove.piece;
opponentMove.piece.row = opponentMove.toRow;
opponentMove.piece.col = opponentMove.toCol;
var result = testFunction();
// Restore position
opponentMove.piece.row = originalRow;
opponentMove.piece.col = originalCol;
gameBoard[originalRow][originalCol] = opponentMove.piece;
gameBoard[opponentMove.toRow][opponentMove.toCol] = capturedPiece;
return result;
}
function findBestTacticalCombination(validMoves) {
// Find complex tactical sequences - pins, forks, discoveries, sacrifices
var bestTactical = null;
var bestTacticalValue = 0;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var tacticalValue = 0;
// Check for forcing sequences
if (isMoveForcingSequence(move)) {
tacticalValue += 50;
}
// Check for piece sacrifice that leads to mate
if (isBrilliantSacrifice(move)) {
tacticalValue += 100;
}
// Check for advanced pin patterns
if (createsAdvancedPin(move)) {
tacticalValue += 40;
}
// Check for discovered attack combinations
if (createsDiscoveredCombination(move)) {
tacticalValue += 60;
}
// Check for deflection tactics
if (createsDeflection(move)) {
tacticalValue += 35;
}
if (tacticalValue > bestTacticalValue) {
bestTacticalValue = tacticalValue;
bestTactical = move;
}
}
return bestTacticalValue >= 30 ? bestTactical : null;
}
function isMoveForcingSequence(move) {
// Check if move forces opponent into bad position
return simulateMove(move, function () {
var opponentMoves = getAllValidMoves('white');
var forcedBadMoves = 0;
for (var i = 0; i < opponentMoves.length; i++) {
var opMove = opponentMoves[i];
if (simulateOpponentMove(opMove, function () {
return isInCheck('white') || isPieceUnderThreat(opMove.piece);
})) {
forcedBadMoves++;
}
}
return forcedBadMoves >= opponentMoves.length * 0.7; // 70% of moves are bad
});
}
function isBrilliantSacrifice(move) {
// Check for brilliant sacrifices that lead to winning positions
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (!targetPiece) return false;
var sacrificeValue = getPieceValue(move.piece.pieceType);
var gainValue = getPieceValue(targetPiece.pieceType);
// Only consider if we're sacrificing material
if (sacrificeValue <= gainValue) return false;
return simulateMove(move, function () {
// Check if sacrifice leads to overwhelming position or mate threat
var ourAdvantageAfter = calculatePositionalAdvantage('black');
var opponentMoves = getAllValidMoves('white');
// If opponent has very limited good responses, sacrifice is brilliant
var goodOpponentMoves = 0;
for (var i = 0; i < opponentMoves.length; i++) {
if (!isPieceUnderThreatAt(opponentMoves[i].piece, opponentMoves[i].toRow, opponentMoves[i].toCol)) {
goodOpponentMoves++;
}
}
return ourAdvantageAfter > 30 || goodOpponentMoves <= 2;
});
}
function calculatePositionalAdvantage(color) {
var advantage = 0;
var ourColor = color;
var enemyColor = color === 'white' ? 'black' : 'white';
// Material count
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
var value = getPieceValue(piece.pieceType);
if (piece.pieceColor === ourColor) {
advantage += value;
} else {
advantage -= value;
}
}
// Positional factors
var ourKing = findKing(ourColor);
var enemyKing = findKing(enemyColor);
if (ourKing && enemyKing) {
// King safety difference
advantage += (countThreatsToKing(enemyColor) - countThreatsToKing(ourColor)) * 5;
// Center control
var ourCenterControl = 0;
var enemyCenterControl = 0;
var centerSquares = [{
row: 3,
col: 7
}, {
row: 3,
col: 8
}, {
row: 4,
col: 7
}, {
row: 4,
col: 8
}];
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
for (var j = 0; j < centerSquares.length; j++) {
if (piece.canMoveTo(centerSquares[j].row, centerSquares[j].col)) {
if (piece.pieceColor === ourColor) {
ourCenterControl++;
} else {
enemyCenterControl++;
}
}
}
}
advantage += (ourCenterControl - enemyCenterControl) * 2;
}
return advantage;
}
function calculatePerfectFork(move) {
// Calculate exact value of fork attacks
var forkTargets = [];
return simulateMove(move, function () {
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'white' && move.piece.canMoveTo(piece.row, piece.col)) {
forkTargets.push(getPieceValue(piece.pieceType));
}
}
// Return value of second-best target (since we can only capture one)
if (forkTargets.length >= 2) {
forkTargets.sort(function (a, b) {
return b - a;
});
return forkTargets[1]; // Second highest value
}
return 0;
});
}
function createsPerfectPin(move) {
var enemyKing = findKing('white');
if (!enemyKing) return false;
return simulateMove(move, function () {
// Check if move creates pin against enemy king
var direction = {
row: enemyKing.row - move.toRow,
col: enemyKing.col - move.toCol
};
// Normalize direction
var gcd = Math.abs(direction.row) || Math.abs(direction.col);
if (gcd > 0) {
direction.row = direction.row / gcd;
direction.col = direction.col / gcd;
}
// Check if there's exactly one enemy piece between attacker and king
var currentRow = move.toRow + direction.row;
var currentCol = move.toCol + direction.col;
var blockerFound = false;
while (currentRow !== enemyKing.row || currentCol !== enemyKing.col) {
var piece = getPieceAt(currentRow, currentCol);
if (piece) {
if (piece.pieceColor === 'white' && !blockerFound) {
blockerFound = true; // First blocker found
} else {
return false; // Multiple pieces or our piece blocking
}
}
currentRow += direction.row;
currentCol += direction.col;
if (currentRow < 0 || currentRow >= 8 || currentCol < 0 || currentCol >= 16) break;
}
return blockerFound;
});
}
function createsPerfectDiscovery(move) {
// Check for discovered attacks with perfect accuracy
var originalRow = move.piece.row;
var originalCol = move.piece.col;
return simulateMove(move, function () {
// Check if moving this piece discovers an attack from another piece
for (var i = 0; i < pieces.length; i++) {
var ally = pieces[i];
if (ally.pieceColor === 'black' && ally !== move.piece) {
// Temporarily put moving piece back
gameBoard[move.toRow][move.toCol] = null;
gameBoard[originalRow][originalCol] = move.piece;
move.piece.row = originalRow;
move.piece.col = originalCol;
var couldAttackBefore = false;
for (var j = 0; j < pieces.length; j++) {
var enemy = pieces[j];
if (enemy.pieceColor === 'white' && ally.canMoveTo(enemy.row, enemy.col)) {
couldAttackBefore = true;
break;
}
}
// Restore position
gameBoard[originalRow][originalCol] = null;
gameBoard[move.toRow][move.toCol] = move.piece;
move.piece.row = move.toRow;
move.piece.col = move.toCol;
if (!couldAttackBefore) {
// Check if ally can attack enemy pieces now
for (var j = 0; j < pieces.length; j++) {
var enemy = pieces[j];
if (enemy.pieceColor === 'white' && ally.canMoveTo(enemy.row, enemy.col)) {
return true; // Discovered attack found
}
}
}
}
}
return false;
});
}
function createsPerfectDeflection(move) {
// Check if move deflects a key defender
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (!targetPiece || targetPiece.pieceColor !== 'white') return false;
return simulateMove(move, function () {
// Check if capturing this piece leaves important pieces undefended
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'white' && piece !== targetPiece) {
var wasDefended = targetPiece.canMoveTo(piece.row, piece.col);
if (wasDefended) {
// Check if piece is now undefended and can be captured
var isNowUndefended = true;
for (var j = 0; j < pieces.length; j++) {
var defender = pieces[j];
if (defender.pieceColor === 'white' && defender !== targetPiece && defender.canMoveTo(piece.row, piece.col)) {
isNowUndefended = false;
break;
}
}
if (isNowUndefended) {
return true; // Successful deflection
}
}
}
}
return false;
});
}
function createsDeflection(move) {
// Check if move deflects a key defender piece
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (!targetPiece || targetPiece.pieceColor !== 'black') return false;
return simulateMove(move, function () {
// Check if capturing this piece leaves important pieces undefended
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'black' && piece !== targetPiece) {
var wasDefended = targetPiece.canMoveTo(piece.row, piece.col);
if (wasDefended) {
// Check if piece is now undefended and can be captured
var isNowUndefended = true;
for (var j = 0; j < pieces.length; j++) {
var defender = pieces[j];
if (defender.pieceColor === 'black' && defender !== targetPiece && defender.canMoveTo(piece.row, piece.col)) {
isNowUndefended = false;
break;
}
}
if (isNowUndefended) {
return true; // Successful deflection
}
}
}
}
return false;
});
}
function createsDiscoveredCombination(move) {
// Check for discovered attack combinations - complex tactical patterns
var originalRow = move.piece.row;
var originalCol = move.piece.col;
return simulateMove(move, function () {
// Check if moving this piece creates multiple discovered attacks
var discoveredAttacks = 0;
var highValueTargets = 0;
// Check each ally piece to see if it gains new attacking opportunities
for (var i = 0; i < pieces.length; i++) {
var ally = pieces[i];
if (ally.pieceColor === 'black' && ally !== move.piece) {
// Temporarily put moving piece back to check what was blocked before
gameBoard[move.toRow][move.toCol] = null;
gameBoard[originalRow][originalCol] = move.piece;
move.piece.row = originalRow;
move.piece.col = originalCol;
var enemyTargetsBefore = [];
for (var j = 0; j < pieces.length; j++) {
var enemy = pieces[j];
if (enemy.pieceColor === 'white' && ally.canMoveTo(enemy.row, enemy.col)) {
enemyTargetsBefore.push(enemy);
}
}
// Restore position after move
gameBoard[originalRow][originalCol] = null;
gameBoard[move.toRow][move.toCol] = move.piece;
move.piece.row = move.toRow;
move.piece.col = move.toCol;
var enemyTargetsAfter = [];
for (var j = 0; j < pieces.length; j++) {
var enemy = pieces[j];
if (enemy.pieceColor === 'white' && ally.canMoveTo(enemy.row, enemy.col)) {
enemyTargetsAfter.push(enemy);
}
}
// Check for new attacks that weren't possible before
for (var k = 0; k < enemyTargetsAfter.length; k++) {
var newTarget = enemyTargetsAfter[k];
var wasTargetedBefore = false;
for (var l = 0; l < enemyTargetsBefore.length; l++) {
if (enemyTargetsBefore[l] === newTarget) {
wasTargetedBefore = true;
break;
}
}
if (!wasTargetedBefore) {
discoveredAttacks++;
var targetValue = getPieceValue(newTarget.pieceType);
if (targetValue >= 5) {
// Queen, rook, or higher value pieces
highValueTargets++;
}
}
}
}
}
// Discovered combination is valuable if it creates multiple new attacks
// or targets high-value pieces
return discoveredAttacks >= 2 || highValueTargets >= 1;
});
}
function createsAdvancedPin(move) {
// Check for advanced pin patterns - more sophisticated than basic pins
var enemyKing = findKing('white');
if (!enemyKing) return false;
return simulateMove(move, function () {
// Check if move creates a pin that restricts enemy piece movement
for (var i = 0; i < pieces.length; i++) {
var enemyPiece = pieces[i];
if (enemyPiece.pieceColor === 'white' && enemyPiece.pieceType !== 'king') {
// Check if this enemy piece is now pinned to their king
var direction = {
row: enemyKing.row - enemyPiece.row,
col: enemyKing.col - enemyPiece.col
};
// Normalize direction
var maxDist = Math.max(Math.abs(direction.row), Math.abs(direction.col));
if (maxDist > 0) {
direction.row = Math.round(direction.row / maxDist);
direction.col = Math.round(direction.col / maxDist);
// Check if our piece can attack along this line through the pinned piece to the king
var canPinThroughPiece = false;
if (move.piece.canMoveTo(enemyKing.row, enemyKing.col)) {
// Temporarily remove the potentially pinned piece
var originalRow = enemyPiece.row;
var originalCol = enemyPiece.col;
gameBoard[originalRow][originalCol] = null;
// Check if our piece can still attack the king
if (move.piece.canMoveTo(enemyKing.row, enemyKing.col)) {
canPinThroughPiece = true;
}
// Restore the piece
gameBoard[originalRow][originalCol] = enemyPiece;
}
if (canPinThroughPiece) {
// Verify the pin is along a straight line (rook/queen) or diagonal (bishop/queen)
var rowDiff = Math.abs(move.toRow - enemyKing.row);
var colDiff = Math.abs(move.toCol - enemyKing.col);
var isStraightLine = rowDiff === 0 || colDiff === 0;
var isDiagonal = rowDiff === colDiff;
if (move.piece.pieceType === 'rook' && isStraightLine || move.piece.pieceType === 'bishop' && isDiagonal || move.piece.pieceType === 'quenn' && (isStraightLine || isDiagonal)) {
return true; // Advanced pin created
}
}
}
}
}
return false;
});
}
function createsPerfectSkewer(move) {
// Check for skewer patterns (attacking valuable piece in front of less valuable)
return simulateMove(move, function () {
for (var i = 0; i < pieces.length; i++) {
var frontPiece = pieces[i];
if (frontPiece.pieceColor === 'white' && move.piece.canMoveTo(frontPiece.row, frontPiece.col)) {
// Check if there's a less valuable piece behind this one
var direction = {
row: frontPiece.row - move.toRow,
col: frontPiece.col - move.toCol
};
var gcd = Math.max(Math.abs(direction.row), Math.abs(direction.col));
if (gcd > 0) {
direction.row = Math.round(direction.row / gcd);
direction.col = Math.round(direction.col / gcd);
var checkRow = frontPiece.row + direction.row;
var checkCol = frontPiece.col + direction.col;
while (checkRow >= 0 && checkRow < 8 && checkCol >= 0 && checkCol < 16) {
var behindPiece = getPieceAt(checkRow, checkCol);
if (behindPiece && behindPiece.pieceColor === 'white') {
var frontValue = getPieceValue(frontPiece.pieceType);
var behindValue = getPieceValue(behindPiece.pieceType);
if (frontValue > behindValue || frontPiece.pieceType === 'king') {
return true; // Successful skewer
}
break;
} else if (behindPiece) {
break; // Our piece blocking
}
checkRow += direction.row;
checkCol += direction.col;
}
}
}
}
return false;
});
}
function findOptimalDefensiveMove(validMoves) {
// Find the best defensive move with perfect calculation
var bestDefense = null;
var bestDefenseScore = -10000;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var defenseScore = 0;
// Check if move gets us out of check
if (simulateMove(move, function () {
return !isInCheck('black');
})) {
defenseScore += 1000;
// Bonus for counter-attacking while defending
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
defenseScore += getPieceValue(targetPiece.pieceType) * 50;
// Extra bonus for capturing the threatening piece
var blackKing = findKing('black');
var threateningPiece = findThreateningPiece(blackKing);
if (targetPiece === threateningPiece) {
defenseScore += 500; // Eliminate the threat directly
}
}
// Bonus for creating counter-threats
if (simulateMove(move, function () {
return isInCheck('white');
})) {
defenseScore += 200;
}
}
if (defenseScore > bestDefenseScore) {
bestDefenseScore = defenseScore;
bestDefense = move;
}
}
return bestDefense;
}
function getPieceFeatureDescription(piece) {
var descriptions = {
'pawn': 'Moves 1 square forward, captures diagonally. Can move 2 squares on first move.',
'rook': 'Moves any distance horizontally or vertically. Strong in open files.',
'knight': 'Moves in L-shape (2+1 squares). Can jump over other pieces.',
'bishop': 'Moves any distance diagonally. Controls long diagonals.',
'queen': 'Combines rook and bishop moves. Requires 1 stamina to move.',
'king': 'Moves 1 square in any direction. Has 4 lives. Can castle.',
'snake': 'Moves 3 times per turn: straight, diagonal, any direction. Poisons adjacent enemies. Requires 1 stamina for full sequence.',
'jester': 'Moves exactly 2 squares in any direction. Can jump over pieces to capture them.',
'princess': 'Moves 2 squares straight or 1 square for positioning. Double-click to eliminate 2 enemies within 2 squares.',
'summoner': 'Moves 1 square like a king. After 3 moves, summons 2 pawns in adjacent squares.'
};
return descriptions[piece.pieceType] || 'Unknown piece type.';
}
function showPieceTooltip(piece, x, y) {
if (hoverTooltip) {
hoverTooltip.destroy();
hoverTooltip = null;
}
var description = getPieceFeatureDescription(piece);
var pieceTitle = piece.pieceColor.charAt(0).toUpperCase() + piece.pieceColor.slice(1) + ' ' + piece.pieceType.charAt(0).toUpperCase() + piece.pieceType.slice(1);
// Break description into lines that fit within the box
var maxLineWidth = 35; // Characters per line
var words = description.split(' ');
var lines = [];
var currentLine = '';
for (var i = 0; i < words.length; i++) {
var word = words[i];
if ((currentLine + word).length <= maxLineWidth) {
currentLine += (currentLine ? ' ' : '') + word;
} else {
if (currentLine) {
lines.push(currentLine);
currentLine = word;
} else {
// Word is too long, break it
lines.push(word.substring(0, maxLineWidth));
currentLine = word.substring(maxLineWidth);
}
}
}
if (currentLine) {
lines.push(currentLine);
}
// Calculate box size based on content
var boxWidth = Math.max(600, titleText ? titleText.width + 40 : 600);
var boxHeight = Math.max(100, 80 + lines.length * 35);
// Create tooltip background with proper size
var tooltipBg = LK.getAsset('darkSquare', {
anchorX: 0,
anchorY: 0,
alpha: 0.9,
scaleX: boxWidth / 128,
scaleY: boxHeight / 128
});
// Create tooltip container
var tooltipContainer = new Container();
tooltipContainer.addChild(tooltipBg);
// Create title text
var titleText = new Text2(pieceTitle, {
size: 40,
fill: piece.pieceColor === 'white' ? 0xFFFFFF : 0xFF6666
});
titleText.anchor.set(0, 0);
titleText.x = 20;
titleText.y = 15;
tooltipContainer.addChild(titleText);
// Create description text lines
for (var i = 0; i < lines.length; i++) {
var lineText = new Text2(lines[i], {
size: 30,
fill: 0xFFFFFF
});
lineText.anchor.set(0, 0);
lineText.x = 20;
lineText.y = 60 + i * 35;
tooltipContainer.addChild(lineText);
}
// Position tooltip near piece but keep it on screen
var tooltipX = Math.min(x + 50, 2048 - boxWidth - 20); // Keep within screen bounds
var tooltipY = Math.max(y - boxHeight - 20, 50); // Keep within screen bounds
tooltipContainer.x = tooltipX;
tooltipContainer.y = tooltipY;
tooltipContainer.alpha = 0;
game.addChild(tooltipContainer);
hoverTooltip = tooltipContainer;
// Fade in animation
tween(hoverTooltip, {
alpha: 1.0
}, {
duration: 300,
easing: tween.easeOut
});
}
function hidePieceTooltip() {
if (hoverTooltip) {
tween(hoverTooltip, {
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (hoverTooltip && !hoverTooltip.destroyed) {
hoverTooltip.destroy();
}
hoverTooltip = null;
}
});
}
}
function updateQueenSnakePinkSquares() {
// Clear existing pink squares
for (var i = 0; i < queenSnakePinkSquares.length; i++) {
if (!queenSnakePinkSquares[i].destroyed) {
queenSnakePinkSquares[i].destroy();
}
}
queenSnakePinkSquares = [];
// Add pink squares for white stamina consumers when stamina is 0
if (staminaPoints === 0 && window.whiteStaminaConsumers) {
for (var i = 0; i < window.whiteStaminaConsumers.length; i++) {
var consumer = window.whiteStaminaConsumers[i];
// Check if piece still exists, if so use current position, otherwise use stored position
var pieceExists = false;
var currentRow = consumer.row;
var currentCol = consumer.col;
for (var j = 0; j < pieces.length; j++) {
if (pieces[j] === consumer.id) {
pieceExists = true;
currentRow = pieces[j].row;
currentCol = pieces[j].col;
break;
}
}
var pinkSquare = LK.getAsset('staminaBar', {
anchorX: 0,
anchorY: 0,
alpha: 0.4,
scaleX: 128 / 40,
// Scale to fit square size
scaleY: 128 / 40
});
pinkSquare.x = boardStartX + currentCol * squareSize;
pinkSquare.y = boardStartY + currentRow * squareSize;
game.addChild(pinkSquare);
queenSnakePinkSquares.push(pinkSquare);
// Animate pink square appearance
tween(pinkSquare, {
alpha: 0.6
}, {
duration: 300,
easing: tween.easeOut
});
}
}
// Add pink squares for AI stamina consumers when AI stamina is 0
if (aiStaminaPoints === 0 && window.blackStaminaConsumers) {
for (var i = 0; i < window.blackStaminaConsumers.length; i++) {
var consumer = window.blackStaminaConsumers[i];
// Check if piece still exists, if so use current position, otherwise use stored position
var pieceExists = false;
var currentRow = consumer.row;
var currentCol = consumer.col;
for (var j = 0; j < pieces.length; j++) {
if (pieces[j] === consumer.id) {
pieceExists = true;
currentRow = pieces[j].row;
currentCol = pieces[j].col;
break;
}
}
var pinkSquare = LK.getAsset('staminaBar', {
anchorX: 0,
anchorY: 0,
alpha: 0.4,
scaleX: 128 / 40,
// Scale to fit square size
scaleY: 128 / 40
});
pinkSquare.x = boardStartX + currentCol * squareSize;
pinkSquare.y = boardStartY + currentRow * squareSize;
game.addChild(pinkSquare);
queenSnakePinkSquares.push(pinkSquare);
// Animate pink square appearance
tween(pinkSquare, {
alpha: 0.6
}, {
duration: 300,
easing: tween.easeOut
});
}
}
} /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var ChessPiece = Container.expand(function (type, color, row, col) {
var self = Container.call(this);
self.pieceType = type;
self.pieceColor = color;
self.row = row;
self.col = col;
self.hasMoved = false;
self.poisonTurns = 0;
self.poisonEffect = null;
self.poisonCounterText = null;
self.snakeMovesThisTurn = 0;
self.maxSnakeMoves = 3;
self.snakeLastMoveDirection = null; // Track last move direction for straight ahead requirement
self.snakeOnCooldown = false; // Prevent consecutive snake usage
self.princessEliminationMode = false; // Track if princess is in elimination mode
self.princessTargetsSelected = 0; // Track how many targets princess has selected
self.princessSelectedTargets = []; // Store selected targets for elimination
self.summonerMoveCount = 0; // Track moves for summoner ability
self.hasSummoned = false; // Track if summoner has summoned this cycle
var assetName = color + type.charAt(0).toUpperCase() + type.slice(1);
var pieceGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.updatePosition = function (animate) {
var targetX = boardStartX + self.col * squareSize + squareSize / 2;
var targetY = boardStartY + self.row * squareSize + squareSize / 2;
if (animate && (self.x !== targetX || self.y !== targetY)) {
// Add subtle bounce animation for piece movement
tween(self, {
x: targetX,
y: targetY,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Bounce back to normal size
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.bounceOut
});
}
});
} else {
self.x = targetX;
self.y = targetY;
}
};
self.canMoveTo = function (targetRow, targetCol) {
// Poisoned pieces cannot move
if (self.poisonTurns > 0) {
return false;
}
if (targetRow < 0 || targetRow > 7 || targetCol < 0 || targetCol > 15) {
return false;
}
var targetPiece = getPieceAt(targetRow, targetCol);
if (targetPiece && targetPiece.pieceColor === self.pieceColor) {
return false;
}
switch (self.pieceType) {
case 'pawn':
return self.canPawnMove(targetRow, targetCol);
case 'rook':
return self.canRookMove(targetRow, targetCol);
case 'knight':
return self.canKnightMove(targetRow, targetCol);
case 'bishop':
return self.canBishopMove(targetRow, targetCol);
case 'queen':
// Check stamina for queen moves
if (self.pieceColor === 'white' && staminaPoints <= 0) {
return false;
}
if (self.pieceColor === 'black' && aiStaminaPoints <= 0) {
return false;
}
return self.canQueenMove(targetRow, targetCol);
case 'princess':
return self.canPrincessMove(targetRow, targetCol);
case 'king':
return self.canKingMove(targetRow, targetCol);
case 'snake':
// Check stamina for snake moves
if (self.pieceColor === 'white' && staminaPoints <= 0) {
return false;
}
if (self.pieceColor === 'black' && aiStaminaPoints <= 0) {
return false;
}
return self.canSnakeMove(targetRow, targetCol);
case 'jester':
return self.canJesterMove(targetRow, targetCol);
case 'summoner':
return self.canSummonerMove(targetRow, targetCol);
}
return false;
};
self.canPawnMove = function (targetRow, targetCol) {
var direction = self.pieceColor === 'white' ? -1 : 1;
var startRow = self.pieceColor === 'white' ? 6 : 1;
if (targetCol === self.col) {
if (targetRow === self.row + direction && !getPieceAt(targetRow, targetCol)) {
return true;
}
if (self.row === startRow && targetRow === self.row + 2 * direction && !getPieceAt(targetRow, targetCol)) {
// Check if path is clear for 2-square pawn move
return isPathClear(self.row, self.col, targetRow, targetCol);
}
} else if (Math.abs(targetCol - self.col) === 1 && targetRow === self.row + direction) {
var targetPiece = getPieceAt(targetRow, targetCol);
if (targetPiece && targetPiece.pieceColor !== self.pieceColor) {
return true;
}
}
return false;
};
self.canRookMove = function (targetRow, targetCol) {
if (self.row !== targetRow && self.col !== targetCol) {
return false;
}
return isPathClear(self.row, self.col, targetRow, targetCol);
};
self.canKnightMove = function (targetRow, targetCol) {
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
return rowDiff === 2 && colDiff === 1 || rowDiff === 1 && colDiff === 2;
};
self.canBishopMove = function (targetRow, targetCol) {
if (Math.abs(targetRow - self.row) !== Math.abs(targetCol - self.col)) {
return false;
}
return isPathClear(self.row, self.col, targetRow, targetCol);
};
self.canPrincessMove = function (targetRow, targetCol) {
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
// Princess can move exactly 2 squares straight (horizontal or vertical only)
if (rowDiff === 2 && colDiff === 0 || rowDiff === 0 && colDiff === 2) {
// Cannot jump over pieces - path must be clear
return isPathClear(self.row, self.col, targetRow, targetCol);
}
// Princess can move 1 square straight for positioning
if (rowDiff === 1 && colDiff === 0 || rowDiff === 0 && colDiff === 1) {
return true;
}
return false;
};
self.canQueenMove = function (targetRow, targetCol) {
// Queen moves like both rook and bishop (any distance in straight lines or diagonals)
if (self.row === targetRow || self.col === targetCol) {
// Rook-like movement (horizontal or vertical)
return isPathClear(self.row, self.col, targetRow, targetCol);
}
if (Math.abs(targetRow - self.row) === Math.abs(targetCol - self.col)) {
// Bishop-like movement (diagonal)
return isPathClear(self.row, self.col, targetRow, targetCol);
}
return false;
};
self.canKingMove = function (targetRow, targetCol) {
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
// Normal king move (one square in any direction)
if (rowDiff <= 1 && colDiff <= 1) {
// Check if king would be safe at target position
return isKingSafeAt(self, targetRow, targetCol);
}
// Castling logic (king moves 2 squares horizontally)
if (!self.hasMoved && rowDiff === 0 && colDiff === 2) {
// Check if castling is possible
if (targetCol > self.col) {
// King-side castling (right)
var rook = getPieceAt(self.row, 15); // Rightmost rook
if (rook && rook.pieceType === 'rook' && !rook.hasMoved && rook.pieceColor === self.pieceColor) {
// Check if path is clear and king doesn't move through check
for (var col = self.col + 1; col < 15; col++) {
if (getPieceAt(self.row, col) || !isKingSafeAt(self, self.row, col)) {
return false;
}
}
return isKingSafeAt(self, targetRow, targetCol);
}
} else {
// Queen-side castling (left)
var rook = getPieceAt(self.row, 0); // Leftmost rook
if (rook && rook.pieceType === 'rook' && !rook.hasMoved && rook.pieceColor === self.pieceColor) {
// Check if path is clear and king doesn't move through check
for (var col = self.col - 1; col > 0; col--) {
if (getPieceAt(self.row, col) || !isKingSafeAt(self, self.row, col)) {
return false;
}
}
return isKingSafeAt(self, targetRow, targetCol);
}
}
}
return false;
};
self.applyPoison = function (turns) {
self.poisonTurns = turns;
self.showPoisonEffect();
};
self.showPoisonEffect = function () {
if (self.poisonEffect) {
self.poisonEffect.destroy();
self.poisonEffect = null;
}
if (self.poisonCounterText) {
self.poisonCounterText.destroy();
self.poisonCounterText = null;
}
if (self.poisonTurns > 0) {
self.poisonEffect = self.attachAsset('poisonEffect', {
anchorX: 0.5,
anchorY: 0.5
});
self.poisonEffect.x = 30;
self.poisonEffect.y = -30;
self.poisonCounterText = new Text2(self.poisonTurns.toString(), {
size: 24,
fill: 0xFFFFFF
});
self.poisonCounterText.anchor.set(0.5, 0.5);
self.poisonCounterText.x = 30;
self.poisonCounterText.y = -30;
self.addChild(self.poisonCounterText);
// Tint piece red when poisoned
if (pieceGraphics && !pieceGraphics.destroyed) {
tween(pieceGraphics, {
tint: 0xff6666
}, {
duration: 300,
easing: tween.easeOut
});
}
} else {
// Remove red tint when poison wears off
if (pieceGraphics && !pieceGraphics.destroyed) {
tween(pieceGraphics, {
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeOut
});
}
}
};
self.reducePoisonTurns = function () {
if (self.poisonTurns > 0) {
self.poisonTurns--;
self.showPoisonEffect();
}
};
self.canJesterMove = function (targetRow, targetCol) {
// Jester moves exactly 2 squares in any direction (like checker piece)
var rowDiff = targetRow - self.row;
var colDiff = targetCol - self.col;
var absRowDiff = Math.abs(rowDiff);
var absColDiff = Math.abs(colDiff);
// Must move exactly 2 squares in any direction
if (Math.max(absRowDiff, absColDiff) !== 2) {
return false;
}
// Check if move is in valid direction (straight or diagonal, 2 squares)
var isValidDirection = false;
if (absRowDiff === 2 && absColDiff === 0 ||
// Vertical 2 squares
absRowDiff === 0 && absColDiff === 2 ||
// Horizontal 2 squares
absRowDiff === 2 && absColDiff === 2) {
// Diagonal 2 squares
isValidDirection = true;
}
if (!isValidDirection) {
return false;
}
// Calculate middle square position
var middleRow = self.row + (rowDiff > 0 ? 1 : rowDiff < 0 ? -1 : 0);
var middleCol = self.col + (colDiff > 0 ? 1 : colDiff < 0 ? -1 : 0);
// Check if middle square is within bounds
if (middleRow < 0 || middleRow > 7 || middleCol < 0 || middleCol > 15) {
return false;
}
var middlePiece = getPieceAt(middleRow, middleCol);
var targetPiece = getPieceAt(targetRow, targetCol);
// Jester cannot land on an occupied square - target square must always be empty
// Also jester cannot capture the king
if (targetPiece) {
if (targetPiece.pieceType === 'king') {
return false; // Jester cannot capture the king
}
return false; // Cannot capture pieces on destination square
}
// Normal move: middle square empty, target square empty
if (!middlePiece) {
return true; // Can move to empty square through empty middle
}
// Jump capture move: middle square has enemy piece, target square must be empty (already checked above)
if (middlePiece.pieceColor !== self.pieceColor) {
return true; // Can jump over enemy piece to empty square
}
// Can jump over own pawns
if (middlePiece.pieceColor === self.pieceColor && middlePiece.pieceType === 'pawn') {
return true; // Can jump over own pawns to empty square
}
return false; // Can't jump over other own pieces
};
self.canSummonerMove = function (targetRow, targetCol) {
// Summoner can only move 1 square in any direction (like a king)
var rowDiff = Math.abs(targetRow - self.row);
var colDiff = Math.abs(targetCol - self.col);
// Summoner can move exactly 1 square in any direction (horizontal, vertical, or diagonal)
if (rowDiff <= 1 && colDiff <= 1 && (rowDiff > 0 || colDiff > 0)) {
return true;
}
return false;
};
self.canSnakeMove = function (targetRow, targetCol) {
// Check if snake is on cooldown
if (self.snakeOnCooldown) {
return false;
}
var rowDiff = targetRow - self.row;
var colDiff = targetCol - self.col;
var distance = Math.max(Math.abs(rowDiff), Math.abs(colDiff));
// Check if poisoning will occur
var willPoison = false;
var poisonPositions = [{
row: self.row - 1,
col: self.col - 1
}, {
row: self.row - 1,
col: self.col
}, {
row: self.row - 1,
col: self.col + 1
}, {
row: self.row,
col: self.col - 1
}, {
row: self.row,
col: self.col + 1
}, {
row: self.row + 1,
col: self.col - 1
}, {
row: self.row + 1,
col: self.col
}, {
row: self.row + 1,
col: self.col + 1
}];
for (var i = 0; i < poisonPositions.length; i++) {
var pos = poisonPositions[i];
if (pos.row >= 0 && pos.row < 8 && pos.col >= 0 && pos.col < 16) {
var pieceAtPos = getPieceAt(pos.row, pos.col);
if (pieceAtPos && pieceAtPos.pieceColor !== self.pieceColor) {
willPoison = true;
break;
}
}
}
// Snake can only move exactly 1 square
if (distance !== 1) {
return false;
}
// Check if the path is clear (no jumping over pieces)
if (!isPathClear(self.row, self.col, targetRow, targetCol)) {
return false;
}
// First move: 1 square in any direction (straight lines only)
if (self.snakeMovesThisTurn === 0) {
// Must be straight line movement (horizontal, vertical, or diagonal)
if (rowDiff === 0 || colDiff === 0 || Math.abs(rowDiff) === Math.abs(colDiff)) {
return true;
}
return false;
}
// Second move: 1 square diagonally in any direction
if (self.snakeMovesThisTurn === 1) {
// Must be diagonal movement
if (Math.abs(rowDiff) === Math.abs(colDiff) && Math.abs(rowDiff) === 1) {
return true;
}
return false;
}
// Third move: 1 square in any direction - only if not poisoning
if (self.snakeMovesThisTurn === 2 && !willPoison) {
// Can move to any adjacent square (any direction)
return true;
}
return false;
};
self.executeSummon = function () {
// Check if summoner can summon (moved 3 times and not already summoned)
if (self.summonerMoveCount < 3 || self.hasSummoned) return;
var summonPositions = [
// Orthogonal squares first: up, right, down, left
{
row: self.row - 1,
col: self.col
},
// up
{
row: self.row,
col: self.col + 1
},
// right
{
row: self.row + 1,
col: self.col
},
// down
{
row: self.row,
col: self.col - 1
},
// left
// Diagonal squares: up-right, down-right, down-left, up-left
{
row: self.row - 1,
col: self.col + 1
},
// up-right
{
row: self.row + 1,
col: self.col + 1
},
// down-right
{
row: self.row + 1,
col: self.col - 1
},
// down-left
{
row: self.row - 1,
col: self.col - 1
} // up-left
];
var summonedCount = 0;
var maxSummons = 2;
for (var i = 0; i < summonPositions.length && summonedCount < maxSummons; i++) {
var pos = summonPositions[i];
// Check if position is valid and empty
if (pos.row >= 0 && pos.row < 8 && pos.col >= 0 && pos.col < 16) {
if (!getPieceAt(pos.row, pos.col)) {
// Summon pawn at this position
var newPawn = new ChessPiece('pawn', self.pieceColor, pos.row, pos.col);
pieces.push(newPawn);
gameBoard[pos.row][pos.col] = newPawn;
game.addChild(newPawn);
newPawn.updatePosition(true);
summonedCount++;
// Create summoning effect
tween(newPawn, {
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.5
}, {
duration: 0
});
tween(newPawn, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 600,
easing: tween.bounceOut
});
}
}
}
if (summonedCount > 0) {
// Reset summoner state
self.summonerMoveCount = 0;
self.hasSummoned = true;
// Show summoning message
var summonText = new Text2('Summoner conjured ' + summonedCount + ' pawn' + (summonedCount > 1 ? 's' : '') + '!', {
size: 50,
fill: self.pieceColor === 'white' ? 0xFFFFFF : 0xFF0000
});
summonText.anchor.set(0.5, 0.5);
summonText.x = 1024;
summonText.y = 1200;
summonText.alpha = 0;
game.addChild(summonText);
tween(summonText, {
alpha: 1.0,
y: summonText.y - 50
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(summonText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
summonText.destroy();
}
});
}, 1500);
}
});
}
};
// Add hover effects for better interactivity
self.startHover = function () {
tween(self, {
scaleX: 1.05,
scaleY: 1.05,
y: self.y - 5
}, {
duration: 200,
easing: tween.easeOut
});
};
self.endHover = function () {
tween(self, {
scaleX: 1.0,
scaleY: 1.0,
y: boardStartY + self.row * squareSize + squareSize / 2
}, {
duration: 200,
easing: tween.easeOut
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2D1810
});
/****
* Game Code
****/
// Game dimensions and board setup
// Chess board squares
// White pieces
// Black pieces
// Sound effects
var squareSize = 128;
var boardStartX = (2048 - squareSize * 16) / 2;
var boardStartY = (2732 - squareSize * 8) / 2 + 100;
// Game state
var currentPlayer = 'white';
var selectedPiece = null;
var validMoves = [];
var pieces = [];
var gameBoard = [];
var highlightSquares = [];
var validMoveSquares = [];
var escapeSquares = [];
var kingSquare = null;
var blackKingSquare = null;
var dragNode = null;
var gameState = 'playing'; // playing, check, checkmate, stalemate
var whiteKingLives = 4;
var blackKingLives = 4;
var currentSnakePiece = null;
var snakeMovesRemaining = 0;
var activeSnakePiece = null; // Track which specific snake is currently active
// Stamina system variables
var staminaPoints = 1; // Start with 1 point
var maxStaminaPoints = 3;
var staminaContainer = null;
var staminaBars = [];
var aiStaminaBars = [];
// Pink square overlays for Queen and Snake pieces when stamina is depleted
var queenSnakePinkSquares = [];
// AI stamina system variables
var aiStaminaPoints = 1; // AI starts with 1 point
var aiMaxStaminaPoints = 3;
// Timer variables
var whiteTimeRemaining = 60000; // 60 seconds in milliseconds
var blackTimeRemaining = 60000; // 60 seconds in milliseconds
var timerInterval = null;
var gameStartTime = null;
var currentTurnStartTime = null;
// Initialize empty board
var _loop = function _loop() {
gameBoard[row] = [];
for (col = 0; col < 16; col++) {
gameBoard[row][col] = null;
}
// Missing function definitions for AI evaluation
function calculatePerfectActivity(move) {
// Simple activity calculation based on piece mobility
return simulateMove(move, function () {
var mobility = 0;
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
if (move.piece.canMoveTo(row, col)) {
mobility++;
}
}
}
return mobility;
});
}
function evaluatePerfectPawnStructure(move) {
// Simple pawn structure evaluation
var score = 0;
if (move.piece.pieceType === 'pawn') {
// Bonus for pawn advancement
var advancement = move.piece.pieceColor === 'black' ? move.toRow - move.piece.row : move.piece.row - move.toRow;
if (advancement > 0) score += advancement * 2;
}
return score;
}
function evaluateEndgameTechnique(move) {
// Simple endgame evaluation
var totalPieces = pieces.length;
if (totalPieces < 12) {
// In endgame, centralize king and activate pieces
var score = 0;
if (move.piece.pieceType === 'king') {
var centerDistance = Math.abs(move.toRow - 3.5) + Math.abs(move.toCol - 7.5);
score += (16 - centerDistance) * 3;
}
return score;
}
return 0;
}
function evaluateGrandmasterPositional(move) {
// Simple positional evaluation
return evaluatePositionalFactors(move) + evaluatePieceCoordination(move);
}
},
col;
for (var row = 0; row < 8; row++) {
_loop();
}
// Create visual board
var boardContainer = game.addChild(new Container());
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
var isLight = (row + col) % 2 === 0;
var square = LK.getAsset(isLight ? 'lightSquare' : 'darkSquare', {
anchorX: 0,
anchorY: 0
});
square.x = boardStartX + col * squareSize;
square.y = boardStartY + row * squareSize;
boardContainer.addChild(square);
}
}
// Add coordinate system labels
var coordinateContainer = game.addChild(new Container());
// Column labels (a-p for 16 columns)
var columnLabels = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'];
for (var col = 0; col < 16; col++) {
// Bottom row labels
var bottomLabel = new Text2(columnLabels[col], {
size: 32,
fill: 0xFFFFFF
});
bottomLabel.anchor.set(0.5, 0.5);
bottomLabel.x = boardStartX + col * squareSize + squareSize / 2;
bottomLabel.y = boardStartY + 8 * squareSize + 25;
coordinateContainer.addChild(bottomLabel);
// Top row labels
var topLabel = new Text2(columnLabels[col], {
size: 32,
fill: 0xFFFFFF
});
topLabel.anchor.set(0.5, 0.5);
topLabel.x = boardStartX + col * squareSize + squareSize / 2;
topLabel.y = boardStartY - 25;
coordinateContainer.addChild(topLabel);
}
// Row labels (8-1 from top to bottom)
for (var row = 0; row < 8; row++) {
var rowNumber = (8 - row).toString();
// Left side labels
var leftLabel = new Text2(rowNumber, {
size: 32,
fill: 0xFFFFFF
});
leftLabel.anchor.set(0.5, 0.5);
leftLabel.x = boardStartX - 25;
leftLabel.y = boardStartY + row * squareSize + squareSize / 2;
coordinateContainer.addChild(leftLabel);
// Right side labels
var rightLabel = new Text2(rowNumber, {
size: 32,
fill: 0xFFFFFF
});
rightLabel.anchor.set(0.5, 0.5);
rightLabel.x = boardStartX + 16 * squareSize + 25;
rightLabel.y = boardStartY + row * squareSize + squareSize / 2;
coordinateContainer.addChild(rightLabel);
}
// Initialize pieces with mixed layout - includes quenns, rooks, knights, 2 princesses, and 2 jesters
var basePieceSetup = [['summoner', 'knight', 'bishop', 'queen', 'king', 'queen', 'bishop', 'knight', 'rook', 'jester', 'knight', 'bishop', 'princess', 'princess', 'snake', 'jester'], ['pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn']];
var pieceSetup = shufflePiecesForNewRound(basePieceSetup);
// Create black pieces
for (var row = 0; row < 2; row++) {
for (var col = 0; col < 16; col++) {
var piece = new ChessPiece(pieceSetup[row][col], 'black', row, col);
pieces.push(piece);
gameBoard[row][col] = piece;
game.addChild(piece);
piece.updatePosition();
}
}
// Create white pieces
for (var row = 6; row < 8; row++) {
for (var col = 0; col < 16; col++) {
var pieceType = row === 6 ? 'pawn' : pieceSetup.whiteSetup[col];
var piece = new ChessPiece(pieceType, 'white', row, col);
pieces.push(piece);
gameBoard[row][col] = piece;
game.addChild(piece);
piece.updatePosition();
}
}
// Initialize king square
updateKingSquare();
// UI Elements
var turnText = new Text2('Your Turn', {
size: 80,
fill: 0xFFFFFF
});
turnText.anchor.set(0.5, 0);
LK.gui.top.addChild(turnText);
// Lives display - positioned behind chess pieces on the board
var whiteLivesContainer = new Container();
game.addChild(whiteLivesContainer);
var blackLivesContainer = new Container();
game.addChild(blackLivesContainer);
var whiteLivesText = new Text2('Your Lives: 4', {
size: 40,
fill: 0xFFFFFF
});
whiteLivesText.anchor.set(0.5, 0.5);
whiteLivesText.x = boardStartX + 16 * squareSize / 2;
whiteLivesText.y = boardStartY + 8 * squareSize + 50;
whiteLivesContainer.addChild(whiteLivesText);
var blackLivesText = new Text2('Enemy Lives: 4', {
size: 40,
fill: 0xFF0000
});
blackLivesText.anchor.set(0.5, 0.5);
blackLivesText.x = boardStartX + 16 * squareSize / 2;
blackLivesText.y = boardStartY - 50;
blackLivesContainer.addChild(blackLivesText);
// Lives visual indicators
var whiteLivesHearts = [];
var blackLivesHearts = [];
for (var i = 0; i < 4; i++) {
var whiteHeart = LK.getAsset('playerLifeBar', {
anchorX: 0.5,
anchorY: 0.5
});
whiteHeart.x = boardStartX + 16 * squareSize / 2 - 75 + i * 50;
whiteHeart.y = boardStartY + 8 * squareSize + 100;
whiteLivesContainer.addChild(whiteHeart);
whiteLivesHearts.push(whiteHeart);
var blackHeart = LK.getAsset('aiLifeBar', {
anchorX: 0.5,
anchorY: 0.5
});
blackHeart.x = boardStartX + 16 * squareSize / 2 - 75 + i * 50;
blackHeart.y = boardStartY - 100;
blackLivesContainer.addChild(blackHeart);
blackLivesHearts.push(blackHeart);
}
// Create stamina bar display
staminaContainer = new Container();
game.addChild(staminaContainer);
var staminaText = new Text2('Stamina', {
size: 40,
fill: 0xff69b4
});
staminaText.anchor.set(0.5, 0.5);
staminaText.x = boardStartX + 16 * squareSize - 100;
staminaText.y = boardStartY + 8 * squareSize + 150;
staminaContainer.addChild(staminaText);
for (var i = 0; i < maxStaminaPoints; i++) {
var staminaBar = LK.getAsset('staminaBar', {
anchorX: 0.5,
anchorY: 0.5
});
staminaBar.x = boardStartX + 16 * squareSize - 200 + i * 50;
staminaBar.y = boardStartY + 8 * squareSize + 200;
staminaContainer.addChild(staminaBar);
staminaBars.push(staminaBar);
}
// Create AI stamina bar display
var aiStaminaContainer = new Container();
game.addChild(aiStaminaContainer);
var aiStaminaText = new Text2('Enemy Stamina', {
size: 40,
fill: 0xff69b4
});
aiStaminaText.anchor.set(0.5, 0.5);
aiStaminaText.x = boardStartX + 16 * squareSize - 100;
aiStaminaText.y = boardStartY - 150;
aiStaminaContainer.addChild(aiStaminaText);
var aiStaminaBars = [];
// Hover tooltip system variables
var hoveredPiece = null;
var hoverStartTime = null;
var hoverTooltip = null;
var hoverTimer = null;
var HOVER_DELAY = 2000; // 2 seconds in milliseconds
for (var i = 0; i < aiMaxStaminaPoints; i++) {
var aiStaminaBar = LK.getAsset('staminaBar', {
anchorX: 0.5,
anchorY: 0.5
});
aiStaminaBar.x = boardStartX + 16 * squareSize - 200 + i * 50;
aiStaminaBar.y = boardStartY - 100;
aiStaminaContainer.addChild(aiStaminaBar);
aiStaminaBars.push(aiStaminaBar);
}
updateStaminaDisplay();
// Timer display elements
var whiteTimerText = new Text2('1:00', {
size: 60,
fill: 0xFFFFFF
});
whiteTimerText.anchor.set(0.5, 0.5);
whiteTimerText.x = boardStartX + 0 * squareSize + squareSize / 2; // Align with "a" column
whiteTimerText.y = boardStartY + 8 * squareSize + 75; // Below the bottom "a" label
game.addChild(whiteTimerText);
var blackTimerText = new Text2('1:00', {
size: 60,
fill: 0xFF0000
});
blackTimerText.anchor.set(0.5, 0.5);
blackTimerText.x = boardStartX + 0 * squareSize + squareSize / 2; // Align with "a" column
blackTimerText.y = boardStartY - 75; // Above the top "a" label
game.addChild(blackTimerText);
// Initialize and start timer after UI elements are created
updateTimerDisplay();
startTimer();
function getPieceAt(row, col) {
if (row === undefined || col === undefined || row < 0 || row > 7 || col < 0 || col > 15) {
return null;
}
return gameBoard[row][col];
}
function isPathClear(fromRow, fromCol, toRow, toCol) {
var rowStep = toRow > fromRow ? 1 : toRow < fromRow ? -1 : 0;
var colStep = toCol > fromCol ? 1 : toCol < fromCol ? -1 : 0;
var currentRow = fromRow + rowStep;
var currentCol = fromCol + colStep;
while (currentRow !== toRow || currentCol !== toCol) {
if (getPieceAt(currentRow, currentCol)) {
return false;
}
currentRow += rowStep;
currentCol += colStep;
}
return true;
}
function getSquareFromPosition(x, y) {
var col = Math.floor((x - boardStartX) / squareSize);
var row = Math.floor((y - boardStartY) / squareSize);
if (row >= 0 && row < 8 && col >= 0 && col < 16) {
return {
row: row,
col: col
};
}
return null;
}
function clearHighlights() {
// Smooth fade-out for highlights
for (var i = 0; i < highlightSquares.length; i++) {
var highlight = highlightSquares[i];
tween(highlight, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (!highlight.destroyed) highlight.destroy();
}
});
}
highlightSquares = [];
for (var i = 0; i < validMoveSquares.length; i++) {
var highlight = validMoveSquares[i];
// Immediately destroy flashing green squares instead of animating them out
if (!highlight.destroyed) highlight.destroy();
}
validMoveSquares = [];
for (var i = 0; i < escapeSquares.length; i++) {
var highlight = escapeSquares[i];
tween(highlight, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (!highlight.destroyed) highlight.destroy();
}
});
}
escapeSquares = [];
}
function createCaptureParticles(x, y, color) {
// Create sparkling particle effect for captures
for (var i = 0; i < 8; i++) {
var particle = LK.getAsset('poisonEffect', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: color || 0xFFD700,
alpha: 1.0
});
particle.x = x;
particle.y = y;
game.addChild(particle);
// Animate particles spreading out
var angle = i / 8 * Math.PI * 2;
var distance = 60 + Math.random() * 40;
var targetX = x + Math.cos(angle) * distance;
var targetY = y + Math.sin(angle) * distance;
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1,
rotation: Math.random() * Math.PI * 2
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (!particle.destroyed) particle.destroy();
}
});
}
}
function createExplosionEffect(centerRow, centerCol) {
// Create explosion effect in 3x3 area around the center position
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
var explosionRow = centerRow + dr;
var explosionCol = centerCol + dc;
// Check bounds
if (explosionRow >= 0 && explosionRow < 8 && explosionCol >= 0 && explosionCol < 16) {
var explosionX = boardStartX + explosionCol * squareSize + squareSize / 2;
var explosionY = boardStartY + explosionRow * squareSize + squareSize / 2;
// Create multiple explosion particles for each square
for (var i = 0; i < 12; i++) {
var explosionParticle = LK.getAsset('poisonEffect', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6 + Math.random() * 0.4,
scaleY: 0.6 + Math.random() * 0.4,
tint: 0xFF6600 + Math.floor(Math.random() * 0x9900),
// Orange to red explosion colors
alpha: 0.9
});
explosionParticle.x = explosionX + (Math.random() - 0.5) * 60;
explosionParticle.y = explosionY + (Math.random() - 0.5) * 60;
game.addChild(explosionParticle);
// Animate explosion particles
var explosionAngle = Math.random() * Math.PI * 2;
var explosionDistance = 80 + Math.random() * 60;
var explosionTargetX = explosionX + Math.cos(explosionAngle) * explosionDistance;
var explosionTargetY = explosionY + Math.sin(explosionAngle) * explosionDistance;
tween(explosionParticle, {
x: explosionTargetX,
y: explosionTargetY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1,
rotation: Math.random() * Math.PI * 4
}, {
duration: 600 + Math.random() * 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (!explosionParticle.destroyed) explosionParticle.destroy();
}
});
}
// Create bright flash effect in center of explosion
if (dr === 0 && dc === 0) {
var flash = LK.getAsset('highlightSquare', {
anchorX: 0,
anchorY: 0,
alpha: 0.8,
tint: 0xFF3300,
scaleX: 1.5,
scaleY: 1.5
});
flash.x = boardStartX + explosionCol * squareSize - squareSize * 0.25;
flash.y = boardStartY + explosionRow * squareSize - squareSize * 0.25;
game.addChild(flash);
tween(flash, {
alpha: 0,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (!flash.destroyed) flash.destroy();
}
});
}
}
}
}
}
function updateKingSquare() {
// Remove existing king squares
if (kingSquare) {
kingSquare.destroy();
kingSquare = null;
}
if (blackKingSquare) {
blackKingSquare.destroy();
blackKingSquare = null;
}
// Find the white king and create purple square under it
var whiteKing = findKing('white');
if (whiteKing) {
kingSquare = LK.getAsset('kingSquare', {
anchorX: 0,
anchorY: 0,
alpha: 0.8
});
kingSquare.x = boardStartX + whiteKing.col * squareSize;
kingSquare.y = boardStartY + whiteKing.row * squareSize;
boardContainer.addChild(kingSquare);
}
// Find the black king and create purple square under it
var blackKing = findKing('black');
if (blackKing) {
blackKingSquare = LK.getAsset('kingSquare', {
anchorX: 0,
anchorY: 0,
alpha: 0.8
});
blackKingSquare.x = boardStartX + blackKing.col * squareSize;
blackKingSquare.y = boardStartY + blackKing.row * squareSize;
boardContainer.addChild(blackKingSquare);
}
}
function highlightSquare(row, col, color) {
var assetName = 'validMoveSquare';
if (color === 'blue') {
assetName = 'highlightSquare';
} else if (color === 'yellow') {
assetName = 'escapeSquare';
}
var highlight = LK.getAsset(assetName, {
anchorX: 0,
anchorY: 0,
alpha: 0.0,
scaleX: 0.8,
scaleY: 0.8
});
highlight.x = boardStartX + col * squareSize;
highlight.y = boardStartY + row * squareSize;
game.addChild(highlight);
// Smooth fade-in and scale animation for highlights
tween(highlight, {
alpha: 0.7,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 250,
easing: tween.easeOut
});
// Add subtle pulsing animation for valid moves
if (color === 'green') {
var _pulseTween = function pulseTween() {
tween(highlight, {
alpha: 0.4
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(highlight, {
alpha: 0.7
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: _pulseTween
});
}
});
};
_pulseTween();
}
if (color === 'blue') {
highlightSquares.push(highlight);
} else if (color === 'yellow') {
escapeSquares.push(highlight);
} else {
validMoveSquares.push(highlight);
}
}
function showValidMoves(piece) {
validMoves = [];
var playerInCheck = isInCheck(piece.pieceColor);
// Always highlight the selected piece position in blue first
highlightSquare(piece.row, piece.col, 'blue');
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
if (piece.canMoveTo(row, col)) {
// If in check, only allow moves that escape check
if (playerInCheck) {
if (canEscapeCheck(piece, row, col)) {
validMoves.push({
row: row,
col: col
});
// Highlight escape move in yellow when in check
highlightSquare(row, col, 'yellow');
}
} else {
validMoves.push({
row: row,
col: col
});
highlightSquare(row, col, 'green');
}
}
}
}
}
function movePiece(piece, newRow, newCol) {
var capturedPiece = getPieceAt(newRow, newCol);
var isCapture = capturedPiece !== null;
// Record player move for AI imitation
recordPlayerMove(piece, piece.row, piece.col, newRow, newCol);
// Remove piece from old position
gameBoard[piece.row][piece.col] = null;
// Check for king auto-capture of adjacent snake before normal capture handling
if (piece.pieceType === 'snake') {
// Check if any enemy king is adjacent to the snake's new position
var enemyColor = piece.pieceColor === 'white' ? 'black' : 'white';
var enemyKing = findKing(enemyColor);
if (enemyKing) {
var kingRowDiff = Math.abs(enemyKing.row - newRow);
var kingColDiff = Math.abs(enemyKing.col - newCol);
// If king is adjacent (including diagonal), king automatically captures snake
if (kingRowDiff <= 1 && kingColDiff <= 1) {
// King auto-captures the snake
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === piece) {
pieces[i].destroy();
pieces.splice(i, 1);
break;
}
}
// Move king to snake position
gameBoard[enemyKing.row][enemyKing.col] = null;
enemyKing.row = newRow;
enemyKing.col = newCol;
gameBoard[enemyKing.row][enemyKing.col] = enemyKing;
enemyKing.updatePosition(true);
updateKingSquare();
LK.getSound('capture').play();
turnText.setText(enemyKing.pieceColor.charAt(0).toUpperCase() + enemyKing.pieceColor.slice(1) + ' King Auto-Captures Snake!');
// Reset snake state
currentSnakePiece = null;
snakeMovesRemaining = 0;
activeSnakePiece = null;
// Switch turns
currentPlayer = currentPlayer === 'white' ? 'black' : 'white';
return;
}
}
}
// Handle jester jump captures
if (piece.pieceType === 'jester') {
var rowDiff = newRow - piece.row;
var colDiff = newCol - piece.col;
var absRowDiff = Math.abs(rowDiff);
var absColDiff = Math.abs(colDiff);
// Check if this is a 2-square move (potential jump)
if (Math.max(absRowDiff, absColDiff) === 2) {
// Calculate middle square position
var middleRow = piece.row + (rowDiff > 0 ? 1 : rowDiff < 0 ? -1 : 0);
var middleCol = piece.col + (colDiff > 0 ? 1 : colDiff < 0 ? -1 : 0);
var jumpedPiece = getPieceAt(middleRow, middleCol);
// If there's an enemy piece in the middle, capture it (jump capture)
if (jumpedPiece && jumpedPiece.pieceColor !== piece.pieceColor) {
// Remove jumped piece with effects
var jumpCaptureX = boardStartX + middleCol * squareSize + squareSize / 2;
var jumpCaptureY = boardStartY + middleRow * squareSize + squareSize / 2;
var jumpParticleColor = jumpedPiece.pieceColor === 'white' ? 0xFFD700 : 0xFF4444;
createCaptureParticles(jumpCaptureX, jumpCaptureY, jumpParticleColor);
// Animate jumped piece removal
tween(jumpedPiece, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5,
rotation: Math.PI
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (!jumpedPiece.destroyed) {
jumpedPiece.destroy();
}
}
});
// Play special jester capture sound
LK.getSound('jesterCapture').play();
// Remove jumped piece from game state
gameBoard[middleRow][middleCol] = null;
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === jumpedPiece) {
pieces.splice(i, 1);
break;
}
}
}
}
}
// Remove captured piece with visual effects
if (capturedPiece) {
// Create particle effects at capture position
var captureX = boardStartX + newCol * squareSize + squareSize / 2;
var captureY = boardStartY + newRow * squareSize + squareSize / 2;
var particleColor = capturedPiece.pieceColor === 'white' ? 0xFFD700 : 0xFF4444;
createCaptureParticles(captureX, captureY, particleColor);
// Animate captured piece before removal
tween(capturedPiece, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5,
rotation: Math.PI
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (!capturedPiece.destroyed) {
capturedPiece.destroy();
}
}
});
// Check if captured piece is a snake in the middle of its multi-move sequence
if (capturedPiece.pieceType === 'snake' && capturedPiece.snakeMovesThisTurn > 0 && capturedPiece === activeSnakePiece) {
// Snake was captured before completing its moves - put on cooldown and reset sequence
capturedPiece.snakeOnCooldown = true;
capturedPiece.snakeMovesThisTurn = 0;
capturedPiece.snakeLastMoveDirection = null;
// Reset global snake state
currentSnakePiece = null;
snakeMovesRemaining = 0;
activeSnakePiece = null;
}
// Handle king capture - check if king can counter-capture
if (capturedPiece.pieceType === 'king') {
// Check if captured king can counter-capture the attacking piece
var canCounterCapture = capturedPiece && piece && capturedPiece.canMoveTo(piece.row, piece.col) && isKingSafeAt(capturedPiece, piece.row, piece.col);
if (canCounterCapture) {
// Auto counter-capture: king takes the attacking piece
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === piece) {
pieces[i].destroy();
pieces.splice(i, 1);
break;
}
}
// Move king to attacking piece position
gameBoard[capturedPiece.row][capturedPiece.col] = null;
capturedPiece.row = piece.row;
capturedPiece.col = piece.col;
gameBoard[capturedPiece.row][capturedPiece.col] = capturedPiece;
capturedPiece.updatePosition(true);
updateKingSquare();
LK.getSound('capture').play();
// Apply 1 damage to the king's side for escaping
if (capturedPiece.pieceColor === 'white') {
whiteKingLives--;
activateAbility('white', whiteKingLives);
} else {
blackKingLives--;
activateAbility('black', blackKingLives);
}
updateLivesDisplay();
turnText.setText(capturedPiece.pieceColor.charAt(0).toUpperCase() + capturedPiece.pieceColor.slice(1) + ' King Counter-Attacks! 1 Life Lost for Escaping!');
return;
} else {
// Reduce king lives
if (capturedPiece.pieceColor === 'white') {
whiteKingLives--;
activateAbility('white', whiteKingLives);
} else {
blackKingLives--;
activateAbility('black', blackKingLives);
}
// Update lives display
updateLivesDisplay();
// Check if king is out of lives (restart game when lives reach 0)
if (whiteKingLives <= 0 || blackKingLives <= 0) {
var winner = whiteKingLives <= 0 ? 'Black' : 'White';
turnText.setText(winner + (winner === 'Black' ? ' side won.' : ' Wins! King Defeated! New round begins with shuffled pieces!'));
LK.setTimeout(function () {
restartGame();
}, 2000);
return;
}
}
} else {
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === capturedPiece) {
pieces[i].destroy();
pieces.splice(i, 1);
break;
}
}
// Add stamina point for capturing (exclude Queen and Snake captures)
if (capturedPiece.pieceType !== 'queen' && capturedPiece.pieceType !== 'snake') {
if (piece.pieceColor === 'white' && staminaPoints < maxStaminaPoints) {
staminaPoints++;
updateStaminaDisplay();
} else if (piece.pieceColor === 'black' && aiStaminaPoints < aiMaxStaminaPoints) {
aiStaminaPoints++;
}
}
}
}
// Handle castling before moving the king
if (piece.pieceType === 'king' && Math.abs(newCol - piece.col) === 2) {
var rookCol, rookNewCol;
if (newCol > piece.col) {
// King-side castling
rookCol = 15;
rookNewCol = newCol - 1;
} else {
// Queen-side castling
rookCol = 0;
rookNewCol = newCol + 1;
}
var rook = getPieceAt(piece.row, rookCol);
if (rook) {
// Move rook
gameBoard[rook.row][rook.col] = null;
rook.col = rookNewCol;
rook.hasMoved = true;
gameBoard[rook.row][rook.col] = rook;
rook.updatePosition(true);
}
}
// Move piece to new position
piece.row = newRow;
piece.col = newCol;
piece.hasMoved = true;
gameBoard[newRow][newCol] = piece;
piece.updatePosition(true);
// Handle stamina cost for Queen moves (immediate) and Snake moves (on third move)
if (piece.pieceType === 'queen') {
if (piece.pieceColor === 'white' && staminaPoints > 0) {
staminaPoints--;
// Store reference to piece that consumed stamina for pink square tracking
if (!window.whiteStaminaConsumers) window.whiteStaminaConsumers = [];
window.whiteStaminaConsumers.push({
type: piece.pieceType,
row: piece.row,
col: piece.col,
id: piece
});
updateStaminaDisplay();
} else if (piece.pieceColor === 'black' && aiStaminaPoints > 0) {
aiStaminaPoints--;
// Store reference to piece that consumed stamina for pink square tracking
if (!window.blackStaminaConsumers) window.blackStaminaConsumers = [];
window.blackStaminaConsumers.push({
type: piece.pieceType,
row: piece.row,
col: piece.col,
id: piece
});
}
}
// Update king square if a king moved
if (piece.pieceType === 'king') {
updateKingSquare();
}
// Play sound
if (isCapture) {
if (piece.pieceType === 'jester') {
LK.getSound('jesterCapture').play();
} else {
LK.getSound('capture').play();
}
} else {
LK.getSound('move').play();
}
// Check for pawn promotion
if (piece.pieceType === 'pawn' && (newRow === 0 || newRow === 7)) {
promotePawn(piece);
}
// Handle summoner move counting and summoning
if (piece.pieceType === 'summoner') {
piece.summonerMoveCount++;
piece.hasSummoned = false; // Reset summoned flag when moving
if (piece.summonerMoveCount >= 3) {
// Execute summoning after move animation completes
LK.setTimeout(function () {
piece.executeSummon();
}, 400);
}
}
// Handle snake multi-move mechanics
if (piece.pieceType === 'snake') {
// Set this snake as the active snake if starting its turn
if (piece.snakeMovesThisTurn === 0) {
activeSnakePiece = piece;
var rowDiff = newRow - piece.row;
var colDiff = newCol - piece.col;
piece.snakeLastMoveDirection = {
row: rowDiff === 0 ? 0 : rowDiff > 0 ? 1 : -1,
col: colDiff === 0 ? 0 : colDiff > 0 ? 1 : -1
};
}
// Check if poisoning will occur at the new position after the move
var willPoison = false;
var poisonPositions = [{
row: newRow - 1,
col: newCol - 1
}, {
row: newRow - 1,
col: newCol
}, {
row: newRow - 1,
col: newCol + 1
}, {
row: newRow,
col: newCol - 1
}, {
row: newRow,
col: newCol + 1
}, {
row: newRow + 1,
col: newCol - 1
}, {
row: newRow + 1,
col: newCol
}, {
row: newRow + 1,
col: newCol + 1
}];
for (var i = 0; i < poisonPositions.length; i++) {
var pos = poisonPositions[i];
if (pos.row >= 0 && pos.row < 8 && pos.col >= 0 && pos.col < 16) {
var pieceAtPos = getPieceAt(pos.row, pos.col);
if (pieceAtPos && pieceAtPos.pieceColor !== piece.pieceColor) {
willPoison = true;
break;
}
}
}
piece.snakeMovesThisTurn++;
var maxMoves = willPoison ? 2 : piece.maxSnakeMoves;
snakeMovesRemaining = maxMoves - piece.snakeMovesThisTurn;
currentSnakePiece = piece;
// Apply poison to pieces around the snake's new position
applyPoisonBehindSnake(piece, newRow, newCol);
// Handle additional stamina cost for poisoning
if (willPoison) {
// Deduct additional stamina for poisoning
if (piece.pieceColor === 'white' && staminaPoints > 0) {
staminaPoints--;
updateStaminaDisplay();
} else if (piece.pieceColor === 'black' && aiStaminaPoints > 0) {
aiStaminaPoints--;
}
}
// If poisoning occurred, immediately switch turns after 2 moves
if (willPoison) {
// Snake completed poisoning moves - put on cooldown and reset
piece.snakeMovesThisTurn = 0;
piece.snakeOnCooldown = true;
piece.snakeLastMoveDirection = null;
currentSnakePiece = null;
snakeMovesRemaining = 0;
activeSnakePiece = null; // Reset active snake
turnText.setText(piece.pieceColor.charAt(0).toUpperCase() + piece.pieceColor.slice(1) + ' Snake poisoned enemies! Turn passes to opponent.');
} else {
// Snake eats piece at each stop - already handled by capture logic above
// If snake has more moves, don't switch turns yet
if (piece.snakeMovesThisTurn < maxMoves) {
var moveNames = ['', 'First', 'Second', 'Third'];
turnText.setText(piece.pieceColor.charAt(0).toUpperCase() + piece.pieceColor.slice(1) + ' Snake - ' + moveNames[piece.snakeMovesThisTurn] + ' move complete, ' + snakeMovesRemaining + ' moves remaining');
// Clear current selection to allow new move
clearHighlights();
selectedPiece = null;
return; // Don't switch turns
}
// Snake completed all 3 moves - deduct stamina now and put on cooldown
if (piece.pieceColor === 'white' && staminaPoints > 0) {
staminaPoints--;
// Store reference to piece that consumed stamina for pink square tracking
if (!window.whiteStaminaConsumers) window.whiteStaminaConsumers = [];
window.whiteStaminaConsumers.push({
type: piece.pieceType,
row: piece.row,
col: piece.col,
id: piece
});
updateStaminaDisplay();
} else if (piece.pieceColor === 'black' && aiStaminaPoints > 0) {
aiStaminaPoints--;
// Store reference to piece that consumed stamina for pink square tracking
if (!window.blackStaminaConsumers) window.blackStaminaConsumers = [];
window.blackStaminaConsumers.push({
type: piece.pieceType,
row: piece.row,
col: piece.col,
id: piece
});
}
piece.snakeMovesThisTurn = 0;
piece.snakeOnCooldown = true;
piece.snakeLastMoveDirection = null;
currentSnakePiece = null;
snakeMovesRemaining = 0;
activeSnakePiece = null; // Reset active snake
}
}
// Switch turns
currentPlayer = currentPlayer === 'white' ? 'black' : 'white';
// Reset timer for new turn
currentTurnStartTime = Date.now();
// Reduce poison turns and remove snake cooldown for all pieces of the new current player
for (var i = 0; i < pieces.length; i++) {
if (pieces[i].pieceColor === currentPlayer) {
pieces[i].reducePoisonTurns();
// Remove snake cooldown when it becomes this player's turn
if (pieces[i].pieceType === 'snake') {
pieces[i].snakeOnCooldown = false;
}
}
}
turnText.setText(currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + '\'s Turn');
// Check for check/checkmate
if (isInCheck(currentPlayer)) {
// In check situations, look for defensive moves: capturing the threatening piece, blocking, or king escape
var king = findKing(currentPlayer);
var threateningPiece = findThreateningPiece(king);
var canKingCounterCapture = false;
var canDefend = false;
var defendingPiece = null;
var defenseRow = -1;
var defenseCol = -1;
// Check for defensive options when in check
if (threateningPiece) {
// Priority 1: King can directly counter-capture the threatening piece
if (king && king.canMoveTo(threateningPiece.row, threateningPiece.col) && isKingSafeAt(king, threateningPiece.row, threateningPiece.col)) {
canKingCounterCapture = true;
canDefend = true;
defendingPiece = king;
defenseRow = threateningPiece.row;
defenseCol = threateningPiece.col;
} else {
// Priority 2: Any other piece can capture the threatening piece to protect the king
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === currentPlayer && piece !== king && piece.canMoveTo(threateningPiece.row, threateningPiece.col)) {
// Test if this capture would resolve the check
if (canEscapeCheck(piece, threateningPiece.row, threateningPiece.col)) {
canDefend = true;
defendingPiece = piece;
defenseRow = threateningPiece.row;
defenseCol = threateningPiece.col;
break;
}
}
}
// Priority 3: If no capture is possible, look for blocking moves or king escape moves
if (!canDefend) {
// Check if any piece can block the attack path or if king can escape
var allDefensiveMoves = getAllValidMoves(currentPlayer);
canDefend = allDefensiveMoves.length > 0;
}
}
}
// If defensive action is available, execute it
if (canDefend && defendingPiece) {
// Auto-defense: piece protects king by capturing the threatening piece
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === threateningPiece) {
pieces[i].destroy();
pieces.splice(i, 1);
break;
}
}
// Move defending piece to capture position
gameBoard[defendingPiece.row][defendingPiece.col] = null;
defendingPiece.row = defenseRow;
defendingPiece.col = defenseCol;
gameBoard[defendingPiece.row][defendingPiece.col] = defendingPiece;
defendingPiece.updatePosition(true);
if (defendingPiece.pieceType === 'king') {
updateKingSquare();
// King counter-capture - game continues without penalty when king directly captures threat
if (canKingCounterCapture) {
turnText.setText(defendingPiece.pieceColor.charAt(0).toUpperCase() + defendingPiece.pieceColor.slice(1) + ' King Counter-Captures Threatening Piece!');
} else {
// Apply 1 damage to king's side for escaping by counter-capture in defensive situations
if (defendingPiece.pieceColor === 'white') {
whiteKingLives--;
activateAbility('white', whiteKingLives);
} else {
blackKingLives--;
activateAbility('black', blackKingLives);
}
updateLivesDisplay();
turnText.setText(defendingPiece.pieceColor.charAt(0).toUpperCase() + defendingPiece.pieceColor.slice(1) + ' King Defends! 1 Life Lost for Escaping!');
}
} else {
var pieceTitle = defendingPiece.pieceType.charAt(0).toUpperCase() + defendingPiece.pieceType.slice(1);
turnText.setText(defendingPiece.pieceColor.charAt(0).toUpperCase() + defendingPiece.pieceColor.slice(1) + ' ' + pieceTitle + ' Protects King by Capturing Threat!');
}
LK.getSound('capture').play();
// Continue game - switch turns
currentPlayer = currentPlayer === 'white' ? 'black' : 'white';
turnText.setText(currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + '\'s Turn');
gameState = 'playing';
// Trigger AI move if it's black's turn
if (currentPlayer === 'black') {
makeAIMove();
}
return;
}
// Check if player has no defensive options (no escape moves) - comprehensive defense check
if (!hasValidEscapeMoves(currentPlayer)) {
// Look for all possible defensive options: king counter-capture, piece protection, blocking
var king = findKing(currentPlayer);
var threateningPiece = findThreateningPiece(king);
var canDefendKing = false;
var protectingPiece = null;
if (threateningPiece) {
// Option 1: King can directly counter-capture the threatening piece
if (king && king.canMoveTo(threateningPiece.row, threateningPiece.col) && isKingSafeAt(king, threateningPiece.row, threateningPiece.col)) {
canDefendKing = true;
protectingPiece = king;
} else {
// Option 2: Any other piece can protect the king by capturing the threatening piece
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === currentPlayer && piece !== king && piece.canMoveTo(threateningPiece.row, threateningPiece.col)) {
if (canEscapeCheck(piece, threateningPiece.row, threateningPiece.col)) {
canDefendKing = true;
protectingPiece = piece;
break;
}
}
}
// Option 3: Any piece can block the attack path (if attack is not adjacent)
if (!canDefendKing && king && threateningPiece) {
var blockingMoves = getAllValidMoves(currentPlayer);
for (var i = 0; i < blockingMoves.length; i++) {
if (canEscapeCheck(blockingMoves[i].piece, blockingMoves[i].toRow, blockingMoves[i].toCol)) {
canDefendKing = true;
protectingPiece = blockingMoves[i].piece;
break;
}
}
}
}
}
if (canDefendKing && protectingPiece && threateningPiece) {
// Execute king protection with explosion effect
var threateningPieceRow = threateningPiece.row;
var threateningPieceCol = threateningPiece.col;
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === threateningPiece) {
pieces[i].destroy();
pieces.splice(i, 1);
break;
}
}
// Create explosion effect where threatening piece was destroyed
createExplosionEffect(threateningPieceRow, threateningPieceCol);
// Move protecting piece to capture position
gameBoard[protectingPiece.row][protectingPiece.col] = null;
protectingPiece.row = threateningPiece.row;
protectingPiece.col = threateningPiece.col;
gameBoard[protectingPiece.row][protectingPiece.col] = protectingPiece;
protectingPiece.updatePosition(true);
if (protectingPiece.pieceType === 'king') {
updateKingSquare();
}
LK.getSound('capture').play();
var pieceTitle = protectingPiece.pieceType.charAt(0).toUpperCase() + protectingPiece.pieceType.slice(1);
turnText.setText(protectingPiece.pieceColor.charAt(0).toUpperCase() + protectingPiece.pieceColor.slice(1) + ' ' + pieceTitle + ' Protects King! Game Continues!');
// Switch turns and continue game
currentPlayer = currentPlayer === 'white' ? 'black' : 'white';
turnText.setText(currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + '\'s Turn');
gameState = 'playing';
// Trigger AI move if it's black's turn
if (currentPlayer === 'black') {
makeAIMove();
}
return;
}
// Checkmate situation - king is trapped and cannot escape, lose 2 lives and remove only the checking piece
if (currentPlayer === 'white') {
whiteKingLives -= 2;
if (whiteKingLives < 0) whiteKingLives = 0;
} else {
blackKingLives -= 2;
if (blackKingLives < 0) blackKingLives = 0;
}
// Remove only the piece that is currently checking the king (the threatening piece)
var king = findKing(currentPlayer);
var checkingPiece = findThreateningPiece(king);
if (checkingPiece) {
var checkingPieceRow = checkingPiece.row;
var checkingPieceCol = checkingPiece.col;
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === checkingPiece) {
gameBoard[checkingPiece.row][checkingPiece.col] = null;
checkingPiece.destroy();
pieces.splice(i, 1);
break;
}
}
// Create explosion effect where checking piece was destroyed
createExplosionEffect(checkingPieceRow, checkingPieceCol);
}
updateLivesDisplay();
// Check if king is out of lives (restart game when lives reach 0)
if (whiteKingLives <= 0 || blackKingLives <= 0) {
var winner = whiteKingLives <= 0 ? 'Black' : 'White';
turnText.setText(winner + (winner === 'Black' ? ' side won.' : ' Wins! King Out of Lives!'));
LK.setTimeout(function () {
restartGame();
}, 2000);
return;
}
turnText.setText('Checkmate! ' + currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + ' King loses 2 lives! Threatening piece destroyed!');
// Switch turns and continue game
currentPlayer = currentPlayer === 'white' ? 'black' : 'white';
LK.setTimeout(function () {
turnText.setText(currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + '\'s Turn');
gameState = 'playing';
// Trigger AI move if it's black's turn
if (currentPlayer === 'black') {
makeAIMove();
}
}, 2000);
return;
}
// Reduce king lives when in check but not checkmated
if (currentPlayer === 'white') {
whiteKingLives--;
activateAbility('white', whiteKingLives);
} else {
blackKingLives--;
activateAbility('black', blackKingLives);
}
updateLivesDisplay();
// Check if king is out of lives (restart game when lives reach 0)
if (whiteKingLives <= 0 || blackKingLives <= 0) {
var winner = whiteKingLives <= 0 ? 'Black' : 'White';
turnText.setText(winner + (winner === 'Black' ? ' side won.' : ' Wins! King Out of Lives! New round begins with shuffled pieces!'));
LK.setTimeout(function () {
restartGame();
}, 2000);
return;
}
gameState = 'check';
LK.getSound('check').play();
// Add dramatic check visual effect
LK.effects.flashScreen(0xFF0000, 800);
var kingInCheck = findKing(currentPlayer);
if (kingInCheck) {
// Make the king flash red
tween(kingInCheck, {
tint: 0xFF6666
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(kingInCheck, {
tint: 0xFFFFFF
}, {
duration: 300,
easing: tween.easeOut
});
}
});
}
var currentLives = currentPlayer === 'white' ? whiteKingLives : blackKingLives;
if (currentLives === 0) {
turnText.setText(currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + ' in Check! FINAL LIFE - Must Escape or Lose!');
} else {
turnText.setText(currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + ' in Check! Lives: ' + currentLives + ' - Must Escape!');
}
} else if (isStalemate(currentPlayer)) {
gameState = 'stalemate';
turnText.setText('Stalemate!');
LK.setTimeout(function () {
restartGame();
}, 2000);
} else {
gameState = 'playing';
}
// Trigger AI move if it's black's turn
if (currentPlayer === 'black' && (gameState === 'playing' || gameState === 'check')) {
makeAIMove();
}
}
function getPieceValue(pieceType) {
switch (pieceType) {
case 'pawn':
return 1;
case 'bishop':
return 3;
case 'knight':
return 3;
case 'rook':
return 5;
case 'queen':
return 9;
case 'snake':
return 6;
// Higher value due to special abilities
case 'jester':
return 4;
// Moderate value for unique movement
case 'princess':
return 8;
case 'summoner':
return 8;
// Same value as princess
case 'king':
return 0;
default:
return 0;
}
}
function promotePawn(pawn) {
// Promote pawn to quenn (most powerful standard piece available)
var originalRow = pawn.row;
var originalCol = pawn.col;
var originalColor = pawn.pieceColor;
// Remove old pawn
pawn.destroy();
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === pawn) {
pieces.splice(i, 1);
break;
}
}
gameBoard[originalRow][originalCol] = null;
// Create new queen
var newQueen = new ChessPiece('queen', originalColor, originalRow, originalCol);
pieces.push(newQueen);
gameBoard[originalRow][originalCol] = newQueen;
game.addChild(newQueen);
newQueen.updatePosition();
}
function findKing(color) {
for (var i = 0; i < pieces.length; i++) {
if (pieces[i].pieceType === 'king' && pieces[i].pieceColor === color) {
return pieces[i];
}
}
return null;
}
function isInCheck(color) {
var king = findKing(color);
if (!king) return false;
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor !== color && piece.canMoveTo(king.row, king.col)) {
return true;
}
}
return false;
}
function isCheckmate(color) {
if (!isInCheck(color)) return false;
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === color) {
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
if (piece.canMoveTo(row, col)) {
// Simulate move
var originalRow = piece.row;
var originalCol = piece.col;
var capturedPiece = getPieceAt(row, col);
gameBoard[originalRow][originalCol] = null;
gameBoard[row][col] = piece;
piece.row = row;
piece.col = col;
var stillInCheck = isInCheck(color);
// Restore position
piece.row = originalRow;
piece.col = originalCol;
gameBoard[originalRow][originalCol] = piece;
gameBoard[row][col] = capturedPiece;
if (!stillInCheck) {
return false;
}
}
}
}
}
}
return true;
}
function isStalemate(color) {
if (isInCheck(color)) return false;
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === color) {
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
if (piece.canMoveTo(row, col)) {
return false;
}
}
}
}
}
return true;
}
function getAllValidMoves(color) {
var allMoves = [];
var playerInCheck = isInCheck(color);
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === color) {
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
if (piece.canMoveTo(row, col)) {
// If in check, only allow moves that escape check
if (playerInCheck) {
if (canEscapeCheck(piece, row, col)) {
allMoves.push({
piece: piece,
toRow: row,
toCol: col
});
}
} else {
allMoves.push({
piece: piece,
toRow: row,
toCol: col
});
}
}
}
}
}
}
return allMoves;
}
function makeAIMove() {
if (currentPlayer !== 'black' || gameState !== 'playing' && gameState !== 'check') return;
// If there's an active snake continuing its multi-move sequence, prioritize it
if (activeSnakePiece && activeSnakePiece.pieceColor === 'black' && snakeMovesRemaining > 0) {
var snakeMoves = [];
// Get valid moves only for the active snake
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
if (activeSnakePiece.canMoveTo(row, col)) {
snakeMoves.push({
piece: activeSnakePiece,
toRow: row,
toCol: col
});
}
}
}
if (snakeMoves.length > 0) {
var bestSnakeMove = chooseBestAIMove(snakeMoves);
if (bestSnakeMove) {
LK.setTimeout(function () {
movePiece(bestSnakeMove.piece, bestSnakeMove.toRow, bestSnakeMove.toCol);
// Continue snake moves if still the AI's turn and snake has moves left
if (currentSnakePiece && currentSnakePiece.pieceColor === 'black' && snakeMovesRemaining > 0 && currentPlayer === 'black') {
LK.setTimeout(function () {
makeAIMove(); // Recursive call for next snake move
}, 800);
}
}, 800);
return;
}
}
}
var validAIMoves = getAllValidMoves('black');
// Check if AI has no valid moves but can defend king
if (validAIMoves.length === 0 && isInCheck('black')) {
var blackKing = findKing('black');
var threateningPiece = findThreateningPiece(blackKing);
var canDefendAI = false;
var defendingAIPiece = null;
var defenseAIRow = -1;
var defenseAICol = -1;
// Look for defensive options for AI king
if (threateningPiece) {
// Priority 1: AI king can directly counter-capture the threatening piece
if (blackKing && blackKing.canMoveTo(threateningPiece.row, threateningPiece.col) && isKingSafeAt(blackKing, threateningPiece.row, threateningPiece.col)) {
canDefendAI = true;
defendingAIPiece = blackKing;
defenseAIRow = threateningPiece.row;
defenseAICol = threateningPiece.col;
} else {
// Priority 2: Any other AI piece can protect the king by capturing the threatening piece
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'black' && piece !== blackKing && piece.canMoveTo(threateningPiece.row, threateningPiece.col)) {
// Test if this capture would protect the AI king
if (canEscapeCheck(piece, threateningPiece.row, threateningPiece.col)) {
canDefendAI = true;
defendingAIPiece = piece;
defenseAIRow = threateningPiece.row;
defenseAICol = threateningPiece.col;
break;
}
}
}
}
}
if (canDefendAI) {
// AI defense: piece protects king by capturing the threatening piece
LK.setTimeout(function () {
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === threateningPiece) {
pieces[i].destroy();
pieces.splice(i, 1);
break;
}
}
// Move AI defending piece to capture position
gameBoard[defendingAIPiece.row][defendingAIPiece.col] = null;
defendingAIPiece.row = defenseAIRow;
defendingAIPiece.col = defenseAICol;
gameBoard[defendingAIPiece.row][defendingAIPiece.col] = defendingAIPiece;
defendingAIPiece.updatePosition(true);
if (defendingAIPiece.pieceType === 'king') {
updateKingSquare();
// AI King counter-capture - game continues normally
if (defendingAIPiece === blackKing) {
turnText.setText('Black King Protects Itself! Game Continues!');
} else {
// Apply 1 damage to AI king for defensive action in extreme situations
blackKingLives--;
activateAbility('black', blackKingLives);
updateLivesDisplay();
turnText.setText('Black King Defends! 1 Life Lost for Escaping!');
}
} else {
var pieceTitle = defendingAIPiece.pieceType.charAt(0).toUpperCase() + defendingAIPiece.pieceType.slice(1);
turnText.setText('Black ' + pieceTitle + ' Protects King by Capturing Threat!');
}
LK.getSound('capture').play();
// Continue game - switch turns
currentPlayer = 'white';
turnText.setText('Your Turn');
gameState = 'playing';
}, 800);
return;
} else {
// No auto-capture possible and no valid escape moves - checkmate, lose 2 lives and remove only the checking piece
blackKingLives -= 2;
if (blackKingLives < 0) blackKingLives = 0;
// Remove only the piece that is currently checking the AI king (the threatening piece)
var blackKing = findKing('black');
var checkingPiece = findThreateningPiece(blackKing);
if (checkingPiece) {
var checkingPieceRow = checkingPiece.row;
var checkingPieceCol = checkingPiece.col;
for (var i = 0; i < pieces.length; i++) {
if (pieces[i] === checkingPiece) {
gameBoard[checkingPiece.row][checkingPiece.col] = null;
checkingPiece.destroy();
pieces.splice(i, 1);
break;
}
}
// Create explosion effect where checking piece was destroyed
createExplosionEffect(checkingPieceRow, checkingPieceCol);
}
updateLivesDisplay();
// Check if AI king is out of lives
if (blackKingLives <= 0) {
turnText.setText('White Wins! AI King Out of Lives! New round begins with shuffled pieces!');
LK.setTimeout(function () {
restartGame();
}, 2000);
return;
}
turnText.setText('AI Checkmate! Black King loses 2 lives! Threatening piece destroyed!');
// Switch turns and continue game
currentPlayer = 'white';
LK.setTimeout(function () {
turnText.setText('Your Turn');
gameState = 'playing';
}, 2000);
return;
}
}
if (validAIMoves.length === 0) return;
// Advanced AI Strategy Implementation
var bestMove = chooseBestAIMove(validAIMoves);
if (bestMove) {
// Check if this is a snake move and handle multi-move sequence
if (bestMove.piece.pieceType === 'snake') {
// Execute the move after a short delay
LK.setTimeout(function () {
movePiece(bestMove.piece, bestMove.toRow, bestMove.toCol);
// If snake has more moves remaining and it's still the AI's turn, continue with AI moves
if (currentSnakePiece && currentSnakePiece.pieceColor === 'black' && snakeMovesRemaining > 0 && currentPlayer === 'black') {
// Delay before next snake move
LK.setTimeout(function () {
makeAIMove(); // Recursive call for next snake move
}, 800);
}
}, 800);
} else {
// Execute regular move after a short delay
LK.setTimeout(function () {
movePiece(bestMove.piece, bestMove.toRow, bestMove.toCol);
}, 800);
}
}
}
function isMoveThreatening(fromRow, fromCol, toRow, toCol, targetRow, targetCol) {
// Simple check if move gets closer to target (used for king protection)
var currentDistance = Math.abs(fromRow - targetRow) + Math.abs(fromCol - targetCol);
var newDistance = Math.abs(toRow - targetRow) + Math.abs(toCol - targetCol);
return newDistance < currentDistance;
}
function chooseBestAIMove(validMoves) {
// AI opponent behavior with 19% chance for special behaviors
var randomValue = Math.random();
// 19% chance for special AI behaviors
if (randomValue < 0.19) {
var behaviorType = Math.floor(Math.random() * 3); // 0, 1, or 2
if (behaviorType === 0) {
// Wrong move - choose a suboptimal move
var wrongMove = findWrongMove(validMoves);
if (wrongMove) {
return wrongMove;
}
} else if (behaviorType === 1) {
// Think for 2 seconds before making normal move
var normalMove = findNormalAIMove(validMoves);
if (normalMove) {
// Delay the move by 2 seconds
LK.setTimeout(function () {
// The actual move execution happens in the calling function
}, 2000);
return normalMove;
}
} else if (behaviorType === 2) {
// Imitate player's last move if possible
var imitationMove = findPlayerImitationMove(validMoves);
if (imitationMove) {
return imitationMove;
}
}
}
// Super Grandmaster AI - Perfect calculation depth and evaluation
var bestMove = null;
var bestScore = -100000;
var calculationDepth = 5; // Deep calculation
// Priority 1: Immediate checkmate (highest priority)
var checkmateMove = findCheckmateInOne(validMoves);
if (checkmateMove) {
return checkmateMove;
}
// Priority 2: Prevent opponent checkmate threats
var preventCheckmateMove = preventOpponentCheckmate(validMoves);
if (preventCheckmateMove) {
return preventCheckmateMove;
}
// Priority 3: Emergency king defense when in check
if (isInCheck('black')) {
var defensiveMove = findOptimalDefensiveMove(validMoves);
if (defensiveMove) {
return defensiveMove;
}
}
// Priority 4: Princess elimination for maximum material gain
var princessEliminationMove = findBestPrincessElimination();
if (princessEliminationMove) {
return princessEliminationMove;
}
// Priority 5: Deep tactical combinations
var tacticalMove = findBestTacticalCombination(validMoves);
if (tacticalMove) {
return tacticalMove;
}
// Priority 6: Advanced positional play with perfect evaluation
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var score = evaluateMovePerfectly(move, calculationDepth);
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
}
return bestMove || validMoves[0];
}
function findCheckmateMove(validMoves) {
// Look for moves that can checkmate the opponent
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (simulateMove(move, function () {
return isCheckmate('white');
})) {
return move;
}
}
return null;
}
function findBestCapture(validMoves) {
var bestCapture = null;
var bestValue = 0;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
var captureValue = getPieceValue(targetPiece.pieceType);
// Prioritize king captures highly
if (targetPiece.pieceType === 'king') {
captureValue = 1000;
}
if (captureValue > bestValue) {
bestValue = captureValue;
bestCapture = move;
}
}
}
return bestCapture;
}
function shouldTakeCapture(captureMove) {
// Analyze if capture is safe by looking ahead 2 moves
var isSafe = simulateMove(captureMove, function () {
// Check if the capturing piece can be counter-captured
var capturedPosition = {
row: captureMove.toRow,
col: captureMove.toCol
};
var opponentMoves = getAllValidMoves('white');
for (var i = 0; i < opponentMoves.length; i++) {
var counterMove = opponentMoves[i];
if (counterMove.toRow === capturedPosition.row && counterMove.toCol === capturedPosition.col) {
var counterValue = getPieceValue(counterMove.piece.pieceType);
var captureValue = getPieceValue(getPieceAt(captureMove.toRow, captureMove.toCol).pieceType);
// Only take if gain is worth the risk
return captureValue > counterValue;
}
}
return true; // Safe capture
});
return isSafe;
}
function findTacticalMove(validMoves) {
// Look for moves that create pressure or tactical advantages
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
// Check if move puts opponent in check
if (simulateMove(move, function () {
return isInCheck('white');
})) {
return move;
}
// Check if move creates fork or pin opportunities
if (createsFork(move) || createsPin(move)) {
return move;
}
}
return null;
}
function findWithdrawMove(validMoves) {
// Sometimes withdraw valuable pieces when under threat
var valuablePieces = [];
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'black' && (piece.pieceType === 'princess' || piece.pieceType === 'queen' || piece.pieceType === 'snake' || piece.pieceType === 'rook')) {
if (isPieceUnderThreat(piece)) {
valuablePieces.push(piece);
}
}
}
for (var i = 0; i < valuablePieces.length; i++) {
var piece = valuablePieces[i];
for (var j = 0; j < validMoves.length; j++) {
var move = validMoves[j];
if (move.piece === piece && !isPieceUnderThreatAt(piece, move.toRow, move.toCol)) {
// Use knight to create pressure occasionally
if (Math.random() < 0.3 && piece.pieceType === 'knight') {
if (createsPressure(move)) {
return move;
}
}
return move; // Safe withdrawal
}
}
}
return null;
}
function findSafePositionalMove(validMoves) {
// Choose a move that improves position without risk
var safeMoves = [];
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (!isPieceUnderThreatAt(move.piece, move.toRow, move.toCol)) {
safeMoves.push(move);
}
}
if (safeMoves.length > 0) {
return safeMoves[Math.floor(Math.random() * safeMoves.length)];
}
return validMoves[Math.floor(Math.random() * validMoves.length)];
}
function simulateMove(move, testFunction) {
// Temporarily execute move and test condition
var originalRow = move.piece.row;
var originalCol = move.piece.col;
var capturedPiece = getPieceAt(move.toRow, move.toCol);
// Make temporary move
gameBoard[originalRow][originalCol] = null;
gameBoard[move.toRow][move.toCol] = move.piece;
move.piece.row = move.toRow;
move.piece.col = move.toCol;
var result = testFunction();
// Restore position
move.piece.row = originalRow;
move.piece.col = originalCol;
gameBoard[originalRow][originalCol] = move.piece;
gameBoard[move.toRow][move.toCol] = capturedPiece;
return result;
}
function isPieceUnderThreat(piece) {
var whitePieces = pieces.filter(function (p) {
return p.pieceColor === 'white';
});
for (var i = 0; i < whitePieces.length; i++) {
if (whitePieces[i].canMoveTo(piece.row, piece.col)) {
return true;
}
}
return false;
}
function isPieceUnderThreatAt(piece, row, col) {
var whitePieces = pieces.filter(function (p) {
return p.pieceColor === 'white';
});
for (var i = 0; i < whitePieces.length; i++) {
if (whitePieces[i].canMoveTo(row, col)) {
return true;
}
}
return false;
}
function createsFork(move) {
// Check if move attacks two or more enemy pieces
var attackCount = 0;
var whitePieces = pieces.filter(function (p) {
return p.pieceColor === 'white';
});
return simulateMove(move, function () {
for (var i = 0; i < whitePieces.length; i++) {
if (move.piece.canMoveTo(whitePieces[i].row, whitePieces[i].col)) {
attackCount++;
}
}
return attackCount >= 2;
});
}
function createsPin(move) {
// Simplified pin detection - attacks piece that's protecting the king
var whiteKing = findKing('white');
if (!whiteKing) return false;
return simulateMove(move, function () {
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
// Check if removing this piece would expose king
var originalRow = targetPiece.row;
var originalCol = targetPiece.col;
gameBoard[originalRow][originalCol] = null;
var exposesKing = move.piece.canMoveTo(whiteKing.row, whiteKing.col);
gameBoard[originalRow][originalCol] = targetPiece;
return exposesKing;
}
return false;
});
}
function createsPressure(move) {
// Check if move creates pressure on opponent's position
var pressureScore = 0;
var whitePieces = pieces.filter(function (p) {
return p.pieceColor === 'white';
});
return simulateMove(move, function () {
for (var i = 0; i < whitePieces.length; i++) {
var piece = whitePieces[i];
if (move.piece.canMoveTo(piece.row, piece.col)) {
pressureScore += getPieceValue(piece.pieceType);
}
}
return pressureScore > 3; // Creates meaningful pressure
});
}
function findHighValueCapture(validMoves) {
// AI prioritizes capturing high value pieces with any piece
var bestCapture = null;
var bestPriority = 0;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
// Prioritize capturing high value pieces normally
var priority = getPieceValue(targetPiece.pieceType);
if (priority > bestPriority) {
bestPriority = priority;
bestCapture = move;
}
}
}
return bestCapture;
}
function findPoisonOpportunity(validMoves) {
// AI uses snake to move closer to enemy pieces protecting the king to poison them
var whiteKing = findKing('white');
if (!whiteKing) return null;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (move.piece.pieceType === 'snake') {
// Check if this snake move would poison pieces near the white king
var kingProtectors = getKingProtectors('white');
if (kingProtectors.length > 0) {
// Check if moving snake to this position would poison any king protectors
var wouldPoisonProtector = simulateMove(move, function () {
var poisonPositions = [{
row: move.toRow - 1,
col: move.toCol - 1
}, {
row: move.toRow - 1,
col: move.toCol
}, {
row: move.toRow - 1,
col: move.toCol + 1
}, {
row: move.toRow,
col: move.toCol - 1
}, {
row: move.toRow,
col: move.toCol + 1
}, {
row: move.toRow + 1,
col: move.toCol - 1
}, {
row: move.toRow + 1,
col: move.toCol
}, {
row: move.toRow + 1,
col: move.toCol + 1
}];
for (var j = 0; j < poisonPositions.length; j++) {
var pos = poisonPositions[j];
if (pos.row >= 0 && pos.row < 8 && pos.col >= 0 && pos.col < 16) {
var pieceAtPos = getPieceAt(pos.row, pos.col);
if (pieceAtPos && pieceAtPos.pieceColor === 'white') {
for (var k = 0; k < kingProtectors.length; k++) {
if (kingProtectors[k] === pieceAtPos) {
return true;
}
}
}
}
}
return false;
});
if (wouldPoisonProtector) {
return move;
}
}
}
}
return null;
}
function getKingProtectors(color) {
// Find pieces that are protecting the king
var king = findKing(color);
if (!king) return [];
var protectors = [];
var enemyColor = color === 'white' ? 'black' : 'white';
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === color && piece !== king) {
// Check if removing this piece would expose king to attack
var originalRow = piece.row;
var originalCol = piece.col;
gameBoard[originalRow][originalCol] = null;
var exposesKing = false;
for (var j = 0; j < pieces.length; j++) {
var enemyPiece = pieces[j];
if (enemyPiece.pieceColor === enemyColor && enemyPiece.canMoveTo(king.row, king.col)) {
exposesKing = true;
break;
}
}
gameBoard[originalRow][originalCol] = piece;
if (exposesKing) {
protectors.push(piece);
}
}
}
return protectors;
}
function canEscapeCheck(piece, newRow, newCol) {
// Simulate the move to see if it gets the king out of check
var originalRow = piece.row;
var originalCol = piece.col;
var capturedPiece = getPieceAt(newRow, newCol);
// Make temporary move
gameBoard[originalRow][originalCol] = null;
gameBoard[newRow][newCol] = piece;
piece.row = newRow;
piece.col = newCol;
// Check if still in check after move
var stillInCheck = isInCheck(piece.pieceColor);
// Restore original position
piece.row = originalRow;
piece.col = originalCol;
gameBoard[originalRow][originalCol] = piece;
gameBoard[newRow][newCol] = capturedPiece;
return !stillInCheck;
}
function isKingSafeAt(king, newRow, newCol) {
// Check if king would be safe at this position
var originalRow = king.row;
var originalCol = king.col;
var capturedPiece = getPieceAt(newRow, newCol);
// Temporarily move king to test position
gameBoard[originalRow][originalCol] = null;
gameBoard[newRow][newCol] = king;
king.row = newRow;
king.col = newCol;
// Check if king would be in check at new position (including snake threats)
var wouldBeInCheck = isInCheck(king.pieceColor);
// Additional check specifically for snake multi-move threats
if (!wouldBeInCheck) {
wouldBeInCheck = isKingThreatenedBySnake(king, newRow, newCol);
}
// Restore original position
king.row = originalRow;
king.col = originalCol;
gameBoard[originalRow][originalCol] = king;
gameBoard[newRow][newCol] = capturedPiece;
return !wouldBeInCheck;
}
function hasValidEscapeMoves(color) {
// Check if color has any valid moves when in check, considering snake threats
var king = findKing(color);
if (!king) return false;
// Check all possible king moves
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
var newRow = king.row + dr;
var newCol = king.col + dc;
if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 16) {
var targetPiece = getPieceAt(newRow, newCol);
if (!targetPiece || targetPiece.pieceColor !== color) {
if (isKingSafeAt(king, newRow, newCol)) {
return true; // Found a safe escape square
}
}
}
}
}
// Check if any other piece can block or capture to protect king
var allMoves = getAllValidMoves(color);
return allMoves.length > 0;
}
function findThreateningPiece(king) {
// Find the piece that is threatening the king, including snake multi-move threats
if (!king) return null;
// Check immediate threats first
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor !== king.pieceColor && piece.canMoveTo(king.row, king.col)) {
return piece;
}
}
// Check for snake multi-move threats
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceType === 'snake' && piece.pieceColor !== king.pieceColor && !piece.snakeOnCooldown) {
if (canSnakeReachPosition(piece, king.row, king.col)) {
return piece; // Snake can reach king position
}
}
}
return null;
}
function formatTime(milliseconds) {
var totalSeconds = Math.max(0, Math.floor(milliseconds / 1000));
var minutes = Math.floor(totalSeconds / 60);
var seconds = totalSeconds % 60;
return minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
}
function updateTimerDisplay() {
whiteTimerText.setText(formatTime(whiteTimeRemaining));
blackTimerText.setText(formatTime(blackTimeRemaining));
}
function startTimer() {
if (timerInterval) {
LK.clearInterval(timerInterval);
}
currentTurnStartTime = Date.now();
gameStartTime = gameStartTime || currentTurnStartTime;
timerInterval = LK.setInterval(function () {
if (gameState === 'playing' || gameState === 'check') {
var now = Date.now();
var elapsed = now - currentTurnStartTime;
if (currentPlayer === 'white') {
whiteTimeRemaining -= elapsed;
if (whiteTimeRemaining <= 0) {
whiteTimeRemaining = 0;
handleTimeOut('white');
return;
}
} else {
blackTimeRemaining -= elapsed;
if (blackTimeRemaining <= 0) {
blackTimeRemaining = 0;
handleTimeOut('black');
return;
}
}
updateTimerDisplay();
currentTurnStartTime = now;
}
}, 100);
}
function stopTimer() {
if (timerInterval) {
LK.clearInterval(timerInterval);
timerInterval = null;
}
}
function handleTimeOut(player) {
stopTimer();
var winner = player === 'white' ? 'Black' : 'White';
if (player === 'white') {
turnText.setText('Black side won! White ran out of time!');
} else {
turnText.setText('White Wins! Black ran out of time!');
}
gameState = 'gameover';
LK.setTimeout(function () {
restartGame();
}, 2000);
}
function updateLivesDisplay() {
// Update text
whiteLivesText.setText('Your Lives: ' + whiteKingLives);
blackLivesText.setText('Enemy Lives: ' + blackKingLives);
// Update visual hearts with smooth animation
for (var i = 0; i < 4; i++) {
if (i < whiteKingLives) {
tween(whiteLivesHearts[i], {
alpha: 1.0,
tint: 0xFFFFFF
}, {
duration: 300,
easing: tween.easeOut
});
} else {
tween(whiteLivesHearts[i], {
alpha: 0.3,
tint: 0x666666
}, {
duration: 300,
easing: tween.easeOut
});
}
if (i < blackKingLives) {
tween(blackLivesHearts[i], {
alpha: 1.0,
tint: 0xFF0000
}, {
duration: 300,
easing: tween.easeOut
});
} else {
tween(blackLivesHearts[i], {
alpha: 0.3,
tint: 0x666666
}, {
duration: 300,
easing: tween.easeOut
});
}
}
}
function updateStaminaDisplay() {
// Update stamina bar visual indicators
for (var i = 0; i < maxStaminaPoints; i++) {
if (i < staminaPoints) {
tween(staminaBars[i], {
alpha: 1.0,
tint: 0xff69b4
}, {
duration: 300,
easing: tween.easeOut
});
} else {
tween(staminaBars[i], {
alpha: 0.3,
tint: 0x666666
}, {
duration: 300,
easing: tween.easeOut
});
}
}
// Update AI stamina bar visual indicators
for (var i = 0; i < aiMaxStaminaPoints; i++) {
if (i < aiStaminaPoints) {
tween(aiStaminaBars[i], {
alpha: 1.0,
tint: 0xff69b4
}, {
duration: 300,
easing: tween.easeOut
});
} else {
tween(aiStaminaBars[i], {
alpha: 0.3,
tint: 0x666666
}, {
duration: 300,
easing: tween.easeOut
});
}
}
// Update Queen and Snake pink square highlighting
updateQueenSnakePinkSquares();
}
function activateAbility(color, remainingLives) {
var abilityName = '';
var abilityEffect = '';
switch (remainingLives) {
case 3:
abilityName = 'Shield Boost';
abilityEffect = 'All pieces gain temporary protection';
// Flash all pieces of that color green to show protection
flashPiecesOfColor(color, 0x00FF00, 1000);
break;
case 2:
abilityName = 'Speed Rush';
abilityEffect = 'Pieces move faster for next 3 turns';
// Make pieces glow blue to show speed boost
flashPiecesOfColor(color, 0x0099FF, 1500);
break;
case 1:
abilityName = 'Rage Mode';
abilityEffect = 'Double damage on next capture';
// Make pieces glow red to show rage
flashPiecesOfColor(color, 0xFF3333, 2000);
break;
case 0:
abilityName = 'Final Stand';
abilityEffect = 'Last desperate attempt';
break;
}
// Show ability activation message
var message = color.charAt(0).toUpperCase() + color.slice(1) + ' Ability: ' + abilityName;
var abilityText = new Text2(message, {
size: 60,
fill: color === 'white' ? 0xFFFFFF : 0xFF0000
});
abilityText.anchor.set(0.5, 0.5);
abilityText.x = 1024;
abilityText.y = 1200;
game.addChild(abilityText);
// Start with small scale and animate in
abilityText.scaleX = 0.5;
abilityText.scaleY = 0.5;
abilityText.alpha = 0;
// Animate ability text entrance
tween(abilityText, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 300,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Hold for a moment then fade out
LK.setTimeout(function () {
tween(abilityText, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0,
y: abilityText.y - 50
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
abilityText.destroy();
}
});
}, 1000);
}
});
}
function flashPiecesOfColor(color, flashColor, duration) {
for (var i = 0; i < pieces.length; i++) {
if (pieces[i].pieceColor === color) {
LK.effects.flashObject(pieces[i], flashColor, duration);
}
}
}
function applyPoisonBehindSnake(snake, oldRow, oldCol) {
// Apply poison to pieces around the snake's previous position (behind it)
var poisonPositions = [{
row: oldRow - 1,
col: oldCol - 1
}, {
row: oldRow - 1,
col: oldCol
}, {
row: oldRow - 1,
col: oldCol + 1
}, {
row: oldRow,
col: oldCol - 1
}, {
row: oldRow,
col: oldCol + 1
}, {
row: oldRow + 1,
col: oldCol - 1
}, {
row: oldRow + 1,
col: oldCol
}, {
row: oldRow + 1,
col: oldCol + 1
}];
for (var i = 0; i < poisonPositions.length; i++) {
var pos = poisonPositions[i];
if (pos.row >= 0 && pos.row < 8 && pos.col >= 0 && pos.col < 16) {
var pieceAtPos = getPieceAt(pos.row, pos.col);
if (pieceAtPos && pieceAtPos.pieceColor !== snake.pieceColor) {
// Kings are immune to poison - skip poisoning kings completely
if (pieceAtPos.pieceType === 'king') {
continue; // Skip poisoning kings
}
// Snake cannot poison the king
if (pieceAtPos.pieceType === 'king') {
continue; // Skip poisoning kings
}
pieceAtPos.applyPoison(3); // Poison for 3 turns
// Flash piece red to show poison application with proper closure
var targetPiece = pieceAtPos; // Capture the piece in closure
tween(targetPiece, {
tint: 0xff0000
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(targetPiece, {
tint: 0xff6666
}, {
duration: 200,
easing: tween.easeOut
});
}
});
}
}
}
}
function restartGame() {
// Stop timer when game ends
stopTimer();
// Determine winner before clearing state
var winner = whiteKingLives <= 0 ? 'Black' : 'White';
// Show win screen with team name
showWinScreen(winner);
}
function showWinScreen(winningTeam) {
// Create win screen overlay
var winScreen = new Container();
game.addChild(winScreen);
// Semi-transparent background
var background = LK.getAsset('darkSquare', {
anchorX: 0,
anchorY: 0,
alpha: 0.8,
scaleX: 20,
scaleY: 25
});
background.x = 0;
background.y = 0;
winScreen.addChild(background);
// Win text
var winText = new Text2(winningTeam + (winningTeam === 'Black' ? ' side won.' : ' Team Wins!'), {
size: 120,
fill: winningTeam === 'White' ? 0xFFFFFF : 0xFF0000
});
winText.anchor.set(0.5, 0.5);
winText.x = 1024;
winText.y = 1366;
winScreen.addChild(winText);
// Animate win text
tween(winText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeOut
});
// Auto-restart after 1 second
LK.setTimeout(function () {
winScreen.destroy();
actualRestartGame();
}, 1000);
}
function shufflePiecesForNewRound(pieceSetup) {
// Create array of non-pawn, non-king pieces for swapping
var swappablePieces = ['summoner', 'knight', 'bishop', 'queen', 'queen', 'knight', 'rook', 'jester', 'knight', 'bishop', 'princess', 'princess', 'snake', 'jester'];
// Create shuffled version of swappable pieces for each side
var shuffledPiecesBlack = swappablePieces.slice(); // Copy array for black
var shuffledPiecesWhite = swappablePieces.slice(); // Copy array for white
// Shuffle black pieces
for (var i = shuffledPiecesBlack.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = shuffledPiecesBlack[i];
shuffledPiecesBlack[i] = shuffledPiecesBlack[j];
shuffledPiecesBlack[j] = temp;
}
// Shuffle white pieces separately
for (var i = shuffledPiecesWhite.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = shuffledPiecesWhite[i];
shuffledPiecesWhite[i] = shuffledPiecesWhite[j];
shuffledPiecesWhite[j] = temp;
}
// Replace pieces in setup while keeping king in center
var blackPieceIndex = 0;
var whitePieceIndex = 0;
// Shuffle black pieces (top row)
for (var col = 0; col < 16; col++) {
if (pieceSetup[0][col] !== 'king' && pieceSetup[0][col] !== 'pawn') {
pieceSetup[0][col] = shuffledPiecesBlack[blackPieceIndex % shuffledPiecesBlack.length];
blackPieceIndex++;
}
}
// Create white setup by shuffling pieces for bottom row (this will be used in piece creation)
var whiteSetup = pieceSetup[0].slice(); // Copy the structure
for (var col = 0; col < 16; col++) {
if (whiteSetup[col] !== 'king' && whiteSetup[col] !== 'pawn') {
whiteSetup[col] = shuffledPiecesWhite[whitePieceIndex % shuffledPiecesWhite.length];
whitePieceIndex++;
}
}
// Store white setup for use in piece creation
pieceSetup.whiteSetup = whiteSetup;
return pieceSetup;
}
function randomlySwapNonPawnPieces(pieceSetup) {
// Keep the original function for backward compatibility
return shufflePiecesForNewRound(pieceSetup);
}
function actualRestartGame() {
// Clear all pieces
for (var i = 0; i < pieces.length; i++) {
pieces[i].destroy();
}
pieces = [];
// Clear highlights
clearHighlights();
// Clear pink squares
for (var i = 0; i < queenSnakePinkSquares.length; i++) {
if (!queenSnakePinkSquares[i].destroyed) {
queenSnakePinkSquares[i].destroy();
}
}
queenSnakePinkSquares = [];
// Clear king squares
if (kingSquare) {
kingSquare.destroy();
kingSquare = null;
}
if (blackKingSquare) {
blackKingSquare.destroy();
blackKingSquare = null;
}
// Reset game state
currentPlayer = 'white';
selectedPiece = null;
validMoves = [];
dragNode = null;
gameState = 'playing';
whiteKingLives = 4;
blackKingLives = 4;
currentSnakePiece = null;
snakeMovesRemaining = 0;
activeSnakePiece = null; // Reset active snake
// Reset stamina system
staminaPoints = 1;
aiStaminaPoints = 1;
// Clear stamina consumer tracking
window.whiteStaminaConsumers = [];
window.blackStaminaConsumers = [];
updateStaminaDisplay();
// Reset timer variables
whiteTimeRemaining = 60000;
blackTimeRemaining = 60000;
gameStartTime = null;
currentTurnStartTime = null;
// Clear hover tooltip system
if (hoverTimer) {
LK.clearTimeout(hoverTimer);
hoverTimer = null;
}
hidePieceTooltip();
hoveredPiece = null;
hoverStartTime = null;
// Snake cooldowns will be reset when new pieces are created - no need to access destroyed pieces
updateLivesDisplay();
turnText.setText('Your Turn');
// Clear board
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
gameBoard[row][col] = null;
}
}
// Create base piece setup and shuffle non-pawn pieces for new round
var basePieceSetup = [['summoner', 'knight', 'bishop', 'queen', 'king', 'queen', 'bishop', 'knight', 'rook', 'jester', 'knight', 'bishop', 'princess', 'princess', 'snake', 'jester'], ['pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn', 'pawn']];
var pieceSetup = shufflePiecesForNewRound(basePieceSetup);
// Create black pieces
for (var row = 0; row < 2; row++) {
for (var col = 0; col < 16; col++) {
var piece = new ChessPiece(pieceSetup[row][col], 'black', row, col);
pieces.push(piece);
gameBoard[row][col] = piece;
game.addChild(piece);
piece.updatePosition();
}
}
// Create white pieces
for (var row = 6; row < 8; row++) {
for (var col = 0; col < 16; col++) {
var pieceType = row === 6 ? 'pawn' : pieceSetup.whiteSetup[col];
var piece = new ChessPiece(pieceType, 'white', row, col);
pieces.push(piece);
gameBoard[row][col] = piece;
game.addChild(piece);
piece.updatePosition();
}
}
// Initialize king square after restart
updateKingSquare();
// Restart timer
updateTimerDisplay();
startTimer();
}
game.down = function (x, y, obj) {
if (gameState !== 'playing' && gameState !== 'check') return;
if (currentPlayer !== 'white') return; // Only allow white player to move pieces
// Clear hover tooltip when clicking
if (hoverTimer) {
LK.clearTimeout(hoverTimer);
hoverTimer = null;
}
hidePieceTooltip();
hoveredPiece = null;
var square = getSquareFromPosition(x, y);
if (!square) return;
var clickedPiece = getPieceAt(square.row, square.col);
// If we have a selected piece, try to move it
if (selectedPiece) {
// Check if clicked on valid move
var isValidMove = false;
for (var i = 0; i < validMoves.length; i++) {
if (validMoves[i].row === square.row && validMoves[i].col === square.col) {
isValidMove = true;
break;
}
}
if (isValidMove) {
movePiece(selectedPiece, square.row, square.col);
clearHighlights();
selectedPiece = null;
return;
} else {
// Invalid move or clicking elsewhere - clear selection
clearHighlights();
selectedPiece = null;
}
}
// Handle princess elimination mode
if (selectedPiece && selectedPiece.pieceType === 'princess' && selectedPiece.princessEliminationMode) {
if (clickedPiece && clickedPiece.pieceColor !== selectedPiece.pieceColor) {
// Check if target is within princess's elimination range (2 squares in any direction)
var rowDiff = Math.abs(clickedPiece.row - selectedPiece.row);
var colDiff = Math.abs(clickedPiece.col - selectedPiece.col);
var maxRange = Math.max(rowDiff, colDiff);
if (maxRange <= 2 && selectedPiece.princessSelectedTargets.indexOf(clickedPiece) === -1) {
selectedPiece.princessSelectedTargets.push(clickedPiece);
selectedPiece.princessTargetsSelected++;
highlightSquare(clickedPiece.row, clickedPiece.col, 'blue');
turnText.setText('Princess selected target ' + selectedPiece.princessTargetsSelected + '/2');
if (selectedPiece.princessTargetsSelected === 2) {
// Execute elimination
for (var i = 0; i < selectedPiece.princessSelectedTargets.length; i++) {
var target = selectedPiece.princessSelectedTargets[i];
for (var j = 0; j < pieces.length; j++) {
if (pieces[j] === target) {
gameBoard[target.row][target.col] = null;
target.destroy();
pieces.splice(j, 1);
break;
}
}
}
LK.getSound('capture').play();
// Reset princess state and switch turns
selectedPiece.princessEliminationMode = false;
selectedPiece.princessTargetsSelected = 0;
selectedPiece.princessSelectedTargets = [];
clearHighlights();
selectedPiece = null;
currentPlayer = currentPlayer === 'white' ? 'black' : 'white';
turnText.setText(currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1) + '\'s Turn');
if (currentPlayer === 'black') {
makeAIMove();
}
return;
}
}
}
return;
}
// If there's an active snake continuing its moves, only allow that snake to be selected
if (activeSnakePiece && activeSnakePiece.pieceColor === 'white' && snakeMovesRemaining > 0) {
if (clickedPiece === activeSnakePiece) {
selectedPiece = activeSnakePiece;
dragNode = activeSnakePiece;
showValidMoves(activeSnakePiece);
}
return;
}
// Normal piece selection - only allow white pieces to be selected
if (clickedPiece && clickedPiece.pieceColor === 'white') {
// Add selection animation
tween(clickedPiece, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(clickedPiece, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
}
});
// Check for princess double-click to enter elimination mode
if (clickedPiece.pieceType === 'princess' && selectedPiece === clickedPiece) {
clickedPiece.princessEliminationMode = true;
clickedPiece.princessTargetsSelected = 0;
clickedPiece.princessSelectedTargets = [];
clearHighlights();
highlightSquare(clickedPiece.row, clickedPiece.col, 'blue');
// Show elimination range (2 squares in all directions)
for (var dr = -2; dr <= 2; dr++) {
for (var dc = -2; dc <= 2; dc++) {
if (dr === 0 && dc === 0) continue; // Skip princess's own square
var targetRow = clickedPiece.row + dr;
var targetCol = clickedPiece.col + dc;
if (targetRow >= 0 && targetRow < 8 && targetCol >= 0 && targetCol < 16) {
var targetPiece = getPieceAt(targetRow, targetCol);
if (targetPiece && targetPiece.pieceColor !== clickedPiece.pieceColor) {
highlightSquare(targetRow, targetCol, 'green'); // Show valid targets in green
} else if (!targetPiece) {
highlightSquare(targetRow, targetCol, 'yellow'); // Show empty squares in elimination range
}
}
}
}
turnText.setText('Princess Elimination Mode: Select 2 enemies within 2 squares');
return;
}
// Select the piece and show valid moves
selectedPiece = clickedPiece;
dragNode = clickedPiece;
showValidMoves(clickedPiece);
}
};
game.move = function (x, y, obj) {
if (dragNode) {
dragNode.x = x;
dragNode.y = y;
}
// Handle piece hover for tooltips
var square = getSquareFromPosition(x, y);
var currentPiece = null;
if (square) {
currentPiece = getPieceAt(square.row, square.col);
}
// Check if we're hovering over a different piece or no piece
if (currentPiece !== hoveredPiece) {
// Clear existing hover state
if (hoverTimer) {
LK.clearTimeout(hoverTimer);
hoverTimer = null;
}
hidePieceTooltip();
// Set new hover state
hoveredPiece = currentPiece;
if (hoveredPiece) {
hoverStartTime = Date.now();
hoverTimer = LK.setTimeout(function () {
if (hoveredPiece && !hoverTooltip) {
showPieceTooltip(hoveredPiece, x, y);
}
}, HOVER_DELAY);
}
}
};
game.up = function (x, y, obj) {
if (dragNode && selectedPiece) {
var square = getSquareFromPosition(x, y);
if (square) {
var isValidMove = false;
for (var i = 0; i < validMoves.length; i++) {
if (validMoves[i].row === square.row && validMoves[i].col === square.col) {
isValidMove = true;
break;
}
}
if (isValidMove) {
movePiece(selectedPiece, square.row, square.col);
clearHighlights();
selectedPiece = null;
} else {
selectedPiece.updatePosition(true);
}
} else {
selectedPiece.updatePosition(true);
}
}
dragNode = null;
};
game.update = function () {
// Game logic runs automatically through event handlers
};
// Track the last player move for imitation
var lastPlayerMove = null;
function recordPlayerMove(piece, fromRow, fromCol, toRow, toCol) {
if (piece.pieceColor === 'white') {
lastPlayerMove = {
pieceType: piece.pieceType,
fromRow: fromRow,
fromCol: fromCol,
toRow: toRow,
toCol: toCol,
rowDiff: toRow - fromRow,
colDiff: toCol - fromCol
};
}
}
function findWrongMove(validMoves) {
// Choose a move that's clearly suboptimal
var wrongMoves = [];
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var moveValue = evaluateMovePerfectly(move, 2);
// Consider moves that are clearly bad (negative value or very low)
if (moveValue < 10) {
wrongMoves.push(move);
}
// Also consider moves that put pieces in danger
if (isPieceUnderThreatAt(move.piece, move.toRow, move.toCol)) {
wrongMoves.push(move);
}
}
// If no clearly wrong moves, pick a random low-value move
if (wrongMoves.length === 0) {
var sortedMoves = validMoves.slice().sort(function (a, b) {
return evaluateMovePerfectly(a, 2) - evaluateMovePerfectly(b, 2);
});
wrongMoves = sortedMoves.slice(0, Math.min(3, sortedMoves.length));
}
return wrongMoves.length > 0 ? wrongMoves[Math.floor(Math.random() * wrongMoves.length)] : null;
}
function findNormalAIMove(validMoves) {
// Find a decent move using simplified evaluation
var bestMove = null;
var bestScore = -1000;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var score = 0;
// Basic capture evaluation
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
score += getPieceValue(targetPiece.pieceType) * 10;
}
// Basic safety check
if (!isPieceUnderThreatAt(move.piece, move.toRow, move.toCol)) {
score += 5;
}
// Basic center control
var centerDistance = Math.abs(move.toRow - 3.5) + Math.abs(move.toCol - 7.5);
score += (16 - centerDistance) * 0.5;
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
}
return bestMove;
}
function findPlayerImitationMove(validMoves) {
if (!lastPlayerMove) return null;
// Try to find a similar move pattern
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
// Check if piece type matches
if (move.piece.pieceType === lastPlayerMove.pieceType) {
// Check if move pattern is similar (same direction/distance)
var rowDiff = move.toRow - move.piece.row;
var colDiff = move.toCol - move.piece.col;
if (rowDiff === lastPlayerMove.rowDiff && colDiff === lastPlayerMove.colDiff) {
return move; // Exact pattern match
}
// Check for similar pattern (same direction, different distance)
var playerDirection = {
row: lastPlayerMove.rowDiff === 0 ? 0 : lastPlayerMove.rowDiff > 0 ? 1 : -1,
col: lastPlayerMove.colDiff === 0 ? 0 : lastPlayerMove.colDiff > 0 ? 1 : -1
};
var moveDirection = {
row: rowDiff === 0 ? 0 : rowDiff > 0 ? 1 : -1,
col: colDiff === 0 ? 0 : colDiff > 0 ? 1 : -1
};
if (playerDirection.row === moveDirection.row && playerDirection.col === moveDirection.col) {
return move; // Same direction
}
}
}
// If no exact imitation possible, try similar piece types
var similarPieceTypes = {
'queen': ['princess', 'rook', 'bishop'],
'princess': ['queen', 'rook'],
'rook': ['queen', 'princess'],
'bishop': ['queen'],
'knight': ['jester'],
'jester': ['knight'],
'snake': ['queen', 'princess']
};
if (similarPieceTypes[lastPlayerMove.pieceType]) {
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (similarPieceTypes[lastPlayerMove.pieceType].indexOf(move.piece.pieceType) !== -1) {
return move; // Similar piece type
}
}
}
return null;
}
function findAggressiveSnakeMove(validMoves) {
// AI uses snake aggressively like a queen for attacks and board control
var bestSnakeMove = null;
var bestValue = 0;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (move.piece.pieceType === 'snake') {
var moveValue = 0;
// Prioritize captures with snake
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
moveValue += getPieceValue(targetPiece.pieceType) * 2; // Double weight for snake captures
}
// Prioritize moves that put enemy king in check
if (simulateMove(move, function () {
return isInCheck('white');
})) {
moveValue += 15; // High priority for check moves
}
// Prioritize moves that control center squares
var centerDistance = Math.abs(move.toRow - 3.5) + Math.abs(move.toCol - 7.5);
moveValue += (16 - centerDistance) * 0.5; // Favor central positions
// Prioritize moves that attack multiple pieces
var attackCount = 0;
var whitePieces = pieces.filter(function (p) {
return p.pieceColor === 'white';
});
simulateMove(move, function () {
for (var j = 0; j < whitePieces.length; j++) {
if (move.piece.canMoveTo(whitePieces[j].row, whitePieces[j].col)) {
attackCount++;
}
}
return false;
});
moveValue += attackCount * 3; // Bonus for forking
// Avoid moves that put snake in immediate danger unless high value
if (!isPieceUnderThreatAt(move.piece, move.toRow, move.toCol) || moveValue > 10) {
if (moveValue > bestValue) {
bestValue = moveValue;
bestSnakeMove = move;
}
}
}
}
return bestSnakeMove;
}
function findSnakeTacticalMove(validMoves) {
// Find tactical moves specifically for snake pieces - aggressive positioning
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (move.piece.pieceType === 'snake') {
// Snake moves to attack enemy pieces near their king
var whiteKing = findKing('white');
if (whiteKing) {
var distanceToKing = Math.abs(move.toRow - whiteKing.row) + Math.abs(move.toCol - whiteKing.col);
// Prioritize moves that get snake closer to enemy king area
if (distanceToKing <= 3) {
var currentDistance = Math.abs(move.piece.row - whiteKing.row) + Math.abs(move.piece.col - whiteKing.col);
if (distanceToKing < currentDistance) {
return move; // Move closer to king
}
}
}
// Snake moves to create pressure on enemy back rank
if (move.toRow <= 2) {
// Near enemy back rank
return move;
}
// Snake moves to control key squares
var keySquares = [{
row: 3,
col: 7
}, {
row: 3,
col: 8
}, {
row: 4,
col: 7
}, {
row: 4,
col: 8
}]; // Center control
for (var j = 0; j < keySquares.length; j++) {
if (move.toRow === keySquares[j].row && move.toCol === keySquares[j].col) {
return move;
}
}
}
}
return null;
}
function findDefensivePrincessMove(validMoves) {
// AI uses princess and queen more timidly for solid compression and defensive checkmate setups
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (move.piece.pieceType === 'princess' || move.piece.pieceType === 'queen') {
// Queen stays back and supports other pieces
var backRankSafety = move.toRow <= 2; // Stay in back ranks
if (backRankSafety) {
// Quenn supports checkmate patterns by controlling escape squares
var whiteKing = findKing('white');
if (whiteKing) {
// Check if queen move controls king's escape squares
var controlsEscape = false;
var escapeSquares = [{
row: whiteKing.row - 1,
col: whiteKing.col - 1
}, {
row: whiteKing.row - 1,
col: whiteKing.col
}, {
row: whiteKing.row - 1,
col: whiteKing.col + 1
}, {
row: whiteKing.row,
col: whiteKing.col - 1
}, {
row: whiteKing.row,
col: whiteKing.col + 1
}, {
row: whiteKing.row + 1,
col: whiteKing.col - 1
}, {
row: whiteKing.row + 1,
col: whiteKing.col
}, {
row: whiteKing.row + 1,
col: whiteKing.col + 1
}];
simulateMove(move, function () {
for (var j = 0; j < escapeSquares.length; j++) {
var escSquare = escapeSquares[j];
if (escSquare.row >= 0 && escSquare.row < 8 && escSquare.col >= 0 && escSquare.col < 16) {
if (move.piece.canMoveTo(escSquare.row, escSquare.col)) {
controlsEscape = true;
break;
}
}
}
return false;
});
if (controlsEscape) {
return move; // Queen controls escape squares for checkmate
}
}
// Queen defends other pieces
var defendsAllies = false;
simulateMove(move, function () {
for (var j = 0; j < pieces.length; j++) {
var ally = pieces[j];
if (ally.pieceColor === 'black' && ally !== move.piece) {
if (isPieceUnderThreat(ally)) {
// Check if queen can defend this piece
if (move.piece.canMoveTo(ally.row, ally.col)) {
defendsAllies = true;
break;
}
}
}
}
return false;
});
if (defendsAllies) {
return move; // Queen defends allies
}
}
// Only make aggressive queen moves if very safe and high value
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
var captureValue = getPieceValue(targetPiece.pieceType);
if (captureValue >= 5 && !isPieceUnderThreatAt(move.piece, move.toRow, move.toCol)) {
return move; // Safe high-value capture only
}
}
}
}
return null;
}
function isKingThreatenedBySnake(king, kingRow, kingCol) {
// Check if any enemy snake can reach the king position through its multi-move capability
var enemyColor = king.pieceColor === 'white' ? 'black' : 'white';
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceType === 'snake' && piece.pieceColor === enemyColor && !piece.snakeOnCooldown) {
// Check if snake can reach king in 1-3 moves
if (canSnakeReachPosition(piece, kingRow, kingCol)) {
return true;
}
}
}
return false;
}
function canSnakeReachPosition(snake, targetRow, targetCol) {
// Check if snake can reach target position within its 3-move sequence
// This simulates the snake's complex movement pattern
var visited = {};
function canReachRecursive(currentRow, currentCol, movesLeft, moveType) {
if (movesLeft === 0) return false;
if (currentRow === targetRow && currentCol === targetCol) return true;
var key = currentRow + ',' + currentCol + ',' + movesLeft + ',' + moveType;
if (visited[key]) return false;
visited[key] = true;
// Generate possible moves based on snake move type
var possibleMoves = [];
if (moveType === 0) {
// First move: straight lines only
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
// Must be straight line movement
if (dr === 0 || dc === 0 || Math.abs(dr) === Math.abs(dc)) {
var newRow = currentRow + dr;
var newCol = currentCol + dc;
if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 16) {
var piece = getPieceAt(newRow, newCol);
if (!piece || piece.pieceColor !== snake.pieceColor) {
possibleMoves.push({
row: newRow,
col: newCol,
nextMoveType: 1
});
}
}
}
}
}
} else if (moveType === 1) {
// Second move: diagonal only
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (Math.abs(dr) === Math.abs(dc) && Math.abs(dr) === 1) {
var newRow = currentRow + dr;
var newCol = currentCol + dc;
if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 16) {
var piece = getPieceAt(newRow, newCol);
if (!piece || piece.pieceColor !== snake.pieceColor) {
possibleMoves.push({
row: newRow,
col: newCol,
nextMoveType: 2
});
}
}
}
}
}
} else if (moveType === 2) {
// Third move: any direction
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue;
var newRow = currentRow + dr;
var newCol = currentCol + dc;
if (newRow >= 0 && newRow < 8 && newCol >= 0 && newCol < 16) {
var piece = getPieceAt(newRow, newCol);
if (!piece || piece.pieceColor !== snake.pieceColor) {
possibleMoves.push({
row: newRow,
col: newCol,
nextMoveType: 3
});
}
}
}
}
}
// Try each possible move
for (var j = 0; j < possibleMoves.length; j++) {
var move = possibleMoves[j];
if (canReachRecursive(move.row, move.col, movesLeft - 1, move.nextMoveType)) {
return true;
}
}
return false;
}
// Start with snake's current position and first move type
return canReachRecursive(snake.row, snake.col, snake.maxSnakeMoves, 0);
}
function evaluateMovePerfectly(move, depth) {
// Perfect evaluation with grandmaster-level understanding
var score = 0;
var piece = move.piece;
var targetPiece = getPieceAt(move.toRow, move.toCol);
// 1. Checkmate detection (perfect priority)
if (simulateMove(move, function () {
return isCheckmate('white');
})) {
return 50000; // Mate is always best
}
// 2. Prevent our own checkmate (critical)
var opponentThreats = getAllValidMoves('white');
var preventsCheckmate = false;
for (var i = 0; i < opponentThreats.length; i++) {
if (simulateMove(move, function () {
return !simulateOpponentMove(opponentThreats[i], function () {
return isCheckmate('black');
});
})) {
preventsCheckmate = true;
break;
}
}
if (preventsCheckmate) score += 10000;
// 3. Perfect material evaluation
if (targetPiece && targetPiece.pieceColor === 'white') {
var captureValue = getPieceValue(targetPiece.pieceType);
var exchangeValue = calculatePerfectExchange(move, depth);
score += exchangeValue * 100;
// Grandmaster piece priorities
if (targetPiece.pieceType === 'queen') score += 900;
if (targetPiece.pieceType === 'princess') score += 800;
if (targetPiece.pieceType === 'snake') score += 600;
if (targetPiece.pieceType === 'rook') score += 500;
}
// 4. Perfect tactical pattern recognition
score += evaluatePerfectTactics(move, depth);
// 5. Strategic piece coordination
score += evaluatePerfectStrategy(move);
// 6. King attack precision
score += evaluatePerfectKingAttack(move);
// 7. Basic positional evaluation
score += evaluatePositionalFactors(move);
return score;
}
function calculatePerfectCoordination(move) {
// Calculate how well pieces coordinate after this move
var coordinationScore = 0;
return simulateMove(move, function () {
// Count pieces that support each other
var supportingPairs = 0;
var totalSupport = 0;
for (var i = 0; i < pieces.length; i++) {
var piece1 = pieces[i];
if (piece1.pieceColor === 'black') {
var supportCount = 0;
for (var j = 0; j < pieces.length; j++) {
var piece2 = pieces[j];
if (piece2.pieceColor === 'black' && piece1 !== piece2) {
// Check if piece2 defends piece1
if (piece2.canMoveTo(piece1.row, piece1.col)) {
supportCount++;
totalSupport++;
}
}
}
if (supportCount > 0) {
supportingPairs++;
}
}
}
// Calculate coordination bonuses
coordinationScore += supportingPairs * 3; // Bonus for each supported piece
coordinationScore += totalSupport * 2; // Bonus for total defensive connections
// Special coordination patterns
// Queen and rook coordination
var blackQueens = pieces.filter(function (p) {
return p.pieceColor === 'black' && p.pieceType === 'queen';
});
var blackRooks = pieces.filter(function (p) {
return p.pieceColor === 'black' && p.pieceType === 'rook';
});
for (var i = 0; i < blackQueens.length; i++) {
for (var j = 0; j < blackRooks.length; j++) {
var rowDiff = Math.abs(blackQueens[i].row - blackRooks[j].row);
var colDiff = Math.abs(blackQueens[i].col - blackRooks[j].col);
if (rowDiff === 0 || colDiff === 0) {
coordinationScore += 5; // Same rank/file coordination
}
}
}
// Bishop pair coordination
var blackBishops = pieces.filter(function (p) {
return p.pieceColor === 'black' && p.pieceType === 'bishop';
});
if (blackBishops.length >= 2) {
coordinationScore += 8; // Bishop pair bonus
}
return coordinationScore;
});
}
function calculatePerfectExchange(move, depth) {
// Calculate exact exchange value with perfect accuracy
var ourPiece = move.piece;
var enemyPiece = getPieceAt(move.toRow, move.toCol);
if (!enemyPiece) return 0;
var exchangeSequence = [];
var currentPos = {
row: move.toRow,
col: move.toCol
};
// Calculate all pieces that can recapture
var recapturers = [];
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'white' && piece.canMoveTo(currentPos.row, currentPos.col)) {
recapturers.push({
piece: piece,
value: getPieceValue(piece.pieceType)
});
}
}
// Sort by piece value (sacrifice lowest first)
recapturers.sort(function (a, b) {
return a.value - b.value;
});
var balance = getPieceValue(enemyPiece.pieceType);
var attackerValue = getPieceValue(ourPiece.pieceType);
// Perfect exchange calculation
for (var i = 0; i < Math.min(recapturers.length, depth); i++) {
if (i % 2 === 0) {
balance -= attackerValue;
} else {
balance += recapturers[Math.floor(i / 2)].value;
}
attackerValue = i < recapturers.length ? recapturers[i].value : 0;
}
return balance;
}
function evaluatePerfectTactics(move, depth) {
var score = 0;
// Perfect fork detection
var forkValue = calculatePerfectFork(move);
score += forkValue * 50;
// Perfect pin detection
if (createsPerfectPin(move)) score += 200;
// Perfect discovered attack
if (createsPerfectDiscovery(move)) score += 250;
// Perfect deflection
if (createsPerfectDeflection(move)) score += 180;
// Perfect skewer
if (createsPerfectSkewer(move)) score += 220;
return score;
}
function evaluatePerfectStrategy(move) {
var score = 0;
// Perfect piece coordination
var coordinationValue = calculatePerfectCoordination(move);
score += coordinationValue * 15;
// Basic piece activity
score += evaluatePositionalFactors(move) * 0.5;
// Basic tactical patterns
score += evaluateTacticalPatterns(move) * 0.8;
return score;
}
function evaluatePerfectKingAttack(move) {
var score = 0;
var enemyKing = findKing('white');
if (!enemyKing) return 0;
return simulateMove(move, function () {
var attackingPieces = 0;
var controlledSquares = 0;
// Count pieces attacking king area
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'black') {
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
var kingArea = {
row: enemyKing.row + dr,
col: enemyKing.col + dc
};
if (kingArea.row >= 0 && kingArea.row < 8 && kingArea.col >= 0 && kingArea.col < 16) {
if (piece.canMoveTo(kingArea.row, kingArea.col)) {
controlledSquares++;
if (dr === 0 && dc === 0) attackingPieces++;
}
}
}
}
}
}
return attackingPieces * 30 + controlledSquares * 10;
});
}
function countThreatsToKing(color) {
var king = findKing(color);
if (!king) return 0;
var threats = 0;
var enemyColor = color === 'white' ? 'black' : 'white';
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === enemyColor && piece.canMoveTo(king.row, king.col)) {
threats++;
}
}
return threats;
}
function calculateExchangeValue(move) {
var attackerValue = getPieceValue(move.piece.pieceType);
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (!targetPiece) return 0;
var defenderValue = getPieceValue(targetPiece.pieceType);
// Simulate the capture and see if opponent can recapture
return simulateMove(move, function () {
var opponentMoves = getAllValidMoves('white');
var canRecapture = false;
var recaptureValue = 0;
for (var i = 0; i < opponentMoves.length; i++) {
var opMove = opponentMoves[i];
if (opMove.toRow === move.toRow && opMove.toCol === move.toCol) {
canRecapture = true;
recaptureValue = Math.min(recaptureValue || attackerValue, getPieceValue(opMove.piece.pieceType));
}
}
if (canRecapture) {
return defenderValue - recaptureValue;
} else {
return defenderValue;
}
});
}
function evaluateTacticalPatterns(move) {
var score = 0;
// Check gives reduced bonus (more defensive approach)
if (simulateMove(move, function () {
return isInCheck('white');
})) {
score += 15; // Reduced from 30
}
// Fork pattern - attacking multiple pieces (less aggressive)
var forkTargets = 0;
simulateMove(move, function () {
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'white' && move.piece.canMoveTo(piece.row, piece.col)) {
forkTargets++;
}
}
return false;
});
if (forkTargets >= 2) {
score += 20; // Reduced from 40
} else if (forkTargets >= 1) {
score += 5; // Reduced from 10
}
// Pin detection (reduced)
if (createsPinPattern(move)) {
score += 12; // Reduced from 25
}
// Discovered attack (reduced)
if (createsDiscoveredAttack(move)) {
score += 17; // Reduced from 35
}
return score;
}
function evaluatePieceSpecificMove(move) {
var score = 0;
var piece = move.piece;
switch (piece.pieceType) {
case 'snake':
// Snake special abilities
score += evaluateSnakeMove(move);
break;
case 'princess':
// Princess elimination potential
score += evaluatePrincessMove(move);
break;
case 'summoner':
// Summoner positioning and summoning potential
score += evaluateSummonerMove(move);
break;
case 'queen':
// Queen positioning and power
score += evaluateQueenMove(move);
break;
case 'knight':
// Knight outpost evaluation
score += evaluateKnightMove(move);
break;
case 'rook':
// Rook file and rank control
score += evaluateRookMove(move);
break;
case 'bishop':
// Bishop diagonal control
score += evaluateBishopMove(move);
break;
case 'pawn':
// Pawn advancement and promotion
score += evaluatePawnMove(move);
break;
}
return score;
}
function evaluateSnakeMove(move) {
var score = 0;
// Reduced poison aggression - only target when safe
var poisonTargets = 0;
simulateMove(move, function () {
var poisonPositions = [{
row: move.toRow - 1,
col: move.toCol - 1
}, {
row: move.toRow - 1,
col: move.toCol
}, {
row: move.toRow - 1,
col: move.toCol + 1
}, {
row: move.toRow,
col: move.toCol - 1
}, {
row: move.toRow,
col: move.toCol + 1
}, {
row: move.toRow + 1,
col: move.toCol - 1
}, {
row: move.toRow + 1,
col: move.toCol
}, {
row: move.toRow + 1,
col: move.toCol + 1
}];
for (var i = 0; i < poisonPositions.length; i++) {
var pos = poisonPositions[i];
if (pos.row >= 0 && pos.row < 8 && pos.col >= 0 && pos.col < 16) {
var targetPiece = getPieceAt(pos.row, pos.col);
if (targetPiece && targetPiece.pieceColor === 'white' && targetPiece.pieceType !== 'king' && targetPiece.pieceType !== 'queen') {
poisonTargets++;
score += getPieceValue(targetPiece.pieceType) * 2; // Reduced from 5
}
}
}
return false;
});
// Reduced multi-move tactical advantage
if (move.piece.snakeMovesThisTurn === 0) {
score += 8; // Reduced from 15
}
// Favor defensive positioning
var distanceFromOwnKing = Math.abs(move.toRow - findKing('black').row) + Math.abs(move.toCol - findKing('black').col);
if (distanceFromOwnKing <= 4) {
score += 10; // Bonus for staying near own king
}
return score;
}
function evaluateSummonerMove(move) {
var score = 0;
// Bonus for positioning summoner to summon in useful positions
if (move.piece.summonerMoveCount >= 2) {
// About to summon, evaluate summoning positions
var summonPositions = [{
row: move.toRow - 1,
col: move.toCol
},
// up
{
row: move.toRow,
col: move.toCol + 1
},
// right
{
row: move.toRow + 1,
col: move.toCol
},
// down
{
row: move.toRow,
col: move.toCol - 1
},
// left
{
row: move.toRow - 1,
col: move.toCol + 1
},
// up-right
{
row: move.toRow + 1,
col: move.toCol + 1
},
// down-right
{
row: move.toRow + 1,
col: move.toCol - 1
},
// down-left
{
row: move.toRow - 1,
col: move.toCol - 1
} // up-left
];
var goodSummonSpots = 0;
for (var i = 0; i < summonPositions.length && goodSummonSpots < 2; i++) {
var pos = summonPositions[i];
if (pos.row >= 0 && pos.row < 8 && pos.col >= 0 && pos.col < 16) {
if (!getPieceAt(pos.row, pos.col)) {
goodSummonSpots++;
// Bonus for summoning in aggressive positions
if (move.piece.pieceColor === 'black' && pos.row >= 4 || move.piece.pieceColor === 'white' && pos.row <= 3) {
score += 15;
} else {
score += 8;
}
}
}
}
if (goodSummonSpots >= 2) {
score += 25; // Bonus for full summoning potential
}
}
// Strategic positioning like princess
var centralControl = 0;
var centerSquares = [{
row: 3,
col: 7
}, {
row: 3,
col: 8
}, {
row: 4,
col: 7
}, {
row: 4,
col: 8
}];
for (var i = 0; i < centerSquares.length; i++) {
var square = centerSquares[i];
if (Math.abs(move.toRow - square.row) <= 2 && Math.abs(move.toCol - square.col) <= 2) {
centralControl++;
}
}
score += centralControl * 3;
return score;
}
;
function evaluatePrincessMove(move) {
var score = 0;
// Evaluate elimination potential within 2 squares
var enemiesInRange = [];
for (var dr = -2; dr <= 2; dr++) {
for (var dc = -2; dc <= 2; dc++) {
if (dr === 0 && dc === 0) continue;
var checkRow = move.toRow + dr;
var checkCol = move.toCol + dc;
if (checkRow >= 0 && checkRow < 8 && checkCol >= 0 && checkCol < 16) {
var piece = getPieceAt(checkRow, checkCol);
if (piece && piece.pieceColor === 'white') {
enemiesInRange.push(piece);
score += getPieceValue(piece.pieceType) * 2;
}
}
}
}
// Bonus for positioning where elimination can capture 2+ high-value pieces
if (enemiesInRange.length >= 2) {
// Sort enemies by value and consider best 2
enemiesInRange.sort(function (a, b) {
return getPieceValue(b.pieceType) - getPieceValue(a.pieceType);
});
var topTwoValue = getPieceValue(enemiesInRange[0].pieceType) + getPieceValue(enemiesInRange[1].pieceType);
score += topTwoValue * 5; // High bonus for double elimination potential
}
return score;
}
function evaluateQueenMove(move) {
var score = 0;
// Queens should control center and support attacks
var centralControl = 0;
var centerSquares = [{
row: 3,
col: 7
}, {
row: 3,
col: 8
}, {
row: 4,
col: 7
}, {
row: 4,
col: 8
}];
simulateMove(move, function () {
for (var i = 0; i < centerSquares.length; i++) {
var square = centerSquares[i];
if (move.piece.canMoveTo(square.row, square.col)) {
centralControl++;
}
}
return false;
});
score += centralControl * 5;
// Bonus for long-range queen moves
var rowDiff = Math.abs(move.toRow - move.piece.row);
var colDiff = Math.abs(move.toCol - move.piece.col);
var moveDistance = Math.max(rowDiff, colDiff);
if (moveDistance > 2) {
score += 5; // Bonus for using queen's long-range capability
}
// Support other pieces
var supportedAllies = countSupportedAllies(move);
score += supportedAllies * 8;
return score;
}
function evaluatePositionalFactors(move) {
var score = 0;
// Center control
var centerDistance = Math.abs(move.toRow - 3.5) + Math.abs(move.toCol - 7.5);
score += (16 - centerDistance) * 2;
// Piece mobility after move
var mobilityAfter = simulateMove(move, function () {
var validMovesAfter = 0;
for (var row = 0; row < 8; row++) {
for (var col = 0; col < 16; col++) {
if (move.piece.canMoveTo(row, col)) {
validMovesAfter++;
}
}
}
return validMovesAfter;
});
score += mobilityAfter * 1;
// Safety evaluation
if (isPieceUnderThreatAt(move.piece, move.toRow, move.toCol)) {
score -= getPieceValue(move.piece.pieceType) * 3;
}
return score;
}
function evaluateKingAttack(move) {
var score = 0;
var whiteKing = findKing('white');
if (!whiteKing) return 0;
// Reduced emphasis on approaching enemy king
var distanceToKing = Math.abs(move.toRow - whiteKing.row) + Math.abs(move.toCol - whiteKing.col);
var currentDistance = Math.abs(move.piece.row - whiteKing.row) + Math.abs(move.piece.col - whiteKing.col);
if (distanceToKing < currentDistance) {
score += (currentDistance - distanceToKing) * 1; // Reduced from 3
}
// Reduced control emphasis around enemy king
var controlledKingSquares = 0;
simulateMove(move, function () {
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
var kingAdjacentRow = whiteKing.row + dr;
var kingAdjacentCol = whiteKing.col + dc;
if (kingAdjacentRow >= 0 && kingAdjacentRow < 8 && kingAdjacentCol >= 0 && kingAdjacentCol < 16) {
if (move.piece.canMoveTo(kingAdjacentRow, kingAdjacentCol)) {
controlledKingSquares++;
}
}
}
}
return false;
});
score += controlledKingSquares * 3; // Reduced from 8
return score;
}
function evaluatePieceCoordination(move) {
var score = 0;
// Count how many allied pieces this move supports
var supportedAllies = countSupportedAllies(move);
score += supportedAllies * 5;
// Count how many allied pieces support this piece at new position
var supportingAllies = countSupportingAllies(move);
score += supportingAllies * 3;
return score;
}
function countSupportedAllies(move) {
var supported = 0;
return simulateMove(move, function () {
for (var i = 0; i < pieces.length; i++) {
var ally = pieces[i];
if (ally.pieceColor === 'black' && ally !== move.piece) {
if (move.piece.canMoveTo(ally.row, ally.col)) {
supported++;
}
}
}
return supported;
});
}
function countSupportingAllies(move) {
var supporting = 0;
return simulateMove(move, function () {
for (var i = 0; i < pieces.length; i++) {
var ally = pieces[i];
if (ally.pieceColor === 'black' && ally !== move.piece) {
if (ally.canMoveTo(move.toRow, move.toCol)) {
supporting++;
}
}
}
return supporting;
});
}
function createsPinPattern(move) {
var whiteKing = findKing('white');
if (!whiteKing) return false;
return simulateMove(move, function () {
// Check if this move creates a pin on enemy pieces
for (var i = 0; i < pieces.length; i++) {
var enemyPiece = pieces[i];
if (enemyPiece.pieceColor === 'white' && enemyPiece !== whiteKing) {
// Temporarily remove the piece and see if king becomes attackable
var originalRow = enemyPiece.row;
var originalCol = enemyPiece.col;
gameBoard[originalRow][originalCol] = null;
var pinExists = move.piece.canMoveTo(whiteKing.row, whiteKing.col);
gameBoard[originalRow][originalCol] = enemyPiece;
if (pinExists) {
return true;
}
}
}
return false;
});
}
function createsDiscoveredAttack(move) {
// Check if moving this piece uncovers an attack from another piece
var originalRow = move.piece.row;
var originalCol = move.piece.col;
// Temporarily move piece and check if any ally can now attack
return simulateMove(move, function () {
for (var i = 0; i < pieces.length; i++) {
var ally = pieces[i];
if (ally.pieceColor === 'black' && ally !== move.piece) {
for (var j = 0; j < pieces.length; j++) {
var enemy = pieces[j];
if (enemy.pieceColor === 'white') {
// Check if ally can now attack enemy after piece moved
var couldAttackBefore = false;
// Temporarily put piece back at original position
gameBoard[move.toRow][move.toCol] = null;
gameBoard[originalRow][originalCol] = move.piece;
move.piece.row = originalRow;
move.piece.col = originalCol;
couldAttackBefore = ally.canMoveTo(enemy.row, enemy.col);
// Put piece back at new position
gameBoard[originalRow][originalCol] = null;
gameBoard[move.toRow][move.toCol] = move.piece;
move.piece.row = move.toRow;
move.piece.col = move.toCol;
var canAttackAfter = ally.canMoveTo(enemy.row, enemy.col);
if (!couldAttackBefore && canAttackAfter) {
return true;
}
}
}
}
}
return false;
});
}
function findBestPrincessElimination() {
// Find AI princess pieces that can use elimination ability
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceType === 'princess' && piece.pieceColor === 'black') {
// Find enemies within 2 squares of princess
var targetsInRange = [];
for (var dr = -2; dr <= 2; dr++) {
for (var dc = -2; dc <= 2; dc++) {
if (dr === 0 && dc === 0) continue;
var checkRow = piece.row + dr;
var checkCol = piece.col + dc;
if (checkRow >= 0 && checkRow < 8 && checkCol >= 0 && checkCol < 16) {
var targetPiece = getPieceAt(checkRow, checkCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
targetsInRange.push(targetPiece);
}
}
}
}
// If we can eliminate 2+ pieces, prioritize highest value targets
if (targetsInRange.length >= 2) {
// Sort by piece value, highest first
targetsInRange.sort(function (a, b) {
return getPieceValue(b.pieceType) - getPieceValue(a.pieceType);
});
// Check if the top 2 targets are worth eliminating
var topTwoValue = getPieceValue(targetsInRange[0].pieceType) + getPieceValue(targetsInRange[1].pieceType);
if (topTwoValue >= 6) {
// Worth at least 6 points total
// Execute princess elimination
LK.setTimeout(function () {
// Remove the two highest value targets
for (var j = 0; j < 2; j++) {
var target = targetsInRange[j];
for (var k = 0; k < pieces.length; k++) {
if (pieces[k] === target) {
gameBoard[target.row][target.col] = null;
target.destroy();
pieces.splice(k, 1);
break;
}
}
}
LK.getSound('capture').play();
turnText.setText('Black Princess eliminates 2 pieces!');
// Switch turns
currentPlayer = 'white';
LK.setTimeout(function () {
turnText.setText('Your Turn');
}, 1000);
}, 800);
return {
piece: piece,
isElimination: true
}; // Special marker for elimination
}
}
}
}
return null;
}
function findBestDefensiveMove(validMoves) {
var bestDefense = null;
var bestScore = -1000;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var score = 0;
// Check if this move gets us out of check
if (simulateMove(move, function () {
return !isInCheck('black');
})) {
score += 100;
// Prefer counter-attacks when escaping check
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
score += getPieceValue(targetPiece.pieceType) * 10;
}
if (score > bestScore) {
bestScore = score;
bestDefense = move;
}
}
}
return bestDefense;
}
// Enhanced piece-specific evaluation functions
function evaluateKnightMove(move) {
var score = 0;
// Knights are effective in closed positions and as outposts
var distanceFromCenter = Math.abs(move.toRow - 3.5) + Math.abs(move.toCol - 7.5);
if (distanceFromCenter < 4) {
score += 10; // Favor central knight positions
}
return score;
}
function evaluateRookMove(move) {
var score = 0;
// Rooks benefit from open files and ranks
var openFile = true;
var openRank = true;
// Check if file is open
for (var row = 0; row < 8; row++) {
if (row !== move.toRow) {
var piece = getPieceAt(row, move.toCol);
if (piece && piece.pieceColor === move.piece.pieceColor) {
openFile = false;
break;
}
}
}
// Check if rank is open
for (var col = 0; col < 16; col++) {
if (col !== move.toCol) {
var piece = getPieceAt(move.toRow, col);
if (piece && piece.pieceColor === move.piece.pieceColor) {
openRank = false;
break;
}
}
}
if (openFile) score += 15;
if (openRank) score += 10;
return score;
}
function evaluateBishopMove(move) {
var score = 0;
// Bishops benefit from long diagonals
var diagonalLength = Math.min(Math.min(move.toRow, 7 - move.toRow) + Math.min(move.toCol, 15 - move.toCol), Math.min(move.toRow, 7 - move.toRow) + Math.min(15 - move.toCol, move.toCol));
score += diagonalLength * 2;
return score;
}
function evaluatePawnMove(move) {
var score = 0;
// Pawns should advance toward promotion
var advancement = move.piece.pieceColor === 'black' ? move.toRow - move.piece.row : move.piece.row - move.toRow;
if (advancement > 0) {
score += advancement * 5;
}
// Near promotion gets huge bonus
if (move.piece.pieceColor === 'black' && move.toRow >= 6 || move.piece.pieceColor === 'white' && move.toRow <= 1) {
score += 50;
}
return score;
}
function findCheckmateInOne(validMoves) {
// Find immediate checkmate moves - highest priority
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
if (simulateMove(move, function () {
if (isInCheck('white')) {
// Check if white has any escape moves
var whiteMoves = getAllValidMoves('white');
for (var j = 0; j < whiteMoves.length; j++) {
if (canEscapeCheck(whiteMoves[j].piece, whiteMoves[j].toRow, whiteMoves[j].toCol)) {
return false; // Not checkmate - escape exists
}
}
return true; // Checkmate confirmed
}
return false;
})) {
return move; // Found checkmate in one
}
}
return null;
}
function preventOpponentCheckmate(validMoves) {
// Prevent opponent from achieving checkmate next move
var opponentThreats = getAllValidMoves('white');
for (var i = 0; i < opponentThreats.length; i++) {
var threatMove = opponentThreats[i];
// Check if opponent threat leads to our checkmate
var wouldBeCheckmate = simulateOpponentMove(threatMove, function () {
if (isInCheck('black')) {
var blackMoves = getAllValidMoves('black');
for (var j = 0; j < blackMoves.length; j++) {
if (canEscapeCheck(blackMoves[j].piece, blackMoves[j].toRow, blackMoves[j].toCol)) {
return false; // We can escape
}
}
return true; // We would be checkmated
}
return false;
});
if (wouldBeCheckmate) {
// Find move that prevents this checkmate
for (var k = 0; k < validMoves.length; k++) {
var preventMove = validMoves[k];
if (simulateMove(preventMove, function () {
return !simulateOpponentMove(threatMove, function () {
return isInCheck('black');
});
})) {
return preventMove; // This move prevents checkmate
}
}
}
}
return null;
}
function simulateOpponentMove(opponentMove, testFunction) {
// Temporarily simulate opponent's move
var originalRow = opponentMove.piece.row;
var originalCol = opponentMove.piece.col;
var capturedPiece = getPieceAt(opponentMove.toRow, opponentMove.toCol);
gameBoard[originalRow][originalCol] = null;
gameBoard[opponentMove.toRow][opponentMove.toCol] = opponentMove.piece;
opponentMove.piece.row = opponentMove.toRow;
opponentMove.piece.col = opponentMove.toCol;
var result = testFunction();
// Restore position
opponentMove.piece.row = originalRow;
opponentMove.piece.col = originalCol;
gameBoard[originalRow][originalCol] = opponentMove.piece;
gameBoard[opponentMove.toRow][opponentMove.toCol] = capturedPiece;
return result;
}
function findBestTacticalCombination(validMoves) {
// Find complex tactical sequences - pins, forks, discoveries, sacrifices
var bestTactical = null;
var bestTacticalValue = 0;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var tacticalValue = 0;
// Check for forcing sequences
if (isMoveForcingSequence(move)) {
tacticalValue += 50;
}
// Check for piece sacrifice that leads to mate
if (isBrilliantSacrifice(move)) {
tacticalValue += 100;
}
// Check for advanced pin patterns
if (createsAdvancedPin(move)) {
tacticalValue += 40;
}
// Check for discovered attack combinations
if (createsDiscoveredCombination(move)) {
tacticalValue += 60;
}
// Check for deflection tactics
if (createsDeflection(move)) {
tacticalValue += 35;
}
if (tacticalValue > bestTacticalValue) {
bestTacticalValue = tacticalValue;
bestTactical = move;
}
}
return bestTacticalValue >= 30 ? bestTactical : null;
}
function isMoveForcingSequence(move) {
// Check if move forces opponent into bad position
return simulateMove(move, function () {
var opponentMoves = getAllValidMoves('white');
var forcedBadMoves = 0;
for (var i = 0; i < opponentMoves.length; i++) {
var opMove = opponentMoves[i];
if (simulateOpponentMove(opMove, function () {
return isInCheck('white') || isPieceUnderThreat(opMove.piece);
})) {
forcedBadMoves++;
}
}
return forcedBadMoves >= opponentMoves.length * 0.7; // 70% of moves are bad
});
}
function isBrilliantSacrifice(move) {
// Check for brilliant sacrifices that lead to winning positions
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (!targetPiece) return false;
var sacrificeValue = getPieceValue(move.piece.pieceType);
var gainValue = getPieceValue(targetPiece.pieceType);
// Only consider if we're sacrificing material
if (sacrificeValue <= gainValue) return false;
return simulateMove(move, function () {
// Check if sacrifice leads to overwhelming position or mate threat
var ourAdvantageAfter = calculatePositionalAdvantage('black');
var opponentMoves = getAllValidMoves('white');
// If opponent has very limited good responses, sacrifice is brilliant
var goodOpponentMoves = 0;
for (var i = 0; i < opponentMoves.length; i++) {
if (!isPieceUnderThreatAt(opponentMoves[i].piece, opponentMoves[i].toRow, opponentMoves[i].toCol)) {
goodOpponentMoves++;
}
}
return ourAdvantageAfter > 30 || goodOpponentMoves <= 2;
});
}
function calculatePositionalAdvantage(color) {
var advantage = 0;
var ourColor = color;
var enemyColor = color === 'white' ? 'black' : 'white';
// Material count
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
var value = getPieceValue(piece.pieceType);
if (piece.pieceColor === ourColor) {
advantage += value;
} else {
advantage -= value;
}
}
// Positional factors
var ourKing = findKing(ourColor);
var enemyKing = findKing(enemyColor);
if (ourKing && enemyKing) {
// King safety difference
advantage += (countThreatsToKing(enemyColor) - countThreatsToKing(ourColor)) * 5;
// Center control
var ourCenterControl = 0;
var enemyCenterControl = 0;
var centerSquares = [{
row: 3,
col: 7
}, {
row: 3,
col: 8
}, {
row: 4,
col: 7
}, {
row: 4,
col: 8
}];
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
for (var j = 0; j < centerSquares.length; j++) {
if (piece.canMoveTo(centerSquares[j].row, centerSquares[j].col)) {
if (piece.pieceColor === ourColor) {
ourCenterControl++;
} else {
enemyCenterControl++;
}
}
}
}
advantage += (ourCenterControl - enemyCenterControl) * 2;
}
return advantage;
}
function calculatePerfectFork(move) {
// Calculate exact value of fork attacks
var forkTargets = [];
return simulateMove(move, function () {
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'white' && move.piece.canMoveTo(piece.row, piece.col)) {
forkTargets.push(getPieceValue(piece.pieceType));
}
}
// Return value of second-best target (since we can only capture one)
if (forkTargets.length >= 2) {
forkTargets.sort(function (a, b) {
return b - a;
});
return forkTargets[1]; // Second highest value
}
return 0;
});
}
function createsPerfectPin(move) {
var enemyKing = findKing('white');
if (!enemyKing) return false;
return simulateMove(move, function () {
// Check if move creates pin against enemy king
var direction = {
row: enemyKing.row - move.toRow,
col: enemyKing.col - move.toCol
};
// Normalize direction
var gcd = Math.abs(direction.row) || Math.abs(direction.col);
if (gcd > 0) {
direction.row = direction.row / gcd;
direction.col = direction.col / gcd;
}
// Check if there's exactly one enemy piece between attacker and king
var currentRow = move.toRow + direction.row;
var currentCol = move.toCol + direction.col;
var blockerFound = false;
while (currentRow !== enemyKing.row || currentCol !== enemyKing.col) {
var piece = getPieceAt(currentRow, currentCol);
if (piece) {
if (piece.pieceColor === 'white' && !blockerFound) {
blockerFound = true; // First blocker found
} else {
return false; // Multiple pieces or our piece blocking
}
}
currentRow += direction.row;
currentCol += direction.col;
if (currentRow < 0 || currentRow >= 8 || currentCol < 0 || currentCol >= 16) break;
}
return blockerFound;
});
}
function createsPerfectDiscovery(move) {
// Check for discovered attacks with perfect accuracy
var originalRow = move.piece.row;
var originalCol = move.piece.col;
return simulateMove(move, function () {
// Check if moving this piece discovers an attack from another piece
for (var i = 0; i < pieces.length; i++) {
var ally = pieces[i];
if (ally.pieceColor === 'black' && ally !== move.piece) {
// Temporarily put moving piece back
gameBoard[move.toRow][move.toCol] = null;
gameBoard[originalRow][originalCol] = move.piece;
move.piece.row = originalRow;
move.piece.col = originalCol;
var couldAttackBefore = false;
for (var j = 0; j < pieces.length; j++) {
var enemy = pieces[j];
if (enemy.pieceColor === 'white' && ally.canMoveTo(enemy.row, enemy.col)) {
couldAttackBefore = true;
break;
}
}
// Restore position
gameBoard[originalRow][originalCol] = null;
gameBoard[move.toRow][move.toCol] = move.piece;
move.piece.row = move.toRow;
move.piece.col = move.toCol;
if (!couldAttackBefore) {
// Check if ally can attack enemy pieces now
for (var j = 0; j < pieces.length; j++) {
var enemy = pieces[j];
if (enemy.pieceColor === 'white' && ally.canMoveTo(enemy.row, enemy.col)) {
return true; // Discovered attack found
}
}
}
}
}
return false;
});
}
function createsPerfectDeflection(move) {
// Check if move deflects a key defender
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (!targetPiece || targetPiece.pieceColor !== 'white') return false;
return simulateMove(move, function () {
// Check if capturing this piece leaves important pieces undefended
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'white' && piece !== targetPiece) {
var wasDefended = targetPiece.canMoveTo(piece.row, piece.col);
if (wasDefended) {
// Check if piece is now undefended and can be captured
var isNowUndefended = true;
for (var j = 0; j < pieces.length; j++) {
var defender = pieces[j];
if (defender.pieceColor === 'white' && defender !== targetPiece && defender.canMoveTo(piece.row, piece.col)) {
isNowUndefended = false;
break;
}
}
if (isNowUndefended) {
return true; // Successful deflection
}
}
}
}
return false;
});
}
function createsDeflection(move) {
// Check if move deflects a key defender piece
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (!targetPiece || targetPiece.pieceColor !== 'black') return false;
return simulateMove(move, function () {
// Check if capturing this piece leaves important pieces undefended
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.pieceColor === 'black' && piece !== targetPiece) {
var wasDefended = targetPiece.canMoveTo(piece.row, piece.col);
if (wasDefended) {
// Check if piece is now undefended and can be captured
var isNowUndefended = true;
for (var j = 0; j < pieces.length; j++) {
var defender = pieces[j];
if (defender.pieceColor === 'black' && defender !== targetPiece && defender.canMoveTo(piece.row, piece.col)) {
isNowUndefended = false;
break;
}
}
if (isNowUndefended) {
return true; // Successful deflection
}
}
}
}
return false;
});
}
function createsDiscoveredCombination(move) {
// Check for discovered attack combinations - complex tactical patterns
var originalRow = move.piece.row;
var originalCol = move.piece.col;
return simulateMove(move, function () {
// Check if moving this piece creates multiple discovered attacks
var discoveredAttacks = 0;
var highValueTargets = 0;
// Check each ally piece to see if it gains new attacking opportunities
for (var i = 0; i < pieces.length; i++) {
var ally = pieces[i];
if (ally.pieceColor === 'black' && ally !== move.piece) {
// Temporarily put moving piece back to check what was blocked before
gameBoard[move.toRow][move.toCol] = null;
gameBoard[originalRow][originalCol] = move.piece;
move.piece.row = originalRow;
move.piece.col = originalCol;
var enemyTargetsBefore = [];
for (var j = 0; j < pieces.length; j++) {
var enemy = pieces[j];
if (enemy.pieceColor === 'white' && ally.canMoveTo(enemy.row, enemy.col)) {
enemyTargetsBefore.push(enemy);
}
}
// Restore position after move
gameBoard[originalRow][originalCol] = null;
gameBoard[move.toRow][move.toCol] = move.piece;
move.piece.row = move.toRow;
move.piece.col = move.toCol;
var enemyTargetsAfter = [];
for (var j = 0; j < pieces.length; j++) {
var enemy = pieces[j];
if (enemy.pieceColor === 'white' && ally.canMoveTo(enemy.row, enemy.col)) {
enemyTargetsAfter.push(enemy);
}
}
// Check for new attacks that weren't possible before
for (var k = 0; k < enemyTargetsAfter.length; k++) {
var newTarget = enemyTargetsAfter[k];
var wasTargetedBefore = false;
for (var l = 0; l < enemyTargetsBefore.length; l++) {
if (enemyTargetsBefore[l] === newTarget) {
wasTargetedBefore = true;
break;
}
}
if (!wasTargetedBefore) {
discoveredAttacks++;
var targetValue = getPieceValue(newTarget.pieceType);
if (targetValue >= 5) {
// Queen, rook, or higher value pieces
highValueTargets++;
}
}
}
}
}
// Discovered combination is valuable if it creates multiple new attacks
// or targets high-value pieces
return discoveredAttacks >= 2 || highValueTargets >= 1;
});
}
function createsAdvancedPin(move) {
// Check for advanced pin patterns - more sophisticated than basic pins
var enemyKing = findKing('white');
if (!enemyKing) return false;
return simulateMove(move, function () {
// Check if move creates a pin that restricts enemy piece movement
for (var i = 0; i < pieces.length; i++) {
var enemyPiece = pieces[i];
if (enemyPiece.pieceColor === 'white' && enemyPiece.pieceType !== 'king') {
// Check if this enemy piece is now pinned to their king
var direction = {
row: enemyKing.row - enemyPiece.row,
col: enemyKing.col - enemyPiece.col
};
// Normalize direction
var maxDist = Math.max(Math.abs(direction.row), Math.abs(direction.col));
if (maxDist > 0) {
direction.row = Math.round(direction.row / maxDist);
direction.col = Math.round(direction.col / maxDist);
// Check if our piece can attack along this line through the pinned piece to the king
var canPinThroughPiece = false;
if (move.piece.canMoveTo(enemyKing.row, enemyKing.col)) {
// Temporarily remove the potentially pinned piece
var originalRow = enemyPiece.row;
var originalCol = enemyPiece.col;
gameBoard[originalRow][originalCol] = null;
// Check if our piece can still attack the king
if (move.piece.canMoveTo(enemyKing.row, enemyKing.col)) {
canPinThroughPiece = true;
}
// Restore the piece
gameBoard[originalRow][originalCol] = enemyPiece;
}
if (canPinThroughPiece) {
// Verify the pin is along a straight line (rook/queen) or diagonal (bishop/queen)
var rowDiff = Math.abs(move.toRow - enemyKing.row);
var colDiff = Math.abs(move.toCol - enemyKing.col);
var isStraightLine = rowDiff === 0 || colDiff === 0;
var isDiagonal = rowDiff === colDiff;
if (move.piece.pieceType === 'rook' && isStraightLine || move.piece.pieceType === 'bishop' && isDiagonal || move.piece.pieceType === 'quenn' && (isStraightLine || isDiagonal)) {
return true; // Advanced pin created
}
}
}
}
}
return false;
});
}
function createsPerfectSkewer(move) {
// Check for skewer patterns (attacking valuable piece in front of less valuable)
return simulateMove(move, function () {
for (var i = 0; i < pieces.length; i++) {
var frontPiece = pieces[i];
if (frontPiece.pieceColor === 'white' && move.piece.canMoveTo(frontPiece.row, frontPiece.col)) {
// Check if there's a less valuable piece behind this one
var direction = {
row: frontPiece.row - move.toRow,
col: frontPiece.col - move.toCol
};
var gcd = Math.max(Math.abs(direction.row), Math.abs(direction.col));
if (gcd > 0) {
direction.row = Math.round(direction.row / gcd);
direction.col = Math.round(direction.col / gcd);
var checkRow = frontPiece.row + direction.row;
var checkCol = frontPiece.col + direction.col;
while (checkRow >= 0 && checkRow < 8 && checkCol >= 0 && checkCol < 16) {
var behindPiece = getPieceAt(checkRow, checkCol);
if (behindPiece && behindPiece.pieceColor === 'white') {
var frontValue = getPieceValue(frontPiece.pieceType);
var behindValue = getPieceValue(behindPiece.pieceType);
if (frontValue > behindValue || frontPiece.pieceType === 'king') {
return true; // Successful skewer
}
break;
} else if (behindPiece) {
break; // Our piece blocking
}
checkRow += direction.row;
checkCol += direction.col;
}
}
}
}
return false;
});
}
function findOptimalDefensiveMove(validMoves) {
// Find the best defensive move with perfect calculation
var bestDefense = null;
var bestDefenseScore = -10000;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
var defenseScore = 0;
// Check if move gets us out of check
if (simulateMove(move, function () {
return !isInCheck('black');
})) {
defenseScore += 1000;
// Bonus for counter-attacking while defending
var targetPiece = getPieceAt(move.toRow, move.toCol);
if (targetPiece && targetPiece.pieceColor === 'white') {
defenseScore += getPieceValue(targetPiece.pieceType) * 50;
// Extra bonus for capturing the threatening piece
var blackKing = findKing('black');
var threateningPiece = findThreateningPiece(blackKing);
if (targetPiece === threateningPiece) {
defenseScore += 500; // Eliminate the threat directly
}
}
// Bonus for creating counter-threats
if (simulateMove(move, function () {
return isInCheck('white');
})) {
defenseScore += 200;
}
}
if (defenseScore > bestDefenseScore) {
bestDefenseScore = defenseScore;
bestDefense = move;
}
}
return bestDefense;
}
function getPieceFeatureDescription(piece) {
var descriptions = {
'pawn': 'Moves 1 square forward, captures diagonally. Can move 2 squares on first move.',
'rook': 'Moves any distance horizontally or vertically. Strong in open files.',
'knight': 'Moves in L-shape (2+1 squares). Can jump over other pieces.',
'bishop': 'Moves any distance diagonally. Controls long diagonals.',
'queen': 'Combines rook and bishop moves. Requires 1 stamina to move.',
'king': 'Moves 1 square in any direction. Has 4 lives. Can castle.',
'snake': 'Moves 3 times per turn: straight, diagonal, any direction. Poisons adjacent enemies. Requires 1 stamina for full sequence.',
'jester': 'Moves exactly 2 squares in any direction. Can jump over pieces to capture them.',
'princess': 'Moves 2 squares straight or 1 square for positioning. Double-click to eliminate 2 enemies within 2 squares.',
'summoner': 'Moves 1 square like a king. After 3 moves, summons 2 pawns in adjacent squares.'
};
return descriptions[piece.pieceType] || 'Unknown piece type.';
}
function showPieceTooltip(piece, x, y) {
if (hoverTooltip) {
hoverTooltip.destroy();
hoverTooltip = null;
}
var description = getPieceFeatureDescription(piece);
var pieceTitle = piece.pieceColor.charAt(0).toUpperCase() + piece.pieceColor.slice(1) + ' ' + piece.pieceType.charAt(0).toUpperCase() + piece.pieceType.slice(1);
// Break description into lines that fit within the box
var maxLineWidth = 35; // Characters per line
var words = description.split(' ');
var lines = [];
var currentLine = '';
for (var i = 0; i < words.length; i++) {
var word = words[i];
if ((currentLine + word).length <= maxLineWidth) {
currentLine += (currentLine ? ' ' : '') + word;
} else {
if (currentLine) {
lines.push(currentLine);
currentLine = word;
} else {
// Word is too long, break it
lines.push(word.substring(0, maxLineWidth));
currentLine = word.substring(maxLineWidth);
}
}
}
if (currentLine) {
lines.push(currentLine);
}
// Calculate box size based on content
var boxWidth = Math.max(600, titleText ? titleText.width + 40 : 600);
var boxHeight = Math.max(100, 80 + lines.length * 35);
// Create tooltip background with proper size
var tooltipBg = LK.getAsset('darkSquare', {
anchorX: 0,
anchorY: 0,
alpha: 0.9,
scaleX: boxWidth / 128,
scaleY: boxHeight / 128
});
// Create tooltip container
var tooltipContainer = new Container();
tooltipContainer.addChild(tooltipBg);
// Create title text
var titleText = new Text2(pieceTitle, {
size: 40,
fill: piece.pieceColor === 'white' ? 0xFFFFFF : 0xFF6666
});
titleText.anchor.set(0, 0);
titleText.x = 20;
titleText.y = 15;
tooltipContainer.addChild(titleText);
// Create description text lines
for (var i = 0; i < lines.length; i++) {
var lineText = new Text2(lines[i], {
size: 30,
fill: 0xFFFFFF
});
lineText.anchor.set(0, 0);
lineText.x = 20;
lineText.y = 60 + i * 35;
tooltipContainer.addChild(lineText);
}
// Position tooltip near piece but keep it on screen
var tooltipX = Math.min(x + 50, 2048 - boxWidth - 20); // Keep within screen bounds
var tooltipY = Math.max(y - boxHeight - 20, 50); // Keep within screen bounds
tooltipContainer.x = tooltipX;
tooltipContainer.y = tooltipY;
tooltipContainer.alpha = 0;
game.addChild(tooltipContainer);
hoverTooltip = tooltipContainer;
// Fade in animation
tween(hoverTooltip, {
alpha: 1.0
}, {
duration: 300,
easing: tween.easeOut
});
}
function hidePieceTooltip() {
if (hoverTooltip) {
tween(hoverTooltip, {
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (hoverTooltip && !hoverTooltip.destroyed) {
hoverTooltip.destroy();
}
hoverTooltip = null;
}
});
}
}
function updateQueenSnakePinkSquares() {
// Clear existing pink squares
for (var i = 0; i < queenSnakePinkSquares.length; i++) {
if (!queenSnakePinkSquares[i].destroyed) {
queenSnakePinkSquares[i].destroy();
}
}
queenSnakePinkSquares = [];
// Add pink squares for white stamina consumers when stamina is 0
if (staminaPoints === 0 && window.whiteStaminaConsumers) {
for (var i = 0; i < window.whiteStaminaConsumers.length; i++) {
var consumer = window.whiteStaminaConsumers[i];
// Check if piece still exists, if so use current position, otherwise use stored position
var pieceExists = false;
var currentRow = consumer.row;
var currentCol = consumer.col;
for (var j = 0; j < pieces.length; j++) {
if (pieces[j] === consumer.id) {
pieceExists = true;
currentRow = pieces[j].row;
currentCol = pieces[j].col;
break;
}
}
var pinkSquare = LK.getAsset('staminaBar', {
anchorX: 0,
anchorY: 0,
alpha: 0.4,
scaleX: 128 / 40,
// Scale to fit square size
scaleY: 128 / 40
});
pinkSquare.x = boardStartX + currentCol * squareSize;
pinkSquare.y = boardStartY + currentRow * squareSize;
game.addChild(pinkSquare);
queenSnakePinkSquares.push(pinkSquare);
// Animate pink square appearance
tween(pinkSquare, {
alpha: 0.6
}, {
duration: 300,
easing: tween.easeOut
});
}
}
// Add pink squares for AI stamina consumers when AI stamina is 0
if (aiStaminaPoints === 0 && window.blackStaminaConsumers) {
for (var i = 0; i < window.blackStaminaConsumers.length; i++) {
var consumer = window.blackStaminaConsumers[i];
// Check if piece still exists, if so use current position, otherwise use stored position
var pieceExists = false;
var currentRow = consumer.row;
var currentCol = consumer.col;
for (var j = 0; j < pieces.length; j++) {
if (pieces[j] === consumer.id) {
pieceExists = true;
currentRow = pieces[j].row;
currentCol = pieces[j].col;
break;
}
}
var pinkSquare = LK.getAsset('staminaBar', {
anchorX: 0,
anchorY: 0,
alpha: 0.4,
scaleX: 128 / 40,
// Scale to fit square size
scaleY: 128 / 40
});
pinkSquare.x = boardStartX + currentCol * squareSize;
pinkSquare.y = boardStartY + currentRow * squareSize;
game.addChild(pinkSquare);
queenSnakePinkSquares.push(pinkSquare);
// Animate pink square appearance
tween(pinkSquare, {
alpha: 0.6
}, {
duration: 300,
easing: tween.easeOut
});
}
}
}
a black chess piece that resembles a chess pawn but is dressed like a jester. In-Game asset. 2d. High contrast. No shadows
Draw a black pawn dressed in a priest's robe and holding two crosses in the air in a divine manner. In-Game asset. 2d. High contrast. No shadows
Draw a white pawn dressed in a priest's robe and holding two crosses in the air in a divine manner. In-Game asset. 2d. High contrast. No shadows