chess
Version:
An algebraic notation driven chess engine that can validate board position and produce a list of viable moves (notated).
167 lines (140 loc) • 3.65 kB
JavaScript
/**
Games contain the history of a board and the board itself.
At time of writing this, the game is also intended to store some
degree of information regarding the opponents and keys that
could be used for storage, etc.
*/
import base64 from 'crypto-js/enc-base64.js'
import { Board } from './board.js';
import { EventEmitter } from 'events';
import md5 from 'crypto-js/md5.js';
import { SideType } from './piece.js';
function addToHistory (game) {
return (ev) => {
let
hashCode = game.getHashCode(),
move = new Move(
ev.prevSquare,
ev.postSquare,
ev.capturedPiece,
ev.algebraic,
ev.castle,
ev.enPassant,
hashCode);
game.moveHistory.push(move);
};
}
function denotePromotionInHistory (game) {
return () => {
let
latest = game.moveHistory[
game.moveHistory.length - 1];
if (latest) {
latest.promotion = true;
}
};
}
function removeFromHistory (game) {
return () => {
game.moveHistory.pop();
// find the previous move piece
let m = game.moveHistory[game.moveHistory.length - 1];
// update last moved piece
if (m) {
game.board.lastMovedPiece = m.piece;
}
};
}
export class Game extends EventEmitter {
constructor (board) {
super();
this.board = board;
this.captureHistory = [];
this.moveHistory = [];
}
static create () {
let
board = Board.create(),
game = new Game(board);
// handle move and promotion events correctly
board.on('move', (ev) => {
addToHistory(game)(ev);
if (ev && ev.capturedPiece) {
game.captureHistory.push(ev.capturedPiece);
}
});
board.on('promote', denotePromotionInHistory(game));
board.on('undo', (ev) => {
removeFromHistory(game)(ev);
if (ev && ev.capturedPiece && game.captureHistory.length > 0) {
// last move was a capture, remove it from capture history
game.captureHistory.pop();
}
});
return game;
}
getCurrentSide () {
return this.moveHistory.length % 2 === 0 ?
SideType.White :
SideType.Black;
}
getHashCode () {
let
i = 0,
sum = '';
for (i = 0; i < this.board.squares.length; i++) {
if (this.board.squares[i].piece !== null) {
sum += [
this.board.squares[i].file,
this.board.squares[i].rank,
(this.board.squares[i].piece.side === SideType.White ? 'w' : 'b'),
this.board.squares[i].piece.notation,
(i < (this.board.squares.length - 1) ? '-' : '')].join('');
}
}
// generate hash code for board
let digest = md5(sum);
return base64.stringify(digest);
}
static load (moveHistory) {
let
board = Board.create(),
game = new Game(board),
i = 0;
// handle move and promotion events correctly
board.on('move', (ev) => {
addToHistory(game)(ev);
if (ev && ev.capturedPiece) {
game.captureHistory.push(ev.capturedPiece);
}
});
board.on('promote', denotePromotionInHistory(game));
// apply move history
for (i = 0; i < moveHistory.length; i++) {
board.move(
board.getSquare(
moveHistory[i].prevFile,
moveHistory[i].prevRank),
board.getSquare(
moveHistory[i].postFile,
moveHistory[i].postRank));
}
return game;
}
}
export class Move {
constructor (originSquare, targetSquare, capturedPiece, notation, castle, enPassant, hash) {
this.algebraic = notation;
this.capturedPiece = capturedPiece;
this.castle = castle;
this.enPassant = enPassant;
this.hashCode = hash;
this.piece = targetSquare.piece;
this.promotion = false;
this.postFile = targetSquare.file;
this.postRank = targetSquare.rank;
this.prevFile = originSquare.file;
this.prevRank = originSquare.rank;
}
}
export default { Game, Move };