Skip to content

Commit

Permalink
Merge pull request #52 from z-korp/dev
Browse files Browse the repository at this point in the history
Fix + add rank + animation for bonuses
  • Loading branch information
Matth26 authored Oct 18, 2024
2 parents 29dd269 + e3ffed9 commit 89f2642
Show file tree
Hide file tree
Showing 21 changed files with 422 additions and 90 deletions.
6 changes: 3 additions & 3 deletions client/src/dojo/game/elements/bonuses/wave.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ export class Wave {
combo: number,
_max_combo: number,
): number {
if (combo >= 8) {
if (combo >= 16) {
return 3;
} else if (combo >= 16) {
} else if (combo >= 32) {
return 2;
} else if (combo >= 24) {
} else if (combo >= 64) {
return 1;
} else {
return 0;
Expand Down
2 changes: 0 additions & 2 deletions client/src/hooks/useGame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,5 @@ export const useGame = ({ gameId }: { gameId: string | undefined }) => {
return component ? new GameClass(component) : null;
}, [component]);

//console.log("game", game);

return { game, gameKey };
};
50 changes: 50 additions & 0 deletions client/src/hooks/useGamesFromTournament.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useDojo } from "@/dojo/useDojo";
import { useEffect, useState } from "react";
import { useEntityQuery } from "@dojoengine/react";
import { Has, HasValue, getComponentValue } from "@dojoengine/recs";
import { Game } from "@/dojo/game/models/game";

export const useGamesFromTournament = ({
tournamentId,
}: {
tournamentId: number;
}): { games: Game[] } => {
const [games, setGames] = useState<Game[]>([]);

const {
setup: {
clientModels: {
models: { Game },
classes: { Game: GameClass },
},
},
} = useDojo();

const gameKeys = useEntityQuery([
Has(Game),
HasValue(Game, { tournament_id: tournamentId }),
]);
console.log("useGamesFromTournament", tournamentId, gameKeys.length);

useEffect(() => {
const components = gameKeys
.map((entity) => {
const component = getComponentValue(Game, entity);
if (!component) {
return undefined;
}
return new GameClass(component);
})
.filter((e) => e !== undefined);

if (components.length > 0) {
setGames(
components.sort(
(a: Game, b: Game) => b.score_in_tournament - a.score_in_tournament,
),
);
}
}, [gameKeys]);

return { games };
};
45 changes: 45 additions & 0 deletions client/src/hooks/useRank.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useEffect, useState } from "react";
import { useGamesFromTournament } from "./useGamesFromTournament";

const useRank = ({
tournamentId,
gameId,
}: {
tournamentId: number;
gameId: string;
}) => {
const [rank, setRank] = useState(0);
const [suffix, setSuffix] = useState("th");

const { games } = useGamesFromTournament({ tournamentId });

useEffect(() => {
const playerGameIndex = games.findIndex((game) => game.id === gameId);
if (playerGameIndex !== -1) {
const r = playerGameIndex + 1;
setRank(r);
setSuffix(getOrdinalSuffix(r));
}
}, [games, gameId]);

function getOrdinalSuffix(rank: number) {
const j = rank % 10,
k = rank % 100;

let suffix = "th"; // Default suffix

if (j === 1 && k !== 11) {
suffix = "st";
} else if (j === 2 && k !== 12) {
suffix = "nd";
} else if (j === 3 && k !== 13) {
suffix = "rd";
}

return suffix;
}

return { rank, suffix };
};

export default useRank;
10 changes: 6 additions & 4 deletions client/src/ui/actions/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogClose,
DialogTrigger,
} from "@/ui/elements/dialog";
import { Button } from "@/ui/elements/button";
import { Input } from "@/ui/elements/input";
Expand All @@ -19,6 +19,7 @@ import useAccountCustom from "@/hooks/useAccountCustom";
export const Create = () => {
const [playerName, setPlayerName] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [open, setOpen] = useState(true);
const { account } = useAccountCustom();
const {
master,
Expand All @@ -33,10 +34,11 @@ export const Create = () => {
setIsLoading(true);
try {
await create({ account: account as Account, name: playerName });
setOpen(false); // Close the dialog after successful creation
} finally {
setIsLoading(false);
}
}, [account, playerName]);
}, [account, playerName, create]);

