Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add winner animation and other good stuff #47

Merged
merged 2 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/web/src/app/@modal/(.)join/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ export default function GameJoinModal() {
return (
<Modal
className={`animate-scaleUp transition-all flex flex-col items-center overflow-y-scroll max-w-md duration-1000 ${
loading ? "max-h-80" : "max-h-[90dvh]"
loading ? "max-h-96" : "max-h-[90dvh]"
}`}
closeButton
>
{isJoining ? (
<>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/games/[id]/components/DownloadModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function DownloadModal() {
const isLoading = useSelector(selectIsGameLoading$()) || isWalletLoading;
if (isLoading)
return (
<Modal className="w-96 h-52 animate-grow-in">
<Modal closeButton={false} className="w-96 h-52 animate-grow-in">
<div className="flex flex-col items-center gap-1 text-white text-center">
{percentage ? (
<>
Expand Down
10 changes: 5 additions & 5 deletions apps/web/src/app/games/[id]/components/Pot.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useSelector } from "@legendapp/state/react";
import chips from "@src/assets/images/chips/chips-3-stacks.png";
import Image from "next/image";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { selectPot$ } from "../state/selectors/gameSelectors";

export default function Pot() {
Expand All @@ -12,17 +12,17 @@ export default function Pot() {
return (
<>
<div
className={`text-white items-center bg-black/20 px-4 py-1 mt-2 absolute pot ${
started ? "animate-headShake " : ""
className={`text-white sm:text-lg text-[10px] items-center bg-black/20 px-4 py-1 mt-2 absolute pot ${
started ? "" : ""
}`}
>
${pot}
</div>
{raisedSeats.map((seat) => (
<Image
key={seat}
className={`absolute duration-700 ${
started ? "pot animate-fading opacity-0" : `seat-${seat} opacity-0`
className={`absolute duration-1000 ${
started ? "pot animate-fading opacity-0" : `seat-${seat} opacity-0`
}`}
width={32}
height={32}
Expand Down
65 changes: 54 additions & 11 deletions apps/web/src/app/games/[id]/components/Seat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import Avatar1 from "@src/assets/images/avatars/avatar-1.png";
import Avatar2 from "@src/assets/images/avatars/avatar-2.png";
import Avatar3 from "@src/assets/images/avatars/avatar-3.png";
import Avatar4 from "@src/assets/images/avatars/avatar-4.png";
import Avatar5 from "@src/assets/images/avatars/avatar-5.png";
import Avatar6 from "@src/assets/images/avatars/avatar-6.png";
import Avatar7 from "@src/assets/images/avatars/avatar-7.png";
import Avatar8 from "@src/assets/images/avatars/avatar-8.png";
import Avatar9 from "@src/assets/images/avatars/avatar-9.png";
import Avatar10 from "@src/assets/images/avatars/avatar-10.png";
import WinnerStuff from "@src/assets/images/champagne-pixel-animated.gif";
import { CARDS_MAP } from "@src/lib/constants/cards";
import Image from "next/image";
import { useEffect, useMemo, useRef, useState } from "react";
Expand All @@ -17,20 +24,43 @@ import {
import Card from "./Card";
import DealerBadge from "./DealerBadge";

function hashPlayerIDToAvatar(id: string, avatarCount: number) {
let hash = 0;
for (let i = 0; i < id.length; i++) {
hash = id.charCodeAt(i) + ((hash << 5) - hash);
}
return Math.abs(hash % avatarCount);
}

export default function Seat({
player,
seatNumber,
}: {
player: Player;
seatNumber: number;
}) {
const avatars = [Avatar1, Avatar2, Avatar3, Avatar4];
const avatars = [
Avatar1,
Avatar2,
Avatar3,
Avatar4,
Avatar5,
Avatar6,
Avatar7,
Avatar8,
Avatar9,
Avatar10,
];
const mounted = useRef(false);

// Hash the player ID to consistently pick an avatar
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
const randomAvatar = useMemo(
() => avatars[Math.floor(Math.random() * avatars.length)],
[seatNumber],
);
const playerAvatarIndex = useMemo(() => {
return hashPlayerIDToAvatar(player.id, avatars.length);
}, [player.id]);

const playerAvatar = avatars[playerAvatarIndex];

const shufflingPlayer = useSelector(selectShufflingPlayer$());
const awaitingBetFrom = useSelector(selectAwaitingBetFrom$());
const isPlayerTurn = awaitingBetFrom?.id === player.id;
Expand All @@ -39,6 +69,7 @@ export default function Seat({

const gameStatus = useSelector(selectGameStatus$());
const [lastAction, setLastAction] = useState("");
const isWinner = false;

useEffect(() => {
if (mounted.current) return;
Expand All @@ -62,22 +93,34 @@ export default function Seat({
shufflingPlayer?.id === player.id
? "seat-dealer scale-110"
: `seat-${seatNumber} ${2 > Math.random() ? "z-[501]" : ""} ${
player.status === PlayerStatus.folded ? "opacity-80" : ""
} ${player.status === PlayerStatus.sittingOut ? "opacity-70" : ""}`
player.status === PlayerStatus.sittingOut ? "opacity-80" : ""
}`
}`}
style={{ animationDelay: `${seatNumber * 100 + 100}ms` }}
>
<Image
draggable={false}
src={randomAvatar ?? ""}
src={playerAvatar ?? ""}
alt="avatar"
className={`w-full aspect-square animate-grow-in grow-0 sm:w-14 rounded-full shrink-0 border-2 md:border-8 ${
className={`w-full aspect-square object-contain bg-black/70 h-full animate-grow-in grow-0 sm:w-14 rounded-full shrink-0 border-2 md:border-8 ${
shufflingPlayer?.id === player.id ? "scale-125 animate-bounce delay-1000" : ""
} ${isPlayerTurn ? "ring-8 animate-bounce ring-blue-600 duration-500" : ""}`}
} ${isPlayerTurn ? "ring-8 animate-bounce ring-blue-600 duration-500" : ""} ${
player.status === PlayerStatus.sittingOut || player.status === PlayerStatus.folded
? "scale-95"
: ""
} ${isWinner ? "animate-tada" : ""}`}
style={{
transitionDelay: mounted.current ? "0ms" : `${150 * seatNumber}ms`,
}}
/>
{isWinner && (
<Image
className={`absolute top-0 -right-14 animate-grow-in ${isWinner ? "animate-tada" : ""}`}
alt="chicken winner"
src={WinnerStuff}
/>
)}

{(lastAction || player.status === PlayerStatus.folded) && (
<div className="nes-balloon from-left z-50 animate-grow-in origin-bottom-left absolute top-0 -right-20 text-center p-2 sm:p-2">
<p className="text-[8px] sm:text-sm">
Expand All @@ -103,7 +146,7 @@ export default function Seat({
)}
</div>
)}
<div className="bg-[#b87d5b] shrink-0 flex-col rounded-sm line-clamp-1 relative flex justify-center text-[8px] text-white text-center shadow-2xl md:text-sm px-1 bg-opacity-80">
<div className="bg-black/70 shrink-0 flex-col rounded-sm line-clamp-1 relative flex justify-center text-[8px] text-white text-center shadow-2xl md:text-sm px-1 ">
<span>{player.id.slice(2, 8)} </span> <span>${player.balance}</span>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/app/games/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { GameStatus } from "@jeton/ts-sdk";
import { useSelector } from "@legendapp/state/react";

import { JetonContext } from "@src/components/JetonContextProvider";
import { mockPlayers } from "@src/lib/constants/mocks";
import { orderPlayersSeats } from "@src/utils/seat";
import { useRouter } from "next/navigation";
import { useContext, useEffect, useMemo, useState } from "react";
Expand Down Expand Up @@ -115,7 +116,7 @@ export default function PlayPage({ params }: { params: { id: string } }) {
)}
</Table>
<PlayerActions />
{/* <DownloadModal /> */}
<DownloadModal />
<GameStatusBox />
</GameContainer>
);
Expand Down
24 changes: 15 additions & 9 deletions apps/web/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,20 @@
}

.pot {
top: 65%;
left: 50%;
top: 65% !important;
left: 50% !important;
transform: translateX(-50%);
}
}

.pot {
top: 80%;
left: 50%;
transform: translateX(-50%);
}

.seat-1 {
top: 120%;
top: 110%;
left: 50%;
transform: translateX(-50%);
}
Expand Down Expand Up @@ -229,7 +235,7 @@
}

.seat-4 {
top: 40%;
top: 30%;
left: 20%;
transform: translateY(-50%);
}
Expand All @@ -242,8 +248,8 @@
}

.seat-5 {
top: 10%;
left: 20%;
top: -10%;
left: 25%;
transform: translateY(-50%);
}

Expand All @@ -255,8 +261,8 @@
}

.seat-6 {
top: 10%;
left: 72%;
top: -10%;
left: 68%;
transform: translateY(-50%);
}

Expand All @@ -268,7 +274,7 @@
}

.seat-7 {
top: 40%;
top: 30%;
left: 72%;
transform: translateY(-50%);
}
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/assets/icons/close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/web/src/assets/images/avatars/avatar-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/src/assets/images/avatars/avatar-10.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/web/src/assets/images/avatars/avatar-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/web/src/assets/images/avatars/avatar-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/web/src/assets/images/avatars/avatar-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/src/assets/images/avatars/avatar-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/src/assets/images/avatars/avatar-6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/src/assets/images/avatars/avatar-7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/src/assets/images/avatars/avatar-8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/src/assets/images/avatars/avatar-9.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 13 additions & 11 deletions apps/web/src/components/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
"use client";

import { motion } from "framer-motion";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { type MouseEventHandler, useCallback, useEffect, useRef } from "react";
import { cn } from "../../../../packages/ui/libs/utils";
import CloseIcon from "../assets/icons/close.svg";

export default function Modal({
children,
className,
closeButton = true,
}: {
children: React.ReactNode;
className?: string;
closeButton?: boolean;
}) {
const overlay = useRef(null);
const wrapper = useRef(null);
Expand All @@ -20,15 +24,6 @@ export default function Modal({
router.back();
}, [router]);

const onClick: MouseEventHandler = useCallback(
(e) => {
if (e.target === overlay.current || e.target === wrapper.current) {
if (onDismiss) onDismiss();
}
},
[onDismiss],
);

const onKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.key === "Escape") onDismiss();
Expand All @@ -45,18 +40,25 @@ export default function Modal({
<motion.div
ref={overlay}
className="fixed z-50 inset-0 flex justify-center items-center animate-fadeIn"
onClick={onClick}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
>
<div
ref={wrapper}
className={cn(
`absolute rounded-2xl p-10 bg-[url("/images/wood-pattern-light.png")] bg-repeat bg-center bg-[length:200px_200px] border-8 shadow border-[#b87d5b]`,
`absolute rounded-2xl p-10 bg-[url("/images/wood-pattern-light.png")] pt-20 bg-repeat bg-center bg-[length:200px_200px] border-8 shadow border-[#b87d5b]`,
className,
)}
>
{closeButton && (
<button
className="absolute right-3 top-3 opacity-80 hover:scale-90 duration-300"
onClick={onDismiss}
>
<Image width={48} height={48} src={CloseIcon} alt="close icon" />
</button>
)}
{children}
</div>
</motion.div>
Expand Down
Loading