/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
aiDifficulty: 1,
gameHistory: []
});
/****
* Classes
****/
var Board = Container.expand(function () {
var self = Container.call(this);
self.tiles = [];
self.tileSize = 256;
self.boardSize = 8 * self.tileSize;
self.highlightLayer = new Container();
self.piecesLayer = new Container();
self.createBoard = function () {
// Create tiles
for (var row = 0; row < 8; row++) {
self.tiles[row] = [];
for (var col = 0; col < 8; col++) {
var isWhite = (row + col) % 2 === 0;
var tile = self.attachAsset(isWhite ? 'whiteTile' : 'blackTile', {
x: col * self.tileSize,
y: row * self.tileSize,
anchorX: 0,
anchorY: 0
});
self.tiles[row][col] = tile;
tile.boardRow = row;
tile.boardCol = col;
// Add coordinate labels
if (row === 7) {
// Add file (column) labels at the bottom
var fileLabel = new Text2(String.fromCharCode(97 + col), {
size: 30,
fill: isWhite ? "#333333" : "#ffffff"
});
fileLabel.anchor.set(1.0, 1.0);
fileLabel.x = (col + 1) * self.tileSize - 10;
fileLabel.y = (row + 1) * self.tileSize - 10;
self.addChild(fileLabel);
}
if (col === 0) {
// Add rank (row) labels on the left
var rankLabel = new Text2(String(8 - row), {
size: 30,
fill: isWhite ? "#333333" : "#ffffff"
});
rankLabel.anchor.set(0.0, 0.0);
rankLabel.x = col * self.tileSize + 10;
rankLabel.y = row * self.tileSize + 10;
self.addChild(rankLabel);
}
}
}
self.addChild(self.highlightLayer);
self.addChild(self.piecesLayer);
};
self.highlightTile = function (row, col, type) {
var highlight = LK.getAsset(type, {
x: col * self.tileSize,
y: row * self.tileSize,
anchorX: 0,
anchorY: 0,
alpha: 0.5
});
self.highlightLayer.addChild(highlight);
return highlight;
};
self.clearHighlights = function () {
while (self.highlightLayer.children.length > 0) {
self.highlightLayer.removeChildAt(0);
}
};
self.getTileAt = function (x, y) {
var col = Math.floor(x / self.tileSize);
var row = Math.floor(y / self.tileSize);
if (row >= 0 && row < 8 && col >= 0 && col < 8) {
return {
row: row,
col: col
};
}
return null;
};
return self;
});
var Piece = Container.expand(function (type, color, row, col) {
var self = Container.call(this);
self.type = type;
self.color = color;
self.row = row;
self.col = col;
self.hasMoved = false;
self.getPieceSymbol = function () {
switch (self.type) {
case 'king':
return '♚';
case 'queen':
return '♛';
case 'rook':
return '♜';
case 'bishop':
return '♝';
case 'knight':
return '♞';
case 'pawn':
return '♟';
default:
return '';
}
};
// Get asset id based on color and type
var assetId = color + type.charAt(0).toUpperCase() + type.slice(1);
self.sprite = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Add chess piece symbol
self.symbol = new Text2(self.getPieceSymbol(), {
size: 150,
fill: color === 'white' ? "#333333" : "#ffffff"
});
self.symbol.anchor.set(0.5, 0.5);
self.addChild(self.symbol);
self.moveTo = function (row, col, duration) {
self.row = row;
self.col = col;
var newX = col * chessBoard.tileSize + chessBoard.tileSize / 2;
var newY = row * chessBoard.tileSize + chessBoard.tileSize / 2;
if (duration) {
tween(self, {
x: newX,
y: newY
}, {
duration: duration,
easing: tween.easeOutQuad
});
} else {
self.x = newX;
self.y = newY;
}
self.hasMoved = true;
};
self.update = function () {
// For any per-frame updates
};
self.down = function (x, y, obj) {
if (gameState.currentTurn === self.color && !gameState.isAnimating) {
gameState.selectedPiece = self;
chessBoard.clearHighlights();
// Highlight the current piece's tile
self.currentHighlight = chessBoard.highlightTile(self.row, self.col, 'highlightTile');
// Highlight valid moves
var validMoves = getValidMoves(self);
gameState.validMoves = validMoves;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
chessBoard.highlightTile(move.row, move.col, 'validMoveTile');
}
}
};
// Place piece at its position
self.x = col * 256 + 128;
self.y = row * 256 + 128;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50
});
/****
* Game Code
****/
// Game state
var gameState = {
currentTurn: 'white',
selectedPiece: null,
validMoves: [],
board: Array(8).fill().map(function () {
return Array(8).fill(null);
}),
isAnimating: false,
lastMove: null,
aiDifficulty: storage.aiDifficulty || 1,
gameMode: 'ai',
// 'ai' or 'twoPlayer'
status: 'playing',
// 'playing', 'check', 'checkmate', 'stalemate'
aiPlayer: 'black'
};
// Create UI
var titleText = new Text2('Chess Challenge', {
size: 80,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0);
LK.gui.top.addChild(titleText);
var turnText = new Text2('White to move', {
size: 50,
fill: 0xFFFFFF
});
turnText.anchor.set(0.5, 0);
turnText.y = 100;
LK.gui.top.addChild(turnText);
var statusText = new Text2('', {
size: 50,
fill: 0xFFFF00
});
statusText.anchor.set(0.5, 0);
statusText.y = 160;
LK.gui.top.addChild(statusText);
// Create and position the chess board
var chessBoard = new Board();
game.addChild(chessBoard);
chessBoard.createBoard();
// Center the board
chessBoard.x = (2048 - chessBoard.boardSize) / 2;
chessBoard.y = (2732 - chessBoard.boardSize) / 2;
// Initialize pieces
function initializeChessBoard() {
// Clear existing pieces
while (chessBoard.piecesLayer.children.length > 0) {
chessBoard.piecesLayer.removeChildAt(0);
}
// Reset game state
gameState.board = Array(8).fill().map(function () {
return Array(8).fill(null);
});
gameState.currentTurn = 'white';
gameState.selectedPiece = null;
gameState.validMoves = [];
gameState.isAnimating = false;
gameState.lastMove = null;
gameState.status = 'playing';
// Update UI
updateTurnText();
// Create pieces
var pieces = [{
type: 'rook',
color: 'black',
row: 0,
col: 0
}, {
type: 'knight',
color: 'black',
row: 0,
col: 1
}, {
type: 'bishop',
color: 'black',
row: 0,
col: 2
}, {
type: 'queen',
color: 'black',
row: 0,
col: 3
}, {
type: 'king',
color: 'black',
row: 0,
col: 4
}, {
type: 'bishop',
color: 'black',
row: 0,
col: 5
}, {
type: 'knight',
color: 'black',
row: 0,
col: 6
}, {
type: 'rook',
color: 'black',
row: 0,
col: 7
}, {
type: 'rook',
color: 'white',
row: 7,
col: 0
}, {
type: 'knight',
color: 'white',
row: 7,
col: 1
}, {
type: 'bishop',
color: 'white',
row: 7,
col: 2
}, {
type: 'queen',
color: 'white',
row: 7,
col: 3
}, {
type: 'king',
color: 'white',
row: 7,
col: 4
}, {
type: 'bishop',
color: 'white',
row: 7,
col: 5
}, {
type: 'knight',
color: 'white',
row: 7,
col: 6
}, {
type: 'rook',
color: 'white',
row: 7,
col: 7
}];
// Add pawns
for (var i = 0; i < 8; i++) {
pieces.push({
type: 'pawn',
color: 'black',
row: 1,
col: i
});
pieces.push({
type: 'pawn',
color: 'white',
row: 6,
col: i
});
}
// Create and position pieces
for (var i = 0; i < pieces.length; i++) {
var p = pieces[i];
var piece = new Piece(p.type, p.color, p.row, p.col);
chessBoard.piecesLayer.addChild(piece);
gameState.board[p.row][p.col] = piece;
}
// Clear highlights
chessBoard.clearHighlights();
}
// Chess move validation
function getValidMoves(piece) {
var moves = [];
if (!piece) return moves;
function addMove(row, col) {
moves.push({
row: row,
col: col
});
}
function isOnBoard(row, col) {
return row >= 0 && row < 8 && col >= 0 && col < 8;
}
function isOccupied(row, col) {
return gameState.board[row][col] !== null;
}
function isEnemyPiece(row, col) {
return isOccupied(row, col) && gameState.board[row][col].color !== piece.color;
}
function isFriendlyPiece(row, col) {
return isOccupied(row, col) && gameState.board[row][col].color === piece.color;
}
var row = piece.row;
var col = piece.col;
switch (piece.type) {
case 'pawn':
var direction = piece.color === 'white' ? -1 : 1;
// Move forward one square
if (isOnBoard(row + direction, col) && !isOccupied(row + direction, col)) {
addMove(row + direction, col);
// Move forward two squares on first move
if (!piece.hasMoved && isOnBoard(row + 2 * direction, col) && !isOccupied(row + 2 * direction, col)) {
addMove(row + 2 * direction, col);
}
}
// Capture diagonally
if (isOnBoard(row + direction, col - 1) && isEnemyPiece(row + direction, col - 1)) {
addMove(row + direction, col - 1);
}
if (isOnBoard(row + direction, col + 1) && isEnemyPiece(row + direction, col + 1)) {
addMove(row + direction, col + 1);
}
// En passant (simplified)
if (gameState.lastMove && gameState.lastMove.piece.type === 'pawn' && gameState.lastMove.fromRow === (piece.color === 'white' ? 1 : 6) && gameState.lastMove.toRow === (piece.color === 'white' ? 3 : 4) && Math.abs(gameState.lastMove.toCol - col) === 1 && row === gameState.lastMove.toRow) {
addMove(row + direction, gameState.lastMove.toCol);
}
break;
case 'rook':
// Horizontal and vertical directions
var directions = [{
dr: -1,
dc: 0
},
// up
{
dr: 1,
dc: 0
},
// down
{
dr: 0,
dc: -1
},
// left
{
dr: 0,
dc: 1
} // right
];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
while (isOnBoard(r, c)) {
if (isOccupied(r, c)) {
if (isEnemyPiece(r, c)) {
addMove(r, c);
}
break;
}
addMove(r, c);
r += dir.dr;
c += dir.dc;
}
});
break;
case 'knight':
// Knight moves
var knightMoves = [{
dr: -2,
dc: -1
}, {
dr: -2,
dc: 1
}, {
dr: -1,
dc: -2
}, {
dr: -1,
dc: 2
}, {
dr: 1,
dc: -2
}, {
dr: 1,
dc: 2
}, {
dr: 2,
dc: -1
}, {
dr: 2,
dc: 1
}];
knightMoves.forEach(function (move) {
var r = row + move.dr;
var c = col + move.dc;
if (isOnBoard(r, c) && !isFriendlyPiece(r, c)) {
addMove(r, c);
}
});
break;
case 'bishop':
// Diagonal directions
var directions = [{
dr: -1,
dc: -1
},
// up-left
{
dr: -1,
dc: 1
},
// up-right
{
dr: 1,
dc: -1
},
// down-left
{
dr: 1,
dc: 1
} // down-right
];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
while (isOnBoard(r, c)) {
if (isOccupied(r, c)) {
if (isEnemyPiece(r, c)) {
addMove(r, c);
}
break;
}
addMove(r, c);
r += dir.dr;
c += dir.dc;
}
});
break;
case 'queen':
// Combine rook and bishop moves
var directions = [{
dr: -1,
dc: 0
}, {
dr: 1,
dc: 0
},
// vertical
{
dr: 0,
dc: -1
}, {
dr: 0,
dc: 1
},
// horizontal
{
dr: -1,
dc: -1
}, {
dr: -1,
dc: 1
},
// diagonal
{
dr: 1,
dc: -1
}, {
dr: 1,
dc: 1
} // diagonal
];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
while (isOnBoard(r, c)) {
if (isOccupied(r, c)) {
if (isEnemyPiece(r, c)) {
addMove(r, c);
}
break;
}
addMove(r, c);
r += dir.dr;
c += dir.dc;
}
});
break;
case 'king':
// King moves (one square in any direction)
var directions = [{
dr: -1,
dc: -1
}, {
dr: -1,
dc: 0
}, {
dr: -1,
dc: 1
}, {
dr: 0,
dc: -1
}, {
dr: 0,
dc: 1
}, {
dr: 1,
dc: -1
}, {
dr: 1,
dc: 0
}, {
dr: 1,
dc: 1
}];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
if (isOnBoard(r, c) && !isFriendlyPiece(r, c)) {
addMove(r, c);
}
});
// Castling
if (!piece.hasMoved) {
// Kingside castling
if (!isOccupied(row, col + 1) && !isOccupied(row, col + 2) && isOccupied(row, col + 3) && !gameState.board[row][col + 3].hasMoved && gameState.board[row][col + 3].type === 'rook') {
addMove(row, col + 2);
}
// Queenside castling
if (!isOccupied(row, col - 1) && !isOccupied(row, col - 2) && !isOccupied(row, col - 3) && isOccupied(row, col - 4) && !gameState.board[row][col - 4].hasMoved && gameState.board[row][col - 4].type === 'rook') {
addMove(row, col - 2);
}
}
break;
}
// Filter out moves that would put the king in check
var validMoves = [];
for (var i = 0; i < moves.length; i++) {
var move = moves[i];
// Make a temporary move
var tempPiece = gameState.board[move.row][move.col];
gameState.board[move.row][move.col] = piece;
gameState.board[row][col] = null;
// Check if the move puts/leaves the king in check
var inCheck = isKingInCheck(piece.color);
// Undo the move
gameState.board[row][col] = piece;
gameState.board[move.row][move.col] = tempPiece;
if (!inCheck) {
validMoves.push(move);
}
}
return validMoves;
}
function isKingInCheck(color) {
// Find the king
var kingRow = -1;
var kingCol = -1;
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var piece = gameState.board[r][c];
if (piece && piece.type === 'king' && piece.color === color) {
kingRow = r;
kingCol = c;
break;
}
}
if (kingRow !== -1) break;
}
// Check if any enemy piece can capture the king
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var piece = gameState.board[r][c];
if (piece && piece.color !== color) {
// Get the enemy's valid moves (ignoring check)
var moves = getBasicMoves(piece);
// See if any of the moves can capture the king
for (var i = 0; i < moves.length; i++) {
if (moves[i].row === kingRow && moves[i].col === kingCol) {
return true;
}
}
}
}
}
return false;
}
function getBasicMoves(piece) {
var moves = [];
if (!piece) return moves;
function addMove(row, col) {
moves.push({
row: row,
col: col
});
}
function isOnBoard(row, col) {
return row >= 0 && row < 8 && col >= 0 && col < 8;
}
function isOccupied(row, col) {
return gameState.board[row][col] !== null;
}
function isEnemyPiece(row, col) {
return isOccupied(row, col) && gameState.board[row][col].color !== piece.color;
}
function isFriendlyPiece(row, col) {
return isOccupied(row, col) && gameState.board[row][col].color === piece.color;
}
var row = piece.row;
var col = piece.col;
switch (piece.type) {
case 'pawn':
var direction = piece.color === 'white' ? -1 : 1;
// Move forward one square
if (isOnBoard(row + direction, col) && !isOccupied(row + direction, col)) {
addMove(row + direction, col);
// Move forward two squares on first move
if (!piece.hasMoved && isOnBoard(row + 2 * direction, col) && !isOccupied(row + 2 * direction, col)) {
addMove(row + 2 * direction, col);
}
}
// Capture diagonally
if (isOnBoard(row + direction, col - 1) && isEnemyPiece(row + direction, col - 1)) {
addMove(row + direction, col - 1);
}
if (isOnBoard(row + direction, col + 1) && isEnemyPiece(row + direction, col + 1)) {
addMove(row + direction, col + 1);
}
break;
case 'rook':
// Horizontal and vertical directions
var directions = [{
dr: -1,
dc: 0
},
// up
{
dr: 1,
dc: 0
},
// down
{
dr: 0,
dc: -1
},
// left
{
dr: 0,
dc: 1
} // right
];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
while (isOnBoard(r, c)) {
if (isOccupied(r, c)) {
if (isEnemyPiece(r, c)) {
addMove(r, c);
}
break;
}
addMove(r, c);
r += dir.dr;
c += dir.dc;
}
});
break;
case 'knight':
// Knight moves
var knightMoves = [{
dr: -2,
dc: -1
}, {
dr: -2,
dc: 1
}, {
dr: -1,
dc: -2
}, {
dr: -1,
dc: 2
}, {
dr: 1,
dc: -2
}, {
dr: 1,
dc: 2
}, {
dr: 2,
dc: -1
}, {
dr: 2,
dc: 1
}];
knightMoves.forEach(function (move) {
var r = row + move.dr;
var c = col + move.dc;
if (isOnBoard(r, c) && !isFriendlyPiece(r, c)) {
addMove(r, c);
}
});
break;
case 'bishop':
// Diagonal directions
var directions = [{
dr: -1,
dc: -1
},
// up-left
{
dr: -1,
dc: 1
},
// up-right
{
dr: 1,
dc: -1
},
// down-left
{
dr: 1,
dc: 1
} // down-right
];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
while (isOnBoard(r, c)) {
if (isOccupied(r, c)) {
if (isEnemyPiece(r, c)) {
addMove(r, c);
}
break;
}
addMove(r, c);
r += dir.dr;
c += dir.dc;
}
});
break;
case 'queen':
// Combine rook and bishop moves
var directions = [{
dr: -1,
dc: 0
}, {
dr: 1,
dc: 0
},
// vertical
{
dr: 0,
dc: -1
}, {
dr: 0,
dc: 1
},
// horizontal
{
dr: -1,
dc: -1
}, {
dr: -1,
dc: 1
},
// diagonal
{
dr: 1,
dc: -1
}, {
dr: 1,
dc: 1
} // diagonal
];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
while (isOnBoard(r, c)) {
if (isOccupied(r, c)) {
if (isEnemyPiece(r, c)) {
addMove(r, c);
}
break;
}
addMove(r, c);
r += dir.dr;
c += dir.dc;
}
});
break;
case 'king':
// King moves (one square in any direction)
var directions = [{
dr: -1,
dc: -1
}, {
dr: -1,
dc: 0
}, {
dr: -1,
dc: 1
}, {
dr: 0,
dc: -1
}, {
dr: 0,
dc: 1
}, {
dr: 1,
dc: -1
}, {
dr: 1,
dc: 0
}, {
dr: 1,
dc: 1
}];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
if (isOnBoard(r, c) && !isFriendlyPiece(r, c)) {
addMove(r, c);
}
});
break;
}
return moves;
}
function isCheckmate(color) {
if (!isKingInCheck(color)) return false;
// Check if any piece can make a valid move
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var piece = gameState.board[r][c];
if (piece && piece.color === color) {
var validMoves = getValidMoves(piece);
if (validMoves.length > 0) {
return false;
}
}
}
}
return true;
}
function isStalemate(color) {
if (isKingInCheck(color)) return false;
// Check if any piece can make a valid move
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var piece = gameState.board[r][c];
if (piece && piece.color === color) {
var validMoves = getValidMoves(piece);
if (validMoves.length > 0) {
return false;
}
}
}
}
return true;
}
function updateGameStatus() {
if (isCheckmate(gameState.currentTurn)) {
gameState.status = 'checkmate';
var winner = gameState.currentTurn === 'white' ? 'Black' : 'White';
statusText.setText(winner + ' wins by checkmate!');
LK.getSound('gameEnd').play();
// Update score if AI was played against
if (gameState.gameMode === 'ai') {
if (winner === 'White' && gameState.aiPlayer === 'black' || winner === 'Black' && gameState.aiPlayer === 'white') {
LK.setScore(LK.getScore() + 1);
}
}
return true;
} else if (isStalemate(gameState.currentTurn)) {
gameState.status = 'stalemate';
statusText.setText('Game drawn by stalemate!');
LK.getSound('gameEnd').play();
return true;
} else if (isKingInCheck(gameState.currentTurn)) {
gameState.status = 'check';
statusText.setText(gameState.currentTurn.charAt(0).toUpperCase() + gameState.currentTurn.slice(1) + ' is in check!');
LK.getSound('check').play();
} else {
gameState.status = 'playing';
statusText.setText('');
}
return false;
}
function updateTurnText() {
var turnName = gameState.currentTurn.charAt(0).toUpperCase() + gameState.currentTurn.slice(1);
turnText.setText(turnName + ' to move');
}
function movePiece(piece, targetRow, targetCol) {
var isCapture = gameState.board[targetRow][targetCol] !== null;
var capturedPiece = gameState.board[targetRow][targetCol];
var fromRow = piece.row;
var fromCol = piece.col;
// Handle castling
if (piece.type === 'king' && Math.abs(targetCol - fromCol) === 2) {
// Kingside castling
if (targetCol > fromCol) {
var rook = gameState.board[fromRow][7];
gameState.board[fromRow][5] = rook;
gameState.board[fromRow][7] = null;
rook.moveTo(fromRow, 5, 300);
}
// Queenside castling
else {
var rook = gameState.board[fromRow][0];
gameState.board[fromRow][3] = rook;
gameState.board[fromRow][0] = null;
rook.moveTo(fromRow, 3, 300);
}
}
// Handle en passant capture
if (piece.type === 'pawn' && Math.abs(targetCol - fromCol) === 1 && !isCapture) {
var enPassantRow = piece.color === 'white' ? targetRow + 1 : targetRow - 1;
capturedPiece = gameState.board[enPassantRow][targetCol];
gameState.board[enPassantRow][targetCol] = null;
if (capturedPiece) {
capturedPiece.parent.removeChild(capturedPiece);
}
isCapture = true;
}
// Update board array
gameState.board[fromRow][fromCol] = null;
if (capturedPiece) {
capturedPiece.parent.removeChild(capturedPiece);
}
gameState.board[targetRow][targetCol] = piece;
// Move the piece with animation
gameState.isAnimating = true;
piece.moveTo(targetRow, targetCol, 300);
// Play sound
if (isCapture) {
LK.getSound('capture').play();
} else {
LK.getSound('move').play();
}
// Handle pawn promotion (always to queen for simplicity)
if (piece.type === 'pawn' && (targetRow === 0 || targetRow === 7)) {
// Wait for the piece to finish moving
var promotionTimeout = LK.setTimeout(function () {
// Remove the pawn
piece.parent.removeChild(piece);
gameState.board[targetRow][targetCol] = null;
// Create a new queen
var queen = new Piece('queen', piece.color, targetRow, targetCol);
chessBoard.piecesLayer.addChild(queen);
gameState.board[targetRow][targetCol] = queen;
LK.clearTimeout(promotionTimeout);
}, 350);
}
// Update last move
gameState.lastMove = {
piece: piece,
fromRow: fromRow,
fromCol: fromCol,
toRow: targetRow,
toCol: targetCol
};
// Highlight the last move
var moveTimeout = LK.setTimeout(function () {
chessBoard.clearHighlights();
chessBoard.highlightTile(fromRow, fromCol, 'lastMoveTile');
chessBoard.highlightTile(targetRow, targetCol, 'lastMoveTile');
// Switch turns
gameState.currentTurn = gameState.currentTurn === 'white' ? 'black' : 'white';
updateTurnText();
// Check for check, checkmate, stalemate
var gameEnded = updateGameStatus();
gameState.isAnimating = false;
gameState.selectedPiece = null;
// If AI is playing and it's AI's turn
if (!gameEnded && gameState.gameMode === 'ai' && gameState.currentTurn === gameState.aiPlayer) {
makeAIMove();
}
LK.clearTimeout(moveTimeout);
}, 350);
}
function makeAIMove() {
var aiTimeout = LK.setTimeout(function () {
var allPossibleMoves = [];
// Collect all possible moves for AI pieces
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var piece = gameState.board[r][c];
if (piece && piece.color === gameState.aiPlayer) {
var validMoves = getValidMoves(piece);
for (var i = 0; i < validMoves.length; i++) {
allPossibleMoves.push({
piece: piece,
move: validMoves[i]
});
}
}
}
}
// Piece value tables - used to evaluate position advantages for each piece type
var piecePositionValues = {
pawn: [[0, 0, 0, 0, 0, 0, 0, 0], [50, 50, 50, 50, 50, 50, 50, 50], [10, 10, 20, 30, 30, 20, 10, 10], [5, 5, 10, 25, 25, 10, 5, 5], [0, 0, 0, 20, 20, 0, 0, 0], [5, -5, -10, 0, 0, -10, -5, 5], [5, 10, 10, -20, -20, 10, 10, 5], [0, 0, 0, 0, 0, 0, 0, 0]],
knight: [[-50, -40, -30, -30, -30, -30, -40, -50], [-40, -20, 0, 0, 0, 0, -20, -40], [-30, 0, 10, 15, 15, 10, 0, -30], [-30, 5, 15, 20, 20, 15, 5, -30], [-30, 0, 15, 20, 20, 15, 0, -30], [-30, 5, 10, 15, 15, 10, 5, -30], [-40, -20, 0, 5, 5, 0, -20, -40], [-50, -40, -30, -30, -30, -30, -40, -50]],
bishop: [[-20, -10, -10, -10, -10, -10, -10, -20], [-10, 0, 0, 0, 0, 0, 0, -10], [-10, 0, 10, 10, 10, 10, 0, -10], [-10, 5, 5, 10, 10, 5, 5, -10], [-10, 0, 5, 10, 10, 5, 0, -10], [-10, 5, 5, 5, 5, 5, 5, -10], [-10, 0, 5, 0, 0, 5, 0, -10], [-20, -10, -10, -10, -10, -10, -10, -20]],
rook: [[0, 0, 0, 0, 0, 0, 0, 0], [5, 10, 10, 10, 10, 10, 10, 5], [-5, 0, 0, 0, 0, 0, 0, -5], [-5, 0, 0, 0, 0, 0, 0, -5], [-5, 0, 0, 0, 0, 0, 0, -5], [-5, 0, 0, 0, 0, 0, 0, -5], [-5, 0, 0, 0, 0, 0, 0, -5], [0, 0, 0, 5, 5, 0, 0, 0]],
queen: [[-20, -10, -10, -5, -5, -10, -10, -20], [-10, 0, 0, 0, 0, 0, 0, -10], [-10, 0, 5, 5, 5, 5, 0, -10], [-5, 0, 5, 5, 5, 5, 0, -5], [0, 0, 5, 5, 5, 5, 0, -5], [-10, 5, 5, 5, 5, 5, 0, -10], [-10, 0, 5, 0, 0, 0, 0, -10], [-20, -10, -10, -5, -5, -10, -10, -20]],
king: [[-30, -40, -40, -50, -50, -40, -40, -30], [-30, -40, -40, -50, -50, -40, -40, -30], [-30, -40, -40, -50, -50, -40, -40, -30], [-30, -40, -40, -50, -50, -40, -40, -30], [-20, -30, -30, -40, -40, -30, -30, -20], [-10, -20, -20, -20, -20, -20, -20, -10], [20, 20, 0, 0, 0, 0, 20, 20], [20, 30, 10, 0, 0, 10, 30, 20]]
};
// Base piece values
var pieceValues = {
pawn: 100,
knight: 320,
bishop: 330,
rook: 500,
queen: 900,
king: 20000
};
if (allPossibleMoves.length > 0) {
var selectedMove;
// Basic AI difficulty levels
if (gameState.aiDifficulty === 1) {
// Easy: Random move with slight preference for captures
var captureMoves = allPossibleMoves.filter(function (moveObj) {
return gameState.board[moveObj.move.row][moveObj.move.col] !== null;
});
if (captureMoves.length > 0 && Math.random() > 0.5) {
selectedMove = captureMoves[Math.floor(Math.random() * captureMoves.length)];
} else {
selectedMove = allPossibleMoves[Math.floor(Math.random() * allPossibleMoves.length)];
}
} else if (gameState.aiDifficulty === 2) {
// Medium: Prefer captures and positional advantages
var moveScores = [];
for (var i = 0; i < allPossibleMoves.length; i++) {
var moveObj = allPossibleMoves[i];
var targetPiece = gameState.board[moveObj.move.row][moveObj.move.col];
var score = 0;
// Capture value
if (targetPiece) {
score += pieceValues[targetPiece.type];
}
// Position value (flipped for black pieces)
var row = moveObj.move.row;
var col = moveObj.move.col;
if (gameState.aiPlayer === 'black') {
row = 7 - row;
}
score += piecePositionValues[moveObj.piece.type][row][col] * 0.5;
moveScores.push({
move: moveObj,
score: score
});
}
// Sort moves by score
moveScores.sort(function (a, b) {
return b.score - a.score;
});
// Pick from top 3 moves to add some randomness
var topMoves = moveScores.slice(0, Math.min(3, moveScores.length));
selectedMove = topMoves[Math.floor(Math.random() * topMoves.length)].move;
} else {
// Hard: Minimax with alpha-beta pruning (simplified)
var bestScore = -Infinity;
var bestMoves = [];
for (var i = 0; i < allPossibleMoves.length; i++) {
var moveObj = allPossibleMoves[i];
var piece = moveObj.piece;
var targetRow = moveObj.move.row;
var targetCol = moveObj.move.col;
var originalRow = piece.row;
var originalCol = piece.col;
var capturedPiece = gameState.board[targetRow][targetCol];
// Make temporary move
gameState.board[targetRow][targetCol] = piece;
gameState.board[originalRow][originalCol] = null;
// Evaluate board after move
var score = evaluateBoard(gameState.aiPlayer);
// Unmake the move
gameState.board[originalRow][originalCol] = piece;
gameState.board[targetRow][targetCol] = capturedPiece;
if (score > bestScore) {
bestScore = score;
bestMoves = [moveObj];
} else if (score === bestScore) {
bestMoves.push(moveObj);
}
}
if (bestMoves.length > 0) {
selectedMove = bestMoves[Math.floor(Math.random() * bestMoves.length)];
} else {
selectedMove = allPossibleMoves[Math.floor(Math.random() * allPossibleMoves.length)];
}
}
// Make the selected move
movePiece(selectedMove.piece, selectedMove.move.row, selectedMove.move.col);
}
LK.clearTimeout(aiTimeout);
}, 500);
// Function to evaluate the current board position
function evaluateBoard(playerColor) {
var score = 0;
var opponentColor = playerColor === 'white' ? 'black' : 'white';
// Count material and positional advantages
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var piece = gameState.board[r][c];
if (!piece) continue;
var value = pieceValues[piece.type];
// Add position value
var row = r;
var col = c;
if (piece.color === 'black') {
row = 7 - r; // Flip board for black
}
var positionValue = piecePositionValues[piece.type][row][col];
// Add to score if our piece, subtract if opponent's
if (piece.color === playerColor) {
score += value + positionValue;
// Bonus for attacking opponent pieces
var attackedSquares = getAttackedSquares(piece);
for (var i = 0; i < attackedSquares.length; i++) {
var attackedPiece = gameState.board[attackedSquares[i].row][attackedSquares[i].col];
if (attackedPiece && attackedPiece.color === opponentColor) {
score += value * 0.1; // Small bonus for attacking
// Higher bonus for attacking higher value pieces
score += pieceValues[attackedPiece.type] * 0.05;
}
}
// Bonus for piece mobility (number of legal moves)
score += getValidMoves(piece).length * 2;
// Bonus for developed pieces
if (piece.type === 'knight' || piece.type === 'bishop') {
var initialRow = playerColor === 'white' ? 7 : 0;
if (piece.row !== initialRow) {
score += 15; // Developed minor piece
}
}
} else {
score -= value + positionValue;
}
}
}
// Check if opponent is in check
var tempTurn = gameState.currentTurn;
gameState.currentTurn = opponentColor;
if (isKingInCheck(opponentColor)) {
score += 50; // Bonus for putting opponent in check
// Check if it's checkmate (huge bonus)
if (isCheckmate(opponentColor)) {
score += 10000;
}
}
// Penalize if our king is in check
gameState.currentTurn = playerColor;
if (isKingInCheck(playerColor)) {
score -= 60; // Penalty for being in check
}
gameState.currentTurn = tempTurn;
// Pawn structure evaluation
score += evaluatePawnStructure(playerColor) - evaluatePawnStructure(opponentColor);
// Control of center
score += evaluateCenterControl(playerColor) - evaluateCenterControl(opponentColor);
return score;
}
// Evaluate pawn structure
function evaluatePawnStructure(color) {
var score = 0;
var pawnColumns = [];
// Find all pawns
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var piece = gameState.board[r][c];
if (piece && piece.type === 'pawn' && piece.color === color) {
pawnColumns.push(c);
// Bonus for passed pawns (no enemy pawns ahead)
var passed = true;
var direction = color === 'white' ? -1 : 1;
for (var checkRow = r + direction; checkRow >= 0 && checkRow < 8; checkRow += direction) {
for (var checkCol = c - 1; checkCol <= c + 1; checkCol += 2) {
if (checkCol >= 0 && checkCol < 8) {
var checkPiece = gameState.board[checkRow][checkCol];
if (checkPiece && checkPiece.type === 'pawn' && checkPiece.color !== color) {
passed = false;
break;
}
}
}
if (!passed) break;
}
if (passed) {
score += 30; // Passed pawn bonus
// Bigger bonus for pawns closer to promotion
var promotionDistance = color === 'white' ? r : 7 - r;
score += (7 - promotionDistance) * 5;
}
}
}
}
// Penalize doubled pawns (multiple pawns in same column)
var columnCounts = {};
for (var i = 0; i < pawnColumns.length; i++) {
var col = pawnColumns[i];
columnCounts[col] = (columnCounts[col] || 0) + 1;
}
for (var col in columnCounts) {
if (columnCounts[col] > 1) {
score -= 15 * (columnCounts[col] - 1); // Penalty for doubled pawns
}
}
return score;
}
// Evaluate center control
function evaluateCenterControl(color) {
var score = 0;
var centerSquares = [{
row: 3,
col: 3
}, {
row: 3,
col: 4
}, {
row: 4,
col: 3
}, {
row: 4,
col: 4
}];
for (var i = 0; i < centerSquares.length; i++) {
var square = centerSquares[i];
var piece = gameState.board[square.row][square.col];
// Bonus for occupying center
if (piece && piece.color === color) {
score += 10;
}
// Bonus for attacking center
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var attackingPiece = gameState.board[r][c];
if (attackingPiece && attackingPiece.color === color) {
var attackedSquares = getAttackedSquares(attackingPiece);
for (var j = 0; j < attackedSquares.length; j++) {
if (attackedSquares[j].row === square.row && attackedSquares[j].col === square.col) {
score += 5;
break;
}
}
}
}
}
}
return score;
}
// Get squares a piece can attack
function getAttackedSquares(piece) {
return getBasicMoves(piece);
}
}
// Game event handlers
game.down = function (x, y, obj) {
if (gameState.isAnimating) return;
// Convert screen coordinates to board coordinates
var boardX = x - chessBoard.x;
var boardY = y - chessBoard.y;
var tile = chessBoard.getTileAt(boardX, boardY);
if (tile && gameState.selectedPiece) {
// Check if the clicked tile is a valid move
var isValidMove = false;
for (var i = 0; i < gameState.validMoves.length; i++) {
var move = gameState.validMoves[i];
if (move.row === tile.row && move.col === tile.col) {
isValidMove = true;
break;
}
}
if (isValidMove) {
// Move the piece
movePiece(gameState.selectedPiece, tile.row, tile.col);
return;
}
}
// Clear selection if clicking elsewhere
if (gameState.selectedPiece) {
chessBoard.clearHighlights();
gameState.selectedPiece = null;
}
};
// Game Mode Buttons
var twoPlayerBtn = new Text2('Two Player', {
size: 50,
fill: 0xFFFFFF
});
twoPlayerBtn.anchor.set(0.5, 0);
twoPlayerBtn.y = 2732 - 200;
twoPlayerBtn.x = 2048 / 2 - 300;
LK.gui.addChild(twoPlayerBtn);
twoPlayerBtn.interactive = true;
twoPlayerBtn.buttonMode = true;
twoPlayerBtn.on('pointerdown', function () {
gameState.gameMode = 'twoPlayer';
gameState.aiPlayer = null;
LK.setScore(0);
initializeChessBoard();
updateTurnText();
chessBoard.clearHighlights();
});
var aiBtn = new Text2('Play vs AI', {
size: 50,
fill: 0xFFFFFF
});
aiBtn.anchor.set(0.5, 0);
aiBtn.y = 2732 - 200;
aiBtn.x = 2048 / 2 + 300;
LK.gui.addChild(aiBtn);
aiBtn.interactive = true;
aiBtn.buttonMode = true;
aiBtn.on('pointerdown', function () {
gameState.gameMode = 'ai';
gameState.aiPlayer = 'black';
LK.setScore(0);
initializeChessBoard();
updateTurnText();
chessBoard.clearHighlights();
});
// AI Difficulty Buttons
var easyBtn = new Text2('Easy', {
size: 40,
fill: gameState.aiDifficulty === 1 ? "#ffff00" : "#aaaaaa"
});
easyBtn.style = {
fill: gameState.aiDifficulty === 1 ? "#ffff00" : "#aaaaaa"
};
easyBtn.anchor.set(0.5, 0);
easyBtn.y = 2732 - 120;
easyBtn.x = 2048 / 2 - 300;
LK.gui.addChild(easyBtn);
easyBtn.interactive = true;
easyBtn.buttonMode = true;
easyBtn.on('pointerdown', function () {
gameState.aiDifficulty = 1;
storage.aiDifficulty = 1;
easyBtn.style.fill = "#ffff00";
mediumBtn.style.fill = "#aaaaaa";
hardBtn.style.fill = "#aaaaaa";
});
var mediumBtn = new Text2('Medium', {
size: 40,
fill: gameState.aiDifficulty === 2 ? "#ffff00" : "#aaaaaa"
});
mediumBtn.style = {
fill: gameState.aiDifficulty === 2 ? "#ffff00" : "#aaaaaa"
};
mediumBtn.anchor.set(0.5, 0);
mediumBtn.y = 2732 - 120;
mediumBtn.x = 2048 / 2;
LK.gui.addChild(mediumBtn);
mediumBtn.interactive = true;
mediumBtn.buttonMode = true;
mediumBtn.on('pointerdown', function () {
gameState.aiDifficulty = 2;
storage.aiDifficulty = 2;
easyBtn.style.fill = "#aaaaaa";
mediumBtn.style.fill = "#ffff00";
hardBtn.style.fill = "#aaaaaa";
});
var hardBtn = new Text2('Hard', {
size: 40,
fill: gameState.aiDifficulty === 3 ? "#ffff00" : "#aaaaaa"
});
hardBtn.style = {
fill: gameState.aiDifficulty === 3 ? "#ffff00" : "#aaaaaa"
};
hardBtn.anchor.set(0.5, 0);
hardBtn.y = 2732 - 120;
hardBtn.x = 2048 / 2 + 300;
LK.gui.addChild(hardBtn);
hardBtn.interactive = true;
hardBtn.buttonMode = true;
hardBtn.on('pointerdown', function () {
gameState.aiDifficulty = 3;
storage.aiDifficulty = 3;
easyBtn.style.fill = "#aaaaaa";
mediumBtn.style.fill = "#aaaaaa";
hardBtn.style.fill = "#ffff00";
});
// Update difficulty button colors based on current setting
switch (gameState.aiDifficulty) {
case 1:
easyBtn.style.fill = "#ffff00";
mediumBtn.style.fill = "#aaaaaa";
hardBtn.style.fill = "#aaaaaa";
break;
case 2:
easyBtn.style.fill = "#aaaaaa";
mediumBtn.style.fill = "#ffff00";
hardBtn.style.fill = "#aaaaaa";
break;
case 3:
easyBtn.style.fill = "#aaaaaa";
mediumBtn.style.fill = "#aaaaaa";
hardBtn.style.fill = "#ffff00";
break;
}
// New Game button
var newGameBtn = new Text2('New Game', {
size: 50,
fill: 0xFFFFFF
});
newGameBtn.anchor.set(0.5, 0);
newGameBtn.y = 2732 - 280;
newGameBtn.x = 2048 / 2;
LK.gui.addChild(newGameBtn);
newGameBtn.interactive = true;
newGameBtn.buttonMode = true;
newGameBtn.on('pointerdown', function () {
initializeChessBoard();
updateTurnText();
});
// Initialize the game
initializeChessBoard();
// Main game loop
game.update = function () {
// Game update logic happens in event handlers and callbacks
};
// Play background music
LK.playMusic('bgMusic', {
fade: {
start: 0,
end: 0.3,
duration: 1000
}
}); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
aiDifficulty: 1,
gameHistory: []
});
/****
* Classes
****/
var Board = Container.expand(function () {
var self = Container.call(this);
self.tiles = [];
self.tileSize = 256;
self.boardSize = 8 * self.tileSize;
self.highlightLayer = new Container();
self.piecesLayer = new Container();
self.createBoard = function () {
// Create tiles
for (var row = 0; row < 8; row++) {
self.tiles[row] = [];
for (var col = 0; col < 8; col++) {
var isWhite = (row + col) % 2 === 0;
var tile = self.attachAsset(isWhite ? 'whiteTile' : 'blackTile', {
x: col * self.tileSize,
y: row * self.tileSize,
anchorX: 0,
anchorY: 0
});
self.tiles[row][col] = tile;
tile.boardRow = row;
tile.boardCol = col;
// Add coordinate labels
if (row === 7) {
// Add file (column) labels at the bottom
var fileLabel = new Text2(String.fromCharCode(97 + col), {
size: 30,
fill: isWhite ? "#333333" : "#ffffff"
});
fileLabel.anchor.set(1.0, 1.0);
fileLabel.x = (col + 1) * self.tileSize - 10;
fileLabel.y = (row + 1) * self.tileSize - 10;
self.addChild(fileLabel);
}
if (col === 0) {
// Add rank (row) labels on the left
var rankLabel = new Text2(String(8 - row), {
size: 30,
fill: isWhite ? "#333333" : "#ffffff"
});
rankLabel.anchor.set(0.0, 0.0);
rankLabel.x = col * self.tileSize + 10;
rankLabel.y = row * self.tileSize + 10;
self.addChild(rankLabel);
}
}
}
self.addChild(self.highlightLayer);
self.addChild(self.piecesLayer);
};
self.highlightTile = function (row, col, type) {
var highlight = LK.getAsset(type, {
x: col * self.tileSize,
y: row * self.tileSize,
anchorX: 0,
anchorY: 0,
alpha: 0.5
});
self.highlightLayer.addChild(highlight);
return highlight;
};
self.clearHighlights = function () {
while (self.highlightLayer.children.length > 0) {
self.highlightLayer.removeChildAt(0);
}
};
self.getTileAt = function (x, y) {
var col = Math.floor(x / self.tileSize);
var row = Math.floor(y / self.tileSize);
if (row >= 0 && row < 8 && col >= 0 && col < 8) {
return {
row: row,
col: col
};
}
return null;
};
return self;
});
var Piece = Container.expand(function (type, color, row, col) {
var self = Container.call(this);
self.type = type;
self.color = color;
self.row = row;
self.col = col;
self.hasMoved = false;
self.getPieceSymbol = function () {
switch (self.type) {
case 'king':
return '♚';
case 'queen':
return '♛';
case 'rook':
return '♜';
case 'bishop':
return '♝';
case 'knight':
return '♞';
case 'pawn':
return '♟';
default:
return '';
}
};
// Get asset id based on color and type
var assetId = color + type.charAt(0).toUpperCase() + type.slice(1);
self.sprite = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Add chess piece symbol
self.symbol = new Text2(self.getPieceSymbol(), {
size: 150,
fill: color === 'white' ? "#333333" : "#ffffff"
});
self.symbol.anchor.set(0.5, 0.5);
self.addChild(self.symbol);
self.moveTo = function (row, col, duration) {
self.row = row;
self.col = col;
var newX = col * chessBoard.tileSize + chessBoard.tileSize / 2;
var newY = row * chessBoard.tileSize + chessBoard.tileSize / 2;
if (duration) {
tween(self, {
x: newX,
y: newY
}, {
duration: duration,
easing: tween.easeOutQuad
});
} else {
self.x = newX;
self.y = newY;
}
self.hasMoved = true;
};
self.update = function () {
// For any per-frame updates
};
self.down = function (x, y, obj) {
if (gameState.currentTurn === self.color && !gameState.isAnimating) {
gameState.selectedPiece = self;
chessBoard.clearHighlights();
// Highlight the current piece's tile
self.currentHighlight = chessBoard.highlightTile(self.row, self.col, 'highlightTile');
// Highlight valid moves
var validMoves = getValidMoves(self);
gameState.validMoves = validMoves;
for (var i = 0; i < validMoves.length; i++) {
var move = validMoves[i];
chessBoard.highlightTile(move.row, move.col, 'validMoveTile');
}
}
};
// Place piece at its position
self.x = col * 256 + 128;
self.y = row * 256 + 128;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50
});
/****
* Game Code
****/
// Game state
var gameState = {
currentTurn: 'white',
selectedPiece: null,
validMoves: [],
board: Array(8).fill().map(function () {
return Array(8).fill(null);
}),
isAnimating: false,
lastMove: null,
aiDifficulty: storage.aiDifficulty || 1,
gameMode: 'ai',
// 'ai' or 'twoPlayer'
status: 'playing',
// 'playing', 'check', 'checkmate', 'stalemate'
aiPlayer: 'black'
};
// Create UI
var titleText = new Text2('Chess Challenge', {
size: 80,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0);
LK.gui.top.addChild(titleText);
var turnText = new Text2('White to move', {
size: 50,
fill: 0xFFFFFF
});
turnText.anchor.set(0.5, 0);
turnText.y = 100;
LK.gui.top.addChild(turnText);
var statusText = new Text2('', {
size: 50,
fill: 0xFFFF00
});
statusText.anchor.set(0.5, 0);
statusText.y = 160;
LK.gui.top.addChild(statusText);
// Create and position the chess board
var chessBoard = new Board();
game.addChild(chessBoard);
chessBoard.createBoard();
// Center the board
chessBoard.x = (2048 - chessBoard.boardSize) / 2;
chessBoard.y = (2732 - chessBoard.boardSize) / 2;
// Initialize pieces
function initializeChessBoard() {
// Clear existing pieces
while (chessBoard.piecesLayer.children.length > 0) {
chessBoard.piecesLayer.removeChildAt(0);
}
// Reset game state
gameState.board = Array(8).fill().map(function () {
return Array(8).fill(null);
});
gameState.currentTurn = 'white';
gameState.selectedPiece = null;
gameState.validMoves = [];
gameState.isAnimating = false;
gameState.lastMove = null;
gameState.status = 'playing';
// Update UI
updateTurnText();
// Create pieces
var pieces = [{
type: 'rook',
color: 'black',
row: 0,
col: 0
}, {
type: 'knight',
color: 'black',
row: 0,
col: 1
}, {
type: 'bishop',
color: 'black',
row: 0,
col: 2
}, {
type: 'queen',
color: 'black',
row: 0,
col: 3
}, {
type: 'king',
color: 'black',
row: 0,
col: 4
}, {
type: 'bishop',
color: 'black',
row: 0,
col: 5
}, {
type: 'knight',
color: 'black',
row: 0,
col: 6
}, {
type: 'rook',
color: 'black',
row: 0,
col: 7
}, {
type: 'rook',
color: 'white',
row: 7,
col: 0
}, {
type: 'knight',
color: 'white',
row: 7,
col: 1
}, {
type: 'bishop',
color: 'white',
row: 7,
col: 2
}, {
type: 'queen',
color: 'white',
row: 7,
col: 3
}, {
type: 'king',
color: 'white',
row: 7,
col: 4
}, {
type: 'bishop',
color: 'white',
row: 7,
col: 5
}, {
type: 'knight',
color: 'white',
row: 7,
col: 6
}, {
type: 'rook',
color: 'white',
row: 7,
col: 7
}];
// Add pawns
for (var i = 0; i < 8; i++) {
pieces.push({
type: 'pawn',
color: 'black',
row: 1,
col: i
});
pieces.push({
type: 'pawn',
color: 'white',
row: 6,
col: i
});
}
// Create and position pieces
for (var i = 0; i < pieces.length; i++) {
var p = pieces[i];
var piece = new Piece(p.type, p.color, p.row, p.col);
chessBoard.piecesLayer.addChild(piece);
gameState.board[p.row][p.col] = piece;
}
// Clear highlights
chessBoard.clearHighlights();
}
// Chess move validation
function getValidMoves(piece) {
var moves = [];
if (!piece) return moves;
function addMove(row, col) {
moves.push({
row: row,
col: col
});
}
function isOnBoard(row, col) {
return row >= 0 && row < 8 && col >= 0 && col < 8;
}
function isOccupied(row, col) {
return gameState.board[row][col] !== null;
}
function isEnemyPiece(row, col) {
return isOccupied(row, col) && gameState.board[row][col].color !== piece.color;
}
function isFriendlyPiece(row, col) {
return isOccupied(row, col) && gameState.board[row][col].color === piece.color;
}
var row = piece.row;
var col = piece.col;
switch (piece.type) {
case 'pawn':
var direction = piece.color === 'white' ? -1 : 1;
// Move forward one square
if (isOnBoard(row + direction, col) && !isOccupied(row + direction, col)) {
addMove(row + direction, col);
// Move forward two squares on first move
if (!piece.hasMoved && isOnBoard(row + 2 * direction, col) && !isOccupied(row + 2 * direction, col)) {
addMove(row + 2 * direction, col);
}
}
// Capture diagonally
if (isOnBoard(row + direction, col - 1) && isEnemyPiece(row + direction, col - 1)) {
addMove(row + direction, col - 1);
}
if (isOnBoard(row + direction, col + 1) && isEnemyPiece(row + direction, col + 1)) {
addMove(row + direction, col + 1);
}
// En passant (simplified)
if (gameState.lastMove && gameState.lastMove.piece.type === 'pawn' && gameState.lastMove.fromRow === (piece.color === 'white' ? 1 : 6) && gameState.lastMove.toRow === (piece.color === 'white' ? 3 : 4) && Math.abs(gameState.lastMove.toCol - col) === 1 && row === gameState.lastMove.toRow) {
addMove(row + direction, gameState.lastMove.toCol);
}
break;
case 'rook':
// Horizontal and vertical directions
var directions = [{
dr: -1,
dc: 0
},
// up
{
dr: 1,
dc: 0
},
// down
{
dr: 0,
dc: -1
},
// left
{
dr: 0,
dc: 1
} // right
];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
while (isOnBoard(r, c)) {
if (isOccupied(r, c)) {
if (isEnemyPiece(r, c)) {
addMove(r, c);
}
break;
}
addMove(r, c);
r += dir.dr;
c += dir.dc;
}
});
break;
case 'knight':
// Knight moves
var knightMoves = [{
dr: -2,
dc: -1
}, {
dr: -2,
dc: 1
}, {
dr: -1,
dc: -2
}, {
dr: -1,
dc: 2
}, {
dr: 1,
dc: -2
}, {
dr: 1,
dc: 2
}, {
dr: 2,
dc: -1
}, {
dr: 2,
dc: 1
}];
knightMoves.forEach(function (move) {
var r = row + move.dr;
var c = col + move.dc;
if (isOnBoard(r, c) && !isFriendlyPiece(r, c)) {
addMove(r, c);
}
});
break;
case 'bishop':
// Diagonal directions
var directions = [{
dr: -1,
dc: -1
},
// up-left
{
dr: -1,
dc: 1
},
// up-right
{
dr: 1,
dc: -1
},
// down-left
{
dr: 1,
dc: 1
} // down-right
];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
while (isOnBoard(r, c)) {
if (isOccupied(r, c)) {
if (isEnemyPiece(r, c)) {
addMove(r, c);
}
break;
}
addMove(r, c);
r += dir.dr;
c += dir.dc;
}
});
break;
case 'queen':
// Combine rook and bishop moves
var directions = [{
dr: -1,
dc: 0
}, {
dr: 1,
dc: 0
},
// vertical
{
dr: 0,
dc: -1
}, {
dr: 0,
dc: 1
},
// horizontal
{
dr: -1,
dc: -1
}, {
dr: -1,
dc: 1
},
// diagonal
{
dr: 1,
dc: -1
}, {
dr: 1,
dc: 1
} // diagonal
];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
while (isOnBoard(r, c)) {
if (isOccupied(r, c)) {
if (isEnemyPiece(r, c)) {
addMove(r, c);
}
break;
}
addMove(r, c);
r += dir.dr;
c += dir.dc;
}
});
break;
case 'king':
// King moves (one square in any direction)
var directions = [{
dr: -1,
dc: -1
}, {
dr: -1,
dc: 0
}, {
dr: -1,
dc: 1
}, {
dr: 0,
dc: -1
}, {
dr: 0,
dc: 1
}, {
dr: 1,
dc: -1
}, {
dr: 1,
dc: 0
}, {
dr: 1,
dc: 1
}];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
if (isOnBoard(r, c) && !isFriendlyPiece(r, c)) {
addMove(r, c);
}
});
// Castling
if (!piece.hasMoved) {
// Kingside castling
if (!isOccupied(row, col + 1) && !isOccupied(row, col + 2) && isOccupied(row, col + 3) && !gameState.board[row][col + 3].hasMoved && gameState.board[row][col + 3].type === 'rook') {
addMove(row, col + 2);
}
// Queenside castling
if (!isOccupied(row, col - 1) && !isOccupied(row, col - 2) && !isOccupied(row, col - 3) && isOccupied(row, col - 4) && !gameState.board[row][col - 4].hasMoved && gameState.board[row][col - 4].type === 'rook') {
addMove(row, col - 2);
}
}
break;
}
// Filter out moves that would put the king in check
var validMoves = [];
for (var i = 0; i < moves.length; i++) {
var move = moves[i];
// Make a temporary move
var tempPiece = gameState.board[move.row][move.col];
gameState.board[move.row][move.col] = piece;
gameState.board[row][col] = null;
// Check if the move puts/leaves the king in check
var inCheck = isKingInCheck(piece.color);
// Undo the move
gameState.board[row][col] = piece;
gameState.board[move.row][move.col] = tempPiece;
if (!inCheck) {
validMoves.push(move);
}
}
return validMoves;
}
function isKingInCheck(color) {
// Find the king
var kingRow = -1;
var kingCol = -1;
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var piece = gameState.board[r][c];
if (piece && piece.type === 'king' && piece.color === color) {
kingRow = r;
kingCol = c;
break;
}
}
if (kingRow !== -1) break;
}
// Check if any enemy piece can capture the king
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var piece = gameState.board[r][c];
if (piece && piece.color !== color) {
// Get the enemy's valid moves (ignoring check)
var moves = getBasicMoves(piece);
// See if any of the moves can capture the king
for (var i = 0; i < moves.length; i++) {
if (moves[i].row === kingRow && moves[i].col === kingCol) {
return true;
}
}
}
}
}
return false;
}
function getBasicMoves(piece) {
var moves = [];
if (!piece) return moves;
function addMove(row, col) {
moves.push({
row: row,
col: col
});
}
function isOnBoard(row, col) {
return row >= 0 && row < 8 && col >= 0 && col < 8;
}
function isOccupied(row, col) {
return gameState.board[row][col] !== null;
}
function isEnemyPiece(row, col) {
return isOccupied(row, col) && gameState.board[row][col].color !== piece.color;
}
function isFriendlyPiece(row, col) {
return isOccupied(row, col) && gameState.board[row][col].color === piece.color;
}
var row = piece.row;
var col = piece.col;
switch (piece.type) {
case 'pawn':
var direction = piece.color === 'white' ? -1 : 1;
// Move forward one square
if (isOnBoard(row + direction, col) && !isOccupied(row + direction, col)) {
addMove(row + direction, col);
// Move forward two squares on first move
if (!piece.hasMoved && isOnBoard(row + 2 * direction, col) && !isOccupied(row + 2 * direction, col)) {
addMove(row + 2 * direction, col);
}
}
// Capture diagonally
if (isOnBoard(row + direction, col - 1) && isEnemyPiece(row + direction, col - 1)) {
addMove(row + direction, col - 1);
}
if (isOnBoard(row + direction, col + 1) && isEnemyPiece(row + direction, col + 1)) {
addMove(row + direction, col + 1);
}
break;
case 'rook':
// Horizontal and vertical directions
var directions = [{
dr: -1,
dc: 0
},
// up
{
dr: 1,
dc: 0
},
// down
{
dr: 0,
dc: -1
},
// left
{
dr: 0,
dc: 1
} // right
];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
while (isOnBoard(r, c)) {
if (isOccupied(r, c)) {
if (isEnemyPiece(r, c)) {
addMove(r, c);
}
break;
}
addMove(r, c);
r += dir.dr;
c += dir.dc;
}
});
break;
case 'knight':
// Knight moves
var knightMoves = [{
dr: -2,
dc: -1
}, {
dr: -2,
dc: 1
}, {
dr: -1,
dc: -2
}, {
dr: -1,
dc: 2
}, {
dr: 1,
dc: -2
}, {
dr: 1,
dc: 2
}, {
dr: 2,
dc: -1
}, {
dr: 2,
dc: 1
}];
knightMoves.forEach(function (move) {
var r = row + move.dr;
var c = col + move.dc;
if (isOnBoard(r, c) && !isFriendlyPiece(r, c)) {
addMove(r, c);
}
});
break;
case 'bishop':
// Diagonal directions
var directions = [{
dr: -1,
dc: -1
},
// up-left
{
dr: -1,
dc: 1
},
// up-right
{
dr: 1,
dc: -1
},
// down-left
{
dr: 1,
dc: 1
} // down-right
];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
while (isOnBoard(r, c)) {
if (isOccupied(r, c)) {
if (isEnemyPiece(r, c)) {
addMove(r, c);
}
break;
}
addMove(r, c);
r += dir.dr;
c += dir.dc;
}
});
break;
case 'queen':
// Combine rook and bishop moves
var directions = [{
dr: -1,
dc: 0
}, {
dr: 1,
dc: 0
},
// vertical
{
dr: 0,
dc: -1
}, {
dr: 0,
dc: 1
},
// horizontal
{
dr: -1,
dc: -1
}, {
dr: -1,
dc: 1
},
// diagonal
{
dr: 1,
dc: -1
}, {
dr: 1,
dc: 1
} // diagonal
];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
while (isOnBoard(r, c)) {
if (isOccupied(r, c)) {
if (isEnemyPiece(r, c)) {
addMove(r, c);
}
break;
}
addMove(r, c);
r += dir.dr;
c += dir.dc;
}
});
break;
case 'king':
// King moves (one square in any direction)
var directions = [{
dr: -1,
dc: -1
}, {
dr: -1,
dc: 0
}, {
dr: -1,
dc: 1
}, {
dr: 0,
dc: -1
}, {
dr: 0,
dc: 1
}, {
dr: 1,
dc: -1
}, {
dr: 1,
dc: 0
}, {
dr: 1,
dc: 1
}];
directions.forEach(function (dir) {
var r = row + dir.dr;
var c = col + dir.dc;
if (isOnBoard(r, c) && !isFriendlyPiece(r, c)) {
addMove(r, c);
}
});
break;
}
return moves;
}
function isCheckmate(color) {
if (!isKingInCheck(color)) return false;
// Check if any piece can make a valid move
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var piece = gameState.board[r][c];
if (piece && piece.color === color) {
var validMoves = getValidMoves(piece);
if (validMoves.length > 0) {
return false;
}
}
}
}
return true;
}
function isStalemate(color) {
if (isKingInCheck(color)) return false;
// Check if any piece can make a valid move
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var piece = gameState.board[r][c];
if (piece && piece.color === color) {
var validMoves = getValidMoves(piece);
if (validMoves.length > 0) {
return false;
}
}
}
}
return true;
}
function updateGameStatus() {
if (isCheckmate(gameState.currentTurn)) {
gameState.status = 'checkmate';
var winner = gameState.currentTurn === 'white' ? 'Black' : 'White';
statusText.setText(winner + ' wins by checkmate!');
LK.getSound('gameEnd').play();
// Update score if AI was played against
if (gameState.gameMode === 'ai') {
if (winner === 'White' && gameState.aiPlayer === 'black' || winner === 'Black' && gameState.aiPlayer === 'white') {
LK.setScore(LK.getScore() + 1);
}
}
return true;
} else if (isStalemate(gameState.currentTurn)) {
gameState.status = 'stalemate';
statusText.setText('Game drawn by stalemate!');
LK.getSound('gameEnd').play();
return true;
} else if (isKingInCheck(gameState.currentTurn)) {
gameState.status = 'check';
statusText.setText(gameState.currentTurn.charAt(0).toUpperCase() + gameState.currentTurn.slice(1) + ' is in check!');
LK.getSound('check').play();
} else {
gameState.status = 'playing';
statusText.setText('');
}
return false;
}
function updateTurnText() {
var turnName = gameState.currentTurn.charAt(0).toUpperCase() + gameState.currentTurn.slice(1);
turnText.setText(turnName + ' to move');
}
function movePiece(piece, targetRow, targetCol) {
var isCapture = gameState.board[targetRow][targetCol] !== null;
var capturedPiece = gameState.board[targetRow][targetCol];
var fromRow = piece.row;
var fromCol = piece.col;
// Handle castling
if (piece.type === 'king' && Math.abs(targetCol - fromCol) === 2) {
// Kingside castling
if (targetCol > fromCol) {
var rook = gameState.board[fromRow][7];
gameState.board[fromRow][5] = rook;
gameState.board[fromRow][7] = null;
rook.moveTo(fromRow, 5, 300);
}
// Queenside castling
else {
var rook = gameState.board[fromRow][0];
gameState.board[fromRow][3] = rook;
gameState.board[fromRow][0] = null;
rook.moveTo(fromRow, 3, 300);
}
}
// Handle en passant capture
if (piece.type === 'pawn' && Math.abs(targetCol - fromCol) === 1 && !isCapture) {
var enPassantRow = piece.color === 'white' ? targetRow + 1 : targetRow - 1;
capturedPiece = gameState.board[enPassantRow][targetCol];
gameState.board[enPassantRow][targetCol] = null;
if (capturedPiece) {
capturedPiece.parent.removeChild(capturedPiece);
}
isCapture = true;
}
// Update board array
gameState.board[fromRow][fromCol] = null;
if (capturedPiece) {
capturedPiece.parent.removeChild(capturedPiece);
}
gameState.board[targetRow][targetCol] = piece;
// Move the piece with animation
gameState.isAnimating = true;
piece.moveTo(targetRow, targetCol, 300);
// Play sound
if (isCapture) {
LK.getSound('capture').play();
} else {
LK.getSound('move').play();
}
// Handle pawn promotion (always to queen for simplicity)
if (piece.type === 'pawn' && (targetRow === 0 || targetRow === 7)) {
// Wait for the piece to finish moving
var promotionTimeout = LK.setTimeout(function () {
// Remove the pawn
piece.parent.removeChild(piece);
gameState.board[targetRow][targetCol] = null;
// Create a new queen
var queen = new Piece('queen', piece.color, targetRow, targetCol);
chessBoard.piecesLayer.addChild(queen);
gameState.board[targetRow][targetCol] = queen;
LK.clearTimeout(promotionTimeout);
}, 350);
}
// Update last move
gameState.lastMove = {
piece: piece,
fromRow: fromRow,
fromCol: fromCol,
toRow: targetRow,
toCol: targetCol
};
// Highlight the last move
var moveTimeout = LK.setTimeout(function () {
chessBoard.clearHighlights();
chessBoard.highlightTile(fromRow, fromCol, 'lastMoveTile');
chessBoard.highlightTile(targetRow, targetCol, 'lastMoveTile');
// Switch turns
gameState.currentTurn = gameState.currentTurn === 'white' ? 'black' : 'white';
updateTurnText();
// Check for check, checkmate, stalemate
var gameEnded = updateGameStatus();
gameState.isAnimating = false;
gameState.selectedPiece = null;
// If AI is playing and it's AI's turn
if (!gameEnded && gameState.gameMode === 'ai' && gameState.currentTurn === gameState.aiPlayer) {
makeAIMove();
}
LK.clearTimeout(moveTimeout);
}, 350);
}
function makeAIMove() {
var aiTimeout = LK.setTimeout(function () {
var allPossibleMoves = [];
// Collect all possible moves for AI pieces
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var piece = gameState.board[r][c];
if (piece && piece.color === gameState.aiPlayer) {
var validMoves = getValidMoves(piece);
for (var i = 0; i < validMoves.length; i++) {
allPossibleMoves.push({
piece: piece,
move: validMoves[i]
});
}
}
}
}
// Piece value tables - used to evaluate position advantages for each piece type
var piecePositionValues = {
pawn: [[0, 0, 0, 0, 0, 0, 0, 0], [50, 50, 50, 50, 50, 50, 50, 50], [10, 10, 20, 30, 30, 20, 10, 10], [5, 5, 10, 25, 25, 10, 5, 5], [0, 0, 0, 20, 20, 0, 0, 0], [5, -5, -10, 0, 0, -10, -5, 5], [5, 10, 10, -20, -20, 10, 10, 5], [0, 0, 0, 0, 0, 0, 0, 0]],
knight: [[-50, -40, -30, -30, -30, -30, -40, -50], [-40, -20, 0, 0, 0, 0, -20, -40], [-30, 0, 10, 15, 15, 10, 0, -30], [-30, 5, 15, 20, 20, 15, 5, -30], [-30, 0, 15, 20, 20, 15, 0, -30], [-30, 5, 10, 15, 15, 10, 5, -30], [-40, -20, 0, 5, 5, 0, -20, -40], [-50, -40, -30, -30, -30, -30, -40, -50]],
bishop: [[-20, -10, -10, -10, -10, -10, -10, -20], [-10, 0, 0, 0, 0, 0, 0, -10], [-10, 0, 10, 10, 10, 10, 0, -10], [-10, 5, 5, 10, 10, 5, 5, -10], [-10, 0, 5, 10, 10, 5, 0, -10], [-10, 5, 5, 5, 5, 5, 5, -10], [-10, 0, 5, 0, 0, 5, 0, -10], [-20, -10, -10, -10, -10, -10, -10, -20]],
rook: [[0, 0, 0, 0, 0, 0, 0, 0], [5, 10, 10, 10, 10, 10, 10, 5], [-5, 0, 0, 0, 0, 0, 0, -5], [-5, 0, 0, 0, 0, 0, 0, -5], [-5, 0, 0, 0, 0, 0, 0, -5], [-5, 0, 0, 0, 0, 0, 0, -5], [-5, 0, 0, 0, 0, 0, 0, -5], [0, 0, 0, 5, 5, 0, 0, 0]],
queen: [[-20, -10, -10, -5, -5, -10, -10, -20], [-10, 0, 0, 0, 0, 0, 0, -10], [-10, 0, 5, 5, 5, 5, 0, -10], [-5, 0, 5, 5, 5, 5, 0, -5], [0, 0, 5, 5, 5, 5, 0, -5], [-10, 5, 5, 5, 5, 5, 0, -10], [-10, 0, 5, 0, 0, 0, 0, -10], [-20, -10, -10, -5, -5, -10, -10, -20]],
king: [[-30, -40, -40, -50, -50, -40, -40, -30], [-30, -40, -40, -50, -50, -40, -40, -30], [-30, -40, -40, -50, -50, -40, -40, -30], [-30, -40, -40, -50, -50, -40, -40, -30], [-20, -30, -30, -40, -40, -30, -30, -20], [-10, -20, -20, -20, -20, -20, -20, -10], [20, 20, 0, 0, 0, 0, 20, 20], [20, 30, 10, 0, 0, 10, 30, 20]]
};
// Base piece values
var pieceValues = {
pawn: 100,
knight: 320,
bishop: 330,
rook: 500,
queen: 900,
king: 20000
};
if (allPossibleMoves.length > 0) {
var selectedMove;
// Basic AI difficulty levels
if (gameState.aiDifficulty === 1) {
// Easy: Random move with slight preference for captures
var captureMoves = allPossibleMoves.filter(function (moveObj) {
return gameState.board[moveObj.move.row][moveObj.move.col] !== null;
});
if (captureMoves.length > 0 && Math.random() > 0.5) {
selectedMove = captureMoves[Math.floor(Math.random() * captureMoves.length)];
} else {
selectedMove = allPossibleMoves[Math.floor(Math.random() * allPossibleMoves.length)];
}
} else if (gameState.aiDifficulty === 2) {
// Medium: Prefer captures and positional advantages
var moveScores = [];
for (var i = 0; i < allPossibleMoves.length; i++) {
var moveObj = allPossibleMoves[i];
var targetPiece = gameState.board[moveObj.move.row][moveObj.move.col];
var score = 0;
// Capture value
if (targetPiece) {
score += pieceValues[targetPiece.type];
}
// Position value (flipped for black pieces)
var row = moveObj.move.row;
var col = moveObj.move.col;
if (gameState.aiPlayer === 'black') {
row = 7 - row;
}
score += piecePositionValues[moveObj.piece.type][row][col] * 0.5;
moveScores.push({
move: moveObj,
score: score
});
}
// Sort moves by score
moveScores.sort(function (a, b) {
return b.score - a.score;
});
// Pick from top 3 moves to add some randomness
var topMoves = moveScores.slice(0, Math.min(3, moveScores.length));
selectedMove = topMoves[Math.floor(Math.random() * topMoves.length)].move;
} else {
// Hard: Minimax with alpha-beta pruning (simplified)
var bestScore = -Infinity;
var bestMoves = [];
for (var i = 0; i < allPossibleMoves.length; i++) {
var moveObj = allPossibleMoves[i];
var piece = moveObj.piece;
var targetRow = moveObj.move.row;
var targetCol = moveObj.move.col;
var originalRow = piece.row;
var originalCol = piece.col;
var capturedPiece = gameState.board[targetRow][targetCol];
// Make temporary move
gameState.board[targetRow][targetCol] = piece;
gameState.board[originalRow][originalCol] = null;
// Evaluate board after move
var score = evaluateBoard(gameState.aiPlayer);
// Unmake the move
gameState.board[originalRow][originalCol] = piece;
gameState.board[targetRow][targetCol] = capturedPiece;
if (score > bestScore) {
bestScore = score;
bestMoves = [moveObj];
} else if (score === bestScore) {
bestMoves.push(moveObj);
}
}
if (bestMoves.length > 0) {
selectedMove = bestMoves[Math.floor(Math.random() * bestMoves.length)];
} else {
selectedMove = allPossibleMoves[Math.floor(Math.random() * allPossibleMoves.length)];
}
}
// Make the selected move
movePiece(selectedMove.piece, selectedMove.move.row, selectedMove.move.col);
}
LK.clearTimeout(aiTimeout);
}, 500);
// Function to evaluate the current board position
function evaluateBoard(playerColor) {
var score = 0;
var opponentColor = playerColor === 'white' ? 'black' : 'white';
// Count material and positional advantages
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var piece = gameState.board[r][c];
if (!piece) continue;
var value = pieceValues[piece.type];
// Add position value
var row = r;
var col = c;
if (piece.color === 'black') {
row = 7 - r; // Flip board for black
}
var positionValue = piecePositionValues[piece.type][row][col];
// Add to score if our piece, subtract if opponent's
if (piece.color === playerColor) {
score += value + positionValue;
// Bonus for attacking opponent pieces
var attackedSquares = getAttackedSquares(piece);
for (var i = 0; i < attackedSquares.length; i++) {
var attackedPiece = gameState.board[attackedSquares[i].row][attackedSquares[i].col];
if (attackedPiece && attackedPiece.color === opponentColor) {
score += value * 0.1; // Small bonus for attacking
// Higher bonus for attacking higher value pieces
score += pieceValues[attackedPiece.type] * 0.05;
}
}
// Bonus for piece mobility (number of legal moves)
score += getValidMoves(piece).length * 2;
// Bonus for developed pieces
if (piece.type === 'knight' || piece.type === 'bishop') {
var initialRow = playerColor === 'white' ? 7 : 0;
if (piece.row !== initialRow) {
score += 15; // Developed minor piece
}
}
} else {
score -= value + positionValue;
}
}
}
// Check if opponent is in check
var tempTurn = gameState.currentTurn;
gameState.currentTurn = opponentColor;
if (isKingInCheck(opponentColor)) {
score += 50; // Bonus for putting opponent in check
// Check if it's checkmate (huge bonus)
if (isCheckmate(opponentColor)) {
score += 10000;
}
}
// Penalize if our king is in check
gameState.currentTurn = playerColor;
if (isKingInCheck(playerColor)) {
score -= 60; // Penalty for being in check
}
gameState.currentTurn = tempTurn;
// Pawn structure evaluation
score += evaluatePawnStructure(playerColor) - evaluatePawnStructure(opponentColor);
// Control of center
score += evaluateCenterControl(playerColor) - evaluateCenterControl(opponentColor);
return score;
}
// Evaluate pawn structure
function evaluatePawnStructure(color) {
var score = 0;
var pawnColumns = [];
// Find all pawns
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var piece = gameState.board[r][c];
if (piece && piece.type === 'pawn' && piece.color === color) {
pawnColumns.push(c);
// Bonus for passed pawns (no enemy pawns ahead)
var passed = true;
var direction = color === 'white' ? -1 : 1;
for (var checkRow = r + direction; checkRow >= 0 && checkRow < 8; checkRow += direction) {
for (var checkCol = c - 1; checkCol <= c + 1; checkCol += 2) {
if (checkCol >= 0 && checkCol < 8) {
var checkPiece = gameState.board[checkRow][checkCol];
if (checkPiece && checkPiece.type === 'pawn' && checkPiece.color !== color) {
passed = false;
break;
}
}
}
if (!passed) break;
}
if (passed) {
score += 30; // Passed pawn bonus
// Bigger bonus for pawns closer to promotion
var promotionDistance = color === 'white' ? r : 7 - r;
score += (7 - promotionDistance) * 5;
}
}
}
}
// Penalize doubled pawns (multiple pawns in same column)
var columnCounts = {};
for (var i = 0; i < pawnColumns.length; i++) {
var col = pawnColumns[i];
columnCounts[col] = (columnCounts[col] || 0) + 1;
}
for (var col in columnCounts) {
if (columnCounts[col] > 1) {
score -= 15 * (columnCounts[col] - 1); // Penalty for doubled pawns
}
}
return score;
}
// Evaluate center control
function evaluateCenterControl(color) {
var score = 0;
var centerSquares = [{
row: 3,
col: 3
}, {
row: 3,
col: 4
}, {
row: 4,
col: 3
}, {
row: 4,
col: 4
}];
for (var i = 0; i < centerSquares.length; i++) {
var square = centerSquares[i];
var piece = gameState.board[square.row][square.col];
// Bonus for occupying center
if (piece && piece.color === color) {
score += 10;
}
// Bonus for attacking center
for (var r = 0; r < 8; r++) {
for (var c = 0; c < 8; c++) {
var attackingPiece = gameState.board[r][c];
if (attackingPiece && attackingPiece.color === color) {
var attackedSquares = getAttackedSquares(attackingPiece);
for (var j = 0; j < attackedSquares.length; j++) {
if (attackedSquares[j].row === square.row && attackedSquares[j].col === square.col) {
score += 5;
break;
}
}
}
}
}
}
return score;
}
// Get squares a piece can attack
function getAttackedSquares(piece) {
return getBasicMoves(piece);
}
}
// Game event handlers
game.down = function (x, y, obj) {
if (gameState.isAnimating) return;
// Convert screen coordinates to board coordinates
var boardX = x - chessBoard.x;
var boardY = y - chessBoard.y;
var tile = chessBoard.getTileAt(boardX, boardY);
if (tile && gameState.selectedPiece) {
// Check if the clicked tile is a valid move
var isValidMove = false;
for (var i = 0; i < gameState.validMoves.length; i++) {
var move = gameState.validMoves[i];
if (move.row === tile.row && move.col === tile.col) {
isValidMove = true;
break;
}
}
if (isValidMove) {
// Move the piece
movePiece(gameState.selectedPiece, tile.row, tile.col);
return;
}
}
// Clear selection if clicking elsewhere
if (gameState.selectedPiece) {
chessBoard.clearHighlights();
gameState.selectedPiece = null;
}
};
// Game Mode Buttons
var twoPlayerBtn = new Text2('Two Player', {
size: 50,
fill: 0xFFFFFF
});
twoPlayerBtn.anchor.set(0.5, 0);
twoPlayerBtn.y = 2732 - 200;
twoPlayerBtn.x = 2048 / 2 - 300;
LK.gui.addChild(twoPlayerBtn);
twoPlayerBtn.interactive = true;
twoPlayerBtn.buttonMode = true;
twoPlayerBtn.on('pointerdown', function () {
gameState.gameMode = 'twoPlayer';
gameState.aiPlayer = null;
LK.setScore(0);
initializeChessBoard();
updateTurnText();
chessBoard.clearHighlights();
});
var aiBtn = new Text2('Play vs AI', {
size: 50,
fill: 0xFFFFFF
});
aiBtn.anchor.set(0.5, 0);
aiBtn.y = 2732 - 200;
aiBtn.x = 2048 / 2 + 300;
LK.gui.addChild(aiBtn);
aiBtn.interactive = true;
aiBtn.buttonMode = true;
aiBtn.on('pointerdown', function () {
gameState.gameMode = 'ai';
gameState.aiPlayer = 'black';
LK.setScore(0);
initializeChessBoard();
updateTurnText();
chessBoard.clearHighlights();
});
// AI Difficulty Buttons
var easyBtn = new Text2('Easy', {
size: 40,
fill: gameState.aiDifficulty === 1 ? "#ffff00" : "#aaaaaa"
});
easyBtn.style = {
fill: gameState.aiDifficulty === 1 ? "#ffff00" : "#aaaaaa"
};
easyBtn.anchor.set(0.5, 0);
easyBtn.y = 2732 - 120;
easyBtn.x = 2048 / 2 - 300;
LK.gui.addChild(easyBtn);
easyBtn.interactive = true;
easyBtn.buttonMode = true;
easyBtn.on('pointerdown', function () {
gameState.aiDifficulty = 1;
storage.aiDifficulty = 1;
easyBtn.style.fill = "#ffff00";
mediumBtn.style.fill = "#aaaaaa";
hardBtn.style.fill = "#aaaaaa";
});
var mediumBtn = new Text2('Medium', {
size: 40,
fill: gameState.aiDifficulty === 2 ? "#ffff00" : "#aaaaaa"
});
mediumBtn.style = {
fill: gameState.aiDifficulty === 2 ? "#ffff00" : "#aaaaaa"
};
mediumBtn.anchor.set(0.5, 0);
mediumBtn.y = 2732 - 120;
mediumBtn.x = 2048 / 2;
LK.gui.addChild(mediumBtn);
mediumBtn.interactive = true;
mediumBtn.buttonMode = true;
mediumBtn.on('pointerdown', function () {
gameState.aiDifficulty = 2;
storage.aiDifficulty = 2;
easyBtn.style.fill = "#aaaaaa";
mediumBtn.style.fill = "#ffff00";
hardBtn.style.fill = "#aaaaaa";
});
var hardBtn = new Text2('Hard', {
size: 40,
fill: gameState.aiDifficulty === 3 ? "#ffff00" : "#aaaaaa"
});
hardBtn.style = {
fill: gameState.aiDifficulty === 3 ? "#ffff00" : "#aaaaaa"
};
hardBtn.anchor.set(0.5, 0);
hardBtn.y = 2732 - 120;
hardBtn.x = 2048 / 2 + 300;
LK.gui.addChild(hardBtn);
hardBtn.interactive = true;
hardBtn.buttonMode = true;
hardBtn.on('pointerdown', function () {
gameState.aiDifficulty = 3;
storage.aiDifficulty = 3;
easyBtn.style.fill = "#aaaaaa";
mediumBtn.style.fill = "#aaaaaa";
hardBtn.style.fill = "#ffff00";
});
// Update difficulty button colors based on current setting
switch (gameState.aiDifficulty) {
case 1:
easyBtn.style.fill = "#ffff00";
mediumBtn.style.fill = "#aaaaaa";
hardBtn.style.fill = "#aaaaaa";
break;
case 2:
easyBtn.style.fill = "#aaaaaa";
mediumBtn.style.fill = "#ffff00";
hardBtn.style.fill = "#aaaaaa";
break;
case 3:
easyBtn.style.fill = "#aaaaaa";
mediumBtn.style.fill = "#aaaaaa";
hardBtn.style.fill = "#ffff00";
break;
}
// New Game button
var newGameBtn = new Text2('New Game', {
size: 50,
fill: 0xFFFFFF
});
newGameBtn.anchor.set(0.5, 0);
newGameBtn.y = 2732 - 280;
newGameBtn.x = 2048 / 2;
LK.gui.addChild(newGameBtn);
newGameBtn.interactive = true;
newGameBtn.buttonMode = true;
newGameBtn.on('pointerdown', function () {
initializeChessBoard();
updateTurnText();
});
// Initialize the game
initializeChessBoard();
// Main game loop
game.update = function () {
// Game update logic happens in event handlers and callbacks
};
// Play background music
LK.playMusic('bgMusic', {
fade: {
start: 0,
end: 0.3,
duration: 1000
}
});