|
/** |
|
* Arrange,Act, Assert |
|
* create 5 tests |
|
* 1.That tests the game initialization |
|
* 2-5 test each rules of the game |
|
* |
|
* class Game |
|
- initial conditions |
|
- cell - public Arrays[x,y]: Boolean |
|
- implements a set of mathematical rules |
|
- it has an initiation function(x: Int, y: Int, Array[x: Int,y: Int], steps) |
|
*/ |
|
class Game { |
|
board: boolean[][]; |
|
step: number; |
|
|
|
constructor( |
|
row: number, |
|
column: number, |
|
initialData?: Array<boolean[]>, |
|
steps: number = 1, |
|
) { |
|
this.board = this.createBoard(row, column, initialData); |
|
this.step = steps; |
|
} |
|
|
|
private createBoard( |
|
row: number, |
|
column: number, |
|
initialData?: Array<boolean[]>, |
|
) { |
|
let initialDataRow = initialData?.length; |
|
let initialDataColumn = (index: number, initialData: boolean[][]) => |
|
initialData[index].length; |
|
// if seed data does not have the same number of rows and column throw an error |
|
if (initialData) { |
|
if (row === initialDataRow) { |
|
for (let index = 0; index < initialData?.length; index += 1) { |
|
if (column !== initialDataColumn(index, initialData)) { |
|
throw new Error(`Invalid seed Column ${index}`); |
|
} |
|
} |
|
} else { |
|
throw new Error('Invalid seed row'); |
|
} |
|
return initialData; |
|
} else { |
|
const board: Array<boolean[]> = []; |
|
|
|
for (let index = 0; index < row; index++) { |
|
board[index] = new Array(column); |
|
|
|
board[index].fill(false); |
|
} |
|
|
|
return board; |
|
} |
|
} |
|
|
|
private neighbourCount( |
|
rowIndex: number, |
|
columnIndex: number, |
|
boardState: boolean[][], |
|
) { |
|
const possibleNeighbours = []; |
|
|
|
const confirmPosition = |
|
(board: boolean[][]) => (pointX: number, pointY: number) => { |
|
// console.log({ x, y }); |
|
if (pointX === -1 || pointY === -1) return null; |
|
return board[pointX] && board[pointX][pointY] ? [pointX, pointY] : null; |
|
}; |
|
|
|
const checkPositionOnBoard = confirmPosition(boardState); |
|
|
|
// this is used to create a square of coordinates round rowIndex, columnIndex |
|
const rowNegative = rowIndex - 1; |
|
const rowPositive = rowIndex + 1; |
|
const columnNegative = columnIndex - 1; |
|
const columnPositive = columnIndex + 1; |
|
|
|
// diagonals |
|
possibleNeighbours.push(checkPositionOnBoard(rowNegative, columnNegative)); |
|
possibleNeighbours.push(checkPositionOnBoard(rowNegative, columnPositive)); |
|
possibleNeighbours.push(checkPositionOnBoard(rowPositive, columnNegative)); |
|
possibleNeighbours.push(checkPositionOnBoard(rowPositive, columnPositive)); |
|
|
|
// opposites |
|
possibleNeighbours.push(checkPositionOnBoard(rowNegative, columnIndex)); |
|
possibleNeighbours.push(checkPositionOnBoard(rowPositive, columnIndex)); |
|
possibleNeighbours.push(checkPositionOnBoard(rowIndex, columnNegative)); |
|
possibleNeighbours.push(checkPositionOnBoard(rowIndex, columnPositive)); |
|
|
|
const neighbours = possibleNeighbours.filter(Boolean).length; |
|
|
|
// clean up the coords to return length of neighbours (count) |
|
return neighbours; |
|
} |
|
|
|
private putEmptyArrayInResult(count: number, result: boolean[][] = []) { |
|
for (let index = 0; index < count; index += 1) { |
|
result.push([]); |
|
} |
|
return result; |
|
} |
|
|
|
runStep() { |
|
let result: boolean[][] = this.putEmptyArrayInResult(this.board.length); |
|
|
|
// for (let i = 0; i < this.board.length; i += 1) { |
|
// result.push([]); |
|
// } |
|
|
|
for (let indexX = 0; indexX < this.board.length; indexX += 1) { |
|
for (let indexY = 0; indexY < this.board[indexX].length; indexY += 1) { |
|
const cell = this.board[indexX][indexY]; |
|
const neighbourCount = this.neighbourCount(indexX, indexY, this.board); |
|
|
|
// if cell is alive |
|
// * For a space that is populated: |
|
if (cell) { |
|
//* Each cell with one or no neighbors dies, as if by solitude. |
|
if (neighbourCount <= 1) { |
|
result[indexX][indexY] = !cell; |
|
} |
|
//* Each cell with two or three neighbors survives. |
|
if (neighbourCount === 2 || neighbourCount === 3) { |
|
result[indexX][indexY] = cell; |
|
} |
|
//* Each cell with four or more neighbors dies, as if by overpopulation. |
|
if (neighbourCount >= 4) { |
|
result[indexX][indexY] = !cell; |
|
} |
|
} else { |
|
// if cell is dead |
|
// * For a space that is empty or unpopulated |
|
// Each cell with three neighbors becomes populated. |
|
if (neighbourCount === 3) { |
|
result[indexX][indexY] = !cell; |
|
} else { |
|
result[indexX][indexY] = cell; |
|
} |
|
} |
|
} |
|
} |
|
// tried to use recursion here to runStep if this.step more than 1 |
|
return result; |
|
} |
|
|
|
simulateGame() { |
|
let result; |
|
for (let index = 0; index < this.step; index += 1) { |
|
// console.log(`running ${i}`); |
|
result = this.runStep(); |
|
this.board = result; |
|
} |
|
// console.log('done'); |
|
return result; |
|
} |
|
} |
|
|
|
let a = new Game( |
|
3, |
|
3, |
|
[ |
|
[true, false, false], |
|
[false, true, false], |
|
[true, false, true], |
|
], |
|
3, |
|
); |
|
let res = a.simulateGame(); |
|
console.log(res); |
|
|
|
describe('The Game of Life Implementation', () => { |
|
describe('Game class', () => { |
|
test('should setup a game board with 4 rows and 5 columns with initial data with steps', () => { |
|
const rows = 4; |
|
const columns = 5; |
|
const steps = 2; |
|
const initialData = [ |
|
[true, false, false, true, false], |
|
[true, false, false, true, true], |
|
[false, true, false, false, true], |
|
[false, false, true, true, false], |
|
]; |
|
|
|
const gameBoard = new Game(rows, columns, initialData, steps); |
|
|
|
expect(gameBoard).toBeInstanceOf(Game); |
|
expect(gameBoard).toHaveProperty('board'); |
|
expect(gameBoard.board).toHaveLength(rows); |
|
expect(gameBoard.board[0]).toHaveLength(columns); |
|
expect(gameBoard.board).toStrictEqual(initialData); |
|
expect(gameBoard.step).toEqual(steps); |
|
}); |
|
}); |
|
|
|
describe('Game rules', () => { |
|
test('For a space that is populated:Each cell with one or no neighbors should die', () => { |
|
const rows = 3; |
|
const columns = 3; |
|
const steps = 1; |
|
const initialData = [ |
|
[true, false, false], |
|
[false, true, false], |
|
[true, false, true], |
|
]; |
|
|
|
const gameBoard = new Game(rows, columns, initialData, steps); |
|
let result = gameBoard.runStep(); |
|
|
|
expect(result[0][0]).toBe(!gameBoard.board[0][0]); |
|
expect(result[2][0]).toBe(!gameBoard.board[2][0]); |
|
expect(result[2][2]).toBe(!gameBoard.board[2][2]); |
|
}); |
|
test('For a space that is populated:Each cell with four or more neighbors should die', () => { |
|
const rows = 5; |
|
const columns = 3; |
|
const steps = 1; |
|
const initialData = [ |
|
[true, false, false], |
|
[false, false, false], |
|
[true, true, true], |
|
[true, true, false], |
|
[false, false, false], |
|
]; |
|
|
|
const gameBoard = new Game(rows, columns, initialData, steps); |
|
let result = gameBoard.runStep(); |
|
|
|
expect(result[2][1]).toBe(!gameBoard.board[2][1]); |
|
expect(result[3][1]).toBe(!gameBoard.board[3][1]); |
|
}); |
|
test('For a space that is populated:Each cell with two or three neighbors should survive.', () => { |
|
const rows = 5; |
|
const columns = 3; |
|
const steps = 1; |
|
const initialData = [ |
|
[true, false, false], |
|
[false, false, false], |
|
[true, true, true], |
|
[true, true, false], |
|
[false, false, false], |
|
]; |
|
|
|
const gameBoard = new Game(rows, columns, initialData, steps); |
|
let result = gameBoard.runStep(); |
|
|
|
expect(result[2][0]).toBe(gameBoard.board[2][0]); |
|
expect(result[2][2]).toBe(gameBoard.board[2][2]); |
|
expect(result[3][0]).toBe(gameBoard.board[3][0]); |
|
}); |
|
|
|
test('For a space that is empty or unpopulated:Each cell with three neighbors should populated.', () => { |
|
const rows = 5; |
|
const columns = 3; |
|
const steps = 1; |
|
const initialData = [ |
|
[true, false, false], |
|
[false, false, false], |
|
[true, true, true], |
|
[true, true, false], |
|
[false, false, false], |
|
]; |
|
|
|
const gameBoard = new Game(rows, columns, initialData, steps); |
|
let result = gameBoard.runStep(); |
|
|
|
expect(result[1][0]).toBe(!gameBoard.board[1][0]); |
|
expect(result[3][2]).toBe(!gameBoard.board[3][2]); |
|
}); |
|
}); |
|
}); |