163 lines
No EOL
4.6 KiB
JavaScript
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; |