mirror of
https://github.com/averel10/crypto_clash.git
synced 2026-03-12 10:58:11 +01:00
Add player nickname feature to game registration
This commit is contained in:
@@ -67,7 +67,8 @@ contract Game {
|
|||||||
// If gameId is 0, player will join or create the first available game.
|
// If gameId is 0, player will join or create the first available game.
|
||||||
// Return player's ID and game ID upon successful registration.
|
// Return player's ID and game ID upon successful registration.
|
||||||
function register(
|
function register(
|
||||||
uint gameId
|
uint gameId,
|
||||||
|
string memory nickname
|
||||||
)
|
)
|
||||||
public
|
public
|
||||||
payable
|
payable
|
||||||
@@ -80,11 +81,14 @@ contract Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
require(games[gameId].isActive, "Game is not active");
|
require(games[gameId].isActive, "Game is not active");
|
||||||
|
require(bytes(nickname).length > 0, "Nickname cannot be empty");
|
||||||
|
require(bytes(nickname).length <= 20, "Nickname too long (max 20 characters)");
|
||||||
|
|
||||||
GameState storage game = games[gameId];
|
GameState storage game = games[gameId];
|
||||||
|
|
||||||
if (game.playerA.addr == address(0x0)) {
|
if (game.playerA.addr == address(0x0)) {
|
||||||
game.playerA.addr = payable(msg.sender);
|
game.playerA.addr = payable(msg.sender);
|
||||||
|
game.playerA.nickname = nickname;
|
||||||
game.initialBet = msg.value;
|
game.initialBet = msg.value;
|
||||||
return (1, gameId);
|
return (1, gameId);
|
||||||
} else if (game.playerB.addr == address(0x0)) {
|
} else if (game.playerB.addr == address(0x0)) {
|
||||||
@@ -93,6 +97,7 @@ contract Game {
|
|||||||
"Cannot play against yourself"
|
"Cannot play against yourself"
|
||||||
);
|
);
|
||||||
game.playerB.addr = payable(msg.sender);
|
game.playerB.addr = payable(msg.sender);
|
||||||
|
game.playerB.nickname = nickname;
|
||||||
return (2, gameId);
|
return (2, gameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -226,6 +226,27 @@ export default function Commit({
|
|||||||
return (
|
return (
|
||||||
<div className="border p-6 rounded-lg bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-700 dark:to-slate-800">
|
<div className="border p-6 rounded-lg bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-700 dark:to-slate-800">
|
||||||
|
|
||||||
|
{/* Player Info */}
|
||||||
|
{gameDetails && (
|
||||||
|
<div className="mb-6 p-4 bg-white dark:bg-slate-600 rounded-lg">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div className="text-center flex-1">
|
||||||
|
<p className="text-xs text-slate-500 dark:text-slate-400 mb-1">You</p>
|
||||||
|
<p className="font-semibold text-slate-800 dark:text-slate-200">
|
||||||
|
{whoAmI === "player1" ? gameDetails.playerA.nickname : gameDetails.playerB.nickname}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl text-slate-400">VS</div>
|
||||||
|
<div className="text-center flex-1">
|
||||||
|
<p className="text-xs text-slate-500 dark:text-slate-400 mb-1">Opponent</p>
|
||||||
|
<p className="font-semibold text-slate-800 dark:text-slate-200">
|
||||||
|
{whoAmI === "player1" ? gameDetails.playerB.nickname || "Waiting..." : gameDetails.playerA.nickname}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Show timeout result after game is inactive */}
|
{/* Show timeout result after game is inactive */}
|
||||||
{isGameFinishedByTimeout ? (
|
{isGameFinishedByTimeout ? (
|
||||||
<div className="flex flex-col items-center justify-center py-16">
|
<div className="flex flex-col items-center justify-center py-16">
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ export default function GameList({
|
|||||||
const [games, setGames] = useState<GameDetails[]>([]);
|
const [games, setGames] = useState<GameDetails[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [newGameBet, setNewGameBet] = useState<string>("0.01");
|
const [newGameBet, setNewGameBet] = useState<string>("0.01");
|
||||||
|
const [newGameNickname, setNewGameNickname] = useState<string>("");
|
||||||
|
const [joinNicknames, setJoinNicknames] = useState<Map<number, string>>(new Map());
|
||||||
const [refreshInterval, setRefreshInterval] = useState<NodeJS.Timeout | null>(null);
|
const [refreshInterval, setRefreshInterval] = useState<NodeJS.Timeout | null>(null);
|
||||||
const [userGameIds, setUserGameIds] = useState<Set<number>>(new Set());
|
const [userGameIds, setUserGameIds] = useState<Set<number>>(new Set());
|
||||||
|
|
||||||
@@ -75,10 +77,21 @@ export default function GameList({
|
|||||||
// Join an existing game
|
// Join an existing game
|
||||||
const handleJoinGame = async (gameId: number, bet: string) => {
|
const handleJoinGame = async (gameId: number, bet: string) => {
|
||||||
if (!contract || !web3 || !account) return;
|
if (!contract || !web3 || !account) return;
|
||||||
|
|
||||||
|
const nickname = joinNicknames.get(gameId) || "";
|
||||||
|
if (!nickname.trim()) {
|
||||||
|
showErrorToast("Please enter a nickname");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nickname.length > 20) {
|
||||||
|
showErrorToast("Nickname too long (max 20 characters)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const betWei = bet;
|
const betWei = bet;
|
||||||
const tx = contract.methods.register(gameId);
|
const tx = contract.methods.register(gameId, nickname);
|
||||||
const gas = await tx.estimateGas({ from: account, value: betWei });
|
const gas = await tx.estimateGas({ from: account, value: betWei });
|
||||||
const result = await (globalThis as any).ethereum.request({
|
const result = await (globalThis as any).ethereum.request({
|
||||||
method: "eth_sendTransaction",
|
method: "eth_sendTransaction",
|
||||||
@@ -94,6 +107,10 @@ export default function GameList({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
showSuccessToast("Joined game! Transaction: " + result);
|
showSuccessToast("Joined game! Transaction: " + result);
|
||||||
|
// Clear the nickname input for this game
|
||||||
|
const updatedNicknames = new Map(joinNicknames);
|
||||||
|
updatedNicknames.delete(gameId);
|
||||||
|
setJoinNicknames(updatedNicknames);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
fetchActiveGames();
|
fetchActiveGames();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -107,10 +124,20 @@ export default function GameList({
|
|||||||
// Create a new game
|
// Create a new game
|
||||||
const handleCreateGame = async () => {
|
const handleCreateGame = async () => {
|
||||||
if (!contract || !web3 || !account) return;
|
if (!contract || !web3 || !account) return;
|
||||||
|
|
||||||
|
if (!newGameNickname.trim()) {
|
||||||
|
showErrorToast("Please enter a nickname");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newGameNickname.length > 20) {
|
||||||
|
showErrorToast("Nickname too long (max 20 characters)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const betWei = web3.utils.toWei(newGameBet || "0.01", "ether");
|
const betWei = web3.utils.toWei(newGameBet || "0.01", "ether");
|
||||||
const tx = contract.methods.register(0); // 0 means create new game
|
const tx = contract.methods.register(0, newGameNickname); // 0 means create new game
|
||||||
const gas = await tx.estimateGas({ from: account, value: betWei });
|
const gas = await tx.estimateGas({ from: account, value: betWei });
|
||||||
const result = await (globalThis as any).ethereum.request({
|
const result = await (globalThis as any).ethereum.request({
|
||||||
method: "eth_sendTransaction",
|
method: "eth_sendTransaction",
|
||||||
@@ -127,6 +154,7 @@ export default function GameList({
|
|||||||
});
|
});
|
||||||
showSuccessToast("Created new game! Transaction: " + result);
|
showSuccessToast("Created new game! Transaction: " + result);
|
||||||
setNewGameBet("0.01");
|
setNewGameBet("0.01");
|
||||||
|
setNewGameNickname("");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
fetchActiveGames();
|
fetchActiveGames();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -165,6 +193,14 @@ export default function GameList({
|
|||||||
➕ Create New Game
|
➕ Create New Game
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex gap-3 flex-wrap">
|
<div className="flex gap-3 flex-wrap">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Your nickname (max 20 chars)"
|
||||||
|
value={newGameNickname}
|
||||||
|
onChange={(e) => setNewGameNickname(e.target.value)}
|
||||||
|
maxLength={20}
|
||||||
|
className="flex-1 min-w-[200px]"
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
min="0.01"
|
min="0.01"
|
||||||
@@ -184,7 +220,7 @@ export default function GameList({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-slate-600 dark:text-slate-400 mt-2">
|
<p className="text-xs text-slate-600 dark:text-slate-400 mt-2">
|
||||||
Enter the bet amount in ETH (e.g., 0.01 for 0.01 ETH).
|
Enter your nickname and bet amount in ETH (e.g., 0.01 for 0.01 ETH).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -234,7 +270,10 @@ export default function GameList({
|
|||||||
<p className="text-xs text-slate-500 dark:text-slate-400 mb-1 font-semibold">
|
<p className="text-xs text-slate-500 dark:text-slate-400 mb-1 font-semibold">
|
||||||
Player A
|
Player A
|
||||||
</p>
|
</p>
|
||||||
<p className="font-mono text-sm text-slate-700 dark:text-slate-300 break-all">
|
<p className="font-semibold text-base text-slate-800 dark:text-slate-200">
|
||||||
|
{game.playerA.nickname || "Unknown"}
|
||||||
|
</p>
|
||||||
|
<p className="font-mono text-xs text-slate-500 dark:text-slate-400 break-all">
|
||||||
{formatAddress(game.playerA.addr)}
|
{formatAddress(game.playerA.addr)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -251,16 +290,25 @@ export default function GameList({
|
|||||||
<p className="text-xs text-slate-500 dark:text-slate-400 mb-1 font-semibold">
|
<p className="text-xs text-slate-500 dark:text-slate-400 mb-1 font-semibold">
|
||||||
Player B
|
Player B
|
||||||
</p>
|
</p>
|
||||||
<p className="font-mono text-sm text-slate-700 dark:text-slate-300 break-all">
|
{game.playerB.addr === "0x0000000000000000000000000000000000000000" ? (
|
||||||
{game.playerB.addr === "0x0000000000000000000000000000000000000000"
|
<p className="font-semibold text-base text-slate-500 dark:text-slate-400">
|
||||||
? "⏳ Waiting..."
|
⏳ Waiting...
|
||||||
: formatAddress(game.playerB.addr)}
|
</p>
|
||||||
</p>
|
) : (
|
||||||
|
<>
|
||||||
|
<p className="font-semibold text-base text-slate-800 dark:text-slate-200">
|
||||||
|
{game.playerB.nickname || "Unknown"}
|
||||||
|
</p>
|
||||||
|
<p className="font-mono text-xs text-slate-500 dark:text-slate-400 break-all">
|
||||||
|
{formatAddress(game.playerB.addr)}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Join/Play Button */}
|
{/* Join/Play Button */}
|
||||||
<div className="mt-4 flex justify-center">
|
<div className="mt-4 flex flex-col items-center gap-2">
|
||||||
{userGameIds.has(game.returnGameId) ? (
|
{userGameIds.has(game.returnGameId) ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => onPlayClick?.(game.returnGameId)}
|
onClick={() => onPlayClick?.(game.returnGameId)}
|
||||||
@@ -269,24 +317,40 @@ export default function GameList({
|
|||||||
>
|
>
|
||||||
▶ Play
|
▶ Play
|
||||||
</Button>
|
</Button>
|
||||||
|
) : game.playerB.addr === "0x0000000000000000000000000000000000000000" ? (
|
||||||
|
<div className="flex gap-2 w-full max-w-md">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Your nickname"
|
||||||
|
value={joinNicknames.get(game.returnGameId) || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
const updatedNicknames = new Map(joinNicknames);
|
||||||
|
updatedNicknames.set(game.returnGameId, e.target.value);
|
||||||
|
setJoinNicknames(updatedNicknames);
|
||||||
|
}}
|
||||||
|
maxLength={20}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
handleJoinGame(game.returnGameId, game.initialBet)
|
||||||
|
}
|
||||||
|
disabled={
|
||||||
|
loading ||
|
||||||
|
!account ||
|
||||||
|
!contract
|
||||||
|
}
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
|
Join Game
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
disabled={true}
|
||||||
handleJoinGame(game.returnGameId, game.initialBet)
|
|
||||||
}
|
|
||||||
disabled={
|
|
||||||
loading ||
|
|
||||||
!account ||
|
|
||||||
!contract ||
|
|
||||||
game.playerB.addr !==
|
|
||||||
"0x0000000000000000000000000000000000000000"
|
|
||||||
}
|
|
||||||
variant="primary"
|
variant="primary"
|
||||||
>
|
>
|
||||||
{game.playerB.addr ===
|
Full
|
||||||
"0x0000000000000000000000000000000000000000"
|
|
||||||
? "Join Game"
|
|
||||||
: "Full"}
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -193,6 +193,26 @@ export default function Reveal({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
{/* Player Info */}
|
||||||
|
{gameDetails && (
|
||||||
|
<div className="mb-6 p-4 bg-white dark:bg-slate-700 rounded-lg">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div className="text-center flex-1">
|
||||||
|
<p className="text-xs text-slate-500 dark:text-slate-400 mb-1">You</p>
|
||||||
|
<p className="font-semibold text-slate-800 dark:text-slate-200">
|
||||||
|
{whoAmI === "player1" ? gameDetails.playerA.nickname : gameDetails.playerB.nickname}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl text-slate-400">VS</div>
|
||||||
|
<div className="text-center flex-1">
|
||||||
|
<p className="text-xs text-slate-500 dark:text-slate-400 mb-1">Opponent</p>
|
||||||
|
<p className="font-semibold text-slate-800 dark:text-slate-200">
|
||||||
|
{whoAmI === "player1" ? gameDetails.playerB.nickname : gameDetails.playerA.nickname}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{/* Show timeout result after game is inactive */}
|
{/* Show timeout result after game is inactive */}
|
||||||
{isGameFinishedByTimeout && (
|
{isGameFinishedByTimeout && (
|
||||||
<div className="flex flex-col items-center justify-center py-16">
|
<div className="flex flex-col items-center justify-center py-16">
|
||||||
|
|||||||
Reference in New Issue
Block a user