├── .env.example
├── .gitattributes
├── .gitignore
├── README.md
├── contracts
    ├── IRPSGameV2.sol
    ├── RPSGame.sol
    ├── RPSGameFactory.sol
    └── RPSToken.sol
├── deploy
    └── RPSToken.ts
├── frontend
    ├── .gitignore
    ├── README.md
    ├── craco.config.js
    ├── package-lock.json
    ├── package.json
    ├── pnpm-lock.yaml
    ├── public
    │   ├── favicon.ico
    │   ├── index.html
    │   ├── logo192.png
    │   ├── logo512.png
    │   ├── manifest.json
    │   └── robots.txt
    ├── src
    │   ├── App.test.tsx
    │   ├── App.tsx
    │   ├── RPSGame.d.ts
    │   ├── RPSGameFactory.d.ts
    │   ├── abis
    │   │   ├── RPSGame.json
    │   │   └── RPSGameFactory.json
    │   ├── components
    │   │   ├── Button.tsx
    │   │   ├── CircularLoader.tsx
    │   │   ├── DeployedContracts.tsx
    │   │   ├── Game.tsx
    │   │   ├── GameActionInfoCard.tsx
    │   │   ├── GameStatsCard.tsx
    │   │   ├── GlobalMessage.tsx
    │   │   ├── HiddenMove.tsx
    │   │   ├── InputField.tsx
    │   │   ├── Leaderboard.tsx
    │   │   ├── Loader.tsx
    │   │   ├── OptionButton.module.css
    │   │   ├── OptionButton.tsx
    │   │   ├── PlayerCard.tsx
    │   │   ├── Playground.tsx
    │   │   ├── RevealMove.tsx
    │   │   ├── SubmitMove.tsx
    │   │   └── layout
    │   │   │   ├── Footer.tsx
    │   │   │   └── Navbar.tsx
    │   ├── context
    │   │   ├── MessageContext.tsx
    │   │   ├── RPSGameContractContext
    │   │   │   ├── actions.ts
    │   │   │   ├── contractContext.d.ts
    │   │   │   ├── index.tsx
    │   │   │   ├── reducer.ts
    │   │   │   └── state.ts
    │   │   ├── RPSGameFactoryContext
    │   │   │   └── index.tsx
    │   │   ├── TransactionContext.tsx
    │   │   └── WalletContext.tsx
    │   ├── helpers.ts
    │   ├── images
    │   │   ├── bg-pentagon.svg
    │   │   ├── bg-triangle.svg
    │   │   ├── favicon-32x32.png
    │   │   ├── icon-close.svg
    │   │   ├── icon-lizard.svg
    │   │   ├── icon-paper.svg
    │   │   ├── icon-rock.svg
    │   │   ├── icon-scissors.svg
    │   │   ├── icon-spock.svg
    │   │   ├── image-rules-bonus.svg
    │   │   ├── image-rules.svg
    │   │   ├── logo-bonus.svg
    │   │   └── logo.svg
    │   ├── index.css
    │   ├── index.tsx
    │   ├── provider.ts
    │   ├── react-app-env.d.ts
    │   ├── reportWebVitals.ts
    │   └── setupTests.ts
    ├── tailwind.config.js
    └── tsconfig.json
├── hardhat.config.ts
├── helpers
    └── env_helpers.ts
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── scripts
    ├── gameArgs.js
    ├── rpsFactory.js
    ├── rpsGame.js
    └── rpsToken.js
├── test
    ├── RPSGame.test.ts
    ├── RPSGameFactory.test.ts
    └── RPSToken.test.ts
├── typechain
    ├── ERC20.d.ts
    ├── IERC20.d.ts
    ├── IERC20Metadata.d.ts
    ├── IRPSGameV2.d.ts
    ├── RPSGame.d.ts
    ├── RPSGameFactory.d.ts
    ├── RPSGameV2.d.ts
    ├── RPSToken.d.ts
    ├── factories
    │   ├── ERC20__factory.ts
    │   ├── IERC20Metadata__factory.ts
    │   ├── IERC20__factory.ts
    │   ├── IRPSGameV2__factory.ts
    │   ├── RPSGameFactory__factory.ts
    │   ├── RPSGameV2__factory.ts
    │   ├── RPSGame__factory.ts
    │   └── RPSToken__factory.ts
    └── index.ts
