hive-game-core
Version:
Various stuff implementing the game and rules of Hive, the strategy game
907 lines (830 loc) • 33 kB
JavaScript
const _ = require('lodash')
const { Board } = require('../Board')
const { Move } = require('../Move')
describe('constructor', () => {
test('initialises successfully with log function', () => {
new Board({ logFn: _.identity })
})
test('initialises successfully with no options', () => {
new Board()
})
test('initialises successfully with empty options', () => {
new Board({})
})
})
describe('applyMove', () => {
test('applies valid first move', () => {
const board = new Board()
board.applyMove(new Move('S+0,0'), 1)
expect(board._getCell([0,0])).toEqual([ { piece: 'S', white: true } ])
})
test('applies valid first move as string', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
expect(board._getCell([0,0])).toEqual([ { piece: 'S', white: true } ])
})
test('applies a movement move successfully', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
board.applyMove('Q+0,-1', 3)
board.applyMove('A+2,-1', 4)
board.applyMove('B+-1,0', 5)
board.applyMove('Q+1,1', 6)
board.applyMove('B>0,0,1', 7)
expect(board._getCell([-1, 0])).toEqual([])
expect(board._getCell([0, 0])).toEqual([ { piece: 'S', white: true }, { piece: 'B', white: true } ])
board.applyMove('A>-1,-1', 8)
expect(board._getCell([2, -1])).toEqual([])
expect(board._getCell([-1, -1])).toEqual([ { piece: 'A', white: false } ])
})
test('fails if turn not given', () => {
const board = new Board()
expect(() => board.applyMove('S+0,0')).toThrow()
})
test('disallows a queen on first move for white', () => {
const board = new Board()
expect(() => board.applyMove('Q+0,0', 1)).toThrowError('NO_QUEEN_FIRST')
})
test('disallows a queen on first move for black', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
expect(() => board.applyMove('Q+1,0', 2)).toThrowError('NO_QUEEN_FIRST')
})
test('disallows a first move not at 0,0', () => {
const board = new Board()
expect(() => board.applyMove('S+1,0', 1)).toThrowError('FIRST_MOVE_ORIGIN_REQUIRED')
})
test('disallows a second move not at 1,0', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
expect(() => board.applyMove('S+0,1', 2)).toThrowError('SECOND_MOVE_PRIMARY_REQUIRED')
expect(() => board.applyMove('S+-1,1', 2)).toThrowError('SECOND_MOVE_PRIMARY_REQUIRED')
expect(() => board.applyMove('S+-1,0', 2)).toThrowError('SECOND_MOVE_PRIMARY_REQUIRED')
expect(() => board.applyMove('S+0,-1', 2)).toThrowError('SECOND_MOVE_PRIMARY_REQUIRED')
expect(() => board.applyMove('S+1,-1', 2)).toThrowError('SECOND_MOVE_PRIMARY_REQUIRED')
})
test('disallows placement on top of another piece', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
expect(() => board.applyMove('S+0,0', 3)).toThrowError('NO_PLACEMENT_ON_OTHER_PIECES')
expect(() => board.applyMove('S+1,0', 3)).toThrowError('NO_PLACEMENT_ON_OTHER_PIECES')
})
test('disallows placement next to an opponent\'s piece (except first two)', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
expect(() => board.applyMove('S+0,1', 3)).toThrowError('NO_PLACEMENT_NEXT_TO_OPPONENT')
expect(() => board.applyMove('S+1,1', 3)).toThrowError('NO_PLACEMENT_NEXT_TO_OPPONENT')
expect(() => board.applyMove('S+2,0', 3)).toThrowError('NO_PLACEMENT_NEXT_TO_OPPONENT')
expect(() => board.applyMove('S+2,-1', 3)).toThrowError('NO_PLACEMENT_NEXT_TO_OPPONENT')
expect(() => board.applyMove('S+1,-1', 3)).toThrowError('NO_PLACEMENT_NEXT_TO_OPPONENT')
})
test('requires a queen by fourth move', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
board.applyMove('S+-1,0', 3)
board.applyMove('S+2,0', 4)
board.applyMove('B+-2,0', 5)
board.applyMove('B+3,0', 6)
expect(() => board.applyMove('G+-3,0', 7)).toThrowError('QUEEN_REQUIRED_IN_FIRST_FOUR')
board.applyMove('Q+-3,0', 7)
expect(() => board.applyMove('G+4,0', 8)).toThrowError('QUEEN_REQUIRED_IN_FIRST_FOUR')
board.applyMove('Q+4,0', 8)
})
test('disallows a move before queen placement for white', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
expect(() => board.applyMove('S>2,0', 3)).toThrowError('NO_MOVE_BEFORE_QUEEN')
})
test('disallows a move before queen placement for black', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
board.applyMove('S+-1,0', 3)
expect(() => board.applyMove('S>-1,1', 4)).toThrowError('NO_MOVE_BEFORE_QUEEN')
})
test('disallows movement of a piece that doesn\'t exist by implicit origin', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
expect(() => board.applyMove('B>-1,1', 5)).toThrowError('NO_SUCH_PIECE')
})
test('disallows movement of a piece that doesn\'t exist by explicit 2D origin', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
expect(() => board.applyMove('B[-1,0]>-1,1', 5)).toThrowError('NO_SUCH_PIECE')
})
test('disallows movement of a piece that doesn\'t exist by explicit 3D origin', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
expect(() => board.applyMove('B[-1,0,0]>-1,1', 5)).toThrowError('NO_SUCH_PIECE')
})
test('disallows movement with implicit origin when piece is ambiguous', () => {
const board = new Board()
board.applyMove('A+0,0', 1)
board.applyMove('A+1,0', 2)
board.applyMove('Q+-1,1', 3)
board.applyMove('A+1,1', 4)
board.applyMove('A+0,-1', 5)
board.applyMove('A+2,-1', 6)
expect(() => board.applyMove('A>2,0', 7)).toThrowError('AMBIGUOUS_ORIGIN_PIECE')
})
test('disallows movement of piece trapped under others', () => {
const board = new Board()
board.applyMove('A+0,0', 1)
board.applyMove('A+1,0', 2)
board.applyMove('Q+-1,1', 3)
board.applyMove('Q+1,1', 4)
board.applyMove('B+-2,1', 5)
board.applyMove('B+2,1', 6)
board.applyMove('B>-1,1,1', 7)
board.applyMove('B>1,1,1', 8)
expect(() => board.applyMove('Q>0,1', 9)).toThrowError('PIECE_TRAPPED_UNDER_ANOTHER')
})
test('disallows movement to a taken spot', () => {
const board = new Board()
board.applyMove('A+0,0', 1)
board.applyMove('A+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
expect(() => board.applyMove('Q>0,0', 5)).toThrowError('MOVEMENT_TARGET_OCCUPIED')
board.applyMove('B+-2,0', 5)
expect(() => board.applyMove('Q>1,0', 6)).toThrowError('MOVEMENT_TARGET_OCCUPIED')
})
test('disallows movement to a spot above the top of the hive', () => {
const board = new Board()
board.applyMove('A+0,0', 1)
board.applyMove('A+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
board.applyMove('B+-2,0', 5)
board.applyMove('B+3,0', 6)
expect(() => board.applyMove('B>-1,0,2', 7)).toThrowError('MOVEMENT_TARGET_ABOVE_HIVE')
expect(() => board.applyMove('B>-1,0,3', 7)).toThrowError('MOVEMENT_TARGET_ABOVE_HIVE')
board.applyMove('B>-1,0,1', 7)
expect(() => board.applyMove('B>2,0,2', 8)).toThrowError('MOVEMENT_TARGET_ABOVE_HIVE')
expect(() => board.applyMove('B>2,0,3', 8)).toThrowError('MOVEMENT_TARGET_ABOVE_HIVE')
board.applyMove('B>2,0,1', 8)
})
describe('queen', () => {
test('allows valid queen moves', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
// Apply attempted next moves to clones of the board
board.clone.applyMove('Q>-1,1', 5)
board.clone.applyMove('Q>0,-1', 5)
board.applyMove('Q>0,-1', 5)
// Same for black
board.clone.applyMove('Q>1,1', 6)
board.clone.applyMove('Q>2,-1', 6)
})
test('cannot move more than 1 space', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
// Apply attempted next moves to clones of the board
expect(() => board.applyMove('Q>0,1', 5)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('Q>1,1', 5)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('Q>2,1', 5)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('Q>1,-1', 5)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('Q>2,-1', 5)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('Q>3,-1', 5)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('Q>3,0', 5)).toThrowError('NO_VALID_PATH')
})
test('cannot climb onto hive', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
// Attempt to move onto hive
expect(() => board.applyMove('Q>0,0,1', 5)).toThrowError('CANNOT_CLIMB')
board.applyMove('Q>0,-1', 5)
expect(() => board.applyMove('Q>1,0,1', 6)).toThrowError('CANNOT_CLIMB')
})
test('cannot violate freedom of movement', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'Q', white: true}],
'1,0': [{piece: 'Q', white: false}],
'1,1': [{piece: 'A', white: false}],
'0,2': [{piece: 'A', white: false}],
'-1,2': [{piece: 'A', white: true}],
'-1,1': [{piece: 'A', white: true}],
}
// Attempt to move into impossible gap
expect(() => board.applyMove('Q>0,1', 7)).toThrowError('NO_VALID_PATH')
})
test('cannot violate one hive rule', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'Q', white: true}],
'1,0': [{piece: 'Q', white: false}],
'1,1': [{piece: 'A', white: false}],
'-1,2': [{piece: 'A', white: true}],
'-1,1': [{piece: 'A', white: true}],
}
// Attempt to split hive
expect(() => board.applyMove('Q>-1,0', 6)).toThrowError('NO_VALID_PATH')
})
})
describe('ant', () => {
test('allows valid ant moves', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
board.applyMove('A+-2,0', 5)
board.applyMove('A+3,0', 6)
// Apply attempted next moves to clones of the board
board.clone.applyMove('A>-2,1', 7)
board.clone.applyMove('A>-1,1', 7)
board.clone.applyMove('A>0,1', 7)
board.clone.applyMove('A>1,1', 7)
board.clone.applyMove('A>2,1', 7)
board.clone.applyMove('A>3,1', 7)
board.clone.applyMove('A>4,0', 7)
board.clone.applyMove('A>4,-1', 7)
board.clone.applyMove('A>3,-1', 7)
board.clone.applyMove('A>2,-1', 7)
board.clone.applyMove('A>1,-1', 7)
board.clone.applyMove('A>0,-1', 7)
board.clone.applyMove('A>-1,-1', 7)
})
test('cannot climb onto hive', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
board.applyMove('A+-2,0', 5)
board.applyMove('A+3,0', 6)
// Attempt to move onto hive
expect(() => board.applyMove('A>-1,0,1', 7)).toThrowError('CANNOT_CLIMB')
})
test('cannot violate freedom of movement', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'Q', white: true}],
'1,0': [{piece: 'Q', white: false}],
'1,1': [{piece: 'A', white: false}],
'0,2': [{piece: 'A', white: false}],
'-1,2': [{piece: 'A', white: true}],
'-1,1': [{piece: 'A', white: true}],
}
// Attempt to move into impossible gap
expect(() => board.applyMove('A[-1,1]>0,1', 7)).toThrowError('NO_VALID_PATH')
})
test('cannot violate one hive rule', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'A', white: true}],
'1,0': [{piece: 'Q', white: false}],
'0,1': [{piece: 'A', white: true}],
'0,2': [{piece: 'A', white: false}],
'-1,1': [{piece: 'A', white: false}],
'-1,2': [{piece: 'Q', white: true}],
}
// Attempt to split hive
expect(() => board.applyMove('A[0,0]>0,-1', 7)).toThrowError('NO_VALID_PATH')
})
})
describe('spider', () => {
test('allows valid spider moves', () => {
const board = new Board()
board.applyMove('A+0,0', 1)
board.applyMove('A+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
board.applyMove('S+-2,0', 5)
board.applyMove('S+3,0', 6)
// Apply attempted next moves to clones of the board
board.clone.applyMove('S>0,1', 7)
board.clone.applyMove('S>1,-1', 7)
})
test('cannot move more or less than 3 spaces', () => {
const board = new Board()
board.applyMove('A+0,0', 1)
board.applyMove('A+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
board.applyMove('S+-2,0', 5)
board.applyMove('S+3,0', 6)
// Apply attempted next moves to clones of the board
expect(() => board.applyMove('S>-1,1', 7)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('S>1,1', 7)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('S>0,-1', 7)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('S>2,-1', 7)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('S>-2,1', 7)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('S>4,0', 7)).toThrowError('NO_VALID_PATH')
})
test('cannot climb onto hive', () => {
const board = new Board()
board.applyMove('A+0,0', 1)
board.applyMove('A+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
board.applyMove('S+-2,0', 5)
board.applyMove('S+3,0', 6)
// Attempt to move onto hive
expect(() => board.applyMove('S>1,0,1', 7)).toThrowError('CANNOT_CLIMB')
})
test('cannot violate freedom of movement', () => {
const board = new Board()
board._cells = {
'1,0': [{piece: 'Q', white: false}],
'1,1': [{piece: 'A', white: false}],
'0,2': [{piece: 'A', white: true}],
'-1,2': [{piece: 'Q', white: true}],
'-1,1': [{piece: 'A', white: true}],
'2,-1': [{piece: 'S', white: true}],
}
// Attempt to move into impossible gap
expect(() => board.applyMove('S>0,1', 7)).toThrowError('NO_VALID_PATH')
})
test('cannot violate one hive rule', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'S', white: true}],
'1,0': [{piece: 'Q', white: false}],
'0,1': [{piece: 'A', white: true}],
'0,2': [{piece: 'A', white: false}],
'-1,1': [{piece: 'A', white: false}],
'-1,2': [{piece: 'Q', white: true}],
}
// Attempt to split hive
expect(() => board.applyMove('S[0,0]>0,-3', 7)).toThrowError('NO_VALID_PATH')
})
})
describe('grasshopper', () => {
test('allows valid grasshopper moves', () => {
const board = new Board()
board.applyMove('A+0,0', 1)
board.applyMove('A+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
board.applyMove('G+-2,0', 5)
board.applyMove('G+2,1', 6)
// Apply attempted next moves
board.applyMove('G>3,0', 7)
board.clone.applyMove('G>2,-1', 8)
board.clone.applyMove('G>4,-1', 8)
})
test('allows valid grasshopper moves out of and into a gap', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'Q', white: true}],
'1,0': [{piece: 'A', white: true}],
'1,1': [{piece: 'A', white: true}],
'0,2': [{piece: 'Q', white: false}],
'-1,2': [{piece: 'A', white: false}],
'-1,1': [{piece: 'A', white: false}],
'-1,0': [{piece: 'B', white: false}],
'0,1': [{piece: 'G', white: true}],
}
// Apply attempted next moves
board.clone.applyMove('G>0,-1', 9)
board.applyMove('G>2,-1', 9)
board.applyMove('B>-2,1', 10)
board.applyMove('G>0,1', 11)
})
test('cannot jump over a gap', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'Q', white: true}],
'1,0': [{piece: 'A', white: true}],
'1,1': [{piece: 'A', white: true}],
'0,2': [{piece: 'Q', white: false}],
'-1,2': [{piece: 'A', white: false}],
'-1,1': [{piece: 'A', white: false}],
'-1,0': [{piece: 'B', white: false}],
'0,-1': [{piece: 'G', white: true}],
}
// Apply attempted next moves
expect(() => board.applyMove('G>0,3', 9)).toThrowError('NO_VALID_PATH')
})
test('cannot jump in an off-straight line', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'Q', white: true}],
'1,0': [{piece: 'A', white: true}],
'1,1': [{piece: 'A', white: true}],
'0,2': [{piece: 'Q', white: false}],
'-1,2': [{piece: 'A', white: false}],
'-1,1': [{piece: 'A', white: false}],
'-1,0': [{piece: 'B', white: false}],
'1,-1': [{piece: 'G', white: true}],
}
// Apply attempted next moves
expect(() => board.applyMove('G>0,1', 9)).toThrowError('NO_VALID_PATH')
})
test('cannot jump a single space', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'Q', white: true}],
'1,0': [{piece: 'A', white: true}],
'1,1': [{piece: 'A', white: true}],
'0,2': [{piece: 'Q', white: false}],
'-1,2': [{piece: 'A', white: false}],
'-1,1': [{piece: 'A', white: false}],
'-1,0': [{piece: 'B', white: false}],
'1,-1': [{piece: 'G', white: true}],
}
// Apply attempted next moves
expect(() => board.applyMove('G>2,-1', 9)).toThrowError('NO_VALID_PATH')
})
test('cannot climb onto hive', () => {
const board = new Board()
board.applyMove('A+0,0', 1)
board.applyMove('A+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
board.applyMove('G+-2,0', 5)
board.applyMove('G+3,0', 6)
// Attempt to move onto hive
expect(() => board.applyMove('G>-1,0,1', 7)).toThrowError('CANNOT_CLIMB')
expect(() => board.applyMove('G>0,0,1', 7)).toThrowError('CANNOT_CLIMB')
})
test('cannot violate one hive rule', () => {
const board = new Board()
board.applyMove('A+0,0', 1)
board.applyMove('A+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
board.applyMove('G+-2,0', 5)
board.applyMove('G+2,1', 6)
// Attempt to split hive
expect(() => board.applyMove('G>-3,0', 7)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('G>-3,1', 7)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('G>-2,-1', 7)).toThrowError('NO_VALID_PATH')
})
})
describe('beetle', () => {
test('allows valid beetle moves around hive', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'Q', white: true}],
'1,0': [{piece: 'A', white: true}],
'1,1': [{piece: 'A', white: true}],
'0,2': [{piece: 'Q', white: false}],
'-1,2': [{piece: 'A', white: false}],
'-1,1': [{piece: 'A', white: false}],
'-1,0': [{piece: 'B', white: false}],
'0,-1': [{piece: 'B', white: true}],
}
// Apply attempted next moves
board.clone.applyMove('B>1,-1', 9)
board.clone.applyMove('B>-1,-1', 9)
})
test('cannot move more than 1 space', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('Q+2,0', 4)
board.applyMove('B+-2,0', 5)
board.applyMove('B+3,0', 6)
// Apply attempted next moves
expect(() => board.applyMove('B>-1,1', 7)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('B>0,1', 7)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('B>1,1', 7)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('B>0,-1', 7)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('B>1,-1', 7)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('B>2,-1', 7)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('B>4,0', 7)).toThrowError('NO_VALID_PATH')
})
test('cannot violate freedom of movement', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'B', white: true}],
'1,0': [{piece: 'Q', white: false}],
'1,1': [{piece: 'A', white: false}],
'0,2': [{piece: 'A', white: false}],
'-1,2': [{piece: 'Q', white: true}],
'-1,1': [{piece: 'A', white: true}],
}
// Attempt to move into impossible gap
expect(() => board.applyMove('B>0,1', 7)).toThrowError('NO_VALID_PATH')
})
test('cannot violate freedom of movement above the hive', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'S', white: true}, {piece: 'B', white: true}],
'1,0': [{piece: 'Q', white: false}, {piece: 'Q', white: false}],
'1,1': [{piece: 'A', white: false}],
'0,2': [{piece: 'A', white: false}],
'-1,2': [{piece: 'Q', white: true}],
'-1,1': [{piece: 'A', white: true}, {piece: 'A', white: true}],
'0,1': [{piece: 'S', white: true}]
}
// Attempt to move into impossible gap (implicit and explicit)
expect(() => board.applyMove('B[0,0,1]>0,1,1', 11)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('B>0,1,1', 11)).toThrowError('NO_VALID_PATH')
})
test('cannot violate one hive rule', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'B', white: true}],
'1,0': [{piece: 'Q', white: false}],
'1,1': [{piece: 'A', white: false}],
'0,2': [{piece: 'A', white: true}],
'-1,2': [{piece: 'Q', white: true}],
}
// Attempt to split hive
expect(() => board.applyMove('B>-1,0', 5)).toThrowError('NO_VALID_PATH')
expect(() => board.applyMove('B>-1,1', 5)).toThrowError('NO_VALID_PATH')
})
test('allows valid beetle moves onto the hive and back down', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'Q', white: true}],
'-1,0': [{piece: 'B', white: true}],
'1,0': [{piece: 'Q', white: false}],
'2,0': [{piece: 'B', white: false}],
'0,1': [{piece: 'A', white: true}],
'1,-1': [{piece: 'A', white: false}],
}
// Apply attempted next moves
board.applyMove('B>0,0,1', 7)
board.applyMove('B>1,0,1', 8)
board.applyMove('B>1,0,2', 9)
expect(() => board.applyMove('B>0,0,1', 10)).toThrowError('PIECE_TRAPPED_UNDER_ANOTHER')
board.applyMove('A>2,-1', 10)
board.applyMove('B>2,0', 11)
board.applyMove('B>0,0,1', 12)
})
})
})
describe('getAdjacentCells', () => {
test('successfully gets empty adjacent cells', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
expect(board.getAdjacentCells([0, 0])).toEqual([ [], [], [], [], [], [] ])
expect(board.getAdjacentCells([0, 0], true)).toEqual([
{ cell: [], coords2: [1, 0] },
{ cell: [], coords2: [0, 1] },
{ cell: [], coords2: [-1, 1] },
{ cell: [], coords2: [-1, 0] },
{ cell: [], coords2: [0, -1] },
{ cell: [], coords2: [1, -1] },
])
})
test('successfully gets primary-filled adjacent cells', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
expect(board.getAdjacentCells([0, 0])).toEqual([ [{ piece: 'S', white: false }], [], [], [], [], [] ])
expect(board.getAdjacentCells([2, 0])).toEqual([ [], [], [], [{ piece: 'S', white: false }], [], [] ])
})
})
describe('getValidPlacementLocations', () => {
test('successfully gives 0,0 and 1,0 as first two placement locations', () => {
const board = new Board()
expect(board.getValidPlacementLocations(1)).toEqual([ [0,0] ])
board.applyMove('S+0,0', 1)
expect(board.getValidPlacementLocations(2)).toEqual([ [1,0] ])
})
test('successfully gets placement locations for black and white in a complex board', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
board.applyMove('Q+0,-1', 3)
board.applyMove('A+2,-1', 4)
board.applyMove('B+-1,0', 5)
board.applyMove('Q+1,1', 6)
board.applyMove('B>0,0,1', 7)
board.applyMove('A>-1,-1', 8)
expect(board.getValidPlacementLocations(9)).toEqual([ [-1, 1], [1, -2] ])
board.applyMove('G+1,-2', 9)
expect(board.getValidPlacementLocations(10)).toEqual([
[-1, -2],
[-2, -1],
[-2, 0],
[0, 2],
[1, 2],
[2, -1],
[2, 0],
[2, 1],
])
})
})
describe('getValidMovementTargets', () => {
test('finds all valid movement targets for a queen', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'Q', white: true}],
'1,0': [{piece: 'A', white: true}],
'2,0': [{piece: 'Q', white: false}],
'3,0': [{piece: 'A', white: false}],
}
expect(board.getValidMovementTargets([0, 0, 0], 5)).toEqual([ [0, 1, 0], [1, -1, 0] ])
})
test('finds all valid movement targets for a spider', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'S', white: true}],
'1,0': [{piece: 'Q', white: true}],
'2,0': [{piece: 'Q', white: false}],
'3,0': [{piece: 'A', white: false}],
}
expect(board.getValidMovementTargets([0, 0, 0], 5)).toEqual([ [2, 1, 0], [3, -1, 0] ])
})
test('finds all valid movement targets for an ant', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'A', white: true}],
'1,0': [{piece: 'Q', white: true}],
'2,0': [{piece: 'Q', white: false}],
'3,0': [{piece: 'A', white: false}],
}
expect(board.getValidMovementTargets([0, 0, 0], 5)).toEqual([
[0, 1, 0],
[1, -1, 0],
[1, 1, 0],
[2, -1, 0],
[2, 1, 0],
[3, -1, 0],
[3, 1, 0],
[4, -1, 0],
[4, 0, 0],
])
})
test('finds all valid movement targets for a grasshopper', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'G', white: true}],
'1,0': [{piece: 'Q', white: true}],
'2,0': [{piece: 'Q', white: false}],
'3,0': [{piece: 'A', white: false}],
}
expect(board.getValidMovementTargets([0, 0, 0], 5)).toEqual([ [4, 0, 0] ])
})
test('finds all valid movement targets for a beetle', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'B', white: true}],
'1,0': [{piece: 'Q', white: true}],
'2,0': [{piece: 'Q', white: false}],
'3,0': [{piece: 'A', white: false}],
}
expect(board.getValidMovementTargets([0, 0, 0], 5)).toEqual([ [0, 1, 0], [1, -1, 0], [1, 0, 1] ])
})
})
describe('_occupiedAndAdjacentCoord2s', () => {
test('finds all occupied and adjacent coords', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'Q', white: true}],
'1,0': [{piece: 'A', white: true}],
'2,0': [{piece: 'Q', white: false}],
'3,0': [{piece: 'A', white: false}],
}
expect(board._occupiedAndAdjacentCoord2s).toEqual([
[-1, 0],
[-1, 1],
[0, -1],
[0, 0],
[0, 1],
[1, -1],
[1, 0],
[1, 1],
[2, -1],
[2, 0],
[2, 1],
[3, -1],
[3, 0],
[3, 1],
[4, -1],
[4, 0],
])
})
})
describe('pathfinding', () => {
describe('getPathsAroundHive', () => {
test('finds all clear paths at Z = 0 around a straight hive', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'A', white: true}],
'1,0': [{piece: 'Q', white: true}],
'2,0': [{piece: 'Q', white: false}],
'3,0': [{piece: 'A', white: false}],
}
expect(board.getPathsAroundHive([0, 0], [4, 0])).toEqual([
[ [0, 0], [0, 1], [1, 1], [2, 1], [3, 1], [4, 0] ],
[ [0, 0], [1, -1], [2, -1], [3, -1], [4, -1], [4, 0] ],
])
})
test('finds all clear paths around an off-straight hive', () => {
const board = new Board()
board._cells = {
'0,1': [{piece: 'A', white: true}],
'1,0': [{piece: 'Q', white: true}],
'2,0': [{piece: 'Q', white: false}],
'3,0': [{piece: 'A', white: false}],
}
expect(board.getPathsAroundHive([0, 1], [4, 0])).toEqual([
[ [0, 1], [1, 1], [2, 1], [3, 1], [4, 0] ],
[ [0, 1], [0, 0], [1, -1], [2, -1], [3, -1], [4, -1], [4, 0] ],
])
})
test('finds no path to not-free-to-move spot', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'A', white: true}],
'1,0': [{piece: 'A', white: true}],
'2,0': [{piece: 'Q', white: true}],
'2,1': [{piece: 'A', white: true}],
'1,2': [{piece: 'Q', white: false}],
'0,2': [{piece: 'A', white: false}],
'-1,2': [{piece: 'A', white: false}],
'-2,2': [{piece: 'A', white: false}],
}
expect(board.getPathsAroundHive([0, 0], [1, 1])).toEqual([])
})
test('correctly identifies that jumping directly across gap is invalid', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'A', white: true}],
'1,0': [{piece: 'A', white: true}],
'2,0': [{piece: 'Q', white: true}],
'2,1': [{piece: 'A', white: true}],
'1,2': [{piece: 'Q', white: false}],
'0,2': [{piece: 'A', white: false}],
'-1,2': [{piece: 'A', white: false}],
'-2,2': [{piece: 'A', white: false}],
}
expect(board.getPathsAroundHive([0, 0], [-1, 1])).toContainEqual([ [0, 0], [0, 1], [-1, 1] ])
expect(board.getPathsAroundHive([0, 0], [-1, 1])).not.toContainEqual([ [0, 0], [-1, 1] ])
expect(board.getPathsAroundHive([0, 0], [-1, 1]).length).toBe(2)
})
test('handles path from cell to itself', () => {
const board = new Board()
board._cells = {
'0,0': [{piece: 'A', white: true}],
'1,0': [{piece: 'A', white: true}],
'2,0': [{piece: 'Q', white: true}],
'2,1': [{piece: 'A', white: true}],
'1,2': [{piece: 'Q', white: false}],
'0,2': [{piece: 'A', white: false}],
'-1,2': [{piece: 'A', white: false}],
'-2,2': [{piece: 'A', white: false}],
}
expect(board.getPathsAroundHive([0, 0], [0, 0])).toEqual([])
})
})
})
describe('piecesPlaced', () => {
test('returns correctly after no moves', () => {
const board = new Board()
expect(board.piecesPlaced).toEqual({
white: [],
black: [],
})
})
test('returns correctly after some valid moves', () => {
const board = new Board()
board.applyMove('S+0,0', 1)
board.applyMove('S+1,0', 2)
board.applyMove('Q+-1,0', 3)
board.applyMove('B+2,0', 4)
expect(board.piecesPlaced).toEqual({
white: [ { piece: 'Q', coords3: [-1, 0, 0] } , { piece: 'S', coords3: [0, 0, 0] } ],
black: [ { piece: 'B', coords3: [2, 0, 0] } , { piece: 'S', coords3: [1, 0, 0] } ],
})
})
})
describe('_getSharedNeighbours', () => {
test('returns correctly shared coords between two adjacent cells', () => {
const board = new Board()
expect(board._getSharedNeighbours([0, 0], [1, 0])).toEqual([[0, 1], [1, -1]])
expect(board._getSharedNeighbours([0, 0], [0, 1])).toEqual([[1, 0], [-1, 1]])
})
test('returns correctly shared coord between coords with one space between', () => {
const board = new Board()
expect(board._getSharedNeighbours([0, 0], [2, 0])).toEqual([[1, 0]])
expect(board._getSharedNeighbours([0, 0], [0, 2])).toEqual([[0, 1]])
})
test('returns no shared coords between far-apart coords', () => {
const board = new Board()
expect(board._getSharedNeighbours([0, 0], [3, 0])).toEqual([])
expect(board._getSharedNeighbours([0, 0], [0, 3])).toEqual([])
})
})