Skip to content

Commit

Permalink
Fix pv line collecting, best move, and more (#18)
Browse files Browse the repository at this point in the history
- Fixed PV line collecting going weird by replacing the current system with BBC's triangular PV table. This fixes "bestmove" also going weird as well.
- Added PV move ordering.
  • Loading branch information
nguyenphuminh authored Jul 26, 2024
1 parent 6ca0239 commit 04aaea3
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 17 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Note that the config file is compiled with the engine itself, so if you are usin
* Principle variation search.
* Move ordering:
* Hash move ordering.
* PV move ordering.
* MVV-LVA heuristic.
* Killer heuristic.
* History heuristic.
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.10.0",
version: "v0.10.1",
// 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.10.0",
"version": "0.10.1",
"description": "The Catto chess engine",
"main": "index.js",
"scripts": {
Expand Down
79 changes: 64 additions & 15 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export class Engine {
public maxExtensions: number;
public ply: number = 0;
public prevMove?: Move;
public bestMove?: Move;
public uci: boolean = false;

// Used for engine termination
Expand All @@ -59,7 +58,10 @@ export class Engine {
}

// PV table
public pvTable: string[];
public pvLength: number[];
public pvTable: string[][];
public collectPV = false;
public scorePV = false;

constructor(options: EngineOptions) {
this.fen = options.fen || "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
Expand All @@ -69,7 +71,28 @@ export class Engine {
this.lmrFullDepth = options.lmrFullDepth;
this.maxExtensions = options.maxExtensions;

this.pvTable = new Array(this.searchDepth).fill("");
// Init pv table
this.pvLength = new Array(this.searchDepth).fill(0);
this.pvTable = [];
for (let i = 0; i < this.searchDepth; i++) {
this.pvTable.push(new Array(this.searchDepth).fill(""));
}
}

// PV
enablePVScoring(moves: Move[]) {
// Disable following PV
this.collectPV = false;

for (const move of moves) {
// Make sure we hit PV move
if (this.pvTable[0][this.ply] == move.lan) {
// Enable move scoring
this.scorePV = true;
// Enable collecting PV
this.collectPV = true;
}
}
}

// Tranposition table
Expand Down Expand Up @@ -129,7 +152,19 @@ export class Engine {
) {
priority += 100000;
}


// PV move
if (this.scorePV) {
// Make sure it is PV move
if (this.pvTable[0][this.ply] == move.lan)
{
// Disable PV scoring
this.scorePV = false;

priority += 50000;
}
}

// Non-capture moves
if (!move.captured) {
// 1st killer move
Expand Down Expand Up @@ -241,7 +276,9 @@ export class Engine {

// The main negamax search algorithm
negamax(depth: number, alpha: number, beta: number, extended: number): number {
// Init
const inCheck = this.chess.inCheck();
this.pvLength[this.ply] = this.ply;

this.nodes++;

Expand Down Expand Up @@ -292,7 +329,7 @@ export class Engine {
let possibleMoves = this.chess.moves({ verbose: true });

// Detecting checkmates and stalemates
if (possibleMoves.length === 0) {
if (possibleMoves.length === 0) {
if (inCheck) {
return -49000 + this.ply; // Checkmate

Expand All @@ -308,6 +345,10 @@ export class Engine {
const extensions = extended < this.maxExtensions ? this.calculateExtensions(possibleMoves.length, inCheck) : 0;

// Sort moves
if (this.collectPV)
// Enable PV move scoring
this.enablePVScoring(possibleMoves);

possibleMoves = this.sortMoves(possibleMoves);
let searchedMoves = 0, bestMoveSoFar: Move;

Expand Down Expand Up @@ -415,13 +456,16 @@ export class Engine {

alpha = score;

// Store best move if it's root
if (this.ply === 0) {
this.bestMove = move;
}

// Store pv move
this.pvTable[this.ply] = move.lan;
this.pvTable[this.ply][this.ply] = move.lan;

// Loop over the next ply
for (let nextPly = this.ply + 1; nextPly < this.pvLength[this.ply + 1]; nextPly++)
// Copy move from deeper ply into a current ply's line
this.pvTable[this.ply][nextPly] = this.pvTable[this.ply + 1][nextPly];

// adjust PV length
this.pvLength[this.ply] = this.pvLength[this.ply + 1];
}
}

Expand All @@ -443,6 +487,9 @@ export class Engine {
break;
}

// Reset collect PV flag
this.collectPV = true;

score = this.negamax(depth, alpha, beta, 0);

if (score <= alpha || score >= beta) {
Expand All @@ -455,11 +502,13 @@ export class Engine {
alpha = score - 50;
beta = score + 50;

console.log(`info depth ${depth} score cp ${Math.round(score)} time ${Date.now() - this.startTime} nodes ${this.nodes} pv ${this.pvTable.join(" ").trim()}`);

currentBestMove = this.bestMove;
if (this.pvLength[0]) {
console.log(
`info depth ${depth} score cp ${Math.round(score)} time ${Date.now() - this.startTime} nodes ${this.nodes} pv ${this.pvTable[0].join(" ").trim()}`
);
}
}

console.log(`bestmove ${currentBestMove?.lan}`);
console.log(`bestmove ${this.pvTable[0][0]}`);
}
}

0 comments on commit 04aaea3

Please sign in to comment.