└── yarn.lock
/.env.example:
--------------------------------------------------------------------------------
1 | PRIVATE_KEY=""
2 | INFURA_KEY=""
3 | ETHERSCAN_KEY=""
4 | 
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | 
3 | #hardhat files
4 | cache
5 | artifacts
6 | frontend/src/hardhat
7 | 
8 | .env
9 | 
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # Rock Papers Scissors game Implementation
 2 | 
 3 | 
 4 | ## Version 1
 5 | 
 6 | ### Features
 7 | - Multiple players can create an instance of game and play ✅ 
 8 | - can deposit bet amount if player ✅ 
 9 | - can submit move ✅ 
10 | - Incentivizes the winner ✅ 
11 | - Should reset the game if moves are same ✅ 
12 | - Submit signed move with salt and reveal later.✅ 
13 | 
14 | ## Improvements that can be done
15 |   - Players should be able to bet with any ERC20 currencies.
16 |   - Winner can mint a NFT.
17 |   - Add time limit to reveal move and punish the non cooperative player by incentivizing cooperative player.
18 | 
19 | 
20 | ## Contract Address
21 | [Ether scan - Rinkeby](https://rinkeby.etherscan.io/address/0xb0f9Dfb7c06E2e9b9BaC5Ac397D686C64be87e7B)
22 | 
--------------------------------------------------------------------------------
/contracts/IRPSGameV2.sol:
--------------------------------------------------------------------------------
 1 | //SPDX-License-Identifier: MIT
 2 | pragma solidity ^0.8.0;
 3 | 
 4 | interface IRPSGameV2 {
 5 |     enum Move {
 6 |         None,
 7 |         Rock,
 8 |         Paper,
 9 |         Scissors
10 |     }
11 |     enum GameState {
12 |         Initialized,
13 |         Open,
14 |         Progress
15 |     }
16 |     struct Player {
17 |         address addr;
18 |         uint256 balance;
19 |         bytes32 move;
20 |         bool revealed;
21 |     }
22 |     struct Game {
23 |         Player playerA;
24 |         Player playerB;
25 |         address winner;
26 |         GameState gameState;
27 |         uint256 betAmount;
28 |     }
29 |     event Draw();
30 |     event Challenge(address indexed _from, address indexed _to);
31 |     event DepositSuccess(address indexed _from, uint256 value);
32 |     event ResetGame();
33 |     event Replay(address indexed challanger, address indexed _player);
34 |     event AcceptChallenge(address indexed _challenger);
35 |     event GameStarted(address indexed player1, address indexed player2);
36 |     event GameEnded(address indexed _winner);
37 | 
38 |     function depositBet() external payable;
39 | 
40 |     // Should move submitted be bytes 32 hash of signed message?
41 |     function submitMove(bytes32 _moveHash) external;
42 | 
43 |     function revealMove(Move _move, string memory salt) external;
44 | 
45 |     function challenge() external;
46 | 
47 |     function withdrawFund() external;
48 | 
49 |     // Internal function
50 |     function resetGame() external;
51 | 
52 |     function getWinner() external view returns (address);
53 | 
54 |     function announceWinner() external;
55 | 
56 |     function icentivize() external;
57 | }
58 | 
--------------------------------------------------------------------------------
/contracts/RPSGame.sol:
--------------------------------------------------------------------------------
  1 | //SPDX-License-Identifier: MIT
  2 | pragma solidity ^0.8.0;
  3 | 
  4 | contract RPSGame {
  5 |     enum GameStage {
  6 |         Open,
  7 |         BetsDeposited,
  8 |         MovesSubmitted,
  9 |         MoveRevealed,
 10 |         Completed
 11 |     }
 12 | 
 13 |     enum Move {
 14 |         None,
 15 |         Rock,
 16 |         Paper,
 17 |         Scissors
 18 |     }
 19 |     Player public playerA;
 20 |     Player public playerB;
 21 |     uint256 public betAmount;
 22 |     GameStage public gameStage;
 23 |     address public winner;
 24 | 
 25 |     struct Player {
 26 |         Move move;
 27 |         bytes32 hashedMove;
 28 |         uint256 balance;
 29 |         address addr;
 30 |         bool submitted;
 31 |         bool revealed;
 32 |     }
 33 | 
 34 |     constructor(
 35 |         uint256 _betAmount,
 36 |         address _player,
 37 |         address _opponent
 38 |     ) payable {
 39 |         require(_player != _opponent, "You cannot play against yourself");
 40 |         betAmount = _betAmount;
 41 |         playerA.addr = _player;
 42 |         playerB.addr = _opponent;
 43 |     }
 44 | 
 45 |     event GameStageChanged(GameStage gameStage);
 46 |     event ResetGame();
 47 |     event Winner(address indexed _winner);
 48 |     event Deposit(address indexed depositor);
 49 |     event GameComplete();
 50 |     event SubmitMove(address indexed player);
 51 |     event RevealMove(address indexed player);
 52 |     event Withdraw(address indexed player, uint256 amount);
 53 | 
 54 |     modifier isPlayer() {
 55 |         require(
 56 |             msg.sender == playerA.addr || msg.sender == playerB.addr,
 57 |             "RPSGame: Not a valid player"
 58 |         );
 59 |         _;
 60 |     }
 61 | 
 62 |     function getPlayer(address _player) external view returns (Player memory) {
 63 |         if (playerA.addr == _player) {
 64 |             return playerA;
 65 |         } else {
 66 |             return playerB;
 67 |         }
 68 |     }
 69 | 
 70 |     function depositBet() external payable isPlayer {
 71 |         require(
 72 |             msg.value >= betAmount,
 73 |             "RPSGame: Balance not enough, Send more fund"
 74 |         );
 75 | 
 76 |         msg.sender == playerA.addr
 77 |             ? playerA.balance += msg.value
 78 |             : playerB.balance += msg.value;
 79 |         emit Deposit(msg.sender);
 80 | 
 81 |         if (playerA.balance >= betAmount && playerB.balance >= betAmount) {
 82 |             gameStage = GameStage.BetsDeposited;
 83 |             emit GameStageChanged(GameStage.BetsDeposited);
 84 |         }
 85 |     }
 86 | 
 87 |     function submitMove(bytes32 _hashedMove) external isPlayer {
 88 |         require(
 89 |             gameStage == GameStage.BetsDeposited,
 90 |             "RPSGame: game not under progress"
 91 |         );
 92 |         Player storage player = playerA.addr == msg.sender ? playerA : playerB;
 93 | 
 94 |         require(
 95 |             !player.submitted,
 96 |             "RPSGame: you have already submitted the move"
 97 |         );
 98 |         player.hashedMove = _hashedMove;
 99 |         player.submitted = true;
100 |         emit SubmitMove(player.addr);
101 |         if (playerA.submitted && playerB.submitted) {
102 |             gameStage = GameStage.MovesSubmitted;
103 |             emit GameStageChanged(GameStage.MovesSubmitted);
104 |         }
105 |     }
106 | 
107 |     function revealMove(uint8 _move, bytes32 _salt) external isPlayer {
108 |         require(
109 |             gameStage == GameStage.MovesSubmitted,
110 |             "RPSGame: both players have not submitted move yet."
111 |         );
112 |         // TODO: Should check the reveal time limit
113 |         Player storage currentPlayer = msg.sender == playerA.addr
114 |             ? playerA
115 |             : playerB;
116 |         bytes32 revealedHash = keccak256(abi.encodePacked(_move, _salt));
117 |         // Already revealed
118 |         require(!currentPlayer.revealed, "You have already revealed your move");
119 |         // revealed data not true
120 |         require(
121 |             revealedHash == currentPlayer.hashedMove,
122 |             "RPSGame: Either your salt or move is not same as your submitted hashed move"
123 |         );
124 |         currentPlayer.move = Move(_move);
125 |         currentPlayer.revealed = true;
126 |         emit RevealMove(currentPlayer.addr);
127 |         if (playerA.revealed && playerB.revealed) {
128 |             pickWinner();
129 |         }
130 |     }
131 | 
132 |     function pickWinner() private {
133 |         require(
134 |             playerA.submitted && playerB.submitted,
135 |             "RPSGame: Players have not submitted their move"
136 |         );
137 |         address _winner = getWinner();
138 |         if (_winner != address(0)) {
139 |             winner = _winner;
140 |             emit Winner(_winner);
141 |             gameStage = GameStage.Completed;
142 |             incentivize(_winner);
143 |         }
144 |     }
145 | 
146 |     function incentivize(address _winner) internal {
147 |         // Update contract balances of winners and loosers
148 |         if (_winner == playerA.addr) {
149 |             playerA.balance += betAmount;
150 |             playerB.balance -= betAmount;
151 |         } else {
152 |             playerB.balance += betAmount;
153 |             playerA.balance -= betAmount;
154 |         }
155 |     }
156 | 
157 |     modifier notUnderProgress() {
158 |         require(
159 |             gameStage != GameStage.MovesSubmitted &&
160 |                 gameStage != GameStage.BetsDeposited,
161 |             "RPSGame: Game under progress"
162 |         );
163 |         _;
164 |     }
165 | 
166 |     function withdrawFund() external isPlayer notUnderProgress {
167 |         Player storage player = msg.sender == playerA.addr ? playerA : playerB;
168 |         require(
169 |             player.balance > 0,
170 |             "RPSGame: You don't have anything to withdraw!"
171 |         );
172 |         uint256 balance = player.balance;
173 |         payable(player.addr).transfer(balance);
174 |         player.balance = 0;
175 |         emit Withdraw(player.addr, balance);
176 |     }
177 | 
178 |     function getWinner() internal view returns (address) {
179 |         if (playerA.move == playerB.move) return address(0);
180 |         if (
181 |             (playerA.move == Move.Rock && playerB.move == Move.Scissors) ||
182 |             (playerA.move == Move.Paper && playerB.move == Move.Rock) ||
183 |             (playerA.move == Move.Scissors && playerB.move == Move.Paper)
184 |         ) {
185 |             return playerA.addr;
186 |         }
187 |         return playerB.addr;
188 |     }
189 | 
190 |     modifier isCompleted() {
191 |         require(gameStage == GameStage.Completed);
192 |         _;
193 |     }
194 | 
195 |     // TODO: Should be an external function
196 |     function resetGame() external isCompleted {
197 |         playerA.move = Move.None;
198 |         playerA.submitted = false;
199 |         playerA.revealed = false;
200 |         playerB.move = Move.None;
201 |         playerB.submitted = false;
202 |         playerB.revealed = false;
203 |         if (playerA.balance >= betAmount && playerB.balance >= betAmount) {
204 |             gameStage = GameStage.BetsDeposited;
205 |         } else {
206 |             gameStage = GameStage.Open;
207 |         }
208 |         emit ResetGame();
209 |     }
210 | }
211 | 
--------------------------------------------------------------------------------
/contracts/RPSGameFactory.sol:
--------------------------------------------------------------------------------
 1 | //SPDX-License-Identifier: MIT
 2 | pragma solidity ^0.8.0;
 3 | import "./RPSGame.sol";
 4 | 
 5 | contract RPSGameFactory {
 6 |     struct Game {
 7 |         address gameAddress;
 8 |         address player;
 9 |         address opponent;
10 |         uint256 betAmount;
11 |     }
12 |     Game[] deployedRPSGames;
13 | 
14 |     event RPSGameCreated(Game game);
15 | 
16 |     function createGame(uint256 betAmount, address opponent) external {
17 |         address gameAddress = address(
18 |             new RPSGame(betAmount, msg.sender, opponent)
19 |         );
20 |         Game memory newGame = Game(
21 |             gameAddress,
22 |             msg.sender,
23 |             opponent,
24 |             betAmount
25 |         );
26 |         deployedRPSGames.push(newGame);
27 | 
28 |         emit RPSGameCreated(newGame);
29 |     }
30 | 
31 |     function getDeployedGames() external view returns (Game[] memory) {
32 |         return deployedRPSGames;
33 |     }
34 | }
35 | 
--------------------------------------------------------------------------------
/contracts/RPSToken.sol:
--------------------------------------------------------------------------------
 1 | //SPDX-License-Identifier: Unlicense
 2 | pragma solidity ^0.8.0;
 3 | 
 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
 5 | 
 6 | contract RPSToken is ERC20 {
 7 |     mapping(address => bool) private claimed;
 8 | 
 9 |     constructor(string memory name, string memory symbol) ERC20(name, symbol) {
10 |         _mint(msg.sender, 100 * 10**decimals());
11 |         claimed[msg.sender] = true;
12 |     }
13 | 
14 |     function mint() external returns (bool) {
15 |         require(
16 |             !claimed[msg.sender],
17 |             "RPSToken: You have already minted your share"
18 |         );
19 |         _mint(msg.sender, 100 * 10**decimals());
20 |         claimed[msg.sender] = true;
21 |         return true;
22 |     }
23 | 
24 |     function burn(uint256 amount) external returns (bool) {
25 |         _burn(msg.sender, amount);
26 |         return true;
27 |     }
28 | }
29 | 
--------------------------------------------------------------------------------
/deploy/RPSToken.ts:
--------------------------------------------------------------------------------
 1 | module.exports = async ({
 2 |   getNamedAccounts,
 3 |   deployments,
 4 |   getChainId,
 5 |   getUnnamedAccounts,
 6 | }) => {
 7 |   const { deploy } = deployments;
 8 |   const { deployer } = await getNamedAccounts();
 9 | 
10 |   await deploy("RPSToken", {
11 |     from: deployer,
12 |     gas: 4000000,
13 |     args: ["RPS Token", "RPST"],
14 |   });
15 | };
16 | 
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
 2 | 
 3 | # dependencies
 4 | /node_modules
 5 | /.pnp
 6 | .pnp.js
 7 | 
 8 | # testing
 9 | /coverage
10 | 
11 | # production
12 | /build
13 | 
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 | 
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 | 
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # Frontend for Simple RPS Game Implementation 
2 | 
--------------------------------------------------------------------------------
/frontend/craco.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |   style: {
3 |     postcss: {
4 |       plugins: [require("tailwindcss"), require("autoprefixer")],
5 |     },
6 |   },
7 | };
8 | 
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "frontend",
 3 |   "version": "0.1.0",
 4 |   "private": true,
 5 |   "dependencies": {
 6 |     "@craco/craco": "^6.2.0",
 7 |     "@ethersproject/providers": "^5.4.3",
 8 |     "@testing-library/jest-dom": "^5.14.1",
 9 |     "@testing-library/react": "^11.2.7",
10 |     "@testing-library/user-event": "^12.8.3",
11 |     "@types/jest": "^26.0.24",
12 |     "@types/node": "^12.20.19",
13 |     "@types/react": "^17.0.17",
14 |     "@types/react-dom": "^17.0.9",
15 |     "@types/react-router-dom": "^5.1.8",
16 |     "ethers": "^5.4.4",
17 |     "react": "^17.0.2",
18 |     "react-dom": "^17.0.2",
19 |     "react-router-dom": "^5.2.0",
20 |     "react-scripts": "4.0.3",
21 |     "typescript": "^4.3.5",
22 |     "web-vitals": "^1.1.2"
23 |   },
24 |   "scripts": {
25 |     "start": "craco start",
26 |     "build": "craco build",
27 |     "test": "craco test",
28 |     "eject": "react-scripts eject"
29 |   },
30 |   "eslintConfig": {
31 |     "extends": [
32 |       "react-app",
33 |       "react-app/jest"
34 |     ]
35 |   },
36 |   "browserslist": {
37 |     "production": [
38 |       ">0.2%",
39 |       "not dead",
40 |       "not op_mini all"
41 |     ],
42 |     "development": [
43 |       "last 1 chrome version",
44 |       "last 1 firefox version",
45 |       "last 1 safari version"
46 |     ]
47 |   },
48 |   "devDependencies": {
49 |     "autoprefixer": "^9",
50 |     "postcss": "^7",
51 |     "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.7"
52 |   }
53 | }
54 | 
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chiranz/rps_game/6f567203aa0e3b4eb74da47881d2e8515a3d6cff/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     
 5 |     
 6 |     
 7 |     
 8 |     
