Skip to content

Commit

Permalink
Mobility (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
nguyenphuminh authored Jul 26, 2024
1 parent 8f773c9 commit 6ca0239
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 21 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ In your console, type:
npm run build
```

If you are using Bun:
```
npm run bun-build
```

## Configure

There are several configurations for Catto that you can change in `catto.config.js` if you want:
Expand Down Expand Up @@ -90,7 +95,13 @@ Note that the config file is compiled with the engine itself, so if you are usin
* Evalution:
* PeSTO evaluation (PST, materials, and tapered evaluation).
* Isolated, doubled and passed pawns bonus/penalty.
* Rook on open/half-open file bonus.
* Mobility:
* Rook on open/half-open file bonus.
* Bishop legal moves.
* Queen legal moves.
* King safety:
* Penalty if king is on half-open file.
* Bonus if king has pieces nearby.
* UCI.


Expand Down
2 changes: 1 addition & 1 deletion catto.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module.exports = {
// Current version to show in UCI
version: "v0.9.0",
version: "v0.10.0",
// Late move reduction config
lmrFullDepth: 4, // Number of moves to be searched in full depth
lmrMaxReduction: 3, // Only apply LMR above this depth
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "catto",
"version": "0.9.0",
"version": "0.10.0",
"description": "The Catto chess engine",
"main": "index.js",
"scripts": {
Expand Down
6 changes: 1 addition & 5 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,10 @@ export interface HashEntry {

export interface EngineOptions {
fen: string;
debug: boolean;
stable: boolean;
searchDepth: number;
lmrFullDepth: number;
lmrMaxReduction: number;
maxExtensions: number;
uci: boolean;
version: string;
}

Expand Down Expand Up @@ -68,7 +65,6 @@ export class Engine {
this.fen = options.fen || "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
this.searchDepth = options.searchDepth || 64;
this.chess = new Chess(this.fen);
this.uci = options.uci;
this.lmrMaxReduction = options.lmrMaxReduction;
this.lmrFullDepth = options.lmrFullDepth;
this.maxExtensions = options.maxExtensions;
Expand Down Expand Up @@ -274,7 +270,7 @@ export class Engine {
// Make null move
let tokens = this.chess.fen().split(" ");
tokens[1] = this.chess.turn() === "w" ? "b" : "w";
tokens[3] = '-' // reset the en passant square
tokens[3] = '-'; // reset the en passant square
this.chess.load(tokens.join(" "));

// Search with reduced depth
Expand Down
81 changes: 68 additions & 13 deletions src/evaluate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,75 @@ function pcolor(side: string) {
export function evaluateBoard(chessObj: Chess) {
const board = chessObj.board();
const side = chessObj.turn();
const us = pcolor(side), enemy = pcolor(side) ^ 1;

const mg = [ 0, 0 ]
, eg = [ 0, 0 ]
, file = [
const mg = [ 0, 0 ] // Early-mid game score
, eg = [ 0, 0 ] // Endgame score
, file = [ // Pawns on files
[ 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 0, 0, 0, 0, 0, 0, 0 ]
]
, rooksOnFile = [
, rooksOnFile = [ // Rooks on files
new Array(8).fill(0),
new Array(8).fill(0)
], kingsOnFile = [ // Kings on files
new Array(8).fill(0),
new Array(8).fill(0)
];

let gamePhase = 0;

// King safety
let kingScore = 0;

// Evaluate material and position and guessing current game phase
for (let x = 0; x < board.length; x++) {
for (let y = 0; y < board[x].length; y++) {
if (board[x][y] === null) continue;

const color = pcolor(board[x][y]!.color);
const colorStr = board[x][y]!.color;
const color = pcolor(colorStr);
const pieceType = board[x][y]!.type;

// Count material and square value
mg[color] += mgMaterial[PIECE_NUM[board[x][y]!.type]] + mgTable[board[x][y]!.color + board[x][y]!.type][x][y];
eg[color] += egMaterial[PIECE_NUM[board[x][y]!.type]] + egTable[board[x][y]!.color + board[x][y]!.type][x][y];
mg[color] += mgMaterial[PIECE_NUM[pieceType]] + mgTable[colorStr + pieceType][x][y];
eg[color] += egMaterial[PIECE_NUM[pieceType]] + egTable[colorStr + pieceType][x][y];

// Guess game phase based on material
gamePhase += gamephaseInc[PIECE_NUM[board[x][y]!.type]];
gamePhase += gamephaseInc[PIECE_NUM[pieceType]];

// Count pawns in a file
if (board[x][y]!.type === "p") {
if (pieceType === "p") {
file[color][y] += 1;
}

// Check if rook is in a file
if (board[x][y]!.type === "r") {
if (pieceType === "r") {
rooksOnFile[color][y] += 1;
}

// Check king shield
if (pieceType === "k") {
let shieldBonus = 0;

if (board[x] && board[x][y-1] && board[x][y-1]!.color === colorStr) { shieldBonus += 5; }
if (board[x] && board[x][y+1] && board[x][y+1]!.color === colorStr) { shieldBonus += 5; }
if (board[x-1] && board[x-1][y] && board[x-1][y]!.color === colorStr) { shieldBonus += 5; }
if (board[x+1] && board[x+1][y] && board[x+1][y]!.color === colorStr) { shieldBonus += 5; }
if (board[x-1] && board[x-1][y-1] && board[x-1][y-1]!.color === colorStr) { shieldBonus += 5; }
if (board[x+1] && board[x+1][y+1] && board[x+1][y+1]!.color === colorStr) { shieldBonus += 5; }
if (board[x-1] && board[x-1][y+1] && board[x-1][y+1]!.color === colorStr) { shieldBonus += 5; }
if (board[x+1] && board[x+1][y-1] && board[x+1][y-1]!.color === colorStr) { shieldBonus += 5; }

kingScore += color === us ? shieldBonus : -shieldBonus;

kingsOnFile[color][y] = 1;
}
}
}

// Pawn structure eval
let pawnDeficit = 0, rookScore = 0, us = pcolor(side), enemy = pcolor(side) ^ 1;
// Pawn structure and rooks eval
let pawnDeficit = 0, rookScore = 0;

for (let index = 0; index < 8; index++) {
// Doubled pawns eval
Expand All @@ -58,6 +85,9 @@ export function evaluateBoard(chessObj: Chess) {
// Rooks on open/half open file:
rookScore += (file[us][index] === 0 ? 20 * rooksOnFile[us][index] : 0) - (file[enemy][index] === 0 ? 20 * rooksOnFile[enemy][index] : 0);

// King on open/half open file:
kingScore -= (file[us][index] === 0 ? 20 * kingsOnFile[us][index] : 0) - (file[enemy][index] === 0 ? 20 * kingsOnFile[enemy][index] : 0);

// Isolated/passed pawns eval
let isolatedPawnScore = 0, passedPawnScore = 0;

Expand Down Expand Up @@ -167,6 +197,31 @@ export function evaluateBoard(chessObj: Chess) {
pawnDeficit += isolatedPawnScore + passedPawnScore;
}

// Mobility
// Our bishop mobility
let bishopMoves = chessObj.moves({ piece: "b" });
let bishopScore = bishopMoves.length * 5;

// Our queen mobility
let queenMoves = chessObj.moves({ piece: "q" });
mg[us] += queenMoves.length;
eg[us] += queenMoves.length * 2;

// Switch side
let tokens = chessObj.fen().split(" ");
tokens[1] = side === "w" ? "b" : "w";
tokens[3] = '-'; // reset the en passant square
const opChessObj = new Chess(tokens.join(" "));

// Their bishop mobility
let opBishopMoves = opChessObj.moves({ piece: "b" });
bishopScore -= opBishopMoves.length * 5;

// Their queen mobility
let opQueenMoves = opChessObj.moves({ piece: "q" });
mg[enemy] += opQueenMoves.length;
eg[enemy] += opQueenMoves.length * 2;

// Tapered eval
let mgScore = mg[us] - mg[enemy];
let egScore = eg[us] - eg[enemy];
Expand All @@ -177,5 +232,5 @@ export function evaluateBoard(chessObj: Chess) {

let egPhase = 24 - mgPhase;

return (mgScore * mgPhase + egScore * egPhase) / 24 + pawnDeficit + rookScore;
return (mgScore * mgPhase + egScore * egPhase) / 24 + pawnDeficit + rookScore + bishopScore + kingScore;
}

0 comments on commit 6ca0239

Please sign in to comment.