import React, { useEffect, useMemo, useState } from “react”;
import { motion } from “framer-motion”;
import { Button } from “@/components/ui/button”;
import { Card, CardContent } from “@/components/ui/card”;
const flowers = [
{ name: “Rose”, emoji: “🌹” },
{ name: “Tulipan”, emoji: “🌷” },
{ name: “Solsikke”, emoji: “🌻” },
{ name: “Hibiscus”, emoji: “🌺” },
{ name: “Margerit”, emoji: “🌼” },
{ name: “Blomst”, emoji: “🌸” },
];
function createDeck(items) {
return items.flatMap((flower, index) => [
{ …flower, id: `${index}-a`, pairId: index },
{ …flower, id: `${index}-b`, pairId: index },
]);
}
function shuffle(array) {
const copy = […array];
for (let index = copy.length – 1; index > 0; index -= 1) {
const randomIndex = Math.floor(Math.random() * (index + 1));
[copy[index], copy[randomIndex]] = [copy[randomIndex], copy[index]];
}
return copy;
}
function runGameLogicTests() {
const deck = createDeck(flowers);
console.assert(deck.length === flowers.length * 2, “Deck should contain exactly two cards per flower.”);
const pairCounts = deck.reduce((counts, card) => {
counts[card.pairId] = (counts[card.pairId] || 0) + 1;
return counts;
}, {});
console.assert(
Object.values(pairCounts).every((count) => count === 2),
“Each flower should appear as exactly one pair.”
);
const shuffledDeck = shuffle(deck);
console.assert(shuffledDeck.length === deck.length, “Shuffle should keep the same number of cards.”);
console.assert(shuffledDeck !== deck, “Shuffle should return a new array instead of mutating the original deck.”);
console.assert(
new Set(shuffledDeck.map((card) => card.id)).size === deck.length,
“Shuffle should preserve all unique card ids.”
);
}
runGameLogicTests();
function FlowerIcon() {
return
🌸;
}
function ResetIcon() {
return
↻;
}
export default function FlowerMemoryGame() {
const [cards, setCards] = useState([]);
const [flipped, setFlipped] = useState([]);
const [matched, setMatched] = useState([]);
const [moves, setMoves] = useState(0);
const [startedAt, setStartedAt] = useState(Date.now());
const [seconds, setSeconds] = useState(0);
const totalPairs = flowers.length;
const gameWon = matched.length === totalPairs * 2 && cards.length > 0;
const newDeck = useMemo(() => createDeck(flowers), []);
function resetGame() {
setCards(shuffle(newDeck));
setFlipped([]);
setMatched([]);
setMoves(0);
setStartedAt(Date.now());
setSeconds(0);
}
useEffect(() => {
resetGame();
}, []);
useEffect(() => {
if (gameWon) return undefined;
const timer = setInterval(() => {
setSeconds(Math.floor((Date.now() – startedAt) / 1000));
}, 1000);
return () => clearInterval(timer);
}, [startedAt, gameWon]);
useEffect(() => {
if (flipped.length !== 2) return undefined;
const [first, second] = flipped;
setMoves((count) => count + 1);
if (!cards[first] || !cards[second]) {
setFlipped([]);
return undefined;
}
if (cards[first].pairId === cards[second].pairId) {
setMatched((current) => […current, cards[first].id, cards[second].id]);
setFlipped([]);
return undefined;
}
const timeout = setTimeout(() => setFlipped([]), 850);
return () => clearTimeout(timeout);
}, [flipped, cards]);
function handleCardClick(index) {
const card = cards[index];
if (!card) return;
if (flipped.length === 2) return;
if (flipped.includes(index)) return;
if (matched.includes(card.id)) return;
setFlipped((current) => […current, index]);
}
return (
{moves}
Træk
{matched.length / 2}
Par
{seconds}s
Tid
{gameWon && (
🎉
Flot klaret!
Du fandt alle blomsterpar på {moves} træk.
)}
{cards.map((card, index) => {
const isOpen = flipped.includes(index) || matched.includes(card.id);
return (
);
})}
);
}