12 |     
13 |     
17 |     
18 |     
27 |     React App
28 |   
29 |   
30 |     
31 |     
32 |     
42 |   
43 | 
44 | 
--------------------------------------------------------------------------------
/frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chiranz/rps_game/6f567203aa0e3b4eb74da47881d2e8515a3d6cff/frontend/public/logo192.png
--------------------------------------------------------------------------------
/frontend/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chiranz/rps_game/6f567203aa0e3b4eb74da47881d2e8515a3d6cff/frontend/public/logo512.png
--------------------------------------------------------------------------------
/frontend/public/manifest.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "short_name": "React App",
 3 |   "name": "Create React App Sample",
 4 |   "icons": [
 5 |     {
 6 |       "src": "favicon.ico",
 7 |       "sizes": "64x64 32x32 24x24 16x16",
 8 |       "type": "image/x-icon"
 9 |     },
10 |     {
11 |       "src": "logo192.png",
12 |       "type": "image/png",
13 |       "sizes": "192x192"
14 |     },
15 |     {
16 |       "src": "logo512.png",
17 |       "type": "image/png",
18 |       "sizes": "512x512"
19 |     }
20 |   ],
21 |   "start_url": ".",
22 |   "display": "standalone",
23 |   "theme_color": "#000000",
24 |   "background_color": "#ffffff"
25 | }
26 | 
--------------------------------------------------------------------------------
/frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 | 
--------------------------------------------------------------------------------
/frontend/src/App.test.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react';
 2 | import { render, screen } from '@testing-library/react';
 3 | import App from './App';
 4 | 
 5 | test('renders learn react link', () => {
 6 |   render();
 7 |   const linkElement = screen.getByText(/learn react/i);
 8 |   expect(linkElement).toBeInTheDocument();
 9 | });
