UNPKG

ai-for-shogi-like-games

Version:

javascript library of A.I. algorithms for Shogi-like games

1,078 lines (1,007 loc) 43.4 kB
'use strict'; import 'babel-polyfill'; const assert = require('assert'); import _ from 'lodash'; import {Point, Vector} from 'geometry-2d'; import {Piece, PieceSet, GameBoard} from '../src/board-lib.js'; import {Chick, Hen, Elephant, Giraffe, Lion} from '../src/piece-set.js'; import {createPieceSet} from '../src/piece-set-factory.js'; import {PieceOnSide} from '../src/piece.js'; import {CaptureBag} from '../src/captureBag.js'; import {model000} from '../src/eval-model-library.js'; import {boardA, boardB, boardTwoKings, boardWithSideAKingCaptured, boardWithSideBKingCaptured, board3x3_withPromotionZone} from './common-test-boards.js'; describe('Number.prototype.between', function() { it('should work', function() { assert( Number(3).between(3, 4)); assert(!Number(3).between(3, 3)); assert( Number(4).between(3, 30)); assert(!Number(1).between(3, 30)); }); }); describe('Point', function () { describe('constructor', function () { it('should work', function () { const p = new Point(1,3); const expected = new Point(1,3); assert(_.isEqual(p, expected)); assert(p.equals(expected)); assert(expected.equals(p)); }); }); describe('reflectionInGrid', function() { const sixPacks = [ [0,0,1,1,0,0], [0,0,1,2,0,1], [0,0,1,3,0,2], [1,0,2,1,0,0] , [1,0,2,2,0,1],[1,0,2,3,0,2], [2,3,3,4,0,0], [1,4,2,5,0,0] , [1,4,2,6,0,1],[1,4,5,6,3,1], [1,4,6,6,4,1] ]; it('should work', function() { for (let [a,b,c,d,e,f,g] of sixPacks) { const p = new Point(a,b); const p2 = p.reflectionInGrid(c,d); assert(p2.equals(new Point(e,f))); } }); }); }); describe('Piece', function() { if (false) // new.target not yet supported by Flow and Babel it('is not directly instantiatable', function() { assert.throws( ()=>{ new Piece('foo', true); }); }); }); describe('PieceSet', function () { describe('fromCode', function () { it('should work' , function () { const pieceSet = createPieceSet([Chick, Hen, Lion]); assert.equal(pieceSet.fromCode('c'), Chick); assert.equal(pieceSet.fromCode('h'), Hen); }); }); }); describe('GameBoard', function () { describe('constructor', function () { it('should work on the absolutely minimal grid possible' , function () { const pieceSet = createPieceSet([Lion]); const ss = ['l@0~1 * l@0~0', 'l@0~0 * l@0~1']; for (let s of ss) { const gb = GameBoard.create(1, 2, true, 0, pieceSet, s); } }); it('should fail as expected on the absolutely minimal grid possible' , function () { const pieceSet = createPieceSet([Hen, Lion]); const ss = ['l@1~1 * l@0~0', 'l@1~0 * l@0~1', 'l@1~0 * h@0~1', 'h@1~0 * l@0~1', 'h@1~0 * l@1~0', 'l@0~0*l@0~0', 'l@0~1*l@0~1']; for (let s of ss) { assert.throws( () => { GameBoard.create(1, 2, true, 0, pieceSet, s); }); } }); it('should work on a 2x2 gril' , function () { const ss = ['h@1~1, l@0~0 * h@0~1, l@1~0', 'l@1~1, c@0~0, c@1~0 * l@0~1' ]; const pieceSet = createPieceSet([Chick, Hen, Lion]); for (let s of ss) { GameBoard.create(2, 2, true, 0, pieceSet, s); } }); }); describe('toString', function () { it('should work on the absolutely minimal grid possible' , function () { const pieceSet = createPieceSet([Lion]); const gb = GameBoard.create(1, 2, true, 0, pieceSet, 'l@0~0 * l@0~1'); assert.equal(gb.toString(), '[L@0~0, l@0~1] * []'); }); }); describe('toStringFancy', function () { it('should work on the absolutely minimal grid possible' , function () { const pieceSet = createPieceSet([Lion]); const gb = GameBoard.create(1, 2, true, 0, pieceSet, 'l@0~1 * l@0~0'); assert.equal(gb.toStringFancy(), `l L -- `.trim()); }); it('should work on a 2x2 grid' , function () { const pieceSet = createPieceSet([Hen, Chick, Lion]); const gb = GameBoard.create(2, 2, true, 0, pieceSet, 'l@0~1, c@1~1, h@1~0 * l@0~0'); assert.equal(gb.toStringFancy(), `lH LC -- `.trim()); }); }); describe('kingIsCaptured', function() { it('should work in the negative case', function() { const boards = [boardA(), boardB(), boardTwoKings()]; for (let board of boards) { assert(!board.kingIsCaptured(true)); assert(!board.kingIsCaptured(false)); } }); it('should work for captured king on side A', function() { const boards = [boardWithSideAKingCaptured()]; for (let board of boards) { assert( board.kingIsCaptured(true)); assert(!board.kingIsCaptured(false)); } }); it('should work for captured king on side B', function() { const boards = [boardWithSideBKingCaptured()]; for (let board of boards) { assert(!board.kingIsCaptured(true)); assert( board.kingIsCaptured(false)); } }); }); describe('boardImmediateWinSide', function() { it ('should work in the null case', function() { const boards = [boardA(), boardTwoKings()]; for (let board of boards) { assert(board.boardImmediateWinSide()===null); } }); it ('should correctly throw an exception if both kings are on the last line unchecked', function() { assert.throws( ()=> { const gb = boardB(); gb.boardImmediateWinSide(); }, /^Error: GB:BKULL/); }); it('should work for captured kings', function() { const tests = [{board: boardWithSideAKingCaptured(), sideAWinning: false}, {board: boardWithSideBKingCaptured(), sideAWinning: true}]; for (let test of tests) { assert(test.board.boardImmediateWinSide()===test.sideAWinning); } }); }); describe('capturing', function () { it('should work on a 2x2 grid' , function () { const pieceSet = createPieceSet([Hen, Chick, Lion]); const gb1 = GameBoard.create(2, 2, true, 0, pieceSet, 'l@0~1, c@1~1, h@1~0 * l@0~0'); const STATE_1 = `lH LC -- `.trim(); assert.equal(gb1.toStringFancy(), STATE_1); let gb2 = gb1.clone(); assert(gb2.equals(gb1)); assert(gb1.equals(gb2)); assert.equal(gb2.toStringFancy(), STATE_1); gb2 = gb1.move(new Point(0, 0), new Point(1, 0)); assert(!gb2.equals(gb1)); assert(!gb1.equals(gb2)); const STATE_2 = `.l LC -- c`; assert.equal(gb2.toStringFancy(), STATE_2); const gb3 = gb2.move(new Point(0, 1), new Point(0, 0)); const STATE_3 = `Ll .C -- c`; assert.equal(gb3.toStringFancy(), STATE_3); const gb4 = gb3.move(new Point(0, 0), new Point(1, 0)); const STATE_4 = `.L .C -- Lc`; assert.equal(gb4.toStringFancy(), STATE_4); assert.equal(gb3.toStringFancy(), STATE_3); assert.equal(gb2.toStringFancy(), STATE_2); assert.equal(gb1.toStringFancy(), STATE_1); }); }); describe('moving with promotion', function () { it('should work' , function () { const pieceSet = createPieceSet([Hen, Chick, Lion]); const gb1 = GameBoard.create(3, 3, true, 1, pieceSet, 'l@1~0, c@0~1 * l@0~0, c@2~1'); const STATE_1 = ` lL. C.c ... --`.trim(); assert.equal(gb1.toStringFancy().trim(), STATE_1); let gb2 = gb1.move(new Point(0, 1), new Point(0, 0)); const STATE_2 = ` HL. ..c ... -- L`.trim(); assert.equal(gb2.toStringFancy().trim(), STATE_2); let gb3 = gb2.move(new Point(2, 1), new Point(2, 2)); const STATE_3 = ` HL. ... ..h -- L`.trim(); assert.equal(gb3.toStringFancy().trim(), STATE_3); }); }); describe('dropping', function () { it('should work or fail in expected ways on a 2x2 grid' , function () { const pieceSet = createPieceSet([Hen, Chick, Lion]); const gb1 = GameBoard.create(2, 2, true, 0, pieceSet, 'l@0~1, c@1~1, h@1~0 * l@0~0'); const STATE_1 = `lH LC -- `.trim(); assert.equal(gb1.toStringFancy(), STATE_1); const gb2 = gb1.move(new Point(0, 0), new Point(1, 0)); const STATE_2 = `.l LC -- c`; assert.equal(gb2.toStringFancy(), STATE_2); assert.throws( ()=> { gb2.drop(PieceOnSide(Chick, true), new Point(0, 0)); }); assert.throws( ()=> { gb2.drop(PieceOnSide(Hen, false), new Point(0, 0)); }); const pos = new PieceOnSide(Chick, false); // piece on side assert.equal(null, gb2.drop(pos, new Point(0, 1))); assert.equal(null, gb2.drop(pos, new Point(1, 0))); assert.equal(null, gb2.drop(pos, new Point(1, 1))); const gb3 = gb2.drop(pos, new Point(0, 0)); const STATE_3 = `cl LC -- `.trim(); assert.equal(gb3.toStringFancy(), STATE_3); }); it('no drops allowed in promotion zone', function() { const board = board3x3_withPromotionZone(); const forbiddenDrops = [ [new PieceOnSide(Chick , true), new Point(0,0)], [new PieceOnSide(Chick , true), new Point(1,0)], [new PieceOnSide(Elephant, true), new Point(0,0)], [new PieceOnSide(Elephant, true), new Point(1,0)], [new PieceOnSide(Chick , false), new Point(0,2)], [new PieceOnSide(Chick , false), new Point(1,2)], [new PieceOnSide(Elephant, false), new Point(0,2)], [new PieceOnSide(Elephant, false), new Point(1,2)] ]; forbiddenDrops.forEach( ([piece, point])=>{ let nextBoard = board.drop( piece, point ); assert.equal(nextBoard, null); }); }); }); describe('reflection', function () { it('should work' , function () { const pieceSet = createPieceSet([Hen, Chick, Lion]); const gb1 = GameBoard.create(2, 2, true, 0, pieceSet, 'l@0~1, c@1~1, h@1~0 * l@0~0'); const STATE_1 = `lH LC -- `.trim(); assert.equal(gb1.toStringFancy(), STATE_1); const gb2 = gb1.reflection(); const STATE_2 = `cl hL -- `.trim(); assert.equal(gb2.toStringFancy(), STATE_2); const gb3 = gb2.move(new Point(1,1), new Point(0,0)); const STATE_3 = `Ll h. -- C`; assert.equal(gb3.toStringFancy(), STATE_3); const gb4 = gb3.reflection(); const STATE_4 = `.H Ll -- c`; assert.equal(gb4.toStringFancy(), STATE_4); }); }); describe('nextStatesByMovingPieceOnAParticularSquare', function() { describe('invocation #1', function() { const pieceSet = createPieceSet([Lion]); const gb = GameBoard.create(2, 2, true, 0, pieceSet, 'l@0~1 * l@0~0'); assert.equal(gb.toStringFancy(), `l. L. -- `.trim()); it('should throw on bad arguments - no piece', function () { assert.throws(()=>{gb.nextStatesByMovingPieceOnAParticularSquare(new Point(1,0));}, /^AssertionError: B-NSBMP-CNP/); assert.throws(()=>{gb.nextStatesByMovingPieceOnAParticularSquare(new Point(1,1));}, /^AssertionError: B-NSBMP-CNP/); }); }); describe('invocation #2', function() { const pieceSet = createPieceSet([Hen, Lion]); const gb = GameBoard.create(2, 2, true, 0, pieceSet, 'l@0~1, h@1~1 * h@0~0, l@1~0'); assert.equal(gb.toStringFancy(), `hl LH -- `.trim()); it('should move correctly #1', function() { const newStates = gb.nextStatesByMovingPieceOnAParticularSquare(new Point(0,1)); const EXPECTED = `Ll I hL .H I .H -- I -- C I L`; assert.equal(GameBoard.toStringFancyBoardsOnly(newStates), EXPECTED); }); it('should move correctly #2', function() { const newStates = gb.nextStatesByMovingPieceOnAParticularSquare(new Point(1,0)); const EXPECTED = `h. I h. Ll I lH -- I -- c I l`; assert.equal(GameBoard.toStringFancyBoardsOnly(newStates), EXPECTED); }); }); }); describe('nextStates - without drops', function() { describe('invocation #1', function() { const pieceSet = createPieceSet([Lion]); const gb = GameBoard.create(2, 2, true, 0, pieceSet, 'l@0~1 * l@0~0'); assert.equal(gb.toStringFancy(), `l. L. -- `.trim()); it('should move correctly sideA', function() { const newStates = gb.nextStatesByMovingPieceOnAParticularSquare(new Point(0,1)); const newStates2 = gb.nextStates(true); // should be the same in this particular case const EXPECTED = `L. I lL I l. .. I .. I .L -- I -- I -- L I I `; assert.equal(GameBoard.toStringFancyBoardsOnly(newStates ), EXPECTED); assert.equal(GameBoard.toStringFancyBoardsOnly(newStates2), EXPECTED); const EXPECTED_MOVES = '[(0~1)=>(0~0), (0~1)=>(1~0), (0~1)=>(1~1)]'; assert.equal(GameBoard.toStringFancyMovesOnly(newStates ), EXPECTED_MOVES); assert.equal(GameBoard.toStringFancyMovesOnly(newStates2), EXPECTED_MOVES); }); it('should move correctly sideB', function() { const newStates = gb.nextStatesByMovingPieceOnAParticularSquare(new Point(0,0)); const newStates2 = gb.nextStates(false); const EXPECTED = `.. I .l I .. l. I L. I Ll -- I -- I -- l I I `; assert.equal(GameBoard.toStringFancyBoardsOnly(newStates ), EXPECTED); assert.equal(GameBoard.toStringFancyBoardsOnly(newStates2), EXPECTED); const EXPECTED_MOVES = '[(0~0)=>(0~1), (0~0)=>(1~0), (0~0)=>(1~1)]'; assert.equal(GameBoard.toStringFancyMovesOnly(newStates ), EXPECTED_MOVES); assert.equal(GameBoard.toStringFancyMovesOnly(newStates2), EXPECTED_MOVES); }); }); describe('invocation #2', function() { const pieceSet = createPieceSet([Hen, Lion]); const gb = GameBoard.create(2, 2, true, 0, pieceSet, 'l@0~1, h@1~1 * h@0~0, l@1~0'); assert.equal(gb.toStringFancy(), `hl LH -- `.trim()); it('should move correctly', function() { const newStates = gb.nextStates(true); const EXPECTED = `Ll I hL I hH I Hl .H I .H I L. I L. -- I -- I -- I -- C I L I L I C`; assert.equal(GameBoard.toStringFancyBoardsOnly(newStates), EXPECTED); }); }); }); describe('nextStates - *with* drops', function() { describe('invocation', function() { const pieceSet = createPieceSet([Chick, Hen, Elephant, Lion]); it('should work with only single capture per side - sideA', function() { const cb = new CaptureBag(); cb.capture(new PieceOnSide(Chick, true)); cb.capture(new PieceOnSide(Chick, false)); const gb = GameBoard.create(2, 2, true, 0, pieceSet, 'l@1~0 * l@0~1', cb); assert.equal(gb.toStringFancy(), `.L l. -- Cc`); const newStates = gb.nextStates(true); const EXPECTED = `.. I .. I L. I CL I .L lL I L. I l. I l. I lC -- I -- I -- I -- I -- Cc I CLc I Cc I c I c`; assert.equal(GameBoard.toStringFancyBoardsOnly(newStates), EXPECTED); const EXPECTED_MOVES= '[(1~0)=>(1~1), (1~0)=>(0~1), (1~0)=>(0~0), C=>0~0, C=>1~1]'; assert.equal(GameBoard.toStringFancyMovesOnly(newStates), EXPECTED_MOVES); }); it('should work with only single capture per side - sideB', function() { const cb = new CaptureBag(); cb.capture(new PieceOnSide(Chick, true)); cb.capture(new PieceOnSide(Chick, false)); const gb = GameBoard.create(2, 2, true, 0, pieceSet, 'l@1~0 * l@0~1', cb); assert.equal(gb.toStringFancy(), `.L l. -- Cc`); const newStates = gb.nextStates(false); const EXPECTED = ` lL I .l I .L I cL I .L .. I .. I .l I l. I lc -- I -- I -- I -- I -- Cc I Ccl I Cc I C I C`.trim(); assert.equal(GameBoard.toStringFancyBoardsOnly(newStates), EXPECTED); const EXPECTED_MOVES= '[(0~1)=>(0~0), (0~1)=>(1~0), (0~1)=>(1~1), c=>0~0, c=>1~1]'; assert.equal(GameBoard.toStringFancyMovesOnly(newStates), EXPECTED_MOVES); }); it('should work with only two captures per side -side A', function() { const cb = new CaptureBag(); cb.capture(new PieceOnSide(Chick, false)); cb.capture(new PieceOnSide(Elephant, false)); const gb = GameBoard.create(2, 2, true, 0, pieceSet, 'l@1~0 * l@0~1', cb); assert.equal(gb.toStringFancy(), `.L l. -- CE`); const newStates = gb.nextStates(true); const EXPECTED = `.. I .. I L. I CL I .L I EL I .L lL I L. I l. I l. I lC I l. I lE -- I -- I -- I -- I -- I -- I -- CE I CEL I CE I E I E I C I C`; assert.equal(GameBoard.toStringFancyBoardsOnly(newStates), EXPECTED); const EXPECTED_MOVES = '[(1~0)=>(1~1), (1~0)=>(0~1), (1~0)=>(0~0), C=>0~0, C=>1~1, E=>0~0, E=>1~1]'; assert.equal(GameBoard.toStringFancyMovesOnly(newStates), EXPECTED_MOVES); }); it('should work with only two captures per side -side B', function() { const cb = new CaptureBag(); cb.capture(new PieceOnSide(Chick, true)); cb.capture(new PieceOnSide(Elephant, true)); const gb = GameBoard.create(2, 2, true, 0, pieceSet, 'l@1~0 * l@0~1', cb); assert.equal(gb.toStringFancy(), `.L l. -- ce`); const newStates = gb.nextStates(false); const EXPECTED = ` lL I .l I .L I cL I .L I eL I .L .. I .. I .l I l. I lc I l. I le -- I -- I -- I -- I -- I -- I -- ce I cel I ce I e I e I c I c`.trim(); assert.equal(GameBoard.toStringFancyBoardsOnly(newStates), EXPECTED); const EXPECTED_MOVES = '[(0~1)=>(0~0), (0~1)=>(1~0), (0~1)=>(1~1), c=>0~0, c=>1~1, e=>0~0, e=>1~1]'; assert.equal(GameBoard.toStringFancyMovesOnly(newStates), EXPECTED_MOVES); }); }); }); describe('isPromotionTriggered', function () { describe('setting #1', function () { const pieceSet = createPieceSet([Chick, Hen, Lion]); const notation = 'c@0~0, l@1~0 * l@0~1, c@1~1'; it('should work #1', function() { const gb = GameBoard.create(2, 2, true, 0, pieceSet, notation); assert.equal(gb.toStringFancy(), `CL lc -- `.trim()); const allPoints = [new Point(0,0), new Point(0,1), new Point(1,0), new Point(1,1)]; for (let i = 0 ; i < allPoints.length ; i++) { for (let j = 0 ; j < allPoints.length ; j++) { const p1: Point = allPoints[i]; const p2: Point = allPoints[j]; if (!p1.equals(p2)) assert(!gb.isPromotionTriggered(p1, p2)); } } }); it('should work #2', function() { const gb = GameBoard.create(2, 2, true, 1, pieceSet, notation); assert.equal(gb.toStringFancy(), `CL lc -- `.trim()); const allPoints = [new Point(0,0), new Point(0,1), new Point(1,0), new Point(1,1)]; for (let i = 0 ; i < allPoints.length ; i++) { for (let j = 0 ; j < allPoints.length ; j++) { const p1: Point = allPoints[i]; const p2: Point = allPoints[j]; if (!p1.equals(p2)) assert(gb.isPromotionTriggered(p1, p2)); } } }); }); describe('setting #2', function () { const pieceSet = createPieceSet([Chick, Hen, Lion]); const notation = 'c@0~2, l@1~2 * c@0~0, l@1~0'; it('should work #1', function() { const gb = GameBoard.create(2, 3, true, 1, pieceSet, notation); assert.equal(gb.toStringFancy(), `cl .. CL -- `.trim()); const promotionVectors=[new Vector(new Point(0,0), new Point(0,2)), new Vector(new Point(0,0), new Point(1,2)), new Vector(new Point(1,2), new Point(0,0)), new Vector(new Point(1,2), new Point(1,0)) ]; promotionVectors.forEach( (v) => { assert(gb.isPromotionTriggered(v.from, v.to)); }); const nonPromotionVectors=[new Vector(new Point(0,0), new Point(1,0)), new Vector(new Point(0,0), new Point(0,1)), new Vector(new Point(0,0), new Point(1,1)), new Vector(new Point(1,2), new Point(1,1)), new Vector(new Point(1,2), new Point(0,1)) ]; nonPromotionVectors.forEach( (v) => { assert(!gb.isPromotionTriggered(v.from, v.to)); }); }); }); }); describe('isPieceUnderAttack', function () { describe('setting #1', function () { const pieceSet = createPieceSet([Chick, Hen, Lion]); const notation = 'c@0~0, l@1~0 * l@0~1, c@1~1'; it('should work', function() { const gb = GameBoard.create(2, 2, true, 0, pieceSet, notation); assert.equal(gb.toStringFancy(), `CL lc -- `.trim()); const piecesUnderAttack = [new Point(0,0), new Point(0,1), new Point(1,1)]; piecesUnderAttack.forEach( p => { assert(gb.isPieceUnderAttack(p)); }); const pieceNotUnderAttack = [new Point(1,0)]; pieceNotUnderAttack.forEach( p => { assert(gb.isPieceUnderAttack(p)); }); }); }); describe('setting #2', function () { const pieceSet = createPieceSet([Chick, Hen, Elephant, Lion]); const notation = 'h@0~0, l@1~0, e@1~1 * e@0~1, l@0~2, c@1~2'; it('should work', function() { const gb = GameBoard.create(3, 3, true, 0, pieceSet, notation); assert.equal(gb.toStringFancy(), `HL. eE. lc. -- `.trim()); const piecesUnderAttack = [new Point(1,0), new Point(0,1), new Point(1,1), new Point(0,2)]; const piecesNotUnderAttack = [new Point(0,0), new Point(1,2)]; piecesUnderAttack.forEach( p => { assert(gb.isPieceUnderAttack(p)); }); piecesNotUnderAttack.forEach( p => { assert(!gb.isPieceUnderAttack(p)); }); }); }); describe('setting #3', function () { const pieceSet = createPieceSet([Chick, Hen, Elephant, Lion, Giraffe]); const notation = 'h@1~1, g@1~4, l@4~3 * c@0~0, h@0~2, h@2~2, e@0~1, e@0~4, e@1~0, e@1~2, e@1~3, c@2~0, e@2~1, e@2~4, l@4~2'; it('should work', function() { const gb = GameBoard.create(5, 5, true, 0, pieceSet, notation); assert.equal(gb.toStringFancy().trim(), ` cec.. eHe.. heh.l .e..L eGe.. -- `.trim()); const piecesUnderAttack = [new Point(0,0), new Point(0,1), new Point(0,4), new Point(1,0), new Point(1,2), new Point(1,3), new Point(4, 2), new Point(4,3)]; const piecesNotUnderAttack = [new Point(1,1), new Point(1,4), new Point(0,2), new Point(2,2)]; piecesUnderAttack.forEach( p => { assert(gb.isPieceUnderAttack(p)); }); piecesNotUnderAttack.forEach( p => { assert(!gb.isPieceUnderAttack(p)); }); }); }); describe('setting #4', function () { const pieceSet = createPieceSet([Elephant, Lion, Giraffe]); const notation = 'e@1~1, e@1~3, e@1~5, g@5~1, g@5~3, g@5~5, l@4~5 * e@1~0, e@1~2, e@1~4, l@0~5, g@4~0, g@4~2, g@4~4'; it('should work', function() { const gb = GameBoard.create(6, 6, true, 0, pieceSet, notation); assert.equal(gb.toStringFancy().trim(), ` .e..g. .E...G .e..g. .E...G .e..g. lE..LG -- `.trim()); const piecesUnderAttack = [new Point(1, 5), new Point(4, 4), new Point(4,5)]; const piecesNotUnderAttack = [new Point(1,0), new Point(1,1), new Point(1,2), new Point(1,3), new Point(1,4), new Point(4, 0), new Point(4,2), new Point(5,1), new Point(5,3), new Point(5,5), new Point(0,5)]; piecesUnderAttack.forEach( p => { assert(gb.isPieceUnderAttack(p)); }); piecesNotUnderAttack.forEach( p => { assert(!gb.isPieceUnderAttack(p)); }); }); }); }); describe('numOfPossibleMoves', function () { describe('setting #1', function () { const pieceSet = createPieceSet([Chick, Hen, Lion]); const notation = 'c@0~0, l@1~0 * l@0~1, c@1~1'; it('should work', function() { const gb = GameBoard.create(2, 2, true, 0, pieceSet, notation); assert.equal(gb.toStringFancy(), `CL lc -- `.trim()); assert.equal(gb.numOfPossibleMoves(true), 2); assert.equal(gb.numOfPossibleMoves(false), 2); }); }); describe('setting #2', function () { const pieceSet = createPieceSet([Chick, Hen, Lion]); const notation = 'c@0~0, l@1~0, h@1~2 * l@0~1, c@1~1'; it('should work', function() { const gb = GameBoard.create(3, 3, true, 0, pieceSet, notation); assert.equal(gb.toStringFancy().trim(), ` CL. lc. .H. -- `.trim()); assert.equal(gb.numOfPossibleMoves(true), 0+5+4); assert.equal(gb.numOfPossibleMoves(false), 1+4); }); }); describe('setting #2', function () { it('should work', function() { const gb = boardA(); const drops = 9; assert.equal(gb.numOfPossibleMoves(true ), 0+4+2+6+drops); assert.equal(gb.numOfPossibleMoves(false), 1+4+3+drops); }); }); }); describe('distanceFromPromotionZone', function () { it('fails as expected', function() { const gb = boardA(); const badPoints = [new Point(2,0), new Point(3,0), new Point(2,1), new Point(0,2), new Point(2,2), new Point(0,3), new Point(1,3), new Point(2,3), new Point(3,3)]; badPoints.forEach (p => { assert.throws(()=>{gb.distanceFromPromotionZone(p);}, /^AssertionError: B-NPEOP/); }); }); it('succeeds as expected, board with no promotion zone', function() { const gb = boardA(); const points2Distance = (function() { const rv = new Map(); const pointDistances = [ [new Point(0, 0), Number.POSITIVE_INFINITY], [new Point(1, 0), Number.POSITIVE_INFINITY], [new Point(0, 1), Number.POSITIVE_INFINITY], [new Point(1, 1), Number.POSITIVE_INFINITY], [new Point(3, 1), Number.POSITIVE_INFINITY], [new Point(1, 2), Number.POSITIVE_INFINITY], [new Point(3, 2), Number.POSITIVE_INFINITY] ]; pointDistances.forEach ( ([a,b]) => { rv.set(a.toString(), b); } ); return rv; })(); points2Distance.forEach( (v, k) => { assert.equal(gb.distanceFromPromotionZone(Point.fromString(k)), v); } ); }); it('succeeds as expected, board with promotion zone of thickness 1', function() { const gb = boardA(); gb.breadthOfPromotionZone = 1; const points2Distance = (function() { const rv = new Map(); const pointDistances = [ [new Point(0, 0), 0], [new Point(1, 0), 0], [new Point(0, 1), 2], [new Point(1, 1), 2], [new Point(3, 1), 1], [new Point(1, 2), 2], [new Point(3, 2), 1] ]; pointDistances.forEach ( ([a,b]) => { rv.set(a.toString(), b); } ); return rv; })(); points2Distance.forEach( (v, k) => { assert.equal(gb.distanceFromPromotionZone(Point.fromString(k)), v); } ); }); it('succeeds as expected, board with promotion zone of thickness 2', function() { const gb = boardA(); gb.breadthOfPromotionZone = 2; const points2Distance = (function() { const rv = new Map(); const pointDistances = [ [new Point(0, 0), 0], [new Point(1, 0), 0], [new Point(0, 1), 1], [new Point(1, 1), 1], [new Point(3, 1), 0], [new Point(1, 2), 1], [new Point(3, 2), 0] ]; pointDistances.forEach ( ([a,b]) => { rv.set(a.toString(), b); } ); return rv; })(); points2Distance.forEach( (v, k) => { assert.equal(gb.distanceFromPromotionZone(Point.fromString(k)), v); } ); }); it('succeeds as expected, board with promotion zone of thickness 3 and 4', function() { const gb = boardA(); const thicknesses = [3,4]; for (let thickness of thicknesses) { gb.breadthOfPromotionZone = thickness; const points2Distance = (function() { const rv = new Map(); const pointDistances = [ [new Point(0, 0), 0], [new Point(1, 0), 0], [new Point(0, 1), 0], [new Point(1, 1), 0], [new Point(3, 1), 0], [new Point(1, 2), 0], [new Point(3, 2), 0] ]; pointDistances.forEach ( ([a,b]) => { rv.set(a.toString(), b); } ); return rv; })(); points2Distance.forEach( (v, k) => { assert.equal(gb.distanceFromPromotionZone(Point.fromString(k)), v); } ); } }); }); describe('sideAPieces', function () { const gb = boardA(); describe('onBoard', function() { it('succeeds as expected', function() { assert.equal(gb.sideAPiecesOnBoard().map(([pc, pn])=>pc.code).join(''), 'clhe'); }); }); describe('offBoard', function() { it('succeeds as expected', function() { assert.equal(gb.sideAPiecesOffBoard().map(pc=>pc.code).join(''), 'g'); }); }); }); describe('sideBPieces', function () { const gb = boardA(); describe('onBoard', function() { it('succeeds as expected', function() { assert.equal(gb.sideBPiecesOnBoard().map(([pc, pn])=>pc.code).join(''), 'glc'); }); }); describe('offBoard', function() { it('succeeds as expected', function() { assert.equal(gb.sideBPiecesOffBoard().map(pc=>pc.code).join(''), 'e'); }); }); }); describe('locationOfKing', function() { const gb = boardA(); it('works. for Side A', function() { const p = gb.locationOfKing(true); assert(p.equals( new Point(1,0) )); }); it('works for Side B', function() { const p = gb.locationOfKing(false); assert(p.equals( new Point(0,1) )); }); }); describe('isKingUnderAttack', function() { describe('board A', function() { const gb = boardA(); it('works. for Side A', function() { assert.equal(true, gb.isKingUnderAttack(true)); }); it('works for Side B', function() { assert.equal(true, gb.isKingUnderAttack(false)); }); }); describe('board B', function() { const gb = boardB(); it('works. for Side A', function() { assert.equal(false, gb.isKingUnderAttack(true)); }); it('works for Side B', function() { assert.equal(false, gb.isKingUnderAttack(false)); }); }); }); describe('kingIsOnLastLineUnchecked', function() { it ('should work', function() { const tests=[[boardA (), false, false], [boardB (), true, true], [boardTwoKings(), false, false]]; tests.forEach( ([board, sideA, sideB])=>{ assert(board.kingIsOnLastLineUnchecked(true )===sideA); assert(board.kingIsOnLastLineUnchecked(false)===sideB); }); }); }); describe('royalDistanceFromFarEndForGameWin', function() { it ('should work', function() { const tests = [ [boardA(true), new Point(1,0), 0], [boardA(true), new Point(0,1), 2], [boardA(false), new Point(1,0), Number.POSITIVE_INFINITY], [boardA(false), new Point(0,1), Number.POSITIVE_INFINITY], [boardB(true), new Point(1, 0), 0], [boardB(true), new Point(0, 3), 0], [boardB(false), new Point(1, 0), Number.POSITIVE_INFINITY], [boardB(false), new Point(0, 3), Number.POSITIVE_INFINITY] ]; tests.forEach ( ([board, p, dist]) => { assert.equal(board.royalDistanceFromFarEndForGameWin(p), dist); }); }); }); describe('sideOfMoveS', function() { it('should fail as expected for invalid moves', function() { const board = boardA(true); const invalidMoves = ['(0~0)=>(1~0)', '(3~2)=>(0~1)']; invalidMoves.forEach( (invalidMove)=> { assert.throws( ()=> { board.sideOfMoveS([Chick, Hen, Elephant, Giraffe, Lion], invalidMove); }, /^AssertionError: B#ISMS#IV/); }); }); it('should fail as expected for invalid drops on non-empty squares', function() { const board = boardA(true); const invalidDrops = ['G=>0~0', 'e=>0~0', 'e=>1~0', 'e=>3~2']; invalidDrops.forEach( (invalidDrop)=> { assert.throws( ()=> { board.sideOfMoveS([Chick, Hen, Elephant, Giraffe, Lion], invalidDrop); }, /AssertionError: B#ISMS#DNEC:/); }); }); it('should fail as expected for invalid drops of non-captured pieces', function() { const board = boardA(true); const invalidDrops = ['E=>0~0', 'g=>0~0', 'c=>1~0', 'C=>3~2', 'h=>3~2', 'H=>3~2', 'h=>3~3']; invalidDrops.forEach( (invalidDrop)=> { assert.throws( ()=> { board.sideOfMoveS([Chick, Hen, Elephant, Giraffe, Lion], invalidDrop); }, /AssertionError: B#ISMS#CNHP/); }); }); it('should work correctly for moves', function() { }); it('should work correctly for drops', function() { }); }); }); // GameBoard describe('EvaluationModel', function() { describe('evalPieceValues', function() { it('is working on a board with no promotion zone',function() { const tests = [ [ true, 100, 10], [false, 0, 0] ]; tests.forEach ( ([winOnFarEnd, sideAKinglyBonus, sideBKinglyBonus])=> { const gb = boardA(winOnFarEnd); assert.equal(model000._evalPieceValues(gb, true), sideAKinglyBonus+1+5+4+8*model000.offBoardMultiplier); assert.equal(model000._evalPieceValues(gb, false), sideBKinglyBonus+1+8+4*model000.offBoardMultiplier); }); }); it('is working on a board with a promotion zone of breadth 1',function() { const tests = [ [ true, 100, 10], [false, 0, 0] ]; tests.forEach ( ([winOnFarEnd, sideAKinglyBonus, sideBKinglyBonus])=> { const gb = boardA(winOnFarEnd); gb.breadthOfPromotionZone = 1; assert.equal(model000._evalPieceValues(gb, true), sideAKinglyBonus+1+5*0.9+5+4+8*model000.offBoardMultiplier); assert.equal(model000._evalPieceValues(gb, false), sideBKinglyBonus+1+5*0.1+8+4*model000.offBoardMultiplier); }); }); }); describe('evalFreedomOfMovement', function() { it('works', function () { const gb = boardA(); const freedomOfMove = model000._evalFreedomOfMovement(gb); assert.equal(freedomOfMove, 4); }); }); describe('evaluate royalCheckage', function() { it('works for boardA', function () { const gb = boardA(); const royalCheckage = model000._evalRoyalCheckage(gb); assert.equal(royalCheckage, -10); }); it(`works for boardA's reflection`, function () { const gb = boardA().reflection(); const royalCheckage = model000._evalRoyalCheckage(gb); assert.equal(royalCheckage, -10); }); it('works for boardB', function () { const gb = boardB(); const royalCheckage = model000._evalRoyalCheckage(gb); assert.equal(royalCheckage, 0); }); it(`works for boardB'reflection`, function () { const gb = boardB().reflection(); const royalCheckage = model000._evalRoyalCheckage(gb); assert.equal(royalCheckage, 0); }); }); describe('evaluateBoard', function() { it('should not fail', function() { const boards = [boardA(), boardTwoKings(), boardWithSideAKingCaptured(), boardWithSideBKingCaptured()]; boards.forEach ( (board)=> { model000.evaluateBoard(board); }); }); it('it should work in infinity cases', function() { const tests = [ [boardWithSideAKingCaptured(), Number.NEGATIVE_INFINITY], [boardWithSideBKingCaptured(), Number.POSITIVE_INFINITY]]; tests.forEach ( ([board, score]) => { assert(model000.evaluateBoard(board)===score); } ); }); }); }); describe('Interplay between GameBoard and EvaluationModel', function() { describe('nextStatesValues', function() { describe('board two kings', function() { const gb = boardTwoKings(); it(`doesn't break. for Side A`, function() { const movesToEval = gb.nextStatesValues(true, model000); }); }); }); });