xchess
Version:
Chess Engine
553 lines (428 loc) • 10.1 kB
JavaScript
export {
Move,
Capture,
PawnDoubleMove,
Castling,
KingsideCastling,
QueensideCastling,
EnPassant,
Promotion,
CaptureAndPromotion,
PromotionRequest,
CaptureAndPromotionRequest,
}
import {Square} from './square.js'
import {Piece} from './piece.js'
import {Queen, Rook, Bishop, Knight, Pawn} from './standart-piece.js'
import {GetMoveDisambiguation, SetMoveDisambiguation, DISAMBIGUATION_NONE, DISAMBIGUATION_FILE, DISAMBIGUATION_RANK, DISAMBIGUATION_FULL} from './move-disambiguator.js'
import {INVALID_PIECE_TYPE, MOVE_PROMOTION_NOT_AVAILABLE} from './errors.js'
import {MoveEvent, InsertEvent, DeleteEvent, TransferEvent} from './events.js'
function promotionCode(promoteTo){
if(promoteTo) return promoteTo.code >> 1;
return 0;
}
function code(from, to, promoteTo){
return promotionCode(promoteTo) << 12 | from << 6 | to;
}
function promotionICCF(promoteTo){
if(promoteTo) return promoteTo.iccf;
return '';
}
function iccf(from, to, promoteTo){
return from.iccf + to.iccf + promotionICCF(promoteTo);
}
function IS_PROMOTION_PIECE(piece){
return [Queen, Rook, Bishop, Knight].some(Type => Type.is(piece));
}
function PROMOTION_PIECE(value, color){
const piece = Piece.fromColor(value, color);
if(!IS_PROMOTION_PIECE(piece))
throw INVALID_PIECE_TYPE(piece);
return piece;
}
class Move {
static code(from, to){
return code(Square.from(from), Square.from(to));
}
static is(value){
return value instanceof this;
}
#target;
#from;
#to;
constructor(target, from, to){
this.#target = Piece.from(target);
this.#from = Square.from(from);
this.#to = Square.from(to);
}
get code(){
return code(this.from, this.to, this.promoteTo);
}
get iccf(){
return iccf(this.from, this.to, this.promoteTo);
}
get target(){
return this.#target;
}
get from(){
return this.#from;
}
get to(){
return this.#to;
}
get color(){
return this.#target.color;
}
get kc(){
return false;
}
get qc(){
return false;
}
get promoteTo(){
return null;
}
get disambiguation(){
return GetMoveDisambiguation(this);
}
get fromHint(){
switch(this.disambiguation){
case DISAMBIGUATION_NONE:
return '';
case DISAMBIGUATION_FILE:
return this.from.file.toString();
case DISAMBIGUATION_RANK:
return this.from.rank.toString();
} return this.from.toString();
}
valueOf(){
return this.code;
}
toString({pieceLetter = true} = {}){
if(pieceLetter)
return `${this.target.moveLetter}${this.fromHint}${this.to}`;
return `${this.fromHint}${this.to}`;
}
// stat
match(cond){
const {kc, qc, to, from, file, rank, char, sign, capture, promotion} = cond;
if(kc && !this.kc)
return false;
if(qc && !this.qc)
return false;
if(to && !this.to.eq(to))
return false;
if(from && !this.from.eq(from))
return false;
if(rank && (this.from.rank !== rank))
return false;
if(file && (this.from.file !== file))
return false;
if(char && (this.target.char !== char))
return false;
if(sign && (this.target.sign !== sign))
return false;
if(capture && !this.capture)
return false;
if(promotion && !this.isPromotionRequest)
return false;
return true;
}
resolve(cond){
if(this.match(cond)){
if(cond.promotion)
return this.promote(cond.promotion);
return this;
} return null;
}
// rules
get isIdle(){
return !Pawn.is(this.target);
}
get doubleMovePawn(){
return null;
}
get isPromotionRequest(){
return false;
}
check(board){
this.next(board);
const check = board.check(this.color);
this.prev(board);
return check;
}
includes(piece){
return this.target === piece;
}
promote(piece){
throw MOVE_PROMOTION_NOT_AVAILABLE(this);
}
// board
next(board){
board.set(this.to, this.target);
}
prev(board){
board.set(this.from, this.target);
}
// context
do(context){
this.next(context.board);
}
redo(context){
this.next(context.board);
}
undo(context){
this.prev(context.board);
}
trigger(context){
context.dispatch(new TransferEvent(this.target, this.from, this.to));
}
}
class Capture extends Move {
#capture;
constructor(target, from, to, capture){
super(target, from, to);
this.#capture = Piece.from(capture);
}
get capture(){
return this.#capture;
}
get captureAt(){
return this.to;
}
get fromHint(){
if(Pawn.is(this.target))
return this.from.file.toString();
return super.fromHint;
}
toString({pieceLetter = true} = {}){
if(pieceLetter)
return `${this.target.moveLetter}${this.fromHint}x${this.to}`;
return `${this.fromHint}x${this.to}`;
}
// rules
get isIdle(){
return false;
}
includes(piece){
if(this.target === piece)
return true;
if(this.capture === piece)
return true;
return false;
}
// Board
prev(board){
board.set(this.from, this.target);
board.set(this.captureAt, this.capture);
}
// Context
do(context){
this.next(context.board);
context.captureList.add(this.capture);
}
redo(context){
this.next(context.board);
context.captureList.add(this.capture);
}
undo(context){
this.prev(context.board);
context.captureList.delete(this.capture);
}
trigger(context){
context.dispatch(new TransferEvent(this.target, this.from, this.to));
context.dispatch(new DeleteEvent(this.capture, this.captureAt));
context.dispatch(new MoveEvent('capture', this));
}
}
class PawnDoubleMove extends Move {
get doubleMovePawn(){
return this.target;
}
}
class Castling extends Move {
#submove;
constructor(target, from, to, submove){
super(target, from, to);
this.#submove = submove;
}
get submove(){
return this.#submove;
}
// Board
next(board){
super.next(board);
this.submove.next(board);
}
prev(board){
this.submove.prev(board);
super.prev(board);
}
// Rules
includes(piece){
if(super.includes(piece))
return true;
if(this.submove.includes(piece))
return true;
return false;
}
// Context
trigger(context){
context.dispatch(new TransferEvent(this.target, this.from, this.to));
context.dispatch(new TransferEvent(this.submove.target, this.submove.from, this.submove.to));
context.dispatch(new MoveEvent('castling', this));
}
}
class KingsideCastling extends Castling {
get kc(){
return true;
}
toString(){
return '0-0';
}
}
class QueensideCastling extends Castling {
get qc(){
return true;
}
toString(){
return '0-0-0';
}
}
class EnPassant extends Capture {
#captureAt;
constructor(target, from, to, capture, captureAt){
super(target, from, to, capture);
this.#captureAt = Square.from(captureAt);
}
get captureAt(){
return this.#captureAt;
}
toString(args){
return `${super.toString(args)} e.p.`;
}
// Board
next(board){
board.delete(this.captureAt);
board.set(this.to, this.target);
}
// Context
trigger(context){
super.trigger(context);
context.dispatch(new MoveEvent('enpassant', this));
}
}
class PromotionRequest extends Move {
get isPromotionRequest(){
return true;
}
respond(pieceVal){
const piece = PROMOTION_PIECE(pieceVal, this.color);
const {target, from, to, disambiguation} = this;
const move = new PromotionResponse(target, from, to, piece);
SetMoveDisambiguation(move, disambiguation);
return move;
}
promote(pieceVal){
const piece = PROMOTION_PIECE(pieceVal, this.color);
const {target, from, to, disambiguation} = this;
const move = new Promotion(target, from, to, piece);
SetMoveDisambiguation(move, disambiguation);
return move;
}
}
class CaptureAndPromotionRequest extends Capture {
get isPromotionRequest(){
return true;
}
respond(pieceVal){
const piece = PROMOTION_PIECE(pieceVal, this.color);
const {target, from, to, capture, disambiguation} = this;
const move = new CaptureAndPromotionResponse(target, from, to, capture, piece);
SetMoveDisambiguation(move, disambiguation);
return move;
}
promote(pieceVal){
const piece = PROMOTION_PIECE(pieceVal, this.color);
const {target, from, to, capture, disambiguation} = this;
const move = new CaptureAndPromotion(target, from, to, capture, piece);
SetMoveDisambiguation(move, disambiguation);
return move;
}
}
class Promotion extends Move {
#promoteTo;
constructor(target, from, to, promoteTo){
super(target, from, to);
this.#promoteTo = Piece.from(promoteTo);
}
get promoteTo(){
return this.#promoteTo;
}
toString(args){
return `${super.toString(args)}/${this.promoteTo.moveLetter}`;
}
// Board
next(board){
board.delete(this.from);
board.set(this.to, this.promoteTo);
}
prev(board){
board.delete(this.to);
board.set(this.from, this.target);
}
// Context
trigger(context){
super.trigger(context);
context.dispatch(new DeleteEvent(this.target, this.to));
context.dispatch(new InsertEvent(this.promoteTo, this.to));
}
}
class CaptureAndPromotion extends Capture {
#promoteTo;
constructor(target, from, to, capture, promoteTo){
super(target, from, to, capture);
this.#promoteTo = Piece.from(promoteTo);
}
get promoteTo(){
return this.#promoteTo;
}
toString(args){
return `${super.toString(args)}/${this.promoteTo.moveLetter}`;
}
// Board
next(board){
board.delete(this.from);
board.set(this.to, this.promoteTo);
}
prev(board){
board.set(this.from, this.target);
board.set(this.captureAt, this.capture);
}
// Context
trigger(context){
super.trigger(context);
context.dispatch(new DeleteEvent(this.target, this.to));
context.dispatch(new InsertEvent(this.promoteTo, this.to));
}
}
class PromotionResponse extends Promotion {
// Context
do(context){
context.board.set(this.to, this.promoteTo);
}
trigger(context){
context.dispatch(new DeleteEvent(this.target, this.to));
context.dispatch(new InsertEvent(this.promoteTo, this.to));
}
}
class CaptureAndPromotionResponse extends CaptureAndPromotion {
// Context
do(context){
context.board.set(this.to, this.promoteTo);
}
trigger(context){
context.dispatch(new DeleteEvent(this.target, this.to));
context.dispatch(new InsertEvent(this.promoteTo, this.to));
}
}