|
| 1 | +import React, { useEffect, useMemo, useState } from "react"; |
| 2 | +import "../../styles/pages/games/DontClickBomb.css"; |
| 3 | + |
| 4 | +const SAFE_EMOJIS = ["😀", "😎", "😺", "🦊", "🐶", "🐻", "🍔", "🍩", "🍓", "🌸", "⭐", "💎"]; |
| 5 | + |
| 6 | +export default function DontClickBomb() { |
| 7 | + const [level, setLevel] = useState(1); |
| 8 | + const [score, setScore] = useState(0); |
| 9 | + const [gameOver, setGameOver] = useState(false); |
| 10 | + const [timeLeft, setTimeLeft] = useState(2.5); // seconds per round, will shrink |
| 11 | + const [tick, setTick] = useState(0); // to re-gen grid when timer refreshes |
| 12 | + |
| 13 | + // grid scales with level |
| 14 | + const gridSize = Math.min(3 + Math.floor(level / 1.3), 8); // 3..8 |
| 15 | + const totalCells = gridSize * gridSize; |
| 16 | + |
| 17 | + // number of bombs grows |
| 18 | + const numBombs = Math.min(Math.floor(level * 0.7) + 1, 10); |
| 19 | + |
| 20 | + |
| 21 | + // choose unique bomb positions |
| 22 | + const bombIndexes = useMemo(() => { |
| 23 | + const set = new Set(); |
| 24 | + while (set.size < numBombs) { |
| 25 | + set.add(Math.floor(Math.random() * totalCells)); |
| 26 | + } |
| 27 | + return Array.from(set); |
| 28 | + }, [level, totalCells, numBombs, tick]); |
| 29 | + |
| 30 | + // timer logic: every level you get slightly less time |
| 31 | + useEffect(() => { |
| 32 | + if (gameOver) return; |
| 33 | + setTimeLeft(Math.max(0.9, 2.6 - level * 0.1)); // min ~0.9s |
| 34 | + }, [level, gameOver]); |
| 35 | + |
| 36 | + useEffect(() => { |
| 37 | + if (gameOver) return; |
| 38 | + const interval = setInterval(() => { |
| 39 | + setTimeLeft((t) => { |
| 40 | + if (t <= 0.1) { |
| 41 | + // time up -> lose |
| 42 | + setGameOver(true); |
| 43 | + return 0; |
| 44 | + } |
| 45 | + return Number((t - 0.1).toFixed(1)); |
| 46 | + }); |
| 47 | + }, 100); |
| 48 | + return () => clearInterval(interval); |
| 49 | + }, [gameOver]); |
| 50 | + |
| 51 | + function handleCellClick(idx, isBomb) { |
| 52 | + if (gameOver) return; |
| 53 | + if (isBomb) { |
| 54 | + setGameOver(true); |
| 55 | + return; |
| 56 | + } |
| 57 | + // safe |
| 58 | + setScore((s) => s + 10 + level * 2); |
| 59 | + setLevel((l) => l + 1); |
| 60 | + // refresh grid |
| 61 | + setTick((t) => t + 1); |
| 62 | + } |
| 63 | + |
| 64 | + function handleRestart() { |
| 65 | + setLevel(1); |
| 66 | + setScore(0); |
| 67 | + setGameOver(false); |
| 68 | + setTimeLeft(2.5); |
| 69 | + setTick((t) => t + 1); |
| 70 | + } |
| 71 | + |
| 72 | + return ( |
| 73 | + <div className="bomb-wrapper hard"> |
| 74 | + <div className="bomb-header"> |
| 75 | + <h2>Don't Click the Bomb 💣</h2> |
| 76 | + <p>Click any safe emoji before the timer runs out. More levels = more bombs.</p> |
| 77 | + </div> |
| 78 | + |
| 79 | + <div className="bomb-stats"> |
| 80 | + <div className="bomb-stat"> |
| 81 | + <span className="label">Level</span> |
| 82 | + <span className="value">{level}</span> |
| 83 | + </div> |
| 84 | + <div className="bomb-stat"> |
| 85 | + <span className="label">Score</span> |
| 86 | + <span className="value">{score}</span> |
| 87 | + </div> |
| 88 | + <div className={`bomb-timer ${timeLeft < 1 ? "low" : ""}`}> |
| 89 | + ⏱ {timeLeft.toFixed(1)}s |
| 90 | + </div> |
| 91 | + {gameOver && ( |
| 92 | + <div className="bomb-over">💥 Boom! Too slow / bomb clicked.</div> |
| 93 | + )} |
| 94 | + </div> |
| 95 | + |
| 96 | + <div |
| 97 | + className="bomb-grid" |
| 98 | + style={{ |
| 99 | + gridTemplateColumns: `repeat(${gridSize}, minmax(45px, 1fr))`, |
| 100 | + }} |
| 101 | + > |
| 102 | + {Array.from({ length: totalCells }).map((_, idx) => { |
| 103 | + const isBomb = bombIndexes.includes(idx); |
| 104 | + const emoji = isBomb |
| 105 | + ? "💣" |
| 106 | + : SAFE_EMOJIS[Math.floor(Math.random() * SAFE_EMOJIS.length)]; |
| 107 | + |
| 108 | + return ( |
| 109 | + <button |
| 110 | + key={idx} |
| 111 | + className={`bomb-cell ${isBomb ? "is-bomb" : "is-safe"} ${ |
| 112 | + gameOver && isBomb ? "explode" : "" |
| 113 | + }`} |
| 114 | + onClick={() => handleCellClick(idx, isBomb)} |
| 115 | + disabled={gameOver} |
| 116 | + > |
| 117 | + {emoji} |
| 118 | + </button> |
| 119 | + ); |
| 120 | + })} |
| 121 | + </div> |
| 122 | + |
| 123 | + <div className="bomb-actions"> |
| 124 | + <button onClick={handleRestart} className="bomb-btn"> |
| 125 | + {gameOver ? "Play again" : "Restart"} |
| 126 | + </button> |
| 127 | + </div> |
| 128 | + |
| 129 | + <p className="bomb-hint"> |
| 130 | + From level 4: 2 bombs 🧨. From level 8: 3 bombs 😈. Timer also gets shorter. |
| 131 | + </p> |
| 132 | + </div> |
| 133 | + ); |
| 134 | +} |
0 commit comments