Skip to content
Open
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
55 changes: 0 additions & 55 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="apple-mobile-web-app-capable" content="yes">

<meta name="mobile-web-app-capable" content="yes">

<link rel="manifest" href="/favicon/site.webmanifest">
<link rel="mask-icon" href="/favicon/safari-pinned-tab.svg" color="#5bbad5">
Expand Down
81 changes: 59 additions & 22 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
// src/App.js
import { useEffect, useState, useMemo, useCallback, useRef } from "react";
import { nanoid } from "nanoid";
import "./App.css";
import SingleCard from "./components/singlecard/SingleCard";
import Celebration from "./components/celebration/Celebration";
import ToggleTheme from "./components/toggleTheme/toggleTheme";
import ShowConfetti from "./components/confetti/Confetti";
import GameOver from "./components/gameover/GameOver";
import CustomCursor from "./components/CustomCursor/CustomCursor";

import { cardImages } from "./data/cardImages";
import { numbers } from "./constants/numbers";
import { secureShuffleArray, pickRandomImages } from "./utils/logic";
import { useHint } from "./utils/useHint";

const crypto = globalThis.crypto || globalThis.msCrypto;
import useTrackViewCounter from "./hooks/useTrackViewCounter";

import { useEffect, useState, useMemo, useCallback, useRef } from 'react';
import { nanoid } from 'nanoid';
import './App.css';
Expand All @@ -25,12 +42,6 @@ function App() {
const [celebrationStatus, setCelebrationStatus] = useState(false);
const [elapsedTime, setElapsedTime] = useState(undefined);
const intervalRef = useRef(null);
const hintTimeoutRef = useRef(null);
const hintIntervalRef = useRef(null);
const hintLockedRef = useRef(false);
const [hintCount, setHintCount] = useState(3);
const [hintCooldown, setHintCooldown] = useState(0);
const [hintActive, setHintActive] = useState(false);
const [animateCollapse, setAnimateCollapse] = useState(false);
const [gameOverMessage, setGameOverMessage] = useState(false);
const viewCounter = useTrackViewCounter();
Expand Down Expand Up @@ -72,6 +83,21 @@ function App() {
}
}, []);

const {
hintCount,
hintCooldown,
hintActive,
hintCards,
resetHints,
} = useHint({
cards,
handleTime,
setChoiceOne,
setChoiceTwo,
setTurns,
setDisabled,
});

const clearTimer = useCallback(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
Expand Down Expand Up @@ -162,8 +188,12 @@ function App() {
const selected = pickRandomImages(cardImages, 6);
const dup = [...selected, ...selected]
.sort(() => nanoid(16).localeCompare(nanoid(16)))
.map((card) => {
// const crypto = globalThis.crypto || globalThis.msCrypto;

.map(card => {
const crypto = globalThis.crypto || globalThis.msCrypto;

const rand = new Uint32Array(1);
crypto.getRandomValues(rand);
return { ...card, id: rand[0], matched: false };
Expand All @@ -177,18 +207,20 @@ function App() {
setMatched(0);
setCelebrationStatus(false);
setElapsedTime(undefined);
clearTimer();

setAnimateCollapse(true);
Comment thread
devanshu-puri marked this conversation as resolved.
setTimeout(() => setAnimateCollapse(false), 1200);
setGameOverMessage(false);
}, [clearTimer]);
}, []);

const handleNewGame = useCallback(() => {
resetHints();
playSound("audio/start.mp3");
setHintCount(3);
setHintCooldown(0);
playSound('audio/start.mp3');
shuffledCards();
}, [playSound, shuffledCards]);
}, [playSound, shuffledCards, resetHints]);

const handleChoice = useCallback(
card => {
Expand Down Expand Up @@ -219,7 +251,7 @@ function App() {
} else if (choiceOne) {
playSound('/audio/swap.wav');
}
}, [choiceOne, choiceTwo, resetTurn, playSound]);
}, [choiceOne, choiceTwo, resetTurn, playSound, hintActive]);

useEffect(() => {
if (matched === cards.length && turns) {
Expand Down Expand Up @@ -250,18 +282,9 @@ function App() {
setHighScore(hs);
return () => {
clearTimer();
if (hintTimeoutRef.current) {
clearTimeout(hintTimeoutRef.current);
hintTimeoutRef.current = null;
}
if (hintIntervalRef.current) {
clearInterval(hintIntervalRef.current);
hintIntervalRef.current = null;
}
hintLockedRef.current = false;
setHintCooldown(0);
resetHints();
};
}, [shuffledCards, clearTimer]);
}, [shuffledCards, clearTimer, resetHints]);

return (
<div className='App'>
Expand All @@ -276,6 +299,18 @@ function App() {
{celebrationStatus && <ShowConfetti />}
<img src='/img/logo.png' alt='A&A Match' style={{ height: '60px' }} />
<br />
<div className="button-box">
<button onClick={handleNewGame}>New Game</button>
<div className="hint-box">
<button
className="hint"
onClick={hintCards}
disabled={hintCooldown > 0 || hintCount <= 0 || hintActive}
>
{hintCooldown > 0 ? `Hint (ready in ${hintCooldown}s)` : "Hint"}
</button>
<p className="hint-count">
{hintCount === 1 ? "Hint Remaining: " : "Hints Remaining: "}
<div className='button-box'>
<button onClick={handleNewGame}>New Game</button>
<div className='hint-box'>
Expand All @@ -293,6 +328,8 @@ function App() {
</div>
</div>
<ToggleTheme />
<div className={`card-grid ${animateCollapse ? "collapse-animation" : ""}`}>
{cards.map((card) => (
<div
className={`card-grid ${animateCollapse ? 'collapse-animation' : ''}`}
>
Expand Down
45 changes: 45 additions & 0 deletions src/components/singlecard/SingleCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,51 @@ export default function SingleCard({ card, handleChoice, flipped, disabled }) {
}
};

const handleClick = () => {
if (!disabled) {
handleChoice(card);
}
};

return (
<div className={`card ${flipped && card.matched ? "animation" : ""}`}>
<div className={flipped ? "flipped" : ""}>
<div id='front' className='front' style={{ display: "flex" }}>
<img
className="front-background"
src={flipped ? card.src : undefined}
alt="card front blur background"
width="200"
height="200"
/>
<img
className="front"
src={flipped ? card.src : undefined}
alt="card front"
width="200"
height="200"
/>

</div>
<button
className="back"
onClick={handleClick}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
handleClick();
}
}}
aria-label="card back"
style={{ background: 'none', border: 'none', padding: 0 }}
>
<img
src="/img/memory/cover.png"
alt="card back"
width="200"
height="198"
/>
</button>
</div>
return (
<div className={`card ${flipped && card.matched ? 'animation' : ''}`}>
<div className={flipped ? 'flipped' : ''}>
Expand Down
8 changes: 6 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client'; // <-- make sure this is 'react-dom/client'
import './index.css';
import App from './App';

ReactDOM.render(
const container = document.getElementById('root');
const root = createRoot(container); // <-- create root
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
</React.StrictMode>,
document.getElementById('root')
);
1 change: 0 additions & 1 deletion src/utils/logic.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// src/utils/logic.js
export function secureShuffleArray(array) {
const cryptoObj = globalThis.crypto || globalThis.msCrypto;
for (let i = array.length - 1; i > 0; i--) {
Expand Down
Loading