diff --git a/config.json b/config.json
index 0b3e280..3ce71b7 100644
--- a/config.json
+++ b/config.json
@@ -1,6 +1,6 @@
{
"API_URL": "http://185.48.228.49:8545",
- "CONTRACT_ADDRESS": "0x8aF379ED8C452612676ACB856E26955237358944",
+ "CONTRACT_ADDRESS": "0x6da7dE8330EF1ff087C66e61Aa87FbC29E9b2869",
"ABI": [
{
"inputs": [
@@ -59,7 +59,7 @@
"type": "function"
}
],
- "GAME_CONTRACT_ADDRESS": "0x267d1b8bf8C13ea60D39F90cC68AEc4b8389bb64",
+ "GAME_CONTRACT_ADDRESS": "0x503d096a9a163180F79B1AC2F1d9F7C63f5DC75a",
"GAME_ABI": [
{
"inputs": [],
diff --git a/crypto_clash_contract/contracts/Game.sol b/crypto_clash_contract/contracts/Game.sol
index 0627196..81895c2 100644
--- a/crypto_clash_contract/contracts/Game.sol
+++ b/crypto_clash_contract/contracts/Game.sol
@@ -228,6 +228,27 @@ contract Game {
game.firstReveal = block.timestamp;
}
+ if(
+ game.playerA.move != Moves.None &&
+ game.playerB.move != Moves.None
+ ) {
+ // Both players have revealed, compute outcome
+ if (game.playerA.move == game.playerB.move) {
+ game.outcome = Outcomes.Draw;
+ } else if (
+ (game.playerA.move == Moves.Rock &&
+ game.playerB.move == Moves.Scissors) ||
+ (game.playerA.move == Moves.Paper &&
+ game.playerB.move == Moves.Rock) ||
+ (game.playerA.move == Moves.Scissors &&
+ game.playerB.move == Moves.Paper)
+ ) {
+ game.outcome = Outcomes.PlayerA;
+ } else {
+ game.outcome = Outcomes.PlayerB;
+ }
+ }
+
return move;
}
@@ -272,29 +293,10 @@ contract Game {
uint gameId = playerToActiveGame[msg.sender];
GameState storage game = games[gameId];
- // Only calculate outcome once
- require(game.outcome == Outcomes.None, "Outcome already determined");
-
- Outcomes outcome;
-
- if (game.playerA.move == game.playerB.move) {
- outcome = Outcomes.Draw;
- } else if (
- (game.playerA.move == Moves.Rock &&
- game.playerB.move == Moves.Scissors) ||
- (game.playerA.move == Moves.Paper &&
- game.playerB.move == Moves.Rock) ||
- (game.playerA.move == Moves.Scissors &&
- game.playerB.move == Moves.Paper) ||
- (game.playerA.move != Moves.None && game.playerB.move == Moves.None)
- ) {
- outcome = Outcomes.PlayerA;
- } else {
- outcome = Outcomes.PlayerB;
- }
-
- // Store the outcome permanently before resetting
- game.outcome = outcome;
+ require(
+ game.outcome != Outcomes.None,
+ "Outcome not yet determined"
+ );
address payable addrA = game.playerA.addr;
address payable addrB = game.playerB.addr;
@@ -305,9 +307,9 @@ contract Game {
// Reset and cleanup
resetGame(gameId); // Reset game before paying to avoid reentrancy attacks
- pay(addrA, addrB, betPlayerA, outcome);
+ pay(addrA, addrB, betPlayerA, game.outcome);
- return outcome;
+ return game.outcome;
}
// Pay the winner(s).
diff --git a/crypto_clash_frontend/app/clash/Commit.tsx b/crypto_clash_frontend/app/clash/Commit.tsx
index 65590af..01ef540 100644
--- a/crypto_clash_frontend/app/clash/Commit.tsx
+++ b/crypto_clash_frontend/app/clash/Commit.tsx
@@ -13,6 +13,7 @@ interface CommitProps {
setSelectedMove: (move: string | null) => void;
secret: string;
setSecret: (secret: string) => void;
+ onBothPlayersCommitted?: () => void;
}
type Move = "1" | "2" | "3" | null;
@@ -34,10 +35,13 @@ export default function Commit({
setSelectedMove,
secret,
setSecret,
+ onBothPlayersCommitted,
}: Readonly) {
const [loading, setLoading] = useState(false);
const [playMove, setPlayMove] = useState("");
const [bothPlayed, setBothPlayed] = useState("");
+ const [autoCheckInterval, setAutoCheckInterval] = useState(null);
+ const [moveSubmitted, setMoveSubmitted] = useState(false);
// Generate random secret on mount if not already set
useEffect(() => {
@@ -57,20 +61,42 @@ export default function Commit({
}
}, [selectedMove, secret]);
- // Commit phase read-only handlers
- const handleBothPlayed = async () => {
- if (!contract) return;
- setLoading(true);
- try {
- const res = await contract.methods.bothPlayed().call({from : account});
- setBothPlayed(res ? "true" : "false");
- } catch (err: any) {
- setStatus("Failed to fetch bothPlayed: " + err.message);
- } finally {
- setLoading(false);
+ // Auto-check if both players have committed and trigger callback
+ useEffect(() => {
+ if (!contract || !account || !playMove || bothPlayed === "true") {
+ // Clear interval if conditions not met or already both played
+ if (autoCheckInterval) clearInterval(autoCheckInterval);
+ setAutoCheckInterval(null);
+ return;
}
- };
+ // Check immediately on mount or when dependencies change
+ const checkBothPlayed = async () => {
+ try {
+ const res = await contract.methods.bothPlayed().call({ from: account });
+ if (res) {
+ setBothPlayed("true");
+ if (onBothPlayersCommitted) {
+ onBothPlayersCommitted();
+ }
+ }
+ } catch (err: any) {
+ console.error("Auto-check failed:", err.message);
+ }
+ };
+
+ checkBothPlayed();
+
+ // Set up interval to check every 2 seconds
+ const interval = setInterval(checkBothPlayed, 2000);
+ setAutoCheckInterval(interval);
+
+ return () => {
+ if (interval) clearInterval(interval);
+ };
+ }, [contract, account, playMove, bothPlayed, onBothPlayersCommitted]);
+
+ // Commit phase read-only handlers
const handlePlay = async () => {
if (!contract || !web3 || !account || !playMove) return;
setLoading(true);
@@ -92,6 +118,7 @@ export default function Commit({
],
});
setStatus("Play tx sent: " + result);
+ setMoveSubmitted(true);
} catch (err: any) {
setStatus("Play failed: " + err.message);
} finally {
@@ -110,100 +137,95 @@ export default function Commit({
Select Your Move
- {/* Move Selection */}
-
-
- Choose your move:
-
-
- {(["1", "2", "3"] as const).map((move) => (
-
-
- {/* Secret Input */}
-
-
-
- setSecret(e.target.value)}
- placeholder="Your secret passphrase"
- className="flex-1"
- />
-
- 🔄 New
-
-
-
- Keep this secret safe! It's needed to reveal your move later.
-
-
-
- {/* Encrypted Move Display */}
-
-
-
-
- {playMove || "Select a move and enter a secret"}
-
-
-
-
- {/* Action Buttons */}
-
-
- {loading ? "Submitting..." : "Submit Move"}
-
-
-
- Check Both Played
-
- {bothPlayed && (
-
- {bothPlayed === "true"
- ? "✓ Both players have committed!"
- : "Waiting for opponent..."}
-
- )}
-
+ {loading ? "Submitting..." : "Submit Move"}
+
+
+ >
+ )}
);
}
diff --git a/crypto_clash_frontend/app/clash/page.tsx b/crypto_clash_frontend/app/clash/page.tsx
index 30d8747..8a333e8 100644
--- a/crypto_clash_frontend/app/clash/page.tsx
+++ b/crypto_clash_frontend/app/clash/page.tsx
@@ -17,6 +17,8 @@ export default function Clash() {
const [phase, setPhase] = useState<"games" | "commit" | "reveal">("games");
const [selectedMove, setSelectedMove] = useState(null);
const [secret, setSecret] = useState("");
+ const [availableAccounts, setAvailableAccounts] = useState([]);
+ const [selectedAccount, setSelectedAccount] = useState("");
const handlePlayClick = (gameId: number) => {
setPhase("commit");
@@ -41,13 +43,15 @@ export default function Clash() {
data.GAME_CONTRACT_ADDRESS
);
setContract(contractInstance);
- // Get account
+ // Get accounts from MetaMask
if (globalThis.window !== undefined && (globalThis as any).ethereum) {
try {
const accounts = await (globalThis as any).ethereum.request({
method: "eth_requestAccounts",
});
+ setAvailableAccounts(accounts);
setAccount(accounts[0]);
+ setSelectedAccount(accounts[0]);
} catch (err: any) {
setStatus(
"MetaMask not available or user denied access: " + err.message
@@ -79,10 +83,33 @@ export default function Clash() {
{phase === "reveal" && "Reveal your move."}
+
+
+ {availableAccounts.length > 0 ? (
+
+ ) : (
+
No accounts available
+ )}
+
- Connected Account:{" "}
- {account
- ? `${account.slice(0, 6)}...${account.slice(-4)}`
+ Active Account:{" "}
+ {selectedAccount
+ ? `${selectedAccount.slice(0, 6)}...${selectedAccount.slice(-4)}`
: "Not connected"}
@@ -125,7 +152,7 @@ export default function Clash() {
{phase === "games" && (
setPhase("reveal")}
/>
)}
{phase === "reveal" && (