update contract addresses in config files and enhance player move commitment handling

This commit is contained in:
averel10
2025-11-21 18:42:43 +01:00
parent 06176e74f2
commit 3bd61490f1
5 changed files with 193 additions and 141 deletions

View File

@@ -1,6 +1,6 @@
{ {
"API_URL": "http://185.48.228.49:8545", "API_URL": "http://185.48.228.49:8545",
"CONTRACT_ADDRESS": "0x8aF379ED8C452612676ACB856E26955237358944", "CONTRACT_ADDRESS": "0x6da7dE8330EF1ff087C66e61Aa87FbC29E9b2869",
"ABI": [ "ABI": [
{ {
"inputs": [ "inputs": [
@@ -59,7 +59,7 @@
"type": "function" "type": "function"
} }
], ],
"GAME_CONTRACT_ADDRESS": "0x267d1b8bf8C13ea60D39F90cC68AEc4b8389bb64", "GAME_CONTRACT_ADDRESS": "0x503d096a9a163180F79B1AC2F1d9F7C63f5DC75a",
"GAME_ABI": [ "GAME_ABI": [
{ {
"inputs": [], "inputs": [],

View File

@@ -228,6 +228,27 @@ contract Game {
game.firstReveal = block.timestamp; 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; return move;
} }
@@ -272,29 +293,10 @@ contract Game {
uint gameId = playerToActiveGame[msg.sender]; uint gameId = playerToActiveGame[msg.sender];
GameState storage game = games[gameId]; GameState storage game = games[gameId];
// Only calculate outcome once require(
require(game.outcome == Outcomes.None, "Outcome already determined"); game.outcome != Outcomes.None,
"Outcome not yet 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;
address payable addrA = game.playerA.addr; address payable addrA = game.playerA.addr;
address payable addrB = game.playerB.addr; address payable addrB = game.playerB.addr;
@@ -305,9 +307,9 @@ contract Game {
// Reset and cleanup // Reset and cleanup
resetGame(gameId); // Reset game before paying to avoid reentrancy attacks 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). // Pay the winner(s).

View File

@@ -13,6 +13,7 @@ interface CommitProps {
setSelectedMove: (move: string | null) => void; setSelectedMove: (move: string | null) => void;
secret: string; secret: string;
setSecret: (secret: string) => void; setSecret: (secret: string) => void;
onBothPlayersCommitted?: () => void;
} }
type Move = "1" | "2" | "3" | null; type Move = "1" | "2" | "3" | null;
@@ -34,10 +35,13 @@ export default function Commit({
setSelectedMove, setSelectedMove,
secret, secret,
setSecret, setSecret,
onBothPlayersCommitted,
}: Readonly<CommitProps>) { }: Readonly<CommitProps>) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [playMove, setPlayMove] = useState<string>(""); const [playMove, setPlayMove] = useState<string>("");
const [bothPlayed, setBothPlayed] = useState<string>(""); const [bothPlayed, setBothPlayed] = useState<string>("");
const [autoCheckInterval, setAutoCheckInterval] = useState<NodeJS.Timeout | null>(null);
const [moveSubmitted, setMoveSubmitted] = useState(false);
// Generate random secret on mount if not already set // Generate random secret on mount if not already set
useEffect(() => { useEffect(() => {
@@ -57,20 +61,42 @@ export default function Commit({
} }
}, [selectedMove, secret]); }, [selectedMove, secret]);
// Commit phase read-only handlers // Auto-check if both players have committed and trigger callback
const handleBothPlayed = async () => { useEffect(() => {
if (!contract) return; if (!contract || !account || !playMove || bothPlayed === "true") {
setLoading(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 { try {
const res = await contract.methods.bothPlayed().call({ from: account }); const res = await contract.methods.bothPlayed().call({ from: account });
setBothPlayed(res ? "true" : "false"); if (res) {
setBothPlayed("true");
if (onBothPlayersCommitted) {
onBothPlayersCommitted();
}
}
} catch (err: any) { } catch (err: any) {
setStatus("Failed to fetch bothPlayed: " + err.message); console.error("Auto-check failed:", err.message);
} finally {
setLoading(false);
} }
}; };
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 () => { const handlePlay = async () => {
if (!contract || !web3 || !account || !playMove) return; if (!contract || !web3 || !account || !playMove) return;
setLoading(true); setLoading(true);
@@ -92,6 +118,7 @@ export default function Commit({
], ],
}); });
setStatus("Play tx sent: " + result); setStatus("Play tx sent: " + result);
setMoveSubmitted(true);
} catch (err: any) { } catch (err: any) {
setStatus("Play failed: " + err.message); setStatus("Play failed: " + err.message);
} finally { } finally {
@@ -110,6 +137,21 @@ export default function Commit({
Select Your Move Select Your Move
</h2> </h2>
{moveSubmitted ? (
// Waiting animation after move is submitted
<div className="flex flex-col items-center justify-center py-16">
<div className="mb-6">
<div className="w-16 h-16 border-4 border-slate-300 dark:border-slate-500 border-t-blue-500 rounded-full animate-spin"></div>
</div>
<p className="text-lg font-semibold text-slate-700 dark:text-slate-300 mb-2">
Waiting for opponent...
</p>
<p className="text-sm text-slate-500 dark:text-slate-400">
Your move has been submitted. Stand by while the other player commits.
</p>
</div>
) : (
<>
{/* Move Selection */} {/* Move Selection */}
<div className="mb-8"> <div className="mb-8">
<p className="text-sm text-slate-600 dark:text-slate-300 mb-4 font-medium"> <p className="text-sm text-slate-600 dark:text-slate-300 mb-4 font-medium">
@@ -181,29 +223,9 @@ export default function Commit({
> >
{loading ? "Submitting..." : "Submit Move"} {loading ? "Submitting..." : "Submit Move"}
</Button> </Button>
<Button
onClick={handleBothPlayed}
disabled={loading}
variant="secondary"
className="w-full"
>
Check Both Played
</Button>
{bothPlayed && (
<span
className={`text-center text-sm font-medium ${
bothPlayed === "true"
? "text-green-600 dark:text-green-400"
: "text-slate-600 dark:text-slate-400"
}`}
>
{bothPlayed === "true"
? "✓ Both players have committed!"
: "Waiting for opponent..."}
</span>
)}
</div> </div>
</>
)}
</div> </div>
); );
} }

View File

@@ -17,6 +17,8 @@ export default function Clash() {
const [phase, setPhase] = useState<"games" | "commit" | "reveal">("games"); const [phase, setPhase] = useState<"games" | "commit" | "reveal">("games");
const [selectedMove, setSelectedMove] = useState<string | null>(null); const [selectedMove, setSelectedMove] = useState<string | null>(null);
const [secret, setSecret] = useState<string>(""); const [secret, setSecret] = useState<string>("");
const [availableAccounts, setAvailableAccounts] = useState<string[]>([]);
const [selectedAccount, setSelectedAccount] = useState<string>("");
const handlePlayClick = (gameId: number) => { const handlePlayClick = (gameId: number) => {
setPhase("commit"); setPhase("commit");
@@ -41,13 +43,15 @@ export default function Clash() {
data.GAME_CONTRACT_ADDRESS data.GAME_CONTRACT_ADDRESS
); );
setContract(contractInstance); setContract(contractInstance);
// Get account // Get accounts from MetaMask
if (globalThis.window !== undefined && (globalThis as any).ethereum) { if (globalThis.window !== undefined && (globalThis as any).ethereum) {
try { try {
const accounts = await (globalThis as any).ethereum.request({ const accounts = await (globalThis as any).ethereum.request({
method: "eth_requestAccounts", method: "eth_requestAccounts",
}); });
setAvailableAccounts(accounts);
setAccount(accounts[0]); setAccount(accounts[0]);
setSelectedAccount(accounts[0]);
} catch (err: any) { } catch (err: any) {
setStatus( setStatus(
"MetaMask not available or user denied access: " + err.message "MetaMask not available or user denied access: " + err.message
@@ -79,10 +83,33 @@ export default function Clash() {
{phase === "reveal" && "Reveal your move."} {phase === "reveal" && "Reveal your move."}
</p> </p>
<div className="mb-8 p-4 bg-slate-100 dark:bg-slate-700 rounded-lg"> <div className="mb-8 p-4 bg-slate-100 dark:bg-slate-700 rounded-lg">
<div className="mb-4">
<label className="block text-sm font-semibold text-slate-600 dark:text-slate-300 mb-2">
Select Wallet Address:
</label>
{availableAccounts.length > 0 ? (
<select
value={selectedAccount}
onChange={(e) => {
setSelectedAccount(e.target.value);
setAccount(e.target.value);
}}
className="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{availableAccounts.map((acc) => (
<option key={acc} value={acc}>
{`${acc.slice(0, 6)}...${acc.slice(-4)}`}
</option>
))}
</select>
) : (
<p className="text-sm text-red-600 dark:text-red-400">No accounts available</p>
)}
</div>
<p className="text-sm text-slate-600 dark:text-slate-300"> <p className="text-sm text-slate-600 dark:text-slate-300">
<span className="font-semibold">Connected Account:</span>{" "} <span className="font-semibold">Active Account:</span>{" "}
{account {selectedAccount
? `${account.slice(0, 6)}...${account.slice(-4)}` ? `${selectedAccount.slice(0, 6)}...${selectedAccount.slice(-4)}`
: "Not connected"} : "Not connected"}
</p> </p>
<p className="text-sm text-slate-600 dark:text-slate-300 mt-2"> <p className="text-sm text-slate-600 dark:text-slate-300 mt-2">
@@ -125,7 +152,7 @@ export default function Clash() {
<div className="space-y-6"> <div className="space-y-6">
{phase === "games" && ( {phase === "games" && (
<GameList <GameList
account={account} account={selectedAccount}
contract={contract} contract={contract}
config={config} config={config}
web3={web3} web3={web3}
@@ -135,7 +162,7 @@ export default function Clash() {
)} )}
{phase === "commit" && ( {phase === "commit" && (
<Commit <Commit
account={account} account={selectedAccount}
contract={contract} contract={contract}
config={config} config={config}
web3={web3} web3={web3}
@@ -144,11 +171,12 @@ export default function Clash() {
setSelectedMove={setSelectedMove} setSelectedMove={setSelectedMove}
secret={secret} secret={secret}
setSecret={setSecret} setSecret={setSecret}
onBothPlayersCommitted={() => setPhase("reveal")}
/> />
)} )}
{phase === "reveal" && ( {phase === "reveal" && (
<Reveal <Reveal
account={account} account={selectedAccount}
contract={contract} contract={contract}
config={config} config={config}
web3={web3} web3={web3}

View File

@@ -1,6 +1,6 @@
{ {
"API_URL": "http://185.48.228.49:8545", "API_URL": "http://185.48.228.49:8545",
"CONTRACT_ADDRESS": "0x8aF379ED8C452612676ACB856E26955237358944", "CONTRACT_ADDRESS": "0x6da7dE8330EF1ff087C66e61Aa87FbC29E9b2869",
"ABI": [ "ABI": [
{ {
"inputs": [ "inputs": [
@@ -59,7 +59,7 @@
"type": "function" "type": "function"
} }
], ],
"GAME_CONTRACT_ADDRESS": "0x267d1b8bf8C13ea60D39F90cC68AEc4b8389bb64", "GAME_CONTRACT_ADDRESS": "0x503d096a9a163180F79B1AC2F1d9F7C63f5DC75a",
"GAME_ABI": [ "GAME_ABI": [
{ {
"inputs": [], "inputs": [],