black-horse
Version:
Chess engine built in Javascript using the alpha-beta algorithm
256 lines (210 loc) • 7.75 kB
JavaScript
const PieceService = require('./piece-service.js');
const EvaluationModel = require('../models/evaluation-model.js');
const configs = require('../configurations');
const round = require('lodash/round');
const size = require('lodash/size');
class EvaluationService {
constructor() {
this.evaluationModel = new EvaluationModel();
this.pieceService = PieceService;
}
_pawnsScore(board, color) {
let score = 0;
const pawnList = board.getPawnList(color);
const playColor = color === configs.colors.white ? 1 : -1;
let row;
let column;
pawnList.traverse((key, value) => {
row = value.row;
column = value.column;
//isolated or backward pawns
if (
!pawnList.search(row - playColor + '' + (column - playColor)) &&
!pawnList.search(row - playColor + '' + (column + playColor)) &&
((row > 2 && color === configs.colors.white) || (row < 7 && color === configs.colors.black))
) {
// if (!board.isPieceSecure(row, column)) {
score -= this.evaluationModel.isolatedPawnPenalty();
// }
}
//doubled pawns
if (pawnList.search(row - playColor + '' + column)) {
score -= this.evaluationModel.doubledPawnPenalty();
}
});
return score;
}
_castlingScore(board, color) {
let score = 0;
let pawnStage;
let castlingPenalty;
let castlingStage;
let castleFlags = board.getCastleFlags();
if (!castleFlags[color].kingSide || !castleFlags[color].queenSide) {
pawnStage = this.pieceService.pawnStage(board);
castlingPenalty = this.evaluationModel.castlingPenalty();
castlingStage = round(
(pawnStage * (this.evaluationModel.castlingEndGamePenalty() - castlingPenalty) + castlingPenalty) / castlingPenalty,
2
);
if (!castleFlags[color].kingSide) {
score -= castlingStage * castlingPenalty;
}
if (!castleFlags[color].queenSide) {
score -= castlingStage * castlingPenalty;
}
}
return round(score);
}
_calculateMaterialScore(board) {
const colorsScore = [];
let pieceKeys;
let indexR;
let piece;
let piecesLength;
colorsScore[configs.colors.white] = 0;
colorsScore[configs.colors.black] = 0;
pieceKeys = Object.keys(configs.pieces);
for (indexR = 2, piecesLength = pieceKeys.length; indexR < piecesLength; indexR += 1) {
piece = configs.pieces[pieceKeys[indexR]];
colorsScore[board.getPieceColor(piece)] += round(
board.getPiecesCounter(piece) * this.pieceService.getStartingPieceValue(piece) * this.pieceService.pieceStage(piece, board)
);
}
return colorsScore;
}
_evaluateBoard(boardModel, currentMove) {
let piece;
let row;
let column;
let colorsScore;
let score = 0;
let pieceColor;
let signal;
const traverseCallback = (key, value) => {
row = value.row;
column = value.column;
piece = boardModel.getPiece(row, column);
pieceColor = boardModel.getPieceColor(piece);
signal = pieceColor === configs.colors.white ? 1 : -1;
score += this.evaluatePiece(boardModel, piece, row, column, colorsScore) * signal;
};
colorsScore = this._calculateMaterialScore(boardModel);
score += colorsScore[configs.colors.white] - colorsScore[configs.colors.black];
boardModel.getPieceList(configs.colors.white).traverse(traverseCallback);
boardModel.getPieceList(configs.colors.black).traverse(traverseCallback);
if (boardModel.getPiecesCounter(configs.pieces.wB) >= 2) {
score += this.evaluationModel.bishopPairBonus();
}
if (boardModel.getPiecesCounter(configs.pieces.bB) >= 2) {
score -= this.evaluationModel.bishopPairBonus();
}
score += this._pawnsScore(boardModel, configs.colors.white) - this._pawnsScore(boardModel, configs.colors.black);
score += this._castlingScore(boardModel, configs.colors.white) - this._castlingScore(boardModel, configs.colors.black);
if (
currentMove &&
(currentMove.flag === configs.flags.blackKingCastle ||
currentMove.flag === configs.flags.blackQueenCastle ||
currentMove.flag === configs.flags.whiteKingCastle ||
currentMove.flag === configs.flags.whiteQueenCastle)
) {
piece = boardModel.getPiece(currentMove.rowDest, currentMove.columnDest);
pieceColor = boardModel.getPieceColor(piece);
signal = pieceColor === configs.colors.white ? 1 : -1;
score += this.evaluationModel.castlingBonus() * signal;
}
return score;
}
isMaterialDraw(board) {
//check if there is any Rook or Queen
if (
board.getPiecesCounter(configs.pieces.bQ) > 0 ||
board.getPiecesCounter(configs.pieces.wQ) > 0 ||
board.getPiecesCounter(configs.pieces.bR) > 0 ||
board.getPiecesCounter(configs.pieces.wR) > 0
) {
return false;
}
//check if there is any Pawn
if (board.getPiecesCounter(configs.pieces.wP) === 0 && board.getPiecesCounter(configs.pieces.bP) === 0) {
//check if there is any Knight
if (board.getPiecesCounter(configs.pieces.bN) === 0 && board.getPiecesCounter(configs.pieces.wN) === 0) {
//check if there is any Bishop (King against King Draw)
if (board.getPiecesCounter(configs.pieces.bB) === 0 && board.getPiecesCounter(configs.pieces.wB) === 0) {
return true;
}
//check if there is only one Bishop (King + Bishop against King Draw)
if (
(board.getPiecesCounter(configs.pieces.bB) === 1 && board.getPiecesCounter(configs.pieces.wB) === 0) ||
(board.getPiecesCounter(configs.pieces.wB) === 1 && board.getPiecesCounter(configs.pieces.bB) === 0)
) {
return true;
}
//TODO (King + Bishop against King + same color Bishop Draw)
} else if (board.getPiecesCounter(configs.pieces.bB) === 0 && board.getPiecesCounter(configs.pieces.wB) === 0) {
//check if there is only one Knight (King + Knight against King Draw)
if (
(board.getPiecesCounter(configs.pieces.bN) === 1 && board.getPiecesCounter(configs.pieces.wN) === 0) ||
(board.getPiecesCounter(configs.pieces.bN) === 0 && board.getPiecesCounter(configs.pieces.wN) === 1)
) {
return true;
}
}
}
return false;
}
evaluatePiece(board, pieceOrig, rowDest, columnDest, colorsScore) {
let score;
let pieceColor;
if (!colorsScore && (pieceOrig === configs.pieces.wK || pieceOrig === configs.pieces.bK)) {
colorsScore = this._calculateMaterialScore(board);
}
pieceColor = board.getPieceColor(pieceOrig);
if (pieceColor === configs.colors.black) {
rowDest = 8 - rowDest;
columnDest -= 1;
} else {
rowDest -= 1;
columnDest -= 1;
}
switch (pieceOrig) {
case configs.pieces.wP:
case configs.pieces.bP:
score = this.evaluationModel.pawnScore(rowDest, columnDest);
break;
case configs.pieces.wN:
case configs.pieces.bN:
score = this.evaluationModel.knightScore(rowDest, columnDest);
break;
case configs.pieces.wB:
case configs.pieces.bB:
score = this.evaluationModel.bishopScore(rowDest, columnDest);
break;
case configs.pieces.wR:
case configs.pieces.bR:
score = this.evaluationModel.rookScore(rowDest, columnDest);
break;
case configs.pieces.wQ:
case configs.pieces.bQ:
score = this.evaluationModel.queenScore(rowDest, columnDest);
break;
case configs.pieces.wK:
case configs.pieces.bK:
if (colorsScore[pieceColor] <= this.evaluationModel.endGameValue()) {
score = this.evaluationModel.kingEndGameScore(rowDest, columnDest);
} else {
score = this.evaluationModel.kingScore(rowDest, columnDest);
}
break;
}
return score;
}
evaluateBoard(board, currentMove) {
let score;
const playColor = board.getColor() === configs.colors.white ? 1 : -1;
score = this._evaluateBoard(board, currentMove);
//fix the negative zero issue
return score * playColor + 1 - 1;
}
}
module.exports = new EvaluationService();