UNPKG

xchess

Version:

Chess Engine

344 lines (281 loc) 6.13 kB
export {Board} import {Color} from './color.js' import {Square} from './square.js' import {Piece, King} from './piece.js' import {MoveMap} from './move-map.js' import {fenToBoard, boardToFEN} from './fen.js' import {boardToHash} from './hash.js' import {table} from './table.js' import geo from './geo.js' import {DeadPositionTest, CheckmateChanceTest} from './dead-position.js' import {SQUARE_IS_EMPTY, INVALID_BOARD} from './errors.js' import {IS_EMPTY, IS_STRING, IS_OBJECT, IS_ITERABLE} from './types.js' class Board { #map = new Map(); #all = new Map(); constructor(config){ IS_EMPTY(config) || this.#setAll(config); } // read get size(){ return this.#map.size; } keys(){ return this.#map.keys(); } values(){ return this.#map.values(); } entries(){ return this.#map.entries(); } at(x, y){ return this.#map.get(Square.at(x, y)) ?? null; } has(square){ return this.#map.has(Square.from(square)); } get(square){ return this.#map.get(Square.from(square)) ?? null; } find(piece){ return this.#all.get(piece) ?? null; } contains(piece){ return this.#all.has(piece); } // write #set(square, piece){ this.#all.set(piece, square); this.#map.set(square, piece); } #delete(square, piece){ this.#map.delete(square, piece); this.#all.delete(piece, square); } #setAsMap(iterable){ const map = new Map(); const all = new Map(); for(const [square, piece] of iterable){ const key = Square.from(square); const val = Piece.from(piece); map.set(key, val); all.set(val, key); } this.#map = map; this.#all = all; } #setAsObject(boardObject){ this.#setAsMap(Object.entries(boardObject)); } #setAll(board){ if(IS_STRING(board)) this.fen = board; else if(IS_OBJECT(board)){ if(IS_ITERABLE(board)) this.#setAsMap(board); else this.#setAsObject(board); } else throw INVALID_BOARD(board); } setAll(board){ if(IS_EMPTY(board)) this.clear(); else this.#setAll(board); } set(square, pieceArg){ const piece = Piece.from(pieceArg); const target = Square.from(square); const source = this.find(piece); const victim = this.get(target); if(victim === piece) return {change: false} const report = { change: true, to: target, }; if(source){ report.from = source; report.moved = piece; this.#map.delete(source); } else { report.inserted = piece; } if(victim){ this.#all.delete(victim); report.deleted = victim; } this.#set(target, piece); return report; } move(from, to){ const target = Square.from(to); const source = Square.from(from); const piece = this.get(source); const victim = this.get(target); if(!piece) throw SQUARE_IS_EMPTY(source); if(target === source) return {change: false}; const report = { change: true, from: source, to: target, moved: piece, }; if(victim){ report.deleted = victim; this.#all.delete(victim); } this.#map.delete(source); this.#set(target, piece); return report; } delete(square){ const target = Square.from(square); const piece = this.get(target); if(piece){ this.#delete(target, piece); return {change: true, from: target, deleted: piece}; } return {change: false}; } clear(){ const count = this.size; this.#map.clear(); this.#all.clear(); return count; } // geo crossT(piece, square){ return geo.crossT(piece, square, this); } crossX(piece, square){ return geo.crossX(piece, square, this); } crossAny(piece, square){ return geo.crossAny(piece, square, this); } isNear(piece, square){ return geo.isNear(piece, square, this); } isL(piece, square){ return geo.isL(piece, square, this); } traceT(piece){ return geo.traceT(piece, this); } traceX(piece){ return geo.traceX(piece, this); } traceAll(piece){ return geo.traceAll(piece, this); } L(piece){ return geo.L(piece, this); } near(piece){ return geo.near(piece, this); } // colors * #like(color){ for(const piece of this.values()) if(piece.color === color) yield piece; } like(color){ return [... this.#like(Color.from(color))]; } not(color){ return [... this.#like(Color.invert(color))]; } // rules testAt(square, color){ const at = Square.from(square); const pieces = this.not(color); for(const piece of pieces) if(piece.test(square, this)) return true; return false; } testAllAt(square, color){ const at = Square.from(square); const pieces = this.not(color); const all = []; for(const piece of pieces) if(piece.test(square, this)) all.push(piece); return all; } test(piece){ const square = this.find(piece); if(square) return this.testAt(square, piece.color); return false; } testAll(piece){ const square = this.find(piece); if(square) return this.testAllAt(square, piece.color); return []; } check(color){ const pieces = this.like(color); for(const piece of pieces){ if((piece instanceof King) && this.test(piece)) return piece; } return false; } isDeadPosition(){ return DeadPositionTest(this); } isCheckmateChance(color){ return CheckmateChanceTest(this, Color.from(color)); } // moves * #moves(context){ const pieces = this.like(context.color); for(const piece of pieces) yield * piece.moves(context); } * #allMoves(context){ const pieces = this.like(context.color); for(const piece of pieces) yield * piece.allMoves(context); } moves(context){ return new MoveMap(this.#moves(context)); } allMoves(context){ return new MoveMap(this.#allMoves(context)); } // I/O [Symbol.iterator](){ return this.#map.entries(); } toString(){ return this.fen; } hash(){ return boardToHash(this); } get fen(){ return boardToFEN(this); } set fen(fen){ const board = fenToBoard(fen); this.clear(); for(const [square, piece] of board) this.#set(square, piece); } toJSON(){ const board = {}; for(const [square, piece] of this) board[square] = piece.toJSON(); return board; } text(opts){ return table(this, opts); } log(opts){ console.log(this.text(opts)); } }