const disabled = useMemo(() => {
return !account || !master || account === master || !!player;
Expand All @@ -45,7 +47,7 @@ export const Create = () => {
if (disabled) return null;

return (
<Dialog>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button isLoading={isLoading} disabled={isLoading} className="text-xl">
Sign Up
Expand Down Expand Up @@ -75,7 +77,7 @@ export const Create = () => {
isLoading={isLoading}
onClick={handleClick}
>
Create player
Create Player
</Button>
</DialogClose>
</DialogContent>
Expand Down
4 changes: 3 additions & 1 deletion client/src/ui/components/Block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Block } from "@/types/types";
interface BlockProps {
block: Block;
gridSize: number;
gridHeight?: number;
isTxProcessing?: boolean;
transitionDuration?: number;
state?: GameState;
Expand All @@ -23,6 +24,7 @@ interface BlockProps {
const BlockContainer: React.FC<BlockProps> = ({
block,
gridSize,
gridHeight = 10,
transitionDuration = 100,
isTxProcessing = false,
state,
Expand Down Expand Up @@ -59,7 +61,7 @@ const BlockContainer: React.FC<BlockProps> = ({

return (
<div
className={`block block-${block.width} ${isTxProcessing ? "cursor-wait" : ""}`}
className={`block block-${block.width} ${isTxProcessing ? "cursor-wait" : ""} ${block.y != gridHeight - 1 ? "z-10" : ""}`}
ref={ref}
style={{
position: "absolute",
Expand Down
195 changes: 195 additions & 0 deletions client/src/ui/components/BonusAnimation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import React, { useEffect, useRef, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { BonusType, Bonus } from "@/dojo/game/types/bonus";

interface BonusAnimationProps {
optimisticScore: number;
optimisticCombo: number;
optimisticMaxCombo: number;
isMdOrLarger: boolean;
}

const BonusAnimation: React.FC<BonusAnimationProps> = ({
optimisticScore,
optimisticCombo,
optimisticMaxCombo,
isMdOrLarger,
}) => {
const prevScoreRef = useRef(optimisticScore);
const prevComboRef = useRef(optimisticCombo);
const prevMaxComboRef = useRef(optimisticMaxCombo);

const [wonBonus, setWonBonus] = useState<BonusType | null>(null);

const [unlockedBonuses, setUnlockedBonuses] = useState({
Hammer: false,
Totem: false,
Wave: false,
});

useEffect(() => {
if (wonBonus) {
const timer = setTimeout(() => {
setWonBonus(null);
}, 1200);
return () => clearTimeout(timer);
}
}, [wonBonus]);

const getBonusImage = (bonusType: BonusType) => {
const bonus = new Bonus(bonusType);
return bonus.getIcon();
};

// Effect to check and set score-based bonuses
useEffect(() => {
if (!wonBonus) {
if (
optimisticScore >= 120 &&
prevScoreRef.current < 120 &&
!unlockedBonuses.Hammer
) {
setWonBonus(BonusType.Hammer);
setUnlockedBonuses((prev) => ({ ...prev, Hammer: true }));
} else if (
optimisticScore >= 80 &&
prevScoreRef.current < 80 &&
!unlockedBonuses.Hammer
) {
setWonBonus(BonusType.Hammer);
setUnlockedBonuses((prev) => ({ ...prev, Hammer: true }));
} else if (
optimisticScore >= 40 &&
prevScoreRef.current < 40 &&
!unlockedBonuses.Hammer
) {
setWonBonus(BonusType.Hammer);
setUnlockedBonuses((prev) => ({ ...prev, Hammer: true }));
}
}

prevScoreRef.current = optimisticScore;
}, [optimisticScore, wonBonus, unlockedBonuses]);

// Effect to check and set combo-based bonuses
useEffect(() => {
if (!wonBonus) {
if (
optimisticCombo >= 24 &&
prevComboRef.current < 24 &&
!unlockedBonuses.Wave
) {
setWonBonus(BonusType.Wave);
setUnlockedBonuses((prev) => ({ ...prev, Wave: true }));
} else if (
optimisticCombo >= 16 &&
prevComboRef.current < 16 &&
!unlockedBonuses.Wave
) {
setWonBonus(BonusType.Wave);
setUnlockedBonuses((prev) => ({ ...prev, Wave: true }));
} else if (
optimisticCombo >= 8 &&
prevComboRef.current < 8 &&
!unlockedBonuses.Wave
) {
setWonBonus(BonusType.Wave);
setUnlockedBonuses((prev) => ({ ...prev, Wave: true }));
}
}

prevComboRef.current = optimisticCombo;
}, [optimisticCombo, wonBonus, unlockedBonuses]);

// Effect to check and set maxCombo-based bonuses
useEffect(() => {
if (!wonBonus) {
if (
optimisticMaxCombo >= 6 &&
prevMaxComboRef.current < 6 &&
!unlockedBonuses.Totem
) {
setWonBonus(BonusType.Totem);
setUnlockedBonuses((prev) => ({ ...prev, Totem: true }));
} else if (
optimisticMaxCombo >= 4 &&
prevMaxComboRef.current < 4 &&
!unlockedBonuses.Totem
) {
setWonBonus(BonusType.Totem);
setUnlockedBonuses((prev) => ({ ...prev, Totem: true }));
} else if (
optimisticMaxCombo >= 2 &&
prevMaxComboRef.current < 2 &&
!unlockedBonuses.Totem
) {
setWonBonus(BonusType.Totem);
setUnlockedBonuses((prev) => ({ ...prev, Totem: true }));
}
}

prevMaxComboRef.current = optimisticMaxCombo;
}, [optimisticMaxCombo, wonBonus, unlockedBonuses]);

return (
<AnimatePresence>
{wonBonus && (
<motion.div
key="bonus-animation"
initial={{
id: "init",
scale: 0,
rotate: 360,
top: isMdOrLarger ? 200 : 150,
left: "50%",
x: "-50%",
y: "-50%",
opacity: 0,
}}
animate={{
id: "animate",
scale: isMdOrLarger ? 1 : 0.7,
rotate: 360,
top: isMdOrLarger ? 200 : 150,
left: "50%",
x: "-50%",
y: "-50%",
opacity: 1,
}}
exit={{
id: "exit",
scale: isMdOrLarger ? 0.27 : 0.23,
rotate: 0,
top: isMdOrLarger ? 40 : 35,
left:
wonBonus === BonusType.Hammer
? "9%"
: wonBonus === BonusType.Wave
? "23%"
: "37%",
x: "-50%",
y: "-50%",
transition: {
type: "spring",
duration: 1,
},
}}
transition={{
type: "spring",
stiffness: 150,
damping: 20,
}}
className="absolute flex items-center justify-center z-50"
>
<motion.img
src={getBonusImage(wonBonus)}
alt="Bonus"
className="relative z-10 w-32 h-32"
/>
</motion.div>
)}
</AnimatePresence>
);
};

export default BonusAnimation;
Loading

0 comments on commit 89f2642

Please sign in to comment.