diff --git a/src/board.test.ts b/src/board.test.ts new file mode 100644 index 0000000..6265392 --- /dev/null +++ b/src/board.test.ts @@ -0,0 +1,152 @@ +import { BoardCircuit, BoardUtils } from './client'; + +/* +Each ship is an array [x, y, z] where +x = x coordinate on the board +y = y coordinate on the board +z = is a binary where 0=horizontal & 1=verical orientation ship placements +*/ + +describe('Board Tests', () => { + describe('Valid Board Checks', () => { + function testValidBoard(ships: number[][]) { + const parsedShips = BoardUtils.parse(ships); + const hash = BoardUtils.hash(parsedShips); + + const serializedBoard = BoardUtils.serializeBoard(ships); + const validationHash = BoardCircuit.validateBoard(serializedBoard); + expect(validationHash).toEqual(hash); + } + + it("Prove valid board 1", () => { + const ships1 = [ + [0, 0, 0], + [0, 1, 0], + [0, 2, 0], + [0, 3, 0], + [0, 4, 0], + ]; + testValidBoard(ships1); + }); + + it("Prove valid board 2", () => { + const ships2 = [ + [9, 0, 1], + [9, 5, 1], + [6, 9, 0], + [6, 8, 0], + [7, 7, 0], + ]; + testValidBoard(ships2); + }); + + it("Prove valid board 3", () => { + const ships3 = [ + [0, 1, 1], + [4, 3, 0], + [3, 3, 1], + [5, 9, 0], + [1, 7, 1], + ]; + testValidBoard(ships3); + }); + }); + + describe('Out of Bound Checks', () => { + function testInvalidRange(ships: number[][], errorMessage?: string) { + const validationRangeError = () => { + const serializedBoard = BoardUtils.serializeBoard(ships); + BoardCircuit.validateBoard(serializedBoard); + } + expect(validationRangeError).toThrowError(errorMessage); + } + + it("Range violation: board 1: negative", () => { + const ships = [ + [-1, 0, 0], + [0, 1, 0], + [0, 2, 0], + [0, 3, 0], + [0, 4, 0], + ]; + testInvalidRange(ships); + }); + + it("Range violation: board 2: out of bounds x/ y", () => { + const ships = [ + [0, 0, 0], + [0, 10, 0], + [0, 2, 0], + [0, 3, 0], + [0, 4, 0], + ]; + testInvalidRange(ships, 'Ship2 is out of board range!'); + }); + + it("Range violation: board 3: out of bounds z", () => { + const ships = [ + [0, 0, 0], + [0, 1, 0], + [0, 2, 0], + [0, 3, 0], + [0, 4, 2], + ]; + testInvalidRange(ships, 'Coordinate z should be 1 or 0!'); + }); + }); + + describe("Collision Checks", () => { + function testCollision(ships: number[][], errorMessage: string) { + const serializedBoard = BoardUtils.serializeBoard(ships); + const validationCollisionrror = () => { + BoardCircuit.validateBoard(serializedBoard); + } + expect(validationCollisionrror).toThrowError(errorMessage); + } + + it("Placement violation: board 1", () => { + const ships1 = [ + [0, 0, 0], + [0, 0, 0], + [0, 2, 0], + [0, 3, 0], + [0, 4, 0], + ]; + testCollision(ships1, 'Collision occured when placing Ship2!'); + }); + + it("Placement violation: board 2", () => { + const ships2 = [ + [0, 1, 1], + [4, 3, 0], + [3, 3, 0], + [5, 9, 0], + [1, 8, 1], + ]; + testCollision(ships2, 'Collision occured when placing Ship3!'); + }); + + it("Placement violation: board 3", () => { + const ships3 = [ + [0, 1, 1], + [4, 3, 0], + [5, 5, 0], + [3, 5, 0], + [1, 7, 1], + ]; + testCollision(ships3, 'Collision occured when placing Ship4!'); + }); + + it("Placement violation: board 4", () => { + const ships4 = [ + [9, 0, 1], + [9, 5, 1], + [6, 9, 0], + [6, 8, 0], + [7, 7, 1], + ]; + testCollision(ships4, 'Collision occured when placing Ship5!'); + }); + }); +}); + \ No newline at end of file diff --git a/src/client.ts b/src/client.ts index 7c2d523..6d98c5a 100644 --- a/src/client.ts +++ b/src/client.ts @@ -37,44 +37,46 @@ class BoardUtils { return board } + static parse(board: number[][]) { + return board.map((ship) => ship.map(Field)) + } - static hashBoard(board: Field[][]) { + static hash(board: Field[][]) { return Poseidon.hash(board.flat()); } } class BoardCircuit { - //TODO Add meaningful error logs - static validateShipInRange(ship: Field[], shipLength: number) { + static validateShipInRange(ship: Field[], shipLength: number, errorMessage: string) { // horizontal check: z=ship[2]=0 const checkHorizontal = () => { - ship[0].add(shipLength).assertLessThan(10); - ship[1].assertLessThan(10); - return Bool(true) + const hCheck = ship[0].add(shipLength).lessThan(10); + const vCheck = ship[1].lessThan(10); + return hCheck.and(vCheck) } // vertical check: z=ship[2]=1 const checkVertical = () => { - ship[0].assertLessThan(10); - ship[1].add(shipLength).assertLessThan(10); - return Bool(true) + const hCheck = ship[0].lessThan(10); + const vCheck = ship[1].add(shipLength).lessThan(10); + return hCheck.and(vCheck) } // verify z is binary - ship[2].assertLessThanOrEqual(1); + ship[2].assertLessThanOrEqual(1, 'Coordinate z should be 1 or 0!'); const isInRange = Provable.if(ship[2].equals(1), checkVertical(), checkHorizontal()); - isInRange.assertTrue; + isInRange.assertTrue(errorMessage); } - static placeShip = (ship: Field[], shipLength: number, boardMap: Field[]) => { + static placeShip(ship: Field[], shipLength: number, boardMap: Field[], errorMessage: string) { const increment = Provable.if(ship[2].equals(1), Field(10), Field(0)); let index = ship[0].add(ship[1].mul(10)); for(let i=0; i item.equals(coordinate).toBoolean()); - Provable.log('check: ', check) - Bool(check).assertFalse('Ship collision detected!'); + // Provable.log('check: ', check) + Bool(check).assertFalse(errorMessage); boardMap.push(coordinate); } return boardMap @@ -85,36 +87,40 @@ class BoardCircuit { let boardMap: Field[] = []; for (let i = 0; i < 5; i++) { // range check - BoardCircuit.validateShipInRange(ships[i], shipLength[i]); + BoardCircuit.validateShipInRange(ships[i], shipLength[i], `Ship${i+1} is out of board range!`); // collision check - boardMap = BoardCircuit.placeShip(ships[i], shipLength[i], boardMap); - Provable.log('boardMap: ', boardMap); + boardMap = BoardCircuit.placeShip(ships[i], shipLength[i], boardMap,`Collision occured when placing Ship${i+1}!`); } } static validateBoard(serializedBoard: Field) { const board = BoardUtils.deserializeBoard(serializedBoard); this.validateShipsLocation(board); - const boardHash = BoardUtils.hashBoard(board); + const boardHash = BoardUtils.hash(board); return boardHash } } -const player1Board = [ - [4, 0, 0], - [0, 1, 0], - [0, 2, 0], - [0, 3, 0], - [0, 4, 0], -]; +// const player1Board = [ +// [0, 1, 0], +// [0, 1, 0], +// [0, 2, 0], +// [0, 3, 0], +// [0, 4, 0], +// ]; -const serializedBoard = BoardUtils.serializeBoard(player1Board); -let deserializeBoard = BoardUtils.deserializeBoard(serializedBoard).flat().map(x => Number(x.toBigInt())); +// const serializedBoard = BoardUtils.serializeBoard(player1Board); +// let deserializeBoard = BoardUtils.deserializeBoard(serializedBoard).flat().map(x => Number(x.toBigInt())); -console.log('initial board: ', player1Board.flat()); -console.log('deserialized board: ', deserializeBoard); +// console.log('initial board: ', player1Board.flat()); +// console.log('deserialized board: ', deserializeBoard); -console.log('collision check: ', BoardCircuit.validateShipsLocation(player1Board.map(ship => ship.map(Field)))) +// //TODO Add board integration tests +// //TODO? confirm board circuit is provable -//TODO Add board integration tests +// console.time('board witness'); +// Provable.runAndCheck(() => { +// BoardCircuit.validateShipsLocation(player1Board.map(ship => ship.map(Field))) +// }); +// console.timeEnd('board witness'); \ No newline at end of file