10 | 
--------------------------------------------------------------------------------
/frontend/src/App.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import {
 3 |   BrowserRouter as Router,
 4 |   Switch,
 5 |   Route,
 6 |   Redirect,
 7 | } from "react-router-dom";
 8 | import Footer from "./components/layout/Footer";
 9 | import Navbar from "./components/layout/Navbar";
10 | import { joinClasses } from "./helpers";
11 | import Game from "./components/Game";
12 | import DeployedContracts from "./components/DeployedContracts";
13 | import { useRPSGameFactory } from "./context/RPSGameFactoryContext";
14 | import { useWallet } from "./context/WalletContext";
15 | import { getProvider } from "./provider";
16 | 
17 | function App() {
18 |   const { walletAddress, setWalletAddress } = useWallet();
19 |   const { selectedGameAddress } = useRPSGameFactory();
20 |   React.useEffect(() => {
21 |     async function init() {
22 |       try {
23 |         const _provider = await getProvider();
24 |         const network = await _provider.getNetwork();
25 |         const { chainId } = network;
26 |         if (chainId === 4 && setWalletAddress) {
27 |           const signer = _provider.getSigner();
28 |           setWalletAddress(await signer.getAddress());
29 |         }
30 |       } catch (err) {
31 |         console.log(err);
32 |       }
33 |     }
34 |     init();
35 |   }, [setWalletAddress]);
36 |   return (
37 |     
38 |       
50 |         
51 | 
52 |         
53 |           
54 |             
55 |               
56 |             
57 |             
58 |               {walletAddress && selectedGameAddress ? (
59 |                 
60 |               ) : (
61 |                 
62 |               )}
63 |             
64 |           
65 |         
66 |         
67 |       
 35 |       {!walletAddress ? (
 36 |         
 37 |           Please connect to your metamask wallet
 38 |         
 39 |       ) : null}
 40 |       
 41 |          setOpponent(e.target.value)}
 45 |         />
 46 | 
 47 |          setBetAmount(e.target.value)}
 52 |         />
 53 |         
 61 |       
 62 | 
 63 |       {deployedGames.length > 0 ? (
 64 |         
111 |       ) : (
112 |         
113 |           
No deployed games!
114 |         
115 |       )}
116 |     
12 |         
Please Connect your metamask first
13 |       
14 |     );
15 |   }
16 |   return (
17 |     
18 |       
19 |       
20 |     
21 |   );
22 | }
23 | 
--------------------------------------------------------------------------------
/frontend/src/components/GameActionInfoCard.tsx:
--------------------------------------------------------------------------------
 1 | import React, { ReactElement } from "react";
 2 | import { joinClasses } from "../helpers";
 3 | import Loader from "./Loader";
 4 | 
 5 | interface Props {
 6 |   message: string;
 7 |   loader?: boolean;
 8 | }
 9 | 
10 | export default function GameActionInfoCard({
11 |   message,
12 |   loader,
13 | }: Props): ReactElement {
14 |   return (
15 |     
27 |       {loader && }
28 |       
{message}
29 |     
30 |   );
31 | }
32 | 
--------------------------------------------------------------------------------
/frontend/src/components/GameStatsCard.tsx:
--------------------------------------------------------------------------------
 1 | import React, { ReactElement } from "react";
 2 | import { useRPSGameContract } from "../context/RPSGameContractContext";
 3 | import { joinClasses } from "../helpers";
 4 | import Button from "./Button";
 5 | 
 6 | export enum GameStage {
 7 |   Open,
 8 |   BetsDeposited,
 9 |   MovesSubmitted,
10 |   MoveRevealed,
11 |   Completed,
12 | }
13 | export const getGameStatusText = (id: number): string => {
14 |   const gameStateToText: { [key: number]: string } = {
15 |     0: "Open",
16 |     1: "Bets Deposited",
17 |     2: "Moves Submitted",
18 |     3: "Moves Revealed",
19 |     4: "Completed",
20 |   };
21 |   return gameStateToText[id];
22 | };
23 | 
24 | export default function GameStatsCard({
25 |   betAmount,
26 | }: {
27 |   betAmount: string | null;
28 | }): ReactElement {
29 |   const { depositBet, isPlayer, gameStage, withdrawFund, currentPlayer } =
30 |     useRPSGameContract();
31 | 
32 |   console.log({ balance: parseFloat(currentPlayer?.balance || "") });
33 |   return (
34 |     
49 |       
50 |         
51 |           Rock
52 |           Paper
53 |           Scissors
54 |         
55 |         
56 |           
Bet Amt: {betAmount} ETH
57 |         
58 |       
59 |       
60 |         
61 |           {isPlayer ? "Player" : "Audience"}
62 |         
63 |         
64 |           
75 |           
78 |         
79 |       
80 |     
56 |         {globalMessage?.message}
57 |         
63 |       
64 |     );
65 |   }
66 |   return ;
67 | }
68 | 
--------------------------------------------------------------------------------
/frontend/src/components/HiddenMove.tsx:
--------------------------------------------------------------------------------
 1 | import React, { ReactElement } from "react";
 2 | import { joinClasses } from "../helpers";
 3 | 
 4 | export default function HiddenMove(): ReactElement {
 5 |   return (
 6 |     
17 |       
30 |         Not Revealed
31 |       
32 |     
 {}}
27 |       className={
28 |         `${styles.shadowout} ` +
29 |         joinClasses(
30 |           `${
31 |             bgColor === "yellow"
32 |               ? "bg-yellow-500"
33 |               : bgColor === "red"
34 |               ? "bg-red-500"
35 |               : bgColor === "blue"
36 |               ? "bg-blue-500"
37 |               : "bg-gray-500"
38 |           }`,
39 |           "rounded-full",
40 |           "hover:opacity-80",
41 |           "w-36",
42 |           "h-36",
43 |           "inline-flex",
44 |           "justify-center",
45 |           "items-center",
46 |           "cursor-pointer",
47 |           "m-4"
48 |         ) +
49 |         ` ${className}`
50 |       }
51 |     >
52 |       
68 |     
30 |       
31 |         
32 |           {tag === "player" ? "Player" : "Opponent"}:{" "}
33 |           {getTruncatedAddress(addr)}
34 |         
35 |         Balance: {balance} ETH
36 |       
37 |       
38 |         
39 |           Deposited:{" "}
40 |           
41 |             {parseFloat(balance || "0") >= parseFloat(betAmount || "0") &&
42 |             balance
43 |               ? "✅"
44 |               : "❌"}{" "}
45 |           {" "}
46 |         
47 |         
48 |           Move Submitted: {submitted ? "✅" : "❌"} {" "}
49 |         
50 |         
51 |           Move Revealed: {revealed ? "✅" : "❌"} 
52 |         
53 |       
54 |     
54 |       {gameStage === 0 &&
55 |         (parseFloat(currentPlayer?.balance || "0") <
56 |         parseFloat(betAmount || "0") ? (
57 |           
58 |         ) : parseFloat(opponent?.balance || "0") <
59 |           parseFloat(betAmount || "0") ? (
60 |           
64 |         ) : (
65 |           ""
66 |         ))}
67 |       {gameStage === 1 && currentPlayer?.submitted && !opponent?.submitted ? (
68 |         
72 |       ) : (
73 |         
79 |       )}
80 |       {!currentPlayer?.submitted && gameStage === 1 && (
81 |         
82 |       )}
83 |     
84 |   );
85 | }
86 | 
--------------------------------------------------------------------------------
/frontend/src/components/RevealMove.tsx:
--------------------------------------------------------------------------------
  1 | import React, { ReactElement } from "react";
  2 | import { useRPSGameContract } from "../context/RPSGameContractContext";
  3 | import { joinClasses } from "../helpers";
  4 | import Button from "./Button";
  5 | import HiddenMove from "./HiddenMove";
  6 | import InputField from "./InputField";
  7 | import Loader from "./Loader";
  8 | import { getOptionButton, options } from "./Playground";
  9 | 
 10 | interface RevealMoveProps {
 11 |   salt: string;
 12 |   move: number;
 13 |   setSalt: React.Dispatch>;
 14 |   setMove: React.Dispatch>;
 15 | }
 16 | 
 17 | export default function RevealMove({
 18 |   salt,
 19 |   move,
 20 |   setSalt,
 21 |   setMove,
 22 | }: RevealMoveProps): ReactElement {
 23 |   const { revealMove, currentPlayer, opponent, isPlayer, winner, resetGame } =
 24 |     useRPSGameContract();
 25 |   const handleMoveReveal = () => {
 26 |     if (move === 0) {
 27 |       alert("Move must be selected!!");
 28 |       return;
 29 |     }
 30 |     if (revealMove && move) {
 31 |       revealMove(move, salt);
 32 |     }
 33 |   };
 34 |   const handleResetGame = () => {
 35 |     if (resetGame) {
 36 |       resetGame();
 37 |     }
 38 |   };
 39 |   const getGameResultText = (): string => {
 40 |     if (winner === "0x0000000000000000000000000000000000000000") {
 41 |       return "Game Draw";
 42 |     }
 43 |     if (currentPlayer?.addr === winner) {
 44 |       if (isPlayer) {
 45 |         return "You Won!!";
 46 |       } else {
 47 |         return "Player1 Won!!";
 48 |       }
 49 |     } else {
 50 |       if (isPlayer) {
 51 |         return "You Lost!!";
 52 |       } else {
 53 |         return "Player2 Won!!";
 54 |       }
 55 |     }
 56 |   };
 57 |   return (
 58 |     
 59 |       {currentPlayer?.submitted && (
 60 |         
 70 |           
 79 |             
 80 |               {isPlayer ? "Your Pick" : "Player1 Pick"}
 81 |             
 82 |             {currentPlayer?.revealed && currentPlayer.move ? (
 83 |               getOptionButton(options[currentPlayer.move - 1])
 84 |             ) : (
 85 |               
 86 |             )}
 87 |           
 88 |           
 96 |             {currentPlayer.revealed && !opponent?.revealed && (
 97 |               
 98 |                 
 99 |                 Waiting for the opponent to reveal move
100 |               
101 |             )}
102 |             {!currentPlayer.revealed && (
103 |               
104 |                 
105 |                    setSalt(e.target.value)}
110 |                     disabled={!isPlayer}
111 |                   />
112 |                   
131 |                 
132 |                 
140 |               
141 |             )}
142 |             {currentPlayer.revealed && opponent?.revealed && (
143 |               
144 |                 {getGameResultText()}
145 |                 
153 |               
154 |             )}
155 |           
156 |           
165 |             
166 |               {isPlayer ? "Opponent Pick" : "Player2 Pick"}
167 |             
168 |             {opponent?.revealed && opponent.move ? (
169 |               getOptionButton(options[opponent.move - 1])
170 |             ) : (
171 |               
172 |             )}
173 |             {}
174 |           
175 |