Skip to content

Commit

Permalink
Add test cases to showcase that players are no longer able to select …
Browse files Browse the repository at this point in the history
…the same target multiple times
  • Loading branch information
Shigoto-dev19 committed Feb 29, 2024
1 parent 93c974f commit 9a33ba3
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 33 deletions.
52 changes: 36 additions & 16 deletions src/Battleships.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
});
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]);
});
Expand All @@ -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
27 changes: 10 additions & 17 deletions src/Battleships.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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,
);
Expand Down Expand Up @@ -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);

Expand All @@ -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

0 comments on commit 9a33ba3

Please sign in to comment.