Node Chess is an algebraic notation driven chess engine for validating board position and understanding viable moves.
Node chess is available via npm
:
$ npm install chess
To create a client that lists valid moves in algebraic notation:
const chess = require('chess'), game = chess.create();
To get available moves, simply call the method getStatus()
:
let status = game.getStatus(), validMoves = status.notatedMoves; Object.keys(client.notatedMoves).forEach((notation) => { console.log(notation); });
Each valid move is represented as a hash in the notatedMoves
property where the key of the hash is the notation itself. The following is an example of the details available for each move:
{ "Na3": { "src": { "file": "b", "rank": 1, "piece": { "moveCount": 0, "side": { "name": "white" }, "type": "knight", "notation": "N" } }, "dest": { "file": "a", "rank": 3, "piece": null } }, // ... results abbreviated
Making a move is as simple as calling the move()
function with the proper notation:
const chess = require('chess'), game = chess.create(); // move the White Pawn located on square a2 forward two squares let move = game.move('a4');
The move function returns an object that helps further understand state. This object also contains a method to undo the move.
move.capturedPiece
move.castle
move.enPassant
move.postSquare
move.prevSquare
undo()
Here is the serialized output (with description) of the return object from move()
:
{ move: { // the captured piece (if capture occurred) capturedPiece: null, // was the move a castle? castle: false, // was the move en Passant? enPassant: false, // tje square a piece was moved to postSquare: { file: 'a', rank: 4, piece: { moveCount: 1, side: { name: 'white' }, type: 'pawn', notation: 'R' } }, // the square that the piece came from prevSquare: { file: 'a', rank: 2, piece: null } }, // undo() can be used to back out the previous move undo: __function__ }
The getStatus function returns the current state of the board, all valid moves and an object graph with all the squares of the board (with pieces).
board
isCheck
isCheckmate
isRepetition
notatedMoves
may contain valid moves.isStalemate
notatedMoves
Here is the serialized output (with description) of the return object from getStatus()
:
{ // this is the top level board board: { // an array of all squares on the board squares: [{ file: 'a', rank: 1, piece: { moveCount: 0, side: { name: 'white' }, type: 'rook', notation: 'R' } }, /* the rest of the squares... */ ] }, isCheck: false, // is the King currently in check? isCheckmate: false, // is the King currently in checkmate? isRepetition: false, // has 3-fold repetition occurred? isStalemate: false, // is the board in stalemate? // all possible moves (notated) with details for each move notatedMoves: { a3: { src: { file: 'a' rank: 2, piece: { moveCount: 0, side: { name: 'white' }, type: 'pawn', notation: 'R' } }, dest: { file: 'a', rank: 3, piece: null } }, /* the rest of the available moves... */ } }
The game emits various events throughout the course of play.
capture
move
object (as returned from the move()
> function) is provided with this event.castle
move
object (as returned from the move()
> function) is provided with this event.check
attackingSquare
and kingSquare
).checkmate
attackingSquare
and kingSquare
).enPassant
move
object (as returned from the move()
> function) is provided with this event.move
move
object (as returned from the move()
> function) is provided with this event.promote
square
object is provided with this event (the object contains fields for rank
, file
, and piece
).Here is an example of the events being consumed:
const chess = require('chess'); // create a game client const gameClient = chess.create({ PGN : true }); // when a piece is captured gameClient.on('capture', (move) => { console.log('A piece has been captured!'); console.log(move); }); // when a King is placed in check gameClient.on('check', (attack) => { console.log('The King is under attack!'); console.log(attack); }); // when King is placed in checkmate gameClient.on('checkmate', (attack) => { console.log('The game has ended due to checkmate!'); console.log(attack); }); // when a castle occurs gameClient.on('castle', (move) => { console.log('A castle has occured!'); console.log(move); }); // when en Passant occurs gameClient.on('enPassant', (move) => { console.log('An en Passant has occured!'); console.log(move); }); // when a move occurs on the board gameClient.on('move', (move) => { console.log('A piece was moved!'); console.log(move); }); // when a Pawn promotion occurs gameClient.on('promote', (square) => { console.log('A Pawn has been promoted!'); console.log(square); });
This game is quite interesting as it resulted in a draw due to 3-fold repetition!
const chess = require('chess'), game = chess.create(); // 1. e4 e6 game.move('e4'); game.move('e6'); // 2. d4 d5 game.move('d4'); game.move('d5'); // 3. Nc3 Nf6 game.move('Nc3'); game.move('Nf6'); // 4. Bg5 dxe4 game.move('Bg5'); game.move('dxe4'); // 5. Nxe4 Be7 game.move('Nxe4'); game.move('Be7'); // 6. Bxf6 gxf6 game.move('Bxf6'); game.move('gxf6'); // 7. g3 f5 game.move('g3'); game.move('f5'); // 8. Nc3 Bf6 game.move('Nc3'); game.move('Bf6'); // 9. Nge2 Nc6 game.move('Nge2'); game.move('Nc6'); // 10. d5 exd5 game.move('d5'); game.move('exd5'); // 11. Nxd5 Bxb2 game.move('Nxd5'); game.move('Bxb2'); // 12. Bg2 O-O game.move('Bg2'); game.move('0-0'); // 13. O-O Bh8 game.move('0-0'); game.move('Bh8'); // 14. Nef4 Ne5 game.move('Nef4'); game.move('Ne5'); // 15. Qh5 Ng6 game.move('Qh5'); game.move('Ng6'); // 16. Rad1 c6 game.move('Rad1'); game.move('c6'); // 17. Ne3 Qf6 game.move('Ne3'); game.move('Qf6'); // 18. Kh1 Bg7 game.move('Kh1'); game.move('Bg7'); // 19. Bh3 Ne7 game.move('Bh3'); game.move('Ne7'); // 20. Rd3 Be6 game.move('Rd3'); game.move('Be6'); // 21. Rfd1 Bh6 game.move('Rfd1'); game.move('Bh6'); // 22. Rd4 Bxf4 game.move('Rd4'); game.move('Bxf4'); // 23. Rxf4 Rad8 game.move('Rxf4'); game.move('Rad8'); // 24. Rxd8 Rxd8 game.move('Rxd8'); game.move('Rxd8'); // 25. Bxf5 Nxf5 game.move('Bxf5'); game.move('Nxf5'); // 26. Nxf5 Rd5 game.move('Nxf5'); game.move('Rd5'); // 27. g4 Bxf5 game.move('g4'); game.move('Bxf5'); // 28. gxf5 h6 game.move('gxf5'); game.move('h6'); // 29. h3 Kh7 game.move('h3'); game.move('Kh7'); // 30. Qe2 Qe5 game.move('Qe2'); game.move('Qe5'); // 31. Qh5 Qf6 game.move('Qh5'); game.move('Qf6'); // 32. Qe2 Re5 game.move('Qe2'); game.move('Re5'); // 33. Qd3 Rd5 game.move('Qd3'); game.move('Rd5'); // 34. Qe2 game.move('Qe2'); console.log(JSON.stringify(game.getStatus(), 0, 2));
The above code will output the following:
{ board: { squares: [ { file: 'a', rank: 1, piece: null }, { file: 'b', rank: 1, piece: null }, { file: 'c', rank: 1, piece: null }, { file: 'd', rank: 1, piece: null }, { file: 'e', rank: 1, piece: null }, { file: 'f', rank: 1, piece: null }, { file: 'g', rank: 1, piece: null }, { file: 'h', rank: 1, piece: { moveCount: 2, side: { name: 'white' }, type: 'king', notation: 'K' } }, { file: 'a', rank: 2, piece: { moveCount: 0, side: { name: 'white' }, type: 'pawn', notation: '' } }, { file: 'b', rank: 2, piece: null }, { file: 'c', rank: 2, piece: { moveCount: 0, side: { name: 'white' }, type: 'pawn', notation: '' } }, { file: 'd', rank: 2, piece: null }, { file: 'e', rank: 2, piece: { moveCount: 6, side: { name: 'white' }, type: 'queen', notation: 'Q' } }, { file: 'f', rank: 2, piece: { moveCount: 0, side: { name: 'white' }, type: 'pawn', notation: '' } }, { file: 'g', rank: 2, piece: null }, { file: 'h', rank: 2, piece: null }, { file: 'a', rank: 3, piece: null }, { file: 'b', rank: 3, piece: null }, { file: 'c', rank: 3, piece: null }, { file: 'd', rank: 3, piece: null }, { file: 'e', rank: 3, piece: null }, { file: 'f', rank: 3, piece: null }, { file: 'g', rank: 3, piece: null }, { file: 'h', rank: 3, piece: { moveCount: 1, side: { name: 'white' }, type: 'pawn', notation: '' } }, { file: 'a', rank: 4, piece: null }, { file: 'b', rank: 4, piece: null }, { file: 'c', rank: 4, piece: null }, { file: 'd', rank: 4, piece: null }, { file: 'e', rank: 4, piece: null }, { file: 'f', rank: 4, piece: { moveCount: 4, side: { name: 'white' }, type: 'rook', notation: 'R' } }, { file: 'g', rank: 4, piece: null }, { file: 'h', rank: 4, piece: null }, { file: 'a', rank: 5, piece: null }, { file: 'b', rank: 5, piece: null }, { file: 'c', rank: 5, piece: null }, { file: 'd', rank: 5, piece: { moveCount: 4, side: { name: 'black' }, type: 'rook', notation: 'R' } }, { file: 'e', rank: 5, piece: null }, { file: 'f', rank: 5, piece: { moveCount: 3, side: { name: 'white' }, type: 'pawn', notation: '' } }, { file: 'g', rank: 5, piece: null }, { file: 'h', rank: 5, piece: null }, { file: 'a', rank: 6, piece: null }, { file: 'b', rank: 6, piece: null }, { file: 'c', rank: 6, piece: { moveCount: 1, side: { name: 'black' }, type: 'pawn', notation: '' } }, { file: 'd', rank: 6, piece: null }, { file: 'e', rank: 6, piece: null }, { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, { file: 'g', rank: 6, piece: null }, { file: 'h', rank: 6, piece: { moveCount: 1, side: { name: 'black' }, type: 'pawn', notation: '' } }, { file: 'a', rank: 7, piece: { moveCount: 0, side: { name: 'black' }, type: 'pawn', notation: '' } }, { file: 'b', rank: 7, piece: { moveCount: 0, side: { name: 'black' }, type: 'pawn', notation: '' } }, { file: 'c', rank: 7, piece: null }, { file: 'd', rank: 7, piece: null }, { file: 'e', rank: 7, piece: null }, { file: 'f', rank: 7, piece: { moveCount: 0, side: { name: 'black' }, type: 'pawn', notation: '' } }, { file: 'g', rank: 7, piece: null }, { file: 'h', rank: 7, piece: { moveCount: 2, side: { name: 'black' }, type: 'king', notation: 'K' } }, { file: 'a', rank: 8, piece: null }, { file: 'b', rank: 8, piece: null }, { file: 'c', rank: 8, piece: null }, { file: 'd', rank: 8, piece: null }, { file: 'e', rank: 8, piece: null }, { file: 'f', rank: 8, piece: null }, { file: 'g', rank: 8, piece: null }, { file: 'h', rank: 8, piece: null } ], _events: { move: [Function] } }, isCheck: false, isCheckmate: false, isRepetition: true, isStalemate: false, notatedMoves: { Rd4: { src: { file: 'd', rank: 5, piece: { moveCount: 4, side: { name: 'black' }, type: 'rook', notation: 'R' } }, dest: { file: 'd', rank: 4, piece: null } }, Rd3: { src: { file: 'd', rank: 5, piece: { moveCount: 4, side: { name: 'black' }, type: 'rook', notation: 'R' } }, dest: { file: 'd', rank: 3, piece: null } }, Rd2: { src: { file: 'd', rank: 5, piece: { moveCount: 4, side: { name: 'black' }, type: 'rook', notation: 'R' } }, dest: { file: 'd', rank: 2, piece: null } }, Rd1: { src: { file: 'd', rank: 5, piece: { moveCount: 4, side: { name: 'black' }, type: 'rook', notation: 'R' } }, dest: { file: 'd', rank: 1, piece: null } }, Rd6: { src: { file: 'd', rank: 5, piece: { moveCount: 4, side: { name: 'black' }, type: 'rook', notation: 'R' } }, dest: { file: 'd', rank: 6, piece: null } }, Rd7: { src: { file: 'd', rank: 5, piece: { moveCount: 4, side: { name: 'black' }, type: 'rook', notation: 'R' } }, dest: { file: 'd', rank: 7, piece: null } }, Rd8: { src: { file: 'd', rank: 5, piece: { moveCount: 4, side: { name: 'black' }, type: 'rook', notation: 'R' } }, dest: { file: 'd', rank: 8, piece: null } }, Rc5: { src: { file: 'd', rank: 5, piece: { moveCount: 4, side: { name: 'black' }, type: 'rook', notation: 'R' } }, dest: { file: 'c', rank: 5, piece: null } }, Rb5: { src: { file: 'd', rank: 5, piece: { moveCount: 4, side: { name: 'black' }, type: 'rook', notation: 'R' } }, dest: { file: 'b', rank: 5, piece: null } }, Ra5: { src: { file: 'd', rank: 5, piece: { moveCount: 4, side: { name: 'black' }, type: 'rook', notation: 'R' } }, dest: { file: 'a', rank: 5, piece: null } }, Re5: { src: { file: 'd', rank: 5, piece: { moveCount: 4, side: { name: 'black' }, type: 'rook', notation: 'R' } }, dest: { file: 'e', rank: 5, piece: null } }, Rxf5: { src: { file: 'd', rank: 5, piece: { moveCount: 4, side: { name: 'black' }, type: 'rook', notation: 'R' } }, dest: { file: 'f', rank: 5, piece: { moveCount: 3, side: { name: 'white' }, type: 'pawn', notation: '' } } }, c5: { src: { file: 'c', rank: 6, piece: { moveCount: 1, side: { name: 'black' }, type: 'pawn', notation: '' } }, dest: { file: 'c', rank: 5, piece: null } }, Qxf5: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'f', rank: 5, piece: { moveCount: 3, side: { name: 'white' }, type: 'pawn', notation: '' } } }, Qe6: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'e', rank: 6, piece: null } }, Qd6: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'd', rank: 6, piece: null } }, Qg6: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'g', rank: 6, piece: null } }, Qe7: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'e', rank: 7, piece: null } }, Qd8: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'd', rank: 8, piece: null } }, Qg5: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'g', rank: 5, piece: null } }, Qh4: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'h', rank: 4, piece: null } }, Qe5: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'e', rank: 5, piece: null } }, Qd4: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'd', rank: 4, piece: null } }, Qc3: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'c', rank: 3, piece: null } }, Qb2: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'b', rank: 2, piece: null } }, Qa1: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'a', rank: 1, piece: null } }, Qg7: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'g', rank: 7, piece: null } }, Qh8: { src: { file: 'f', rank: 6, piece: { moveCount: 3, side: { name: 'black' }, type: 'queen', notation: 'Q' } }, dest: { file: 'h', rank: 8, piece: null } }, h5: { src: { file: 'h', rank: 6, piece: { moveCount: 1, side: { name: 'black' }, type: 'pawn', notation: '' } }, dest: { file: 'h', rank: 5, piece: null } }, a6: { src: { file: 'a', rank: 7, piece: { moveCount: 0, side: { name: 'black' }, type: 'pawn', notation: '' } }, dest: { file: 'a', rank: 6, piece: null } }, a5: { src: { file: 'a', rank: 7, piece: { moveCount: 0, side: { name: 'black' }, type: 'pawn', notation: '' } }, dest: { file: 'a', rank: 5, piece: null } }, b6: { src: { file: 'b', rank: 7, piece: { moveCount: 0, side: { name: 'black' }, type: 'pawn', notation: '' } }, dest: { file: 'b', rank: 6, piece: null } }, b5: { src: { file: 'b', rank: 7, piece: { moveCount: 0, side: { name: 'black' }, type: 'pawn', notation: '' } }, dest: { file: 'b', rank: 5, piece: null } }, Kh8: { src: { file: 'h', rank: 7, piece: { moveCount: 2, side: { name: 'black' }, type: 'king', notation: 'K' } }, dest: { file: 'h', rank: 8, piece: null } }, Kg7: { src: { file: 'h', rank: 7, piece: { moveCount: 2, side: { name: 'black' }, type: 'king', notation: 'K' } }, dest: { file: 'g', rank: 7, piece: null } }, Kg8: { src: { file: 'h', rank: 7, piece: { moveCount: 2, side: { name: 'black' }, type: 'king', notation: 'K' } }, dest: { file: 'g', rank: 8, piece: null } } } }