ChessBuilder/Assets/ChessEngines/fairy-chess-server/engine.js

163 lines
No EOL
4.6 KiB
JavaScript

// engine.js
import { spawn } from 'child_process';
import { EventEmitter } from 'events';
class ChessEngine extends EventEmitter {
constructor(enginePath) {
super();
this.enginePath = enginePath;
this.engine = null;
this.isReady = false;
this.currentFen = null;
}
start() {
return new Promise((resolve, reject) => {
try {
this.engine = spawn(this.enginePath);
this.engine.stdout.on('data', (data) => {
const messages = data.toString().trim().split('\n');
messages.forEach(msg => this.handleEngineMessage(msg));
});
this.engine.stderr.on('data', (data) => {
console.error('Engine Error:', data.toString());
});
this.engine.on('close', (code) => {
console.log('Engine process closed with code:', code);
this.isReady = false;
});
// Initialize engine
this.sendCommand('uci');
this.waitforReady(resolve);
} catch (error) {
reject(error);
}
});
}
waitforReady(resolve){
this.sendCommand('isready');
// Wait for readyok
this.once('ready', () => {
this.isReady = true;
resolve();
});
}
handleEngineMessage(message) {
console.log('Engine:', message);
if (message.includes('readyok')) {
this.emit('ready');
}
else if (message.startsWith('bestmove')) {
const [, move] = message.split(' ');
this.emit('bestmove', move);
}
else if (message.startsWith('info')) {
this.emit('info', this.parseInfo(message));
}
}
parseInfo(info) {
const parts = info.split(' ');
const result = {};
for (let i = 1; i < parts.length; i++) {
switch (parts[i]) {
case 'depth':
result.depth = parseInt(parts[++i]);
break;
case 'seldepth':
result.seldepth = parseInt(parts[++i]);
break;
case 'score':
result.score = {
type: parts[++i],
value: parseInt(parts[++i])
};
break;
case 'nodes':
result.nodes = parseInt(parts[++i]);
break;
case 'time':
result.time = parseInt(parts[++i]);
break;
case 'pv':
result.pv = [];
while (++i < parts.length && !parts[i].includes('bmc')) {
result.pv.push(parts[i]);
}
break;
}
}
return result;
}
sendCommand(cmd) {
if (this.engine && this.engine.stdin.writable) {
console.log('Sending:', cmd);
this.engine.stdin.write(cmd + '\n');
}
}
async setBoardPosition(fen) {
if (!this.isReady) throw new Error('Engine not ready');
this.currentFen = fen;
this.sendCommand(`position fen ${fen}`);
// Ensure engine is ready after position set
this.sendCommand('isready');
await new Promise(resolve => this.once('ready', resolve));
}
async getBestMove(options = {}) {
if (!this.isReady) throw new Error('Engine not ready');
if (!this.currentFen) throw new Error('Position not set');
const {
depth = 15,
movetime = 1000,
nodes = null,
} = options;
return new Promise((resolve) => {
// Construct go command
let goCmd = 'go';
if (depth) goCmd += ` depth ${depth}`;
if (movetime) goCmd += ` movetime ${movetime}`;
if (nodes) goCmd += ` nodes ${nodes}`;
this.sendCommand(goCmd);
// Wait for bestmove response
this.once('bestmove', (move) => {
resolve(move);
});
});
}
async getAnalysis(fen, options = {}) {
await this.setBoardPosition(fen);
const bestMove = await this.getBestMove(options);
return { bestMove };
}
quit() {
if (this.engine) {
this.sendCommand('quit');
this.engine = null;
this.isReady = false;
}
}
}
export default ChessEngine;