UNPKG

kokopu

Version:

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

197 lines (143 loc) 8.89 kB
/*! * -------------------------------------------------------------------------- * * * * Kokopu - A JavaScript/TypeScript chess library. * * <https://www.npmjs.com/package/kokopu> * * Copyright (C) 2018-2026 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, isColor, isPiece, isColoredPiece, isFile, isRank, isSquare, isSquareCouple, isCastle, isCastle960, isGameResult, isGameVariant, squareColor, squareToCoordinates, coordinatesToSquare, oppositeColor, variantWithCanonicalStartPosition, nagSymbol, isValidECO } = require('../dist/lib/index'); const readText = require('./common/readtext'); const assert = require('node:assert/strict'); function itIsType(func, validCases, invalidCases) { for (const validCase of validCases) { it(validCase, () => { assert.deepEqual(func(validCase), true); }); } for (const invalidCase of invalidCases) { const label = `Not ${invalidCase === '' ? '<empty string>' : typeof invalidCase === 'number' ? '<number>' : String(invalidCase)}`; it(label, () => { assert.deepEqual(func(invalidCase), false); }); } } describe('Is color', () => { itIsType(isColor, [ 'w', 'b' ], [ '', 42, 'x', 'W' ]); }); describe('Is piece', () => { itIsType(isPiece, [ 'k', 'r', 'p' ], [ '', 42, 'a', 'R' ]); }); describe('Is colored piece', () => { itIsType(isColoredPiece, [ 'wk', 'bn', 'wb', 'bp' ], [ '', 42, 'w', 'r', 'BP' ]); }); describe('Is file', () => { itIsType(isFile, [ 'a', 'b', 'd', 'h' ], [ '', 2, 'i', 'r', 'D', '1' ]); }); describe('Is rank', () => { itIsType(isRank, [ '1', '2', '8' ], [ '', 2, '0', '9', 'a' ]); }); describe('Is square', () => { itIsType(isSquare, [ 'a1', 'b7', 'e6', 'f3', 'h8' ], [ '', 42, 'a', '3', 'E6' ]); }); describe('Is square couple', () => { itIsType(isSquareCouple, [ 'a1b2', 'g6d3', 'd7d7', 'g8h7' ], [ '', 42, 'b2', 'G6D3' ]); }); describe('Is castle', () => { itIsType(isCastle, [ 'wk', 'wq', 'bk', 'bq' ], [ '', 42, 'w', 'q', 'BK', 'wa' ]); }); describe('Is castle 960', () => { itIsType(isCastle960, [ 'wa', 'wc', 'bg', 'bh' ], [ '', 42, 'w', 'b', 'BG', 'wk', 'bi' ]); }); describe('Is game result', () => { itIsType(isGameResult, [ '1-0', '1/2-1/2', '0-1', '*' ], [ '', 42, '1--0', '0-1 ', ' *', '1-O' ]); }); describe('Is game variant', () => { itIsType(isGameVariant, [ 'regular', 'chess960', 'antichess', 'horde' ], [ '', 42, 'Regular', 'fischerandom' ]); }); describe('Square color', () => { it('a1', () => { assert.deepEqual(squareColor('a1'), 'b'); }); it('h1', () => { assert.deepEqual(squareColor('h1'), 'w'); }); it('a8', () => { assert.deepEqual(squareColor('a8'), 'w'); }); it('h8', () => { assert.deepEqual(squareColor('h8'), 'b'); }); it('b3', () => { assert.deepEqual(squareColor('b3'), 'w'); }); it('b4', () => { assert.deepEqual(squareColor('b4'), 'b'); }); it('c4', () => { assert.deepEqual(squareColor('c4'), 'w'); }); it('f5', () => { assert.deepEqual(squareColor('f5'), 'w'); }); it('e5', () => { assert.deepEqual(squareColor('e5'), 'b'); }); it('e6', () => { assert.deepEqual(squareColor('e6'), 'w'); }); it('Error with 7', () => { assert.throws(() => squareColor('7'), exception.IllegalArgument); }); it('Error with b8g', () => { assert.throws(() => squareColor('b8g'), exception.IllegalArgument); }); }); describe('Square to coordinates', () => { it('a1', () => { assert.deepEqual(squareToCoordinates('a1'), { file: 0, rank: 0 }); }); it('h1', () => { assert.deepEqual(squareToCoordinates('h1'), { file: 7, rank: 0 }); }); it('a8', () => { assert.deepEqual(squareToCoordinates('a8'), { file: 0, rank: 7 }); }); it('h8', () => { assert.deepEqual(squareToCoordinates('h8'), { file: 7, rank: 7 }); }); it('e3', () => { assert.deepEqual(squareToCoordinates('e3'), { file: 4, rank: 2 }); }); it('Error with <empty string>', () => { assert.throws(() => squareToCoordinates(''), exception.IllegalArgument); }); it('Error with cc', () => { assert.throws(() => squareToCoordinates('cc'), exception.IllegalArgument); }); }); describe('Coordinates to square', () => { function itCoordinatesToSquare(square, file, rank) { it(square, () => { assert.deepEqual(coordinatesToSquare(file, rank), square); assert.deepEqual(coordinatesToSquare({ file: file, rank: rank }), square); }); } itCoordinatesToSquare('a1', 0, 0); itCoordinatesToSquare('h1', 7, 0); itCoordinatesToSquare('a8', 0, 7); itCoordinatesToSquare('h8', 7, 7); itCoordinatesToSquare('e3', 4, 2); it('Error with (-1,4)', () => { assert.throws(() => coordinatesToSquare(-1, 4), exception.IllegalArgument); }); it('Error with (8,3)', () => { assert.throws(() => coordinatesToSquare(8, 3), exception.IllegalArgument); }); it('Error with (5,-1)', () => { assert.throws(() => coordinatesToSquare(5, -1), exception.IllegalArgument); }); it('Error with (5,8)', () => { assert.throws(() => coordinatesToSquare(7, 8), exception.IllegalArgument); }); }); describe('Opposite color', () => { it('white to black', () => { assert.deepEqual(oppositeColor('w'), 'b'); }); it('black to white', () => { assert.deepEqual(oppositeColor('b'), 'w'); }); it('Error with z', () => { assert.throws(() => oppositeColor('z'), exception.IllegalArgument); }); it('Error with bb', () => { assert.throws(() => oppositeColor('bb'), exception.IllegalArgument); }); }); describe('Variant with canonical start position', () => { it('no-king', () => { assert.deepEqual(variantWithCanonicalStartPosition('no-king'), false); }); it('chess960', () => { assert.deepEqual(variantWithCanonicalStartPosition('chess960'), false); }); it('antichess', () => { assert.deepEqual(variantWithCanonicalStartPosition('antichess'), true); }); it('Error with invalid variant', () => { assert.throws(() => variantWithCanonicalStartPosition('whatever'), exception.IllegalArgument); }); }); describe('NAG symbols', () => { const nags = [ 3, 1, 5, 6, 2, 4, 20, 18, 16, 14, 10, 11, 13, 15, 17, 19, 21, 7, 8, 9, 22, 32, 36, 40, 44, 132, 138, 140, 141, 142, 143, 145, 146 ]; const expectedSymbols = readText('nags.txt').trim().split(/\s+/); function itNagWithSymbol(i) { const nag = nags[i]; it('NAG ' + nag, () => { assert.deepEqual(nagSymbol(nag), expectedSymbols[i]); }); } for (let i = 0; i < nags.length; ++i) { itNagWithSymbol(i); } it('NAG without symbol', () => { assert.deepEqual(nagSymbol(99), '$99'); }); it('Error with string', () => { assert.throws(() => nagSymbol('1'), exception.IllegalArgument); }); it('Error with null', () => { assert.throws(() => nagSymbol(null), exception.IllegalArgument); }); it('Error with non-integer', () => { assert.throws(() => nagSymbol(3.2), exception.IllegalArgument); }); it('Error with negative integer', () => { assert.throws(() => nagSymbol(-1), exception.IllegalArgument); }); }); describe('Is valid ECO', () => { it('Valid ECO 1', () => { assert.deepEqual(isValidECO('A00'), true); }); it('Valid ECO 2', () => { assert.deepEqual(isValidECO('E99'), true); }); it('Invalid ECO 1', () => { assert.deepEqual(isValidECO('F00'), false); }); it('Invalid ECO 2', () => { assert.deepEqual(isValidECO('A00b'), false); }); it('Invalid ECO (number)', () => { assert.deepEqual(isValidECO(42), false); }); });