diff --git a/client/src/hooks/useDragHandlers.tsx b/client/src/hooks/useDragHandlers.tsx new file mode 100644 index 0000000..095930c --- /dev/null +++ b/client/src/hooks/useDragHandlers.tsx @@ -0,0 +1,28 @@ +import { MoveType } from "@/enums/moveEnum"; +import { useCallback } from "react"; + +const useDragHandlers = ( + handleDragMove: (clientX: number, moveType: MoveType) => void, +) => { + const handleMouseMove = useCallback( + (e: React.MouseEvent) => { + handleDragMove(e.clientX, MoveType.MOUSE); + }, + [handleDragMove], + ); + + const handleTouchMove = useCallback( + (e: React.TouchEvent) => { + const touch = e.touches[0]; + handleDragMove(touch.clientX, MoveType.TOUCH); + }, + [handleDragMove], + ); + + return { + handleMouseMove, + handleTouchMove, + }; +}; + +export default useDragHandlers; diff --git a/client/src/hooks/useGrid.tsx b/client/src/hooks/useGrid.tsx index d098751..42a5fba 100644 --- a/client/src/hooks/useGrid.tsx +++ b/client/src/hooks/useGrid.tsx @@ -2,6 +2,7 @@ import { useEffect, useState, useRef } from "react"; import { useGame } from "@/hooks/useGame"; import { formatBigIntToBinaryArrayCustom } from "@/utils/gridUtils"; import useDeepMemo from "./useDeepMemo"; +import { consoleTSLog } from "@/utils/logger"; interface DebugData { blocksRaw: bigint; diff --git a/client/src/ui/components/BackgroundBoard.tsx b/client/src/ui/components/BackgroundBoard.tsx index 2983e69..b91cfcd 100644 --- a/client/src/ui/components/BackgroundBoard.tsx +++ b/client/src/ui/components/BackgroundBoard.tsx @@ -1,8 +1,6 @@ -import { ReactNode } from "react"; import { motion, MotionProps } from "framer-motion"; interface BackgroundImageProps { - children: ReactNode; imageBackground: string; animate?: MotionProps["animate"]; initial?: MotionProps["initial"]; @@ -10,23 +8,19 @@ interface BackgroundImageProps { } const BackgroundBoard: React.FC = ({ - children, imageBackground, animate, initial, transition, }) => { return ( -
- - {children} -
+ ); }; diff --git a/client/src/ui/components/GameBoard.tsx b/client/src/ui/components/GameBoard.tsx index 99c00c8..1bd39ea 100644 --- a/client/src/ui/components/GameBoard.tsx +++ b/client/src/ui/components/GameBoard.tsx @@ -1,10 +1,4 @@ -import React, { - useState, - useCallback, - useEffect, - useMemo, - useRef, -} from "react"; +import React, { useState, useCallback, useEffect, useMemo } from "react"; import { Card } from "@/ui/elements/card"; import { useDojo } from "@/dojo/useDojo"; import { GameBonus } from "../containers/GameBonus"; @@ -24,6 +18,7 @@ import { Game } from "@/dojo/game/models/game"; import useRank from "@/hooks/useRank"; import "../../grid.css"; +import { consoleTSLog } from "@/utils/logger"; interface GameBoardProps { initialGrid: number[][]; @@ -79,6 +74,7 @@ const GameBoard: React.FC = ({ setOptimisticScore(score); setOptimisticCombo(combo); setOptimisticMaxCombo(maxCombo); + consoleTSLog("info", "Initial grid changed in GameBoard"); // eslint-disable-next-line react-hooks/exhaustive-deps }, [initialGrid]); @@ -192,11 +188,15 @@ const GameBoard: React.FC = ({ useEffect(() => { // Reset the isTxProcessing state and the bonus state when the grid changes // meaning the tx as been processed, and the client state updated + consoleTSLog("success", "Game board is trigger"); setBonus(BonusType.None); setBonusDescription(""); }, [initialGrid]); + consoleTSLog("info", "Rendering GameBoard component"); + const memoizedInitialData = useMemo(() => { + consoleTSLog("success", "Transforming data in gameboard"); return transformDataContractIntoBlock(initialGrid); }, [initialGrid]); diff --git a/client/src/ui/components/Grid.tsx b/client/src/ui/components/Grid.tsx index 61948af..813e943 100644 --- a/client/src/ui/components/Grid.tsx +++ b/client/src/ui/components/Grid.tsx @@ -14,6 +14,7 @@ import { deepCompareBlocks, getBlocksSameRow, getBlocksSameWidth, + transformToGridFormat, } from "@/utils/gridUtils"; import { MoveType } from "@/enums/moveEnum"; import AnimatedText from "../elements/animatedText"; @@ -24,6 +25,9 @@ import ConfettiExplosion, { ConfettiExplosionRef } from "./ConfettiExplosion"; import { useMusicPlayer } from "@/contexts/hooks"; import "../../grid.css"; +import { consoleTSLog } from "@/utils/logger"; +import useDragHandlers from "@/hooks/useDragHandlers"; +import { calculateFallDistance, isBlocked } from "@/utils/gridPhysics"; const { VITE_PUBLIC_DEPLOY_TYPE } = import.meta.env; @@ -112,9 +116,14 @@ const Grid: React.FC = ({ useEffect(() => { if (applyData) { if (deepCompareBlocks(saveGridStateblocks, initialData)) { + consoleTSLog( + "Grid state is the same, no need to apply data", + transformToGridFormat(saveGridStateblocks, gridWidth, gridHeight), + ); return; } if (moveTxAwaitDone) { + consoleTSLog("success", "Applying data to grid"); setSaveGridStateblocks(initialData); setBlocks(initialData); setNextLine(nextLineData); @@ -249,14 +258,7 @@ const Grid: React.FC = ({ handleDragStart(touch.clientX, block); }; - const handleMouseMove = (e: React.MouseEvent) => { - handleDragMove(e.clientX, MoveType.MOUSE); - }; - - const handleTouchMove = (e: React.TouchEvent) => { - const touch = e.touches[0]; - handleDragMove(touch.clientX, MoveType.TOUCH); - }; + const { handleMouseMove, handleTouchMove } = useDragHandlers(handleDragMove); const endDrag = () => { if (!dragging) return; @@ -352,69 +354,14 @@ const Grid: React.FC = ({ [account, isMoving, gridHeight, move], ); - const isBlocked = ( - initialX: number, - newX: number, - y: number, - width: number, - blocks: Block[], - blockId: number, - ) => { - const rowBlocks = blocks.filter( - (block) => block.y === y && block.id !== blockId, - ); - - if (newX > initialX) { - for (const block of rowBlocks) { - if (block.x >= initialX + width && block.x < newX + width) { - return true; - } - } - } else { - for (const block of rowBlocks) { - if (block.x + block.width > newX && block.x <= initialX) { - return true; - } - } - } - - return false; - }; - - const calculateFallDistance = useCallback( - (block: Block, blocks: Block[]) => { - let maxFall = gridHeight - block.y - 1; - for (let y = block.y + 1; y < gridHeight; y++) { - if (isCollision(block.x, y, block.width, blocks, block.id)) { - maxFall = y - block.y - 1; - break; - } - } - return maxFall; - }, - [gridHeight], - ); - - const isCollision = ( - x: number, - y: number, - width: number, - blocks: Block[], - blockId: number, - ) => { - return blocks.some( - (block) => - block.id !== blockId && - block.y === y && - x < block.x + block.width && - x + width > block.x, - ); - }; - const applyGravity = useCallback(() => { setBlocks((prevBlocks) => { const newBlocks = prevBlocks.map((block) => { - const fallDistance = calculateFallDistance(block, prevBlocks); + const fallDistance = calculateFallDistance( + block, + prevBlocks, + gridHeight, + ); if (fallDistance > 0) { return { ...block, y: block.y + 1 }; } @@ -430,7 +377,7 @@ const Grid: React.FC = ({ return newBlocks; }); - }, [calculateFallDistance]); + }, [gridHeight]); useEffect(() => { const interval = setInterval(() => { diff --git a/client/src/ui/elements/pagination.tsx b/client/src/ui/elements/pagination.tsx index d516592..420c4ae 100644 --- a/client/src/ui/elements/pagination.tsx +++ b/client/src/ui/elements/pagination.tsx @@ -64,6 +64,11 @@ const PaginationLink = ({ ); PaginationLink.displayName = "PaginationLink"; +const isMdOrLarger = () => { + if (typeof window === "undefined") return false; + return window.matchMedia("(min-width: 768px)").matches; +}; + const PaginationPrevious = ({ className, ...props @@ -75,7 +80,7 @@ const PaginationPrevious = ({ {...props} > - Previous + {!isMdOrLarger && Previous} ); PaginationPrevious.displayName = "PaginationPrevious"; @@ -90,7 +95,7 @@ const PaginationNext = ({ className={cn("gap-1 pr-2.5", className)} {...props} > - Next + {!isMdOrLarger && Next} ); diff --git a/client/src/ui/screens/Home.tsx b/client/src/ui/screens/Home.tsx index 3decf0b..e90bdef 100644 --- a/client/src/ui/screens/Home.tsx +++ b/client/src/ui/screens/Home.tsx @@ -40,6 +40,7 @@ import useViewport from "@/hooks/useViewport"; import { TweetPreview } from "../components/TweetPreview"; import { Schema } from "@dojoengine/recs"; import { useGrid } from "@/hooks/useGrid"; +import { consoleTSLog } from "@/utils/logger"; export const Home = () => { const { @@ -79,6 +80,8 @@ export const Home = () => { // State variables for modals const [isTournamentsOpen, setIsTournamentsOpen] = useState(false); + const gameIsOver = game?.isOver(); + const composeTweet = useCallback(() => { setLevel(player?.points ? Level.fromPoints(player?.points).value : ""); setScore(game?.score); @@ -86,7 +89,7 @@ export const Home = () => { }, [game?.score, player?.points]); useEffect(() => { - if (game?.over) { + if (gameIsOver) { if (gameGrid.current !== null) { toPng(gameGrid.current, { cacheBust: true }) .then((dataUrl) => { @@ -99,7 +102,7 @@ export const Home = () => { } setIsGameOn("isOver"); } - }, [composeTweet, game?.over]); + }, [composeTweet, gameIsOver]); useEffect(() => { // Check if game is defined and not over @@ -110,7 +113,7 @@ export const Home = () => { setIsGameOn("isOver"); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [game?.over]); + }, [gameIsOver]); const imageTotemTheme = theme === "dark" ? imgAssets.imageTotemDark : imgAssets.imageTotemLight; @@ -134,22 +137,22 @@ export const Home = () => { const [chestIsOpen, setChestIsOpen] = useState(false); const [isGameOverOpen, setIsGameOverOpen] = useState(false); - const prevGameOverRef = useRef(game?.over); + const prevGameOverRef = useRef(gameIsOver); useEffect(() => { if (prevGameOverRef.current !== undefined) { // Check if game.over transitioned from false to true - if (!prevGameOverRef.current && game?.over) { + if (!prevGameOverRef.current && gameIsOver) { setIsGameOverOpen(true); } } // Update the ref with the current value of game.over - prevGameOverRef.current = game?.over; - }, [game?.over]); + prevGameOverRef.current = gameIsOver; + }, [gameIsOver]); - /*useEffect(() => { - console.log("==================> Grid is changing"); - }, [grid]);*/ + useEffect(() => { + consoleTSLog("danger", "=============== Grid is changing ==============="); + }, [grid]); // Define render functions const renderDesktopView = () => ( @@ -241,7 +244,10 @@ export const Home = () => { /> {/* Main Content */} - +
{ repeatType: "reverse", ease: "easeInOut", }} - > -
-
- {!isSigning && } - {(!game || (!!game && isGameOn === "isOver")) && ( - <> - {isMdOrLarger - ? renderDesktopView() - : isTournamentsOpen - ? renderTournamentsView() - : renderMobileView()} - - )} - {game && ( - setIsGameOverOpen(false)} - game={game} - /> - )} - {!!game && isGameOn === "isOver" && !isTournamentsOpen && ( - <> -
-
-

Game Over

- -
-
- {game.score} - -
-
- {game.combo} - -
-
- {game.max_combo} - -
+ /> +
+
+ {!isSigning && } + {(!game || (!!game && isGameOn === "isOver")) && ( + <> + {isMdOrLarger + ? renderDesktopView() + : isTournamentsOpen + ? renderTournamentsView() + : renderMobileView()} + + )} + {game && ( + setIsGameOverOpen(false)} + game={game} + /> + )} + {!!game && isGameOn === "isOver" && !isTournamentsOpen && ( + <> +
+
+

Game Over

+ +
+
+ {game.score} + +
+
+ {game.combo} + +
+
+ {game.max_combo} +
- - {!isTournamentsOpen && ( - - - - - - - - Feedback - -
- -
-
-
- )} - - )} - {!!game && isGameOn === "isOn" && ( -
-
- -
- {isMdOrLarger && ( -
- -
- )}
- )} -
-
- - - {!animationDone && ( - <> - - + + {!isTournamentsOpen && ( + + + + + + + + Feedback + +
+ +
+
+
+ )} )} -
- - + {!!game && isGameOn === "isOn" && ( +
+
+ +
+ {isMdOrLarger && ( +
+ +
+ )} +
+ )} +
+
+ + + {!animationDone && ( + <> + + + + )} + +
); diff --git a/client/src/utils/gridPhysics.ts b/client/src/utils/gridPhysics.ts new file mode 100644 index 0000000..a312000 --- /dev/null +++ b/client/src/utils/gridPhysics.ts @@ -0,0 +1,61 @@ +import { Block } from "@/types/types"; + +export const isBlocked = ( + initialX: number, + newX: number, + y: number, + width: number, + blocks: Block[], + blockId: number, +) => { + const rowBlocks = blocks.filter( + (block) => block.y === y && block.id !== blockId, + ); + + if (newX > initialX) { + for (const block of rowBlocks) { + if (block.x >= initialX + width && block.x < newX + width) { + return true; + } + } + } else { + for (const block of rowBlocks) { + if (block.x + block.width > newX && block.x <= initialX) { + return true; + } + } + } + + return false; +}; + +export const isCollision = ( + x: number, + y: number, + width: number, + blocks: Block[], + blockId: number, +) => { + return blocks.some( + (block) => + block.id !== blockId && + block.y === y && + x < block.x + block.width && + x + width > block.x, + ); +}; + +export const calculateFallDistance = ( + block: Block, + blocks: Block[], + gridHeight: number, +) => { + let maxFall = gridHeight - block.y - 1; + for (let y = block.y + 1; y < gridHeight; y++) { + if (isCollision(block.x, y, block.width, blocks, block.id)) { + maxFall = y - block.y - 1; + break; + } + } + return maxFall; +}; diff --git a/client/src/utils/logger.ts b/client/src/utils/logger.ts new file mode 100644 index 0000000..e3e1dfb --- /dev/null +++ b/client/src/utils/logger.ts @@ -0,0 +1,43 @@ +type LogColor = "info" | "success" | "danger"; + +export function consoleTSLog(...args: any[]): void { + const now = new Date(); + const time = now.toTimeString().split(" ")[0]; + const milliseconds = now.getMilliseconds().toString().padStart(3, "0"); + + let color: LogColor | undefined; + let messageArgs: any[]; + + // Vérifier si le premier argument est une couleur + if ( + typeof args[0] === "string" && + ["info", "success", "danger"].includes(args[0]) + ) { + color = args[0] as LogColor; + messageArgs = args.slice(1); // Exclut la couleur des arguments du message + } else { + color = "info"; // Couleur par défaut + messageArgs = args; // Utilise tous les arguments comme message + } + + // Définir la couleur de fond selon le type de log + let backgroundColor; + switch (color) { + case "info": + backgroundColor = "blue"; + break; + case "success": + backgroundColor = "green"; + break; + case "danger": + backgroundColor = "red"; + break; + } + + // Afficher le message avec le timestamp stylisé + console.log( + `%c[${time}.${milliseconds}]`, + `font-weight: bold; background-color: ${backgroundColor}; color: white; padding: 2px 4px; border-radius: 3px;`, + ...messageArgs, + ); +}