From 9a33ba389723fd4d0aca2c5393e67dec66d3a4e4 Mon Sep 17 00:00:00 2001 From: Shigoto-dev19 Date: Thu, 29 Feb 2024 23:23:55 +0100 Subject: [PATCH] Add test cases to showcase that players are no longer able to select the same target multiple times --- src/Battleships.test.ts | 52 ++++++++++++++++++++++++++++------------- src/Battleships.ts | 27 ++++++++------------- 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/src/Battleships.test.ts b/src/Battleships.test.ts index d99d5e9..8c21359 100644 --- a/src/Battleships.test.ts +++ b/src/Battleships.test.ts @@ -14,12 +14,7 @@ import { MerkleTree, Bool, } from 'o1js'; -import { AttackUtils, BoardUtils } from './client'; - -export { - localDeploy, - initializeGame, -} +import { AttackUtils, BoardUtils } from './provableUtils'; const proofsEnabled = false; @@ -340,7 +335,8 @@ describe('Battleships Game Tests', () => { const serializedBoard = BoardUtils.serialize(hostBoard); const serializedTarget = AttackUtils.serializeTarget([3, 4]); - + console.log("serializedTarget: ", serializedTarget.toBigInt()); + console.log('deserialized target: ', AttackUtils.deserializeTarget(serializedTarget).map(f => f.toBigInt())); let firstTurnTx = await Mina.transaction(hostKey.toPublicKey(), () => { zkapp.firstTurn(serializedTarget, serializedBoard, targetWitness); }); @@ -404,7 +400,7 @@ describe('Battleships Game Tests', () => { expect(rejectedAttackTx).rejects.toThrowError(errorMessage); } - async function testValidAttack(playerKey: PrivateKey, board: number[][], shot: number[], expectedHitResult: boolean, expectedHitHistory: number[]) { + async function testValidAttack(playerKey: PrivateKey, board: number[][], target: number[], expectedHitResult: boolean, expectedHitHistory: number[]) { let index = zkapp.turns.get().toBigInt(); let wTarget = targetTree.getWitness(index); let targetWitness = new TargetMerkleWitness(wTarget); @@ -413,7 +409,7 @@ describe('Battleships Game Tests', () => { let hitWitness = new HitMerkleWitness(hTarget); const serializedBoard = BoardUtils.serialize(board); - const serializedTarget = AttackUtils.serializeTarget(shot); + const serializedTarget = AttackUtils.serializeTarget(target); let attackTx = await Mina.transaction(playerKey.toPublicKey(), () => { zkapp.attack(serializedTarget, serializedBoard, targetWitness, hitWitness); @@ -442,7 +438,7 @@ describe('Battleships Game Tests', () => { // fetch the updated serializedHitHistory on-chain const serializedHitHistory = zkapp.serializedHitHistory.get(); - const hitHistory = AttackUtils.deserializeHitHistory(serializedHitHistory); + const hitHistory = AttackUtils.deserializeHitHistory(serializedHitHistory)[0]; expect(hitHistory).toEqual(expectedHitHistory.map(Field)); // fetch & assert the updated turn counter on-chain @@ -507,7 +503,7 @@ describe('Battleships Game Tests', () => { await testInvalidAttack(joinerKey, intruderBoard, errorMessage); }); - // player2 turn --> turn = 1 + // player2 turn --> turn = 1 --> report Player1 Miss it('should accept a valid attack TX and update state on-chain: 1st check', async () => { await testValidAttack(joinerKey, joinerBoard, [0, 0], false, [0, 0]); }); @@ -518,21 +514,45 @@ describe('Battleships Game Tests', () => { await testInvalidAttack(hostKey, intruderBoard, errorMessage); }); - // player1 turn --> turn = 2 + // player1 turn --> turn = 2 --> report player2 Hit it('should accept a valid attack TX and update state on-chain: 2nd check', async () => { await testValidAttack(hostKey, hostBoard, [6, 8], true, [0, 1]); }); - // player2 turn --> turn = 3 + it('should reject player2 selecting a nullified target that caused a hit', async () => { + const errorMessage = 'Invalid Target! Please select a unique target!' + await testInvalidAttack(joinerKey, joinerBoard, errorMessage, false, false, [0, 0]); + }); + + // player2 turn --> turn = 3 --> report player1 Hit it('should accept a valid attack TX and update state on-chain: 3rd check', async () => { await testValidAttack(joinerKey, joinerBoard, [0, 1], true, [1, 1]); }); - // player1 turn --> turn = 4 + it('should reject player1 selecting a nullified target that caused a hit', async () => { + const errorMessage = 'Invalid Target! Please select a unique target!' + await testInvalidAttack(hostKey, hostBoard, errorMessage, false, false, [6, 8]); + }); + + // player1 turn --> turn = 4 --> report player2 Hit it('should accept a valid attack TX and update state on-chain: 4th check', async () => { await testValidAttack(hostKey, hostBoard, [3, 7], true, [1, 2]); }); + + // player2 turn --> turn = 5 --> report player1 Miss + it('should accept a valid attack TX and update state on-chain: 5th check', async () => { + await testValidAttack(joinerKey, joinerBoard, [9, 0], false, [1, 2]); + }); + + // player1 turn --> turn = 6 --> report player2 Miss + it('should accept player1 sending a valid attack TX choosing a previous target that was a MISS: 6th check', async () => { + await testValidAttack(hostKey, hostBoard, [3, 7], false, [1, 2]); + }); + + // player2 turn --> turn = 7 --> report player1 Miss + it('should accept player2 sending a valid attack TX choosing a previous target that was a MISS: 6th check', async () => { + await testValidAttack(joinerKey, joinerBoard, [9, 0], false, [1, 2]); + }); }); -}); +}); -//TODO: Redirect full game test in game.ts that simulated win/lose of one of the players diff --git a/src/Battleships.ts b/src/Battleships.ts index 5cd943a..0d7d89d 100644 --- a/src/Battleships.ts +++ b/src/Battleships.ts @@ -139,6 +139,7 @@ class Battleships extends SmartContract { let turns = this.turns.getAndRequireEquals(); turns.assertGreaterThan(0, "Please wait for the host to play the opening shot first!") + const isHost = turns.value.isEven(); let player1Id = this.player1Id.getAndRequireEquals(); let player2Id = this.player2Id.getAndRequireEquals(); @@ -148,7 +149,7 @@ class Battleships extends SmartContract { * - NOTE: The host(player1) has the privilege to attack first. */ let currentPlayerId = Provable.if( - turns.value.isEven(), + isHost, player1Id, player2Id, ); @@ -213,31 +214,27 @@ class Battleships extends SmartContract { // update hit count history & serialize let updatedSerializedHitCountHistory = Provable.if( - turns.value.isEven(), + isHost, AttackUtils.serializeHitCountHistory([player1HitCount, player2HitCount.add(adversaryHitResult.toField())]), AttackUtils.serializeHitCountHistory([player1HitCount.add(adversaryHitResult.toField()), player2HitCount]), ); const playerTarget = AttackUtils.deserializeTarget(serializedTarget); - let isNullifiedCheck = Provable.if( - turns.value.isEven(), + let isNullified = Provable.if( + isHost, AttackUtils.validateHitTargetUniqueness(playerTarget, player1HitTargets), AttackUtils.validateHitTargetUniqueness(playerTarget, player2HitTargets), ); - isNullifiedCheck.assertFalse('Please select a unique target!') + isNullified.assertFalse('Invalid Target! Please select a unique target!') let updatedSerializedHitTargetHistory = Provable.if( - turns.value.isEven(), + isHost, AttackUtils.serializeHitTargetHistory([player1HitTargets, AttackUtils.updateHitTargetHistory(adversaryTarget, adversaryHitResult, player2HitTargets, player2HitCount)]), AttackUtils.serializeHitTargetHistory([AttackUtils.updateHitTargetHistory(adversaryTarget, adversaryHitResult, player1HitTargets, player1HitCount), player2HitTargets]), ); - //TODO check target uniqueness -> ok! - //TODO update hitHistory -> ok! - //TODO update hit target -> ok! - //TODO should serialize the same target and add one - //? update the on-chain hitHistory + // update the on-chain hit history const updatedSerializedHitHistory = AttackUtils.serializeHitHistory(updatedSerializedHitCountHistory, updatedSerializedHitTargetHistory); this.serializedHitHistory.set(updatedSerializedHitHistory); @@ -259,18 +256,14 @@ class Battleships extends SmartContract { } //TODO Reset game when finished(keep state transition in mind) -//TODO Add nullifer for target to prevent player for attacking the same target more than once //TODO Add salt when generating player ID --> we want player to be able to reuse same board without generating the same ID //TODO? Emit event following game actions -//? 1. Save adversary encrypted boards on-chain -//? 2. encryption privateKey should only be knwon to the zkapp itself -//? 3. the key can be set after two players join - //TODO in the target serialized --> proivde bits to store hit target --> we need to prevent a player from hitting the same target and keep claiming hits? //TODO --> the good thing we will only prevent the player from hitting hit target and the missed ones won't affect the game //TODO --> better than nullifier semantics //TODO --> add one after serialized to distinguish value from initial value //TODO have a last check on zkapp error handling & messages -//TODO have consistent serialization for target + +//players from hitting the same target multiple times \ No newline at end of file