59 lines
2.8 KiB
TypeScript
59 lines
2.8 KiB
TypeScript
/** 체스 computer player: mates, wins material without hanging it, promotes to a queen. */
|
|
import { describe, expect, test } from 'bun:test';
|
|
import { SeededRng } from '@bg/engine';
|
|
import { decideBotAction } from '../bots/index';
|
|
import { chess, type ChessAction, type ChessState } from './index';
|
|
import { fullFen, keyOf, load } from './rules';
|
|
|
|
/** White = 'A' (the bot in these tests), black = 'B'; both clocks running. */
|
|
function make(fen: string): ChessState {
|
|
const options = chess.optionsSchema.parse({ colorAssignment: 'hostWhite' });
|
|
const s = chess.setup({ players: ['A', 'B'], options, rng: SeededRng.fromSeed([1, 2, 3, 4]), now: 0 });
|
|
s.startFen = fullFen(load(fen));
|
|
s.fen = s.startFen;
|
|
s.repetition = { [keyOf(load(fen))]: 1 };
|
|
s.clock.started = { white: true, black: true };
|
|
return s;
|
|
}
|
|
const decide = (s: ChessState, seed = 1) => {
|
|
const r = SeededRng.fromSeed([seed, 7, 7, 7]);
|
|
return decideBotAction(chess, s, 'A', { now: 1000, random: () => r.next() }) as ChessAction;
|
|
};
|
|
const uci = (a: ChessAction) => (a.type === 'move' ? `${a.from}${a.to}${a.promotion ?? ''}` : a.type);
|
|
|
|
describe('chess bot', () => {
|
|
test('plays mate in one', () => {
|
|
for (let seed = 1; seed <= 5; seed++) expect(uci(decide(make('6k1/5ppp/8/8/8/8/8/R5K1 w - - 0 1'), seed))).toBe('a1a8');
|
|
});
|
|
|
|
test('takes a hanging queen', () => {
|
|
expect(uci(decide(make('4k3/8/8/3q4/8/8/8/3RK3 w - - 0 1')))).toBe('d1d5');
|
|
});
|
|
|
|
test('does not grab a defended pawn with the queen, and moves an attacked queen away', () => {
|
|
// Qxd5 would lose the queen to cxd5.
|
|
for (let seed = 1; seed <= 5; seed++) expect(uci(decide(make('4k3/8/2p5/3p4/8/8/8/3QK3 w - - 0 1'), seed))).not.toBe('d1d5');
|
|
// The queen on d5 is attacked by the c6 pawn: it must leave, to a square nothing black attacks.
|
|
for (let seed = 1; seed <= 5; seed++) {
|
|
const fen = '4k3/8/2p5/3Q4/8/8/8/4K3 w - - 0 1';
|
|
const a = decide(make(fen), seed) as Extract<ChessAction, { type: 'move' }>;
|
|
expect(a.from).toBe('d5');
|
|
const c = load(fen);
|
|
c.make(c.findMove(a.from, a.to)!);
|
|
expect(c.isAttacked(a.to, 'b')).toBe(false);
|
|
}
|
|
});
|
|
|
|
test('promotes to a queen', () => {
|
|
expect(uci(decide(make('8/P7/8/8/8/8/k7/4K3 w - - 0 1')))).toBe('a7a8q');
|
|
});
|
|
|
|
test("accepts the opponent's undo request", () => {
|
|
let s = make('4k3/8/8/8/8/8/4P3/4K3 w - - 0 1');
|
|
s = chess.apply(s, 'A', { type: 'move', from: 'e2', to: 'e4' }, { rng: SeededRng.fromSeed([1, 1, 1, 1]), now: 500 }).state;
|
|
s = chess.apply(s, 'B', { type: 'move', from: 'e8', to: 'd7' }, { rng: SeededRng.fromSeed([1, 1, 1, 1]), now: 600 }).state;
|
|
s = chess.apply(s, 'B', { type: 'requestUndo' }, { rng: SeededRng.fromSeed([1, 1, 1, 1]), now: 700 }).state;
|
|
expect(decide(s)).toEqual({ type: 'respondUndo', accept: true });
|
|
});
|
|
});
|