Skip to content

Commit

Permalink
Tutorial grid same behavior as grid
Browse files Browse the repository at this point in the history
  • Loading branch information
Cheelax committed Nov 19, 2024
1 parent af18ac6 commit 141c55a
Show file tree
Hide file tree
Showing 8 changed files with 641 additions and 37 deletions.
2 changes: 1 addition & 1 deletion client/src/ui/components/DesktopHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import LevelIndicator from "./LevelIndicator";
import SettingsDropDown from "./SettingsDropDown";
import { useNavigate } from "react-router-dom";
import { Controller } from "./Controller";
import TutorialModal from "./TutorialModal";
import TutorialModal from "./Tutorial/TutorialModal";

interface DesktopHeaderProps {
onStartTutorial: () => void;
Expand Down
2 changes: 1 addition & 1 deletion client/src/ui/components/MobileHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { useState } from "react";
import { Surrender } from "../actions/Surrender";
import LevelIndicator from "./LevelIndicator";
import { Controller } from "./Controller";
import TutorialModal from "./TutorialModal";
import TutorialModal from "./Tutorial/TutorialModal";

interface MobileHeaderProps {
onStartTutorial: () => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,29 @@
import React, { useState, useCallback, useEffect, useMemo } from "react";
import { Card } from "@/ui/elements/card";
import { useDojo } from "@/dojo/useDojo";
import { GameBonus } from "../containers/GameBonus";
import { GameBonus } from "../../containers/GameBonus";
import { useMediaQuery } from "react-responsive";
import { Account } from "starknet";
import Grid from "./Grid";
import { transformDataContractIntoBlock } from "@/utils/gridUtils";
import NextLine from "./NextLine";
import NextLine from "../NextLine";
import { Block } from "@/types/types";
import GameScores from "./GameScores";
import GameScores from "../GameScores";
import { Bonus, BonusType } from "@/dojo/game/types/bonus";
import BonusAnimation from "./BonusAnimation";
import TournamentTimer from "./TournamentTimer";
import { ModeType } from "@/dojo/game/types/mode";
import useTournament from "@/hooks/useTournament";
import { Game } from "@/dojo/game/models/game";
import useRank from "@/hooks/useRank";
import BonusAnimation from "../BonusAnimation";

import "../../grid.css";
import "../../../grid.css";
import TutorialGrid from "./TutorialGrid";

interface GameBoardProps {
initialGrid: number[][];
nextLine: number[];
score: number;
combo: number;
maxCombo: number;
hammerCount: number;
waveCount: number;
totemCount: number;
tutorialProps?: {
step: number;
targetBlock: { x: number; y: number; type: string } | null;
targetBlock: { x: number; y: number; type: "block" | "row" } | null;
isIntermission: boolean;
};
onBlockSelect?: (block: Block) => void;
Expand All @@ -39,7 +32,6 @@ interface GameBoardProps {
const GameBoardTutorial: React.FC<GameBoardProps> = ({
initialGrid,
nextLine,
score,
combo,
maxCombo,
waveCount,
Expand All @@ -48,12 +40,6 @@ const GameBoardTutorial: React.FC<GameBoardProps> = ({
tutorialProps,
onBlockSelect,
}) => {
const {
setup: {
systemCalls: { applyBonus },
},
} = useDojo();

const isMdOrLarger = useMediaQuery({ query: "(min-width: 768px)" });
const ROWS = 10;
const COLS = 8;
Expand All @@ -63,16 +49,18 @@ const GameBoardTutorial: React.FC<GameBoardProps> = ({
// State that will allow us to hide or display the next line
const [nextLineHasBeenConsumed, setNextLineHasBeenConsumed] = useState(false);
// Optimistic data (score, combo, maxcombo)
const [optimisticScore, setOptimisticScore] = useState(score);
const [optimisticScore, setOptimisticScore] = useState(0);
const [optimisticCombo, setOptimisticCombo] = useState(combo);
const [optimisticMaxCombo, setOptimisticMaxCombo] = useState(maxCombo);
const [bonusDescription, setBonusDescription] = useState("");
const [score, setScore] = useState<number | undefined>(0);
const [isIntermission, setIsIntermission] = useState(false);

useEffect(() => {
// Every time the initial grid changes, we erase the optimistic data
// and set the data to the one returned by the contract
// just in case of discrepancies
setOptimisticScore(score);
setOptimisticScore(0);
setOptimisticCombo(combo);
setOptimisticMaxCombo(maxCombo);
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -112,6 +100,11 @@ const GameBoardTutorial: React.FC<GameBoardProps> = ({
}
};

const updateValue = (intermission: boolean) => {
setScore((score ?? 0) + 15);
setIsIntermission(intermission);
};

const handleBonusWaveTx = useCallback(async (rowIndex: number) => {
setIsTxProcessing(true);
try {
Expand Down Expand Up @@ -185,17 +178,17 @@ const GameBoardTutorial: React.FC<GameBoardProps> = ({
setBonusDescription("");
}, [initialGrid]);

const memoizedInitialData = useMemo(() => {
const memorizedInitialData = useMemo(() => {
return transformDataContractIntoBlock(initialGrid);
}, [initialGrid]);

const memoizedNextLineData = useMemo(() => {
const memorizedNextLineData = useMemo(() => {
return transformDataContractIntoBlock([nextLine]);
// initialGrid on purpose
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialGrid]);

if (memoizedInitialData.length === 0) return null; // otherwise sometimes
if (memorizedInitialData.length === 0) return null; // otherwise sometimes
// the grid is not displayed in Grid because the data is not ready

return (
Expand All @@ -205,7 +198,7 @@ const GameBoardTutorial: React.FC<GameBoardProps> = ({
>
<BonusAnimation
isMdOrLarger={isMdOrLarger}
optimisticScore={optimisticScore}
optimisticScore={optimisticScore ?? 0}
optimisticCombo={optimisticCombo}
optimisticMaxCombo={optimisticMaxCombo}
/>
Expand All @@ -224,7 +217,7 @@ const GameBoardTutorial: React.FC<GameBoardProps> = ({
/>
</div>
<GameScores
score={optimisticScore}
score={optimisticScore ?? 0}
combo={optimisticCombo}
maxCombo={optimisticMaxCombo}
isMdOrLarger={isMdOrLarger}
Expand All @@ -234,7 +227,7 @@ const GameBoardTutorial: React.FC<GameBoardProps> = ({
<div
className={`flex justify-center items-center ${!isTxProcessing && "cursor-move"}`}
>
<Grid
{/* <Grid
initialData={memoizedInitialData}
nextLineData={memoizedNextLineData}
setNextLineHasBeenConsumed={setNextLineHasBeenConsumed}
Expand All @@ -251,6 +244,24 @@ const GameBoardTutorial: React.FC<GameBoardProps> = ({
setIsTxProcessing={setIsTxProcessing}
tutorialHighlight={tutorialProps?.targetBlock}
isIntermission={tutorialProps?.isIntermission}
/> */}
<TutorialGrid
initialData={memorizedInitialData}
nextLineData={memorizedNextLineData}
gridSize={GRID_SIZE}
gridHeight={ROWS}
gridWidth={COLS}
selectBlock={selectBlock}
bonus={bonus}
account={null}
tutorialStep={tutorialProps?.step ?? 0}
intermission={tutorialProps?.isIntermission}
tutorialTargetBlock={tutorialProps?.targetBlock ?? null}
onUpdate={(intermission: boolean) => {
// Ignore the intermission parameter since we only care about score updates
setOptimisticScore((prev) => (prev ?? 0) + 1);
}}
ref={setBonus}
/>
</div>

Expand All @@ -267,7 +278,7 @@ const GameBoardTutorial: React.FC<GameBoardProps> = ({
)}
</div>
<NextLine
nextLineData={nextLineHasBeenConsumed ? [] : memoizedNextLineData}
nextLineData={nextLineHasBeenConsumed ? [] : memorizedNextLineData}
gridSize={GRID_SIZE}
gridHeight={1}
gridWidth={COLS}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useCallback, useMemo } from "react";
import GameBoard from "./GameBoard";
import GameBoard from "../GameBoard";
import { BonusType } from "@/dojo/game/types/bonus";
import { ModeType } from "@/dojo/game/types/mode";
import GameBoardTutorial from "./GameBoardTutorial";
Expand Down
107 changes: 107 additions & 0 deletions client/src/ui/components/Tutorial/TutorialBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React, { useEffect, useRef, useState } from "react";
import { GameState } from "@/enums/gameEnums";
import { Block } from "@/types/types";

interface BlockProps {
block: Block;
gridSize: number;
isTxProcessing?: boolean;
transitionDuration?: number;
state?: GameState;
handleMouseDown?: (
e: React.MouseEvent<HTMLDivElement>,
block: BlockProps["block"],
) => void;
handleTouchStart?: (
e: React.TouchEvent<HTMLDivElement>,
block: BlockProps["block"],
) => void;
onTransitionBlockStart?: () => void;
onTransitionBlockEnd?: () => void;
isHighlighted?: boolean;
highlightType?: "block" | "row";
isClickable?: boolean; // If the block or row is clickable
}

const BlockContainer: React.FC<BlockProps> = ({
block,
gridSize,
transitionDuration = 100,
isTxProcessing = false,
state,
handleMouseDown,
handleTouchStart,
onTransitionBlockStart,
onTransitionBlockEnd,
isHighlighted,
highlightType = "block",
isClickable,
}) => {
const ref = useRef<HTMLDivElement | null>(null);

useEffect(() => {
if (ref.current === null) return;

const element = ref.current; // Capture the current value
const onTransitionStart = () => {
console.log("Transition started for block", block);
onTransitionBlockStart && onTransitionBlockStart();
};

element.addEventListener("transitionstart", onTransitionStart);

return () => {
element.removeEventListener("transitionstart", onTransitionStart);
};
}, [block, onTransitionBlockStart]);

const handleTransitionEnd = () => {
console.log("Transition ended for block", block);
onTransitionBlockEnd && onTransitionBlockEnd();
};

// Determine the CSS class based on highlight status
const highlightClass = isHighlighted
? highlightType === "row"
? "ring-2 ring-yellow-400 ring-opacity-50" // Subtle row highlight
: "ring-4 ring-yellow-400 animate-pulse" // Prominent block highlight
: "";

// Only make the block clickable if it's highlighted
const isBlockClickable = isHighlighted && isClickable;

return (
<div
className={`block block-${block.width} ${isTxProcessing ? "cursor-wait" : ""} ${highlightClass} ${isBlockClickable ? "cursor-pointer" : ""}`}
ref={ref}
style={{
zIndex: isHighlighted ? 999 : "auto",
position: "absolute",
top: `${block.y * gridSize + 1}px`,
left: `${block.x * gridSize + 1}px`,
width: `${block.width * gridSize}px`,
height: `${gridSize}px`,
transition:
state === GameState.GRAVITY ||
state === GameState.GRAVITY2 ||
state === GameState.GRAVITY_BONUS
? `top ${transitionDuration / 1000}s linear`
: "none", // No transition in other states
color: "white",
}}
onMouseDown={(e) => {
if (isBlockClickable && handleMouseDown) {
handleMouseDown(e, block); // Allow clicking only if it's clickable
}
}}
onTouchStart={(e) => {
if (isBlockClickable && handleTouchStart) {
handleTouchStart(e, block); // Allow touch only if it's clickable
}
}}
onTransitionEnd={handleTransitionEnd}
></div>
);
};

export default BlockContainer;
Loading

0 comments on commit 141c55a

Please sign in to comment.