Skip to content

Commit be70e1d

Browse files
Merge pull request #131 from akshat-chd/Bomb
Added New game : Don't click the Bomb
2 parents 7efab39 + ae3e460 commit be70e1d

File tree

3 files changed

+313
-0
lines changed

3 files changed

+313
-0
lines changed

src/data/content.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { CatHttpCode } from "../pages/activities/CatHttpCode";
2828
import FlappyBird from "../pages/games/FlappyBird";
2929
import RandomIdentity from "../pages/activities/RandomIdentity";
3030
import word_scramble_icon from "../assets/games/WordScramble/word_scramble.png";
31+
import DontClickBomb from "../pages/games/DontClickBomb";
3132
import TypingTest from "../pages/games/TypingTest";
3233

3334
export const activities = [
@@ -182,6 +183,11 @@ export const games = [
182183
element: <WordScramble />,
183184
},
184185
{
186+
title: "Don't Click the Bomb",
187+
description: "Click everything except 💣. Gets harder every round.",
188+
icon: "https://img.freepik.com/free-vector/round-black-bomb-realistic-style_52683-15190.jpg?semt=ais_hybrid&w=740&q=80",
189+
urlTerm: "dont-click-the-bomb",
190+
element: <DontClickBomb />,
185191
title: "Typing Test",
186192
description: "Test your typing skills",
187193
icon :"https://typingmentor.com/_next/image/?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fbs3wcomf%2Fproduction%2F9f626374aa1c388a6418a077990771aff53d054f-1200x800.jpg%3Frect%3D0%2C35%2C1200%2C731%26w%3D808%26h%3D492%26fit%3Dcrop%26auto%3Dformat&w=1920&q=75",

src/pages/games/DontClickBomb.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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&apos;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+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
.bomb-wrapper {
2+
max-width: 700px;
3+
margin: 2rem auto;
4+
background: #ffffff;
5+
border: 1px solid #e2e8f0;
6+
border-radius: 16px;
7+
padding: 1.6rem 1.4rem 1.4rem;
8+
box-shadow: 0 10px 25px rgba(15, 23, 42, 0.05);
9+
text-align: center;
10+
font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
11+
}
12+
13+
.bomb-header h2 {
14+
font-size: 1.6rem;
15+
font-weight: 700;
16+
color: #0f172a;
17+
margin-bottom: 0.4rem;
18+
}
19+
20+
.bomb-header p {
21+
color: #64748b;
22+
font-size: 0.9rem;
23+
margin-bottom: 1.1rem;
24+
}
25+
26+
.bomb-stats {
27+
display: flex;
28+
justify-content: center;
29+
gap: 1rem;
30+
flex-wrap: wrap;
31+
margin-bottom: 1rem;
32+
align-items: center;
33+
}
34+
35+
.bomb-stat {
36+
background: #f8fafc;
37+
border: 1px solid #e2e8f0;
38+
border-radius: 10px;
39+
padding: 0.5rem 1.1rem;
40+
min-width: 90px;
41+
}
42+
43+
.bomb-stat .label {
44+
font-size: 0.65rem;
45+
text-transform: uppercase;
46+
color: #94a3b8;
47+
letter-spacing: 0.04em;
48+
}
49+
50+
.bomb-stat .value {
51+
font-size: 1.1rem;
52+
font-weight: 600;
53+
color: #0f172a;
54+
}
55+
56+
.bomb-over {
57+
background: #fee2e2;
58+
color: #b91c1c;
59+
padding: 0.35rem 0.7rem;
60+
border-radius: 999px;
61+
font-weight: 500;
62+
}
63+
64+
.bomb-grid {
65+
display: grid;
66+
gap: 0.6rem;
67+
margin-top: 1.1rem;
68+
}
69+
70+
.bomb-cell {
71+
background: #ffffff;
72+
border: 1px solid #e2e8f0;
73+
border-radius: 12px;
74+
height: 60px;
75+
font-size: 1.7rem;
76+
cursor: pointer;
77+
transition: transform 0.1s ease, box-shadow 0.1s ease;
78+
display: flex;
79+
align-items: center;
80+
justify-content: center;
81+
}
82+
83+
.bomb-cell.is-safe:hover {
84+
transform: translateY(-2px);
85+
box-shadow: 0 6px 15px rgba(37, 99, 235, 0.12);
86+
}
87+
88+
.bomb-cell:disabled {
89+
cursor: not-allowed;
90+
opacity: 0.9;
91+
}
92+
93+
/* bomb click animation */
94+
.explode {
95+
animation: bombBoom 0.4s ease forwards;
96+
}
97+
98+
@keyframes bombBoom {
99+
0% {
100+
transform: scale(1);
101+
background: #fee2e2;
102+
}
103+
50% {
104+
transform: scale(1.3);
105+
background: #fca5a5;
106+
}
107+
100% {
108+
transform: scale(0.9);
109+
background: #fee2e2;
110+
}
111+
}
112+
113+
.bomb-actions {
114+
margin-top: 1.2rem;
115+
}
116+
117+
.bomb-btn {
118+
background: #2563eb;
119+
color: #ffffff;
120+
border: none;
121+
border-radius: 10px;
122+
padding: 0.5rem 1.2rem;
123+
font-weight: 500;
124+
cursor: pointer;
125+
transition: background 0.15s ease;
126+
}
127+
128+
.bomb-btn:hover {
129+
background: #1d4ed8;
130+
}
131+
132+
.bomb-hint {
133+
margin-top: 0.85rem;
134+
color: #94a3b8;
135+
font-size: 0.78rem;
136+
}
137+
138+
@media (max-width: 520px) {
139+
.bomb-grid {
140+
grid-template-columns: repeat(auto-fit, minmax(55px, 1fr)) !important;
141+
}
142+
.bomb-cell {
143+
height: 55px;
144+
font-size: 1.4rem;
145+
}
146+
}
147+
.bomb-wrapper.hard .bomb-grid {
148+
margin-top: 1rem;
149+
}
150+
151+
.bomb-timer {
152+
background: #eff6ff;
153+
border: 1px solid #bfdbfe;
154+
padding: 0.35rem 0.8rem;
155+
border-radius: 999px;
156+
font-weight: 500;
157+
color: #1d4ed8;
158+
display: flex;
159+
align-items: center;
160+
gap: 4px;
161+
}
162+
163+
.bomb-timer.low {
164+
background: #fee2e2;
165+
border-color: #fecaca;
166+
color: #b91c1c;
167+
}
168+
169+
@media (max-width: 520px) {
170+
.bomb-wrapper.hard .bomb-grid {
171+
grid-template-columns: repeat(auto-fit, minmax(45px, 1fr)) !important;
172+
}
173+
}

0 commit comments

Comments
 (0)