xchess
Version:
Chess Engine
362 lines (296 loc) • 6.52 kB
JavaScript
export {Board}
import {Color} from './color.js'
import {Square} from './square.js'
import {Piece} from './piece.js'
import {King} from './standart-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 {
#context;
#map = new Map();
#all = new Map();
constructor(context){
this.#context = context;
}
// read
get context(){
return this.#context;
}
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 = this.context.pieces.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 = this.context.pieces.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
isCapturable(square, color){
const target = this.get(square);
if(target) return target.isCapturable(color, this);
return false;
}
isPassable(square, color){
const target = this.get(square);
if(target) return target.isPassable(color, this);
return true;
}
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.context.pieces);
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));
}
}