xchess
Version:
Chess Engine
328 lines (267 loc) • 5.63 kB
JavaScript
export {MovementState, PlayState, MoveState}
import {EmptyMoveMap} from './move-map.js'
import {MoveEvent} from './events.js'
import {stateToHash} from './hash.js'
import {MOVE_NOT_AVAILABLE} from './errors.js'
import {GameState} from './game-state.js'
import {PromotionState} from './promotion-state.js'
import {Transitional} from './transitional-state.js'
import {
CheckmateState,
StalemateState,
DeadPositionState,
FivefoldRepetitionState,
SeventyFiveMovesState,
} from './end-state.js'
class MovementState extends Transitional(GameState) {
#gameColor;
#count;
#halfmoveClock;
#doubleMovePawn;
#castling;
#prevCastling;
#moves;
#isCheck;
#checkKing = null;
#hash;
#repetition;
#newRepetition;
#drawOffer;
constructor({
context,
prev,
gameColor,
count,
halfmoveClock,
repetition,
doubleMovePawn,
castling,
prevCastling,
drawOffer,
}){
super(context, prev);
this.#gameColor = gameColor;
this.#count = count;
this.#halfmoveClock = halfmoveClock;
this.#doubleMovePawn = doubleMovePawn;
this.#castling = castling;
this.#prevCastling = prevCastling;
this.#moves = this.board.moves(this);
const king = this.board.check(this.color);
if(king){
this.#isCheck = true;
this.#checkKing = king;
} else {
this.#isCheck = false;
this.#checkKing = null;
}
this.#hash = stateToHash(this);
this.#drawOffer = drawOffer;
this.#nextRepetition(repetition);
}
// Config
get color(){
return this.#gameColor.color;
}
get gameColor(){
return this.#gameColor;
}
get count(){
return this.#count;
}
get halfmoveClock(){
return this.#halfmoveClock;
}
get doubleMovePawn(){
return this.#doubleMovePawn;
}
get castling(){
return this.#castling;
}
get prevCastling(){
return this.#prevCastling;
}
get moves(){
return this.#moves;
}
get isCheck(){
return this.#isCheck;
}
get checkKing(){
return this.#checkKing;
}
get checkKingTarget(){
if(this.checkKing)
return this.board.find(this.checkKing);
return null;
}
get hash(){
return this.#hash;
}
get repetition(){
return this.#repetition;
}
get ownRepetition(){
return this.stateCounter.count(this.hash);
}
get newRepetition(){
return this.#newRepetition;
}
get drawOffer(){
return this.#drawOffer;
}
get wk(){
return this.gameColor.wk(this);
}
get wq(){
return this.gameColor.wq(this);
}
get bk(){
return this.gameColor.bk(this);
}
get bq(){
return this.gameColor.bq(this);
}
setDrawOffer(drawOffer){
this.#drawOffer = drawOffer;
}
// Stat
get isMovement(){
return true;
}
get isDrawOffer(){
return this.drawOffer.isDrawOffer;
}
// Move Config
#nextRepetition(prev){
const own = this.stateCounter.add(this.hash);
if(own > prev){
this.#repetition = own;
this.#newRepetition = own;
} else {
this.#repetition = prev;
this.#newRepetition = 0;
}
}
#nextClock(isIdle){
if(isIdle)
return this.halfmoveClock + 1;
return 0;
}
// Game Events
move(moveVal, prev = this){
const move = this.moves.get(moveVal);
if(move){
if(move.isPromotionRequest)
this.#promotion(move, prev);
else
this.do(move, prev);
return move;
} else throw MOVE_NOT_AVAILABLE(moveVal);
}
// Movement
do(move, prev = this){
const {context} = this;
move.do(context);
const {doubleMovePawn, isIdle} = move;
const gameColor = this.gameColor.invert();
const count = this.count + 1;
const halfmoveClock = this.#nextClock(isIdle);
const repetition = this.repetition;
const castling = this.prevCastling.next(move);
const prevCastling = this.castling.next(move);
const drawOffer = this.drawOffer.next();
this.state = new MoveState(move, {
context,
prev,
gameColor,
count,
halfmoveClock,
repetition,
doubleMovePawn,
castling,
prevCastling,
drawOffer,
});
this.state.execute();
this.next = this.state;
this.drawTrigger(drawOffer);
move.trigger(this.context);
this.dispatch(new MoveEvent('move', move));
this.state.trigger();
}
#promotion(move, prev){
move.do(this.context);
this.state = new PromotionState(move, this, prev);
this.next = this.state;
move.trigger(this.context);
this.state.trigger();
}
#execute(){
if(this.moves.size < 1){
if(this.isCheck)
return new CheckmateState(this, this.prev, this.color);
else
return new StalemateState(this, this.prev);
}
if(this.newRepetition == 5){
return new FivefoldRepetitionState(this, this.prev);
}
if(this.halfmoveClock == 150)
return new SeventyFiveMovesState(this, this.prev);
if(this.isDeadPosition())
return new DeadPositionState(this, this.prev);
}
execute(){
const FinalState = this.#execute();
if(FinalState){
this.state = FinalState;
this.next = FinalState;
}
}
drawTrigger(drawOffer){
if(drawOffer !== this.drawOffer)
this.emit('rejected-draw');
}
trigger(){
if(this.isCheck)
this.emit('check');
if(this.halfmoveClock == 100)
this.emit('50-move');
if(this.newRepetition > 0){
this.emit('repeat');
if(this.newRepetition == 3)
this.emit('3-repeat');
}
}
}
class PlayState extends MovementState {
// Stat
get status(){
return 'play';
}
}
class MoveState extends MovementState {
#move;
constructor(move, stateConfig){
super(stateConfig);
this.#move = move;
}
// Stat
get status(){
return 'move';
}
// Config
get lastMove(){
return this.#move;
}
// Log Event
undo(){
this.stateCounter.delete(this.hash);
this.#move.undo(this.context);
}
redo(){
this.stateCounter.add(this.hash);
this.#move.redo(this.context);
}
}