mirror of
https://github.com/averel10/crypto_clash.git
synced 2026-03-12 19:08:11 +01:00
update contract addresses in config files and enhance player move commitment handling
This commit is contained in:
@@ -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": [],
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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": [],
|
||||||
|
|||||||
Reference in New Issue
Block a user