UNPKG

@navigators-exploration-team/mina-mastermind

Version:

[![Integration tests](https://github.com/navigators-exploration-team/recursive-mastermind-zkApp/actions/workflows/ci.yml/badge.svg)](https://github.com/navigators-exploration-team/recursive-mastermind-zkApp/actions/workflows/ci.yml)

192 lines 7.58 kB
var _a; import { Field, Bool, Provable, UInt64, UInt32, Struct, UInt8 } from 'o1js'; import { MAX_ATTEMPTS } from './constants.js'; export { Combination, Clue, GameState }; /** * `Combination` is a class that represents a combination of digits for both the secret combination and the guesses. * @param digits - An array of 4 unique digits between 1 and 7. * * @method `from` - Creates a new Combination instance from an array of numbers. * @method `toBits` - Converts the combination to a bit array. * @method `compress` - Compresses the combination into a single field element. * @method `decompress` - Decompresses the combination from a single field element. * @method `validate` - Validates the combination to ensure all digits are unique and within the range [1, 7]. * @method `updateHistory` - Updates the history of combinations with the new combination. * @method `getElementFromHistory` - Retrieves an element from the history based on the index. */ class Combination extends Struct({ digits: Provable.Array(Field, 4), }) { static from(numbers) { if (numbers.length !== 4) { throw new Error('Combination must have exactly 4 digits'); } const combination = new this({ digits: numbers.map((number) => Field(number)), }); combination.validate(); return combination; } toBits() { return this.digits.map((digit) => digit.toBits(3)).flat(); } compress() { return Field.fromBits(this.toBits()); } static decompress(compressedCombination) { const bits = compressedCombination.toBits(12); return new this({ digits: [ Field.fromBits(bits.slice(0, 3)), Field.fromBits(bits.slice(3, 6)), Field.fromBits(bits.slice(6, 9)), Field.fromBits(bits.slice(9, 12)), ], }); } validate() { for (let i = 0; i < 4; i++) { this.digits[i] .equals(0) .or(this.digits[i].greaterThan(7)) .assertFalse(`Combination digit ${i + 1} is not in range [1, 7]!`); } for (let i = 1; i < 4; i++) { for (let j = i; j < 4; j++) { this.digits[i - 1].assertNotEquals(this.digits[j], `Combination digit ${j + 1} is not unique!`); } } } static decompressHistory(compressedHistory) { const historyBits = compressedHistory.toBits(12 * MAX_ATTEMPTS); const historyBitPacks = []; for (let i = 0; i < historyBits.length; i += 12) { historyBitPacks.push(historyBits.slice(i, i + 12)); } return historyBitPacks.map((bits) => Field.fromBits(bits)); } static updateHistory(newCombination, compressedHistory, index) { const combinationHistory = this.decompressHistory(compressedHistory); const newCombinationCompressed = newCombination.compress(); for (let i = 0; i < MAX_ATTEMPTS; i++) { combinationHistory[i] = Provable.if(index.equals(i), newCombinationCompressed, combinationHistory[i]); } const updatedHistory = combinationHistory.map((c) => c.toBits(12)).flat(); return Field.fromBits(updatedHistory); } static getElementFromHistory(compressedHistory, index) { let combinationHistory = this.decompressHistory(compressedHistory); let element = Field(0); for (let i = 0; i < MAX_ATTEMPTS; i++) { element = Provable.if(index.equals(i), combinationHistory[i], element); } return Combination.decompress(element); } } /** * `Clue` is a class that represents the clue given by the codeMaster after each guess. * @param hits - The number of correct digits in the correct position. * @param blows - The number of correct digits in the wrong position. * * @method `compress` - Compresses the clue into a single field element. * @method `decompress` - Decompresses the clue from a single field element. * @method `giveClue` - Generates a clue based on the guess and solution. * @method `isSolved` - Checks if the game is solved based on the clue. * @method `updateHistory` - Updates the history of clues with the new clue. */ class Clue extends Struct({ hits: Field, blows: Field, }) { compress() { return Field.fromBits(this.hits.toBits(3).concat(this.blows.toBits(3))); } static decompress(compressedClue) { const bits = compressedClue.toBits(6); return new this({ hits: Field.fromBits(bits.slice(0, 3)), blows: Field.fromBits(bits.slice(3, 6)), }); } static giveClue(guess, solution) { let hits = Field(0); let blows = Field(0); for (let i = 0; i < 4; i++) { for (let j = 0; j < 4; j++) { const isEqual = guess[i].equals(solution[j]).toField(); if (i === j) { hits = hits.add(isEqual); } else { blows = blows.add(isEqual); } } } return new this({ hits, blows }); } isSolved() { return this.hits.equals(4); } static updateHistory(newClue, compressedHistory, index) { const historyBits = compressedHistory.toBits(6 * MAX_ATTEMPTS); const historyBitPacks = []; for (let i = 0; i < historyBits.length; i += 6) { historyBitPacks.push(historyBits.slice(i, i + 6)); } let clueHistory = historyBitPacks.map((bits) => Field.fromBits(bits)); const newClueCompressed = newClue.compress(); for (let i = 0; i < MAX_ATTEMPTS; i++) { clueHistory[i] = Provable.if(index.equals(i), newClueCompressed, clueHistory[i]); } const updatedHistory = clueHistory.map((c) => c.toBits(6)).flat(); return Field.fromBits(updatedHistory); } } /** * `GameState` is a class that represents the state of the game. * @param rewardAmount - The total reward amount for the game. * @param finalizeSlot - The slot at which the game is finalized. * @param turnCount - The number of turns taken in the game. * @param isSolved - A flag indicating whether the game is solved or not. * * @method `pack` - Packs the game state into a single field element. * @method `unpack` - Unpacks the game state from a single field element. */ class GameState extends Struct({ rewardAmount: UInt64, finalizeSlot: UInt32, turnCount: UInt8, isSolved: Bool, }) { pack() { const { rewardAmount, finalizeSlot, turnCount, isSolved } = this; const serializedState = [ rewardAmount.toBits(), finalizeSlot.toBits(), turnCount.toBits(), isSolved.toField().toBits(1), ].flat(); return Field.fromBits(serializedState); } static unpack(serializedState) { const bits = serializedState.toBits(); const rewardAmount = UInt64.fromBits(bits.slice(0, 64)); const finalizeSlot = UInt32.fromBits(bits.slice(64, 96)); const turnCount = UInt8.fromBits(bits.slice(96, 104)); const isSolved = bits[104]; return new this({ rewardAmount, finalizeSlot, turnCount, isSolved, }); } } _a = GameState; GameState.default = new _a({ rewardAmount: UInt64.from(1e9), finalizeSlot: UInt32.from(0), turnCount: UInt8.from(0), isSolved: Bool(false), }); //# sourceMappingURL=utils.js.map