Skip to content

Commit

Permalink
Fix bugs and have game instructions (#36)
Browse files Browse the repository at this point in the history
* Updated GuessingGame contract

* Fix a bug on arithmetic overflow in smart contract computation

* Added more condition on the control display

* Have game instructions
  • Loading branch information
jimmychu0807 authored Sep 18, 2024
1 parent dd42c20 commit 728f97f
Show file tree
Hide file tree
Showing 11 changed files with 253 additions and 101 deletions.
24 changes: 11 additions & 13 deletions apps/contracts/contracts/GuessingGame.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {IGuessingGame} from "./interfaces/IGuessingGame.sol";
import {ICommitmentVerifier} from "./interfaces/ICommitmentVerifier.sol";
import {IOpeningVerifier} from "./interfaces/IOpeningVerifier.sol";
import {MIN_NUM, MAX_NUM, MIN_PLAYERS_TO_START, ROUNDS_TO_WIN} from "./base/Constants.sol";
// import "hardhat/console.sol";

contract GuessingGame is IGuessingGame, Ownable {
ICommitmentVerifier public commitmentVerifier;
Expand Down Expand Up @@ -82,6 +83,7 @@ contract GuessingGame is IGuessingGame, Ownable {
GameView({
players: game.players,
currentRound: game.currentRound,
roundWinners: game.roundWinners,
state: game.state,
winner: game.winner,
startTime: game.startTime,
Expand Down Expand Up @@ -113,14 +115,6 @@ contract GuessingGame is IGuessingGame, Ownable {
return game.openings[round][player];
}

function getPlayerGameRoundsWon(
uint32 gameId,
address player
) public view validGameId(gameId) returns (uint8) {
Game storage game = games[gameId];
return game.playerRoundsWon[player];
}

/**
* Internal helper functions
**/
Expand Down Expand Up @@ -310,18 +304,19 @@ contract GuessingGame is IGuessingGame, Ownable {
uint64 sum = 0;
for (uint64 i = 0; i < game.players.length; i++) {
address p = game.players[i];
sum += game.openings[round][p] * 1000;
sum += uint64(game.openings[round][p]) * 1000;
}
uint64 average = uint64(sum / playerCnt);

// Finding the winner
// When two openings have the same distance from the average, the round is draw.
address minPlayer = game.players[0];
uint64 minDiff = _diff(game.openings[round][minPlayer] * 1000, average);
uint64 minDiff = _diff(uint64(game.openings[round][minPlayer]) * 1000, average);

bool draw = false;
for (uint64 i = 1; i < game.players.length; i++) {
address p = game.players[i];
uint64 diff = _diff(game.openings[round][p] * 1000, average);
uint64 diff = _diff(uint64(game.openings[round][p]) * 1000, average);
if (diff == minDiff) {
draw = true;
} else if (diff < minDiff) {
Expand All @@ -335,11 +330,14 @@ contract GuessingGame is IGuessingGame, Ownable {
if (draw) {
emit RoundDraw(gameId, round);
} else {
game.playerRoundsWon[minPlayer] += 1;
game.roundWinners.push(minPlayer);
emit RoundWinner(gameId, round, minPlayer, game.openings[round][minPlayer]);
}

uint8 roundsWon = game.playerRoundsWon[minPlayer];
uint8 roundsWon = 0;
for (uint8 i = 0; i < game.roundWinners.length; ++i) {
if (game.roundWinners[i] == minPlayer) ++roundsWon;
}

// update the game.state or end the game
if (roundsWon == ROUNDS_TO_WIN) {
Expand Down
3 changes: 2 additions & 1 deletion apps/contracts/contracts/interfaces/IGuessingGame.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface IGuessingGame {
// game players. The first player is the game host
address[] players;
uint8 currentRound;
mapping(address => uint8) playerRoundsWon;
address[] roundWinners;
// player bid list
mapping(uint8 => mapping(address => Commitment)) commitments;
mapping(uint8 => mapping(address => uint16)) openings;
Expand All @@ -25,6 +25,7 @@ interface IGuessingGame {
struct GameView {
address[] players;
uint8 currentRound;
address[] roundWinners;
GameState state;
address winner;
uint256 startTime;
Expand Down
58 changes: 41 additions & 17 deletions apps/contracts/test/GuessingGame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { GuessingGame } from "../typechain-types";
const COMMITMENT_VERIFIER_BASEPATH = "./artifacts/circuits/commit-1-100";
const OPENING_VERIFIER_BASEPATH = "./artifacts/circuits/open-1-100";

const defaultSubmissions = { host: 1, bob: 3, charlie: 5 };

describe("GuessingGame", () => {
async function deployContractsCleanSlate() {
const contracts = await run("deploy", { logs: false });
Expand Down Expand Up @@ -45,7 +47,7 @@ describe("GuessingGame", () => {
return { contracts, players: { host, bob, charlie, dave } };
}

async function deployContractsRoundOpen() {
async function deployContractsRoundOpen(submissions = defaultSubmissions) {
const { contracts, players } = await deployContractsGameStarted();
const { host, bob, charlie } = players;
const { gameContract } = contracts;
Expand All @@ -55,9 +57,9 @@ describe("GuessingGame", () => {

// generate proofs
const inputs = {
host: { in: 1, rand },
bob: { in: 3, rand },
charlie: { in: 5, rand },
host: { in: submissions.host, rand },
bob: { in: submissions.bob, rand },
charlie: { in: submissions.charlie, rand },
};

const fullProofs = await Promise.all([
Expand Down Expand Up @@ -91,8 +93,8 @@ describe("GuessingGame", () => {
return { contracts, players, inputs };
}

async function deployContractsRoundEnd() {
const { contracts, players, inputs } = await deployContractsRoundOpen();
async function deployContractsRoundEnd(submissions = defaultSubmissions) {
const { contracts, players, inputs } = await deployContractsRoundOpen(submissions);
const { host, bob, charlie } = players;
const { gameContract } = contracts;
const GAME_ID = 0;
Expand Down Expand Up @@ -120,17 +122,21 @@ describe("GuessingGame", () => {
return { contracts, players, inputs };
}

async function deployContractsGameAlmostEnd() {
async function deployContractsRoundEnd_30_80_20() {
return await deployContractsRoundEnd({ host: 30, bob: 80, charlie: 20 });
}

async function deployContractsGameAlmostEnd(submissions = defaultSubmissions) {
const { contracts, players } = await deployContractsGameStarted();
const { host, bob, charlie } = players;
const { gameContract } = contracts;
const GAME_ID = 0;
const rand = randomInt(281474976710655);

const inputs = {
host: { in: 1, rand },
bob: { in: 3, rand },
charlie: { in: 5, rand },
host: { in: submissions.host, rand },
bob: { in: submissions.bob, rand },
charlie: { in: submissions.charlie, rand },
};

// generate commitment proofs
Expand Down Expand Up @@ -438,11 +444,9 @@ describe("GuessingGame", () => {
.withArgs(GAME_ID, 0, bob.address, inputs.bob.in);

// Check Bob has won
const roundsWon = await gameContract.getPlayerGameRoundsWon(GAME_ID, bob.address);
expect(roundsWon).to.be.equal(1);

// check gamestate back to RoundCommit
const game = await gameContract.getGame(GAME_ID);

expect(game.roundWinners[game.roundWinners.length - 1]).to.be.equal(bob.address);
expect(game.state).to.be.equal(GameState.RoundCommit);
});
});
Expand All @@ -460,11 +464,31 @@ describe("GuessingGame", () => {
.withArgs(GAME_ID, bob.address);

const game = await gameContract.getGame(GAME_ID);
expect(game.winner).to.be.equal(bob.address);
expect(game.state).to.be.equal(GameState.GameEnd);
expect(game.winner).to.be.equal(bob.address);

const winnerRoundsWon = game.roundWinners.filter((p) => p === bob.address).length;
expect(winnerRoundsWon).to.be.equal(ROUNDS_TO_WIN);
});
});

describe("L Testing concludeRound", () => {
it("should calc properly with (30, 80, 20)", async () => {
const { contracts, players, inputs } = await loadFixture(deployContractsRoundEnd_30_80_20);
const { gameContract } = contracts;
const { host, bob } = players;
const GAME_ID = 0;

const roundsWon = await gameContract.getPlayerGameRoundsWon(GAME_ID, bob.address);
expect(roundsWon).to.be.equal(ROUNDS_TO_WIN);
// Expect Bob be the winner
await expect(gameContract.concludeRound(GAME_ID))
.to.emit(gameContract, "RoundWinner")
.withArgs(GAME_ID, 0, host.address, inputs.host.in);

// Check Bob has won
const game = await gameContract.getGame(GAME_ID);

expect(game.roundWinners[game.roundWinners.length - 1]).to.be.equal(host.address);
expect(game.state).to.be.equal(GameState.RoundCommit);
});
});
});
33 changes: 7 additions & 26 deletions apps/web/contract-artifacts/GuessingGame.json

Large diffs are not rendered by default.

9 changes: 1 addition & 8 deletions apps/web/src/app/game/[gameId]/OpenCommitmentActionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,7 @@ export default function OpenCommitmentActionPanel({
/>
</InputGroup>
</FormControl>
<Button
display="block"
margin="0.5em auto"
mt={4}
colorScheme="yellow"
type="submit"
isLoading={isPending}
>
<Button colorScheme="yellow" type="submit" isLoading={isPending}>
Open Commitment
</Button>
</VStack>
Expand Down
Loading

0 comments on commit 728f97f

Please sign in to comment.