mirror of
https://github.com/averel10/crypto_clash.git
synced 2026-03-12 10:58:11 +01:00
Merge pull request #14 from averel10/player-nicknames
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.
|
||||
// Return player's ID and game ID upon successful registration.
|
||||
function register(
|
||||
uint gameId
|
||||
uint gameId,
|
||||
string memory nickname
|
||||
)
|
||||
public
|
||||
payable
|
||||
@@ -80,11 +81,14 @@ contract Game {
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
if (game.playerA.addr == address(0x0)) {
|
||||
game.playerA.addr = payable(msg.sender);
|
||||
game.playerA.nickname = nickname;
|
||||
game.initialBet = msg.value;
|
||||
return (1, gameId);
|
||||
} else if (game.playerB.addr == address(0x0)) {
|
||||
@@ -93,6 +97,7 @@ contract Game {
|
||||
"Cannot play against yourself"
|
||||
);
|
||||
game.playerB.addr = payable(msg.sender);
|
||||
game.playerB.nickname = nickname;
|
||||
return (2, gameId);
|
||||
}
|
||||
|
||||
|
||||
@@ -226,6 +226,27 @@ export default function Commit({
|
||||
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">
|
||||
|
||||
{/* 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 */}
|
||||
{isGameFinishedByTimeout ? (
|
||||
<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 [loading, setLoading] = useState(false);
|
||||
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 [userGameIds, setUserGameIds] = useState<Set<number>>(new Set());
|
||||
|
||||
@@ -75,10 +77,21 @@ export default function GameList({
|
||||
// Join an existing game
|
||||
const handleJoinGame = async (gameId: number, bet: string) => {
|
||||
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);
|
||||
try {
|
||||
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 result = await (globalThis as any).ethereum.request({
|
||||
method: "eth_sendTransaction",
|
||||
@@ -94,6 +107,10 @@ export default function GameList({
|
||||
],
|
||||
});
|
||||
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));
|
||||
fetchActiveGames();
|
||||
} catch (err: any) {
|
||||
@@ -107,10 +124,20 @@ export default function GameList({
|
||||
// Create a new game
|
||||
const handleCreateGame = async () => {
|
||||
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);
|
||||
try {
|
||||
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 result = await (globalThis as any).ethereum.request({
|
||||
method: "eth_sendTransaction",
|
||||
@@ -127,6 +154,7 @@ export default function GameList({
|
||||
});
|
||||
showSuccessToast("Created new game! Transaction: " + result);
|
||||
setNewGameBet("0.01");
|
||||
setNewGameNickname("");
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
fetchActiveGames();
|
||||
} catch (err: any) {
|
||||
@@ -165,6 +193,14 @@ export default function GameList({
|
||||
➕ Create New Game
|
||||
</h3>
|
||||
<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
|
||||
type="number"
|
||||
min="0.01"
|
||||
@@ -184,7 +220,7 @@ export default function GameList({
|
||||
</Button>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -234,7 +270,10 @@ export default function GameList({
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400 mb-1 font-semibold">
|
||||
Player A
|
||||
</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)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -251,16 +290,25 @@ export default function GameList({
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400 mb-1 font-semibold">
|
||||
Player B
|
||||
</p>
|
||||
<p className="font-mono text-sm text-slate-700 dark:text-slate-300 break-all">
|
||||
{game.playerB.addr === "0x0000000000000000000000000000000000000000"
|
||||
? "⏳ Waiting..."
|
||||
: formatAddress(game.playerB.addr)}
|
||||
</p>
|
||||
{game.playerB.addr === "0x0000000000000000000000000000000000000000" ? (
|
||||
<p className="font-semibold text-base text-slate-500 dark:text-slate-400">
|
||||
⏳ Waiting...
|
||||
</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>
|
||||
|
||||
{/* 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) ? (
|
||||
<Button
|
||||
onClick={() => onPlayClick?.(game.returnGameId)}
|
||||
@@ -269,24 +317,40 @@ export default function GameList({
|
||||
>
|
||||
▶ Play
|
||||
</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
|
||||
onClick={() =>
|
||||
handleJoinGame(game.returnGameId, game.initialBet)
|
||||
}
|
||||
disabled={
|
||||
loading ||
|
||||
!account ||
|
||||
!contract ||
|
||||
game.playerB.addr !==
|
||||
"0x0000000000000000000000000000000000000000"
|
||||
}
|
||||
disabled={true}
|
||||
variant="primary"
|
||||
>
|
||||
{game.playerB.addr ===
|
||||
"0x0000000000000000000000000000000000000000"
|
||||
? "Join Game"
|
||||
: "Full"}
|
||||
Full
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -193,6 +193,26 @@ export default function Reveal({
|
||||
|
||||
return (
|
||||
<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 */}
|
||||
{isGameFinishedByTimeout && (
|
||||
<div className="flex flex-col items-center justify-center py-16">
|
||||
|
||||
Reference in New Issue
Block a user