UNPKG

kokopu

Version:

A JavaScript/TypeScript library implementing the chess game rules and providing tools to read/write the standard chess file formats.

531 lines (454 loc) 26.5 kB
/*! * -------------------------------------------------------------------------- * * * * Kokopu - A JavaScript/TypeScript chess library. * * <https://www.npmjs.com/package/kokopu> * * Copyright (C) 2018-2025 Yoann Le Montagner <yo35 -at- melix.net> * * * * Kokopu is free software: you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public License * * as published by the Free Software Foundation, either version 3 of * * the License, or (at your option) any later version. * * * * Kokopu is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General * * Public License along with this program. If not, see * * <http://www.gnu.org/licenses/>. * * * * -------------------------------------------------------------------------- */ const { exception, Position } = require('../dist/lib/index'); const dumpCastlingFlags = require('./common/dumpcastlingflags'); const readCSV = require('./common/readcsv'); const test = require('unit.js'); const startFEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; const startXFEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHah - 0 1'; const startFENAntichess = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1'; const startFENHorde = 'rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1'; const emptyFEN = '8/8/8/8/8/8/8/8 w - - 0 1'; const customFEN = 'r3k2r/pb3pbp/1p4p1/3n4/1PpP4/P4NB1/5PPP/R3KB1R b KQkq d3 0 1'; const customFENNoCastling = 'r3k2r/pb3pbp/1p4p1/3n4/1PpP4/P4NB1/5PPP/R3KB1R b - d3 0 1'; const customFENWhiteCastlingOnly = 'r3k2r/pb3pbp/1p4p1/3n4/1PpP4/P4NB1/5PPP/R3KB1R b KQ d3 0 1'; const customFENBlackCastlingOnly = 'r3k2r/pb3pbp/1p4p1/3n4/1PpP4/P4NB1/5PPP/R3KB1R b kq d3 0 1'; const customXFEN = 'qrkbrnbn/pppppppp/8/8/8/8/PPPPPPPP/QRKBRNBN w BEbe - 0 1'; const customXFENAsFEN = 'qrkbrnbn/pppppppp/8/8/8/8/PPPPPPPP/QRKBRNBN w KQkq - 0 1'; const customFENHorde = '1Q3rk1/2P4p/1P2pp2/2PP4/5P1P/2q1PPPP/2P1PPPP/2PPPPPP b - - 0 1'; const variants = [ 'regular', 'chess960', 'no-king', 'white-king-only', 'black-king-only', 'antichess', 'horde' ]; describe('Position constructor', () => { function doTest(label, expectedVariant, expectedFEN, positionFactory) { it(label, () => { const position = positionFactory(); test.value(position.variant()).is(expectedVariant); test.value(position.fen()).is(expectedFEN); }); } /* eslint-disable @stylistic/comma-spacing, @stylistic/no-multi-spaces */ doTest('Default constructor' , 'regular', startFEN , () => new Position()); doTest('Constructor \'start\'', 'regular', startFEN , () => new Position('start')); doTest('Constructor \'empty\'', 'regular', emptyFEN , () => new Position('empty')); doTest('Constructor FEN-based', 'regular', customFEN, () => new Position(customFEN)); doTest('Default constructor (force regular)' , 'regular', startFEN , () => new Position('regular')); doTest('Constructor \'start\' (force regular)' , 'regular', startFEN , () => new Position('regular', 'start')); doTest('Constructor \'empty\' (force regular)' , 'regular', emptyFEN , () => new Position('regular', 'empty')); doTest('Constructor FEN-based (force regular)' , 'regular', customFEN, () => new Position('regular', customFEN)); doTest('Constructor FEN-based with prefix (force regular)', 'regular', customFEN, () => new Position('regular:' + customFEN)); doTest('Scharnagl constructor' , 'chess960', startXFEN , () => new Position('chess960', 518)); doTest('Constructor \'empty\' (Chess960)' , 'chess960', emptyFEN , () => new Position('chess960', 'empty')); doTest('Constructor FEN-based (Chess960)' , 'chess960', customXFEN, () => new Position('chess960', customXFENAsFEN)); doTest('Constructor X-FEN-based (Chess960)' , 'chess960', customXFEN, () => new Position('chess960', customXFEN)); doTest('Constructor X-FEN-based with prefix (Chess960)', 'chess960', customXFEN, () => new Position('chess960:' + customXFEN)); doTest('Constructor \'empty\' (no-king)' , 'no-king' , emptyFEN , () => new Position('no-king', 'empty')); doTest('Constructor FEN-based (no-king)' , 'no-king' , customFENNoCastling , () => new Position('no-king', customFEN)); doTest('Constructor \'empty\' (white-king-only)', 'white-king-only', emptyFEN , () => new Position('white-king-only', 'empty')); doTest('Constructor FEN-based (white-king-only)', 'white-king-only', customFENWhiteCastlingOnly, () => new Position('white-king-only', customFEN)); doTest('Constructor \'empty\' (black-king-only)', 'black-king-only', emptyFEN , () => new Position('black-king-only', 'empty')); doTest('Constructor FEN-based (black-king-only)', 'black-king-only', customFENBlackCastlingOnly, () => new Position('black-king-only', customFEN)); doTest('Default constructor (antichess)' , 'antichess', startFENAntichess , () => new Position('antichess')); doTest('Constructor \'start\' (antichess)' , 'antichess', startFENAntichess , () => new Position('antichess', 'start')); doTest('Constructor \'empty\' (antichess)' , 'antichess', emptyFEN , () => new Position('antichess', 'empty')); doTest('Constructor FEN-based (antichess)' , 'antichess', customFENNoCastling, () => new Position('antichess', customFEN)); doTest('Constructor FEN-based with prefix (antichess)', 'antichess', customFENNoCastling, () => new Position('antichess:' + customFEN)); doTest('Default constructor (horde)' , 'horde', startFENHorde , () => new Position('horde')); doTest('Constructor \'start\' (horde)' , 'horde', startFENHorde , () => new Position('horde', 'start')); doTest('Constructor \'empty\' (horde)' , 'horde', emptyFEN , () => new Position('horde', 'empty')); doTest('Constructor FEN-based (horde)' , 'horde', customFENHorde, () => new Position('horde', customFENHorde)); doTest('Constructor FEN-based with prefix (horde)', 'horde', customFENHorde, () => new Position('horde:' + customFENHorde)); /* eslint-enable */ function doFailureTest(label, fenParsingErrorExpected, positionFactory) { it(label, () => { test.exception(positionFactory).isInstanceOf(fenParsingErrorExpected ? exception.InvalidFEN : exception.IllegalArgument); }); } doFailureTest('Invalid variant', false, () => new Position('not-a-variant', 'empty')); doFailureTest('Invalid variant (not a string)', false, () => new Position(42, 'empty')); doFailureTest('Invalid variant (FEN-based)', false, () => new Position('not-a-variant', startFEN)); doFailureTest('Invalid variant (FEN-based with prefix)', true, () => new Position('not-a-variant:' + startFEN)); doFailureTest('Invalid form 1', false, () => new Position(42)); doFailureTest('Invalid form 2', false, () => new Position({})); doFailureTest('Invalid form 3', false, () => new Position('regular', 123)); doFailureTest('Invalid form 4', false, () => new Position('regular', {})); doFailureTest('Variant without canonical start 1', false, () => new Position('no-king')); doFailureTest('Variant without canonical start 2', false, () => new Position('chess960', 'start')); doFailureTest('Invalid Scharnagl code NaN', false, () => new Position('chess960', NaN)); doFailureTest('Invalid Scharnagl code -1', false, () => new Position('chess960', -1)); doFailureTest('Invalid FEN string 1', true, () => new Position('rkr/ppp/8/8/8/8/PPP/RKR w - - 0 1')); doFailureTest('Invalid FEN string 2', true, () => new Position('8/8 w - - 0 1')); doFailureTest('Invalid FEN string 3', true, () => new Position('8/8/8/X7/8/8/8/8 w - - 0 1')); doFailureTest('Invalid FEN string 4', true, () => new Position('8/8/8/8/8/8/8/8 x - - 0 1')); doFailureTest('Invalid FEN string 5a', true, () => new Position('8/8/8/8/8/8/8/8 w - j6 0 1')); doFailureTest('Invalid FEN string 5b', true, () => new Position('8/8/8/8/8/8/8/8 w - a5 0 1')); doFailureTest('Invalid FEN string 6a', true, () => new Position('8/8/8/8/8/8/8/8 w - - xxx 1')); doFailureTest('Invalid FEN string 6b', true, () => new Position('8/8/8/8/8/8/8/8 w - - 0 xxx')); doFailureTest('Invalid FEN string with invalid variant', true, () => new Position('Something strange: a string with a colon in it...')); doFailureTest('Invalid FEN string with variant', true, () => new Position('regular', 'NotAFENString')); doFailureTest('Invalid FEN string with variant (as prefix)', true, () => new Position('regular:NotAFENString')); }); describe('Position copy constructor', () => { function itCopy(variant, inputCustomFEN, expectedFEN) { it('Copy from ' + variant, () => { // Initialize the positions. const p1 = new Position(variant, inputCustomFEN); const p2 = new Position(p1); p1.clear(variant); // Check their states test.value(p1.variant()).is(variant); test.value(p2.variant()).is(variant); test.value(p1.fen()).is(emptyFEN); test.value(p2.fen()).is(expectedFEN); }); } itCopy('regular', customFEN, customFEN); itCopy('chess960', customXFEN, customXFEN); itCopy('no-king', customFEN, customFENNoCastling); itCopy('white-king-only', customFEN, customFENWhiteCastlingOnly); itCopy('black-king-only', customFEN, customFENBlackCastlingOnly); itCopy('antichess', customFEN, customFENNoCastling); itCopy('horde', customFENHorde, customFENHorde); }); describe('Clear mutator', () => { for (const variantSource of variants) { it('From ' + variantSource + ' to default', () => { const position = new Position(variantSource, customFEN); position.clear(); test.value(position.variant()).is('regular'); test.value(position.fen()).is(emptyFEN); }); for (const variantTarget of variants) { it('From ' + variantSource + ' to ' + variantTarget, () => { const position = new Position(variantSource, customFEN); position.clear(variantTarget); test.value(position.variant()).is(variantTarget); test.value(position.fen()).is(emptyFEN); }); } it('From ' + variantSource + ' to error', () => { const position = new Position(variantSource, customFEN); test.exception(() => position.clear('not-a-variant')).isInstanceOf(exception.IllegalArgument); }); } }); describe('Reset mutator', () => { for (const variant of variants) { it('From ' + variant, () => { const position = new Position(variant, customFEN); position.reset(); test.value(position.variant()).is('regular'); test.value(position.fen()).is(startFEN); }); } }); describe('Reset 960 mutator', () => { for (const variant of variants) { it('From ' + variant, () => { const position = new Position(variant, customFEN); position.reset960(518); test.value(position.variant()).is('chess960'); test.value(position.fen()).is(startXFEN); }); } for (const elem of [ 960, 18.3, '546' ]) { it('Error with Scharnagl code ' + elem, () => { const p = new Position(); test.exception(() => p.reset960(elem)).isInstanceOf(exception.IllegalArgument); }); } }); describe('Reset antichess mutator', () => { for (const variant of variants) { it('From ' + variant, () => { const position = new Position(variant, customFEN); position.resetAntichess(); test.value(position.variant()).is('antichess'); test.value(position.fen()).is(startFENAntichess); }); } }); describe('Reset horde mutator', () => { for (const variant of variants) { it('From ' + variant, () => { const position = new Position(variant, customFEN); position.resetHorde(); test.value(position.variant()).is('horde'); test.value(position.fen()).is(startFENHorde); }); } }); describe('Position Scharnagl constructor', () => { const testData = readCSV('scharnagl.csv', fields => { return { scharnaglCode: parseInt(fields[0]), fen: fields[3], }; }); for (const elem of testData) { it('Chess960 initial position ' + elem.scharnaglCode, () => { const position = new Position('chess960', elem.scharnaglCode); test.value(position.variant()).is('chess960'); test.value(position.fen()).is(elem.fen); }); } }); describe('Position getters', () => { const currentFEN = 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b Kk e3 0 1'; it('Get board 1', () => { const p = new Position(); test.value(p.square('e1')).is('wk'); }); it('Get board 2', () => { const p = new Position(); test.value(p.square('f7')).is('bp'); }); it('Get board 3', () => { const p = new Position(); test.value(p.square('b4')).is('-'); }); it('Get turn 1', () => { const p = new Position(); test.value(p.turn()).is('w'); }); it('Get turn 2', () => { const p = new Position(currentFEN); test.value(p.turn()).is('b'); }); it('Get castling 1', () => { const p = new Position(); test.value(p.castling('wq')).is(true); }); it('Get castling 2', () => { const p = new Position(currentFEN); test.value(p.castling('bq')).is(false); }); it('Get castling 3', () => { const p = new Position(currentFEN); test.value(p.castling('bk')).is(true); }); it('Get castling 4 (chess960)', () => { const p = new Position('chess960', 763); test.value(p.castling('wa')).is(true); }); it('Get castling 5 (chess960)', () => { const p = new Position('chess960', 763); test.value(p.castling('wb')).is(false); }); it('Get castling 6 (chess960)', () => { const p = new Position('chess960', 763); test.value(p.castling('bf')).is(true); }); it('Get castling 7 (chess960)', () => { const p = new Position('chess960', 763); test.value(p.castling('bh')).is(false); }); it('Get castling 8 (no-king)', () => { const p = new Position('no-king', 'empty'); test.value(p.castling('bq')).is(false); }); it('Get castling 9 (no-king)', () => { const p = new Position('no-king', startFEN); test.value(p.castling('wk')).is(true); }); it('Get castling 10 (white-king-only)', () => { const p = new Position('white-king-only', 'empty'); test.value(p.castling('wk')).is(false); }); it('Get castling 11 (white-king-only)', () => { const p = new Position('white-king-only', 'empty'); test.value(p.castling('bq')).is(false); }); it('Get castling 12 (white-king-only)', () => { const p = new Position('white-king-only', startFEN); test.value(p.castling('wq')).is(true); }); it('Get castling 13 (white-king-only)', () => { const p = new Position('white-king-only', startFEN); test.value(p.castling('bk')).is(true); }); it('Get castling 14 (black-king-only)', () => { const p = new Position('black-king-only', 'empty'); test.value(p.castling('wk')).is(false); }); it('Get castling 15 (black-king-only)', () => { const p = new Position('black-king-only', 'empty'); test.value(p.castling('bq')).is(false); }); it('Get castling 16 (black-king-only)', () => { const p = new Position('black-king-only', startFEN); test.value(p.castling('wq')).is(true); }); it('Get castling 17 (black-king-only)', () => { const p = new Position('black-king-only', startFEN); test.value(p.castling('bk')).is(true); }); it('Get en-passant 1', () => { const p = new Position(); test.value(p.enPassant()).is('-'); }); it('Get en-passant 2', () => { const p = new Position(currentFEN); test.value(p.enPassant()).is('e'); }); for (const elem of [ 'j1', 'f9' ]) { it('Error for board with ' + elem, () => { const p = new Position(); test.exception(() => p.square(elem)).isInstanceOf(exception.IllegalArgument); }); } for (const elem of [ 'bK', 'wa' ]) { it('Error for castling with ' + elem, () => { const p = new Position(); test.exception(() => p.castling(elem)).isInstanceOf(exception.IllegalArgument); test.exception(() => p.effectiveCastling(elem)).isInstanceOf(exception.IllegalArgument); }); } for (const elem of [ 'wA', 'bq' ]) { it('Error for castling (chess960) with ' + elem, () => { const p = new Position('chess960', 123); test.exception(() => p.castling(elem)).isInstanceOf(exception.IllegalArgument); test.exception(() => p.effectiveCastling(elem)).isInstanceOf(exception.IllegalArgument); }); } }); describe('Position setters', () => { function testCastlingEnPassantFEN(position, expectedCastling, expectedEnPassant, expectedFEN) { test.value(dumpCastlingFlags(position, (p, castle) => p.castling(castle))).is(expectedCastling); test.value(position.enPassant()).is(expectedEnPassant); test.value(position.fen()).is(expectedFEN); } it('Scenario 1', () => { const p = new Position('start'); testCastlingEnPassantFEN(p, 'KQkq', '-', 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'); p.square('a8', '-'); testCastlingEnPassantFEN(p, 'KQkq', '-', '1nbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQk - 0 1'); p.square('f6', 'wb'); testCastlingEnPassantFEN(p, 'KQkq', '-', '1nbqkbnr/pppppppp/5B2/8/8/8/PPPPPPPP/RNBQKBNR w KQk - 0 1'); p.turn('w'); testCastlingEnPassantFEN(p, 'KQkq', '-', '1nbqkbnr/pppppppp/5B2/8/8/8/PPPPPPPP/RNBQKBNR w KQk - 0 1'); p.castling('wk', false); testCastlingEnPassantFEN(p, 'Qkq', '-', '1nbqkbnr/pppppppp/5B2/8/8/8/PPPPPPPP/RNBQKBNR w Qk - 0 1'); p.castling('bk', true); testCastlingEnPassantFEN(p, 'Qkq', '-', '1nbqkbnr/pppppppp/5B2/8/8/8/PPPPPPPP/RNBQKBNR w Qk - 0 1'); p.castling('bq', true); testCastlingEnPassantFEN(p, 'Qkq', '-', '1nbqkbnr/pppppppp/5B2/8/8/8/PPPPPPPP/RNBQKBNR w Qk - 0 1'); p.enPassant('e'); testCastlingEnPassantFEN(p, 'Qkq', 'e', '1nbqkbnr/pppppppp/5B2/8/8/8/PPPPPPPP/RNBQKBNR w Qk - 0 1'); p.enPassant('-'); testCastlingEnPassantFEN(p, 'Qkq', '-', '1nbqkbnr/pppppppp/5B2/8/8/8/PPPPPPPP/RNBQKBNR w Qk - 0 1'); }); it('Scenario 2', () => { const p = new Position('empty'); testCastlingEnPassantFEN(p, '-', '-', '8/8/8/8/8/8/8/8 w - - 0 1'); p.square('c3', 'bk'); testCastlingEnPassantFEN(p, '-', '-', '8/8/8/8/8/2k5/8/8 w - - 0 1'); p.square('g5', 'wk'); testCastlingEnPassantFEN(p, '-', '-', '8/8/8/6K1/8/2k5/8/8 w - - 0 1'); p.square('c3', '-'); testCastlingEnPassantFEN(p, '-', '-', '8/8/8/6K1/8/8/8/8 w - - 0 1'); p.turn('b'); testCastlingEnPassantFEN(p, '-', '-', '8/8/8/6K1/8/8/8/8 b - - 0 1'); p.castling('wq', false); testCastlingEnPassantFEN(p, '-', '-', '8/8/8/6K1/8/8/8/8 b - - 0 1'); p.square('e8', 'bk'); p.square('a8', 'br'); testCastlingEnPassantFEN(p, '-', '-', 'r3k3/8/8/6K1/8/8/8/8 b - - 0 1'); p.castling('bq', true); testCastlingEnPassantFEN(p, 'q', '-', 'r3k3/8/8/6K1/8/8/8/8 b q - 0 1'); p.castling('bk', true); testCastlingEnPassantFEN(p, 'kq', '-', 'r3k3/8/8/6K1/8/8/8/8 b q - 0 1'); p.enPassant('a'); testCastlingEnPassantFEN(p, 'kq', 'a', 'r3k3/8/8/6K1/8/8/8/8 b q - 0 1'); p.enPassant('h'); testCastlingEnPassantFEN(p, 'kq', 'h', 'r3k3/8/8/6K1/8/8/8/8 b q - 0 1'); }); it('Scenario 3 (Chess960)', () => { const p = new Position('chess960', 763); testCastlingEnPassantFEN(p, 'AFaf', '-', 'rknnbrqb/pppppppp/8/8/8/8/PPPPPPPP/RKNNBRQB w AFaf - 0 1'); p.castling('wa', false); testCastlingEnPassantFEN(p, 'Faf', '-', 'rknnbrqb/pppppppp/8/8/8/8/PPPPPPPP/RKNNBRQB w Faf - 0 1'); p.castling('wh', true); testCastlingEnPassantFEN(p, 'FHaf', '-', 'rknnbrqb/pppppppp/8/8/8/8/PPPPPPPP/RKNNBRQB w Faf - 0 1'); p.castling('bd', true); testCastlingEnPassantFEN(p, 'FHadf', '-', 'rknnbrqb/pppppppp/8/8/8/8/PPPPPPPP/RKNNBRQB w Faf - 0 1'); p.castling('wh', false); testCastlingEnPassantFEN(p, 'Fadf', '-', 'rknnbrqb/pppppppp/8/8/8/8/PPPPPPPP/RKNNBRQB w Faf - 0 1'); }); for (const elem of [ 'p', 'Q', 'kw' ]) { it('Error for board with colored piece ' + elem, () => { const p = new Position(); test.exception(() => p.square('d4', elem)).isInstanceOf(exception.IllegalArgument); }); } for (const elem of [ '', 'W', 'bb', 'wb' ]) { it('Error for turn with ' + (elem === '' ? '<empty string>' : elem), () => { const p = new Position(); test.exception(() => p.turn(elem)).isInstanceOf(exception.IllegalArgument); }); } for (const elem of [ 0, 1, 'false', 'true' ]) { it('Error for set castling with ' + (elem === '' ? '<empty string>' : elem), () => { const p = new Position(); test.exception(() => p.castling('wk', elem)).isInstanceOf(exception.IllegalArgument); }); } for (const elem of [ '', 'i', 'gg', 'abcdefgh' ]) { it('Error for en-passant with ' + (elem === '' ? '<empty string>' : elem), () => { const p = new Position(); test.exception(() => p.enPassant(elem)).isInstanceOf(exception.IllegalArgument); }); } }); describe('Position equality', () => { function checkIsEqual(p1, p2, expected) { test.value(Position.isEqual(p1, p2)).is(expected); test.value(Position.isEqual(p2, p1)).is(expected); } it('On copy (base)', () => { const p1 = new Position(); const p2 = new Position(p1); checkIsEqual(p1, p2, true); }); it('On copy (FEN)', () => { const p1 = new Position(customFEN); const p2 = new Position(p1); checkIsEqual(p1, p2, true); }); it('On copy (with variant)', () => { const p1 = new Position('horde', customFENHorde); const p2 = new Position(p1); checkIsEqual(p1, p2, true); }); function itOnBoardChange(label, square, firstValue, secondValue) { it('On board changed ' + label, () => { const p1 = new Position(); const p2 = new Position(); p2.square(square, firstValue); checkIsEqual(p1, p2, false); p2.square(square, secondValue); checkIsEqual(p1, p2, true); }); } itOnBoardChange(1, 'b8', '-', 'bn'); itOnBoardChange(2, 'a1', '-', 'wr'); itOnBoardChange(3, 'e4', 'bq', '-'); it('On turn changed', () => { const p1 = new Position(); const p2 = new Position(); p2.turn('b'); checkIsEqual(p1, p2, false); p2.turn('w'); checkIsEqual(p1, p2, true); }); function itOnCastlingChange(label, castle) { it('On castling changed ' + label, () => { const p1 = new Position(); const p2 = new Position(); p2.castling(castle, false); checkIsEqual(p1, p2, false); p2.castling(castle, true); checkIsEqual(p1, p2, true); }); } itOnCastlingChange(1, 'wk'); itOnCastlingChange(2, 'bq'); it('With non-equal but equivalent castling flags', () => { const p1 = new Position('r3k3/pppppppp/8/8/8/8/PPPPPPPP/4K2R w KQkq - 0 1'); const p2 = new Position('r3k3/pppppppp/8/8/8/8/PPPPPPPP/4K2R w Kq - 0 1'); checkIsEqual(p1, p2, true); }); it('On en-passant changed', () => { const p1 = new Position(customFEN); const p2 = new Position(customFEN); p2.enPassant('-'); checkIsEqual(p1, p2, false); p2.enPassant('d'); checkIsEqual(p1, p2, true); }); it('After 2-square pawn move 1', () => { const p1 = new Position(); p1.play('e4'); const p2 = new Position('rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1'); checkIsEqual(p1, p2, true); }); it('After 2-square pawn move 2', () => { const p1 = new Position(); p1.play('e4'); p1.play('d5'); p1.play('d4'); const p2 = new Position(); p2.play('d4'); p2.play('d5'); p2.play('e4'); checkIsEqual(p1, p2, true); }); it('After 2-square pawn move 3', () => { const p1 = new Position(); p1.play('e4'); p1.play('Nc6'); p1.play('e5'); p1.play('f5'); const p2 = new Position(); p2.play('e4'); p2.play('f5'); p2.play('e5'); p2.play('Nc6'); checkIsEqual(p1, p2, false); }); it('With distinct variants', () => { const p1 = new Position('regular', 'empty'); const p2 = new Position('antichess', 'empty'); checkIsEqual(p1, p2, false); }); it('With non-position objects', () => { const pos = new Position(); const obj = {}; checkIsEqual(pos, obj, false); checkIsEqual(obj, obj, false); }); });