Skip to content

Commit 8979a05

Browse files
committed
- add new game "color flood"
1 parent c930f94 commit 8979a05

File tree

1 file changed

+342
-0
lines changed

1 file changed

+342
-0
lines changed

games/color_flood.html

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Color Flood</title>
7+
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap" rel="stylesheet">
8+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/anime.min.js"></script>
9+
<style>
10+
:root {--primary:#3498db;--secondary:#2ecc71;--dark:#2c3e50;--light:#ecf0f1;--color-1:#e74c3c;--color-2:#3498db;--color-3:#2ecc71;--color-4:#f1c40f;--color-5:#9b59b6;--color-6:#e67e22;}
11+
* {margin:0;padding:0;box-sizing:border-box;}
12+
body {font-family:'Poppins',sans-serif;background-color:var(--light);color:var(--dark);min-height:100vh;display:flex;flex-direction:column;align-items:center;padding:20px;}
13+
.container {max-width:600px;width:100%;margin:0 auto;}
14+
header {text-align:center;margin-bottom:20px;}
15+
h1 {color:var(--primary);margin-bottom:10px;}
16+
.game-info {display:flex;justify-content:space-between;margin-bottom:20px;font-size:1.2rem;}
17+
.grid-container {aspect-ratio:1/1;width:100%;display:grid;gap:1px;border:2px solid var(--dark);border-radius:4px;margin-bottom:20px;overflow:hidden;}
18+
.cell {width:100%;height:100%;transition:background-color 0.3s ease;}
19+
.color-palette {display:flex;justify-content:center;gap:10px;margin-bottom:20px;flex-wrap:wrap;}
20+
.color-option {width:40px;height:40px;border-radius:50%;cursor:pointer;transition:transform 0.2s;border:2px solid rgba(0,0,0,0.1);}
21+
.color-option:hover {transform:scale(1.1);}
22+
.controls {display:flex;justify-content:space-between;margin-bottom:20px;}
23+
button {background-color:var(--primary);color:white;border:none;padding:8px 16px;border-radius:4px;cursor:pointer;font-family:inherit;font-weight:500;transition:background-color 0.3s ease;}
24+
button:hover {background-color:#2980b9;}
25+
select {padding:8px;border-radius:4px;border:1px solid #ddd;font-family:inherit;}
26+
.modal {position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,0.5);display:flex;justify-content:center;align-items:center;z-index:1000;opacity:0;pointer-events:none;transition:opacity 0.3s ease;}
27+
.modal.active {opacity:1;pointer-events:all;}
28+
.modal-content {background-color:white;padding:20px;border-radius:8px;max-width:500px;width:90%;box-shadow:0 4px 20px rgba(0,0,0,0.2);}
29+
.modal-header {display:flex;justify-content:space-between;align-items:center;margin-bottom:15px;}
30+
.close-modal {background:none;border:none;font-size:1.5rem;cursor:pointer;color:var(--dark);}
31+
.win-message {text-align:center;font-size:1.2rem;margin-bottom:20px;}
32+
.win-stats {display:flex;justify-content:space-around;margin-bottom:20px;}
33+
.stat-item {text-align:center;}
34+
.stat-value {font-size:1.5rem;font-weight:bold;color:var(--primary);}
35+
@media (max-width:600px) {
36+
.container {padding:10px;}
37+
.color-option {width:35px;height:35px;}
38+
.controls {flex-direction:column;gap:10px;}
39+
.game-info {font-size:1rem;}
40+
}
41+
</style>
42+
</head>
43+
<body>
44+
<div class="container">
45+
<header>
46+
<h1>Color Flood</h1>
47+
<p>Fill the entire grid with a single color in as few moves as possible.</p>
48+
</header>
49+
50+
<div class="controls">
51+
<div>
52+
<select id="difficulty">
53+
<option value="easy">Easy (10x10, 4 colors)</option>
54+
<option value="medium" selected>Medium (14x14, 5 colors)</option>
55+
<option value="hard">Hard (18x18, 6 colors)</option>
56+
</select>
57+
</div>
58+
<div>
59+
<button id="new-game">New Game</button>
60+
<button id="help-btn">How to Play</button>
61+
</div>
62+
</div>
63+
64+
<div class="game-info">
65+
<div>Moves: <span id="moves">0</span></div>
66+
<div>Best: <span id="best-score">-</span></div>
67+
</div>
68+
69+
<div class="grid-container" id="grid"></div>
70+
71+
<div class="color-palette" id="palette"></div>
72+
</div>
73+
74+
<div class="modal" id="help-modal">
75+
<div class="modal-content">
76+
<div class="modal-header">
77+
<h2>How to Play</h2>
78+
<button class="close-modal">&times;</button>
79+
</div>
80+
<div class="modal-body">
81+
<p>The goal of Color Flood is to fill the entire grid with a single color in as few moves as possible.</p>
82+
<ul style="padding-left:20px;margin:10px 0;">
83+
<li>You start from the top-left corner of the grid.</li>
84+
<li>Select a color from the palette to flood the connected area.</li>
85+
<li>The flooded area expands to include any adjacent cells of the same color.</li>
86+
<li>Try to fill the entire grid in as few moves as possible.</li>
87+
</ul>
88+
<p>The fewer moves you make, the better your score!</p>
89+
</div>
90+
<div style="text-align:center;margin-top:15px;">
91+
<button class="close-help">Got it!</button>
92+
</div>
93+
</div>
94+
</div>
95+
96+
<div class="modal" id="win-modal">
97+
<div class="modal-content">
98+
<div class="modal-header">
99+
<h2>Congratulations!</h2>
100+
<button class="close-modal">&times;</button>
101+
</div>
102+
<div class="win-message">
103+
You've flooded the entire grid!
104+
</div>
105+
<div class="win-stats">
106+
<div class="stat-item">
107+
<div class="stat-value" id="final-moves">0</div>
108+
<div>Moves</div>
109+
</div>
110+
<div class="stat-item">
111+
<div class="stat-value" id="best-moves">0</div>
112+
<div>Best</div>
113+
</div>
114+
</div>
115+
<div style="text-align:center;">
116+
<button id="play-again">Play Again</button>
117+
</div>
118+
</div>
119+
</div>
120+
121+
<script>
122+
class ColorFlood {
123+
constructor() {
124+
this.grid = document.getElementById('grid');
125+
this.palette = document.getElementById('palette');
126+
this.movesDisplay = document.getElementById('moves');
127+
this.bestScoreDisplay = document.getElementById('best-score');
128+
this.difficultySelect = document.getElementById('difficulty');
129+
this.newGameBtn = document.getElementById('new-game');
130+
this.helpBtn = document.getElementById('help-btn');
131+
132+
this.settings = {
133+
easy: {size:10,colors:4},
134+
medium: {size:14,colors:5},
135+
hard: {size:18,colors:6}
136+
};
137+
this.colorMap = ['var(--color-1)','var(--color-2)','var(--color-3)','var(--color-4)','var(--color-5)','var(--color-6)'];
138+
this.gridData = [];
139+
this.gridSize = 0;
140+
this.colorCount = 0;
141+
this.moves = 0;
142+
this.bestScores = this.loadBestScores();
143+
144+
this.initEventListeners();
145+
this.startNewGame();
146+
}
147+
148+
initEventListeners() {
149+
this.newGameBtn.addEventListener('click', () => this.startNewGame());
150+
this.helpBtn.addEventListener('click', () => this.openModal('help-modal'));
151+
152+
document.querySelectorAll('.close-modal, .close-help').forEach(btn => {
153+
btn.addEventListener('click', () => this.closeAllModals());
154+
});
155+
156+
document.getElementById('play-again').addEventListener('click', () => {
157+
this.closeAllModals();
158+
this.startNewGame();
159+
});
160+
161+
document.querySelectorAll('.modal').forEach(modal => {
162+
modal.addEventListener('click', (e) => {
163+
if (e.target === modal) this.closeAllModals();
164+
});
165+
});
166+
167+
document.addEventListener('keydown', (e) => {
168+
if (e.key === 'Escape') this.closeAllModals();
169+
});
170+
}
171+
172+
openModal(modalId) {
173+
this.closeAllModals();
174+
document.getElementById(modalId).classList.add('active');
175+
}
176+
177+
closeAllModals() {
178+
document.querySelectorAll('.modal').forEach(modal => {
179+
modal.classList.remove('active');
180+
});
181+
}
182+
183+
loadBestScores() {
184+
const savedScores = localStorage.getItem('colorFloodBestScores');
185+
return savedScores ? JSON.parse(savedScores) : {easy:Infinity,medium:Infinity,hard:Infinity};
186+
}
187+
188+
saveBestScore(difficulty, score) {
189+
if (!this.bestScores[difficulty] || score < this.bestScores[difficulty]) {
190+
this.bestScores[difficulty] = score;
191+
localStorage.setItem('colorFloodBestScores', JSON.stringify(this.bestScores));
192+
}
193+
this.updateBestScoreDisplay();
194+
}
195+
196+
updateBestScoreDisplay() {
197+
const difficulty = this.difficultySelect.value;
198+
const bestScore = this.bestScores[difficulty];
199+
this.bestScoreDisplay.textContent = bestScore === Infinity ? '-' : bestScore;
200+
}
201+
202+
startNewGame() {
203+
const difficulty = this.difficultySelect.value;
204+
this.gridSize = this.settings[difficulty].size;
205+
this.colorCount = this.settings[difficulty].colors;
206+
this.moves = 0;
207+
this.movesDisplay.textContent = '0';
208+
this.updateBestScoreDisplay();
209+
210+
this.initGrid();
211+
this.renderGrid();
212+
this.createColorPalette();
213+
}
214+
215+
initGrid() {
216+
this.gridData = [];
217+
for (let row = 0; row < this.gridSize; row++) {
218+
const rowData = [];
219+
for (let col = 0; col < this.gridSize; col++) {
220+
const colorIndex = Math.floor(Math.random() * this.colorCount);
221+
rowData.push(colorIndex);
222+
}
223+
this.gridData.push(rowData);
224+
}
225+
}
226+
227+
renderGrid() {
228+
this.grid.innerHTML = '';
229+
this.grid.style.gridTemplateColumns = `repeat(${this.gridSize}, 1fr)`;
230+
231+
for (let row = 0; row < this.gridSize; row++) {
232+
for (let col = 0; col < this.gridSize; col++) {
233+
const cell = document.createElement('div');
234+
cell.className = 'cell';
235+
cell.dataset.row = row;
236+
cell.dataset.col = col;
237+
cell.style.backgroundColor = this.colorMap[this.gridData[row][col]];
238+
this.grid.appendChild(cell);
239+
}
240+
}
241+
}
242+
243+
createColorPalette() {
244+
this.palette.innerHTML = '';
245+
246+
for (let i = 0; i < this.colorCount; i++) {
247+
const colorOption = document.createElement('div');
248+
colorOption.className = 'color-option';
249+
colorOption.style.backgroundColor = this.colorMap[i];
250+
colorOption.dataset.colorIndex = i;
251+
252+
colorOption.addEventListener('click', () => this.makeMove(i));
253+
this.palette.appendChild(colorOption);
254+
}
255+
}
256+
257+
makeMove(colorIndex) {
258+
const currentColor = this.gridData[0][0];
259+
if (currentColor === colorIndex) return;
260+
261+
this.moves++;
262+
this.movesDisplay.textContent = this.moves;
263+
264+
this.floodFill(0, 0, currentColor, colorIndex);
265+
this.renderGrid();
266+
267+
if (this.checkWin()) {
268+
this.handleWin();
269+
}
270+
}
271+
272+
floodFill(row, col, oldColorIndex, newColorIndex) {
273+
const stack = [[row, col]];
274+
const visited = new Set();
275+
276+
while (stack.length > 0) {
277+
const [r, c] = stack.pop();
278+
const key = `${r},${c}`;
279+
280+
if (visited.has(key)) continue;
281+
if (r < 0 || r >= this.gridSize || c < 0 || c >= this.gridSize) continue;
282+
if (this.gridData[r][c] !== oldColorIndex) continue;
283+
284+
this.gridData[r][c] = newColorIndex;
285+
visited.add(key);
286+
287+
stack.push([r+1,c], [r-1,c], [r,c+1], [r,c-1]);
288+
}
289+
}
290+
291+
checkWin() {
292+
const targetColor = this.gridData[0][0];
293+
294+
for (let row = 0; row < this.gridSize; row++) {
295+
for (let col = 0; col < this.gridSize; col++) {
296+
if (this.gridData[row][col] !== targetColor) return false;
297+
}
298+
}
299+
300+
return true;
301+
}
302+
303+
handleWin() {
304+
const difficulty = this.difficultySelect.value;
305+
this.saveBestScore(difficulty, this.moves);
306+
307+
document.getElementById('final-moves').textContent = this.moves;
308+
document.getElementById('best-moves').textContent = this.bestScores[difficulty];
309+
310+
this.playWinAnimation().then(() => {
311+
this.openModal('win-modal');
312+
});
313+
}
314+
315+
playWinAnimation() {
316+
return new Promise(resolve => {
317+
const cells = document.querySelectorAll('.cell');
318+
319+
anime({
320+
targets: cells,
321+
scale: [
322+
{value:0.8,duration:150,easing:'easeOutSine'},
323+
{value:1,duration:250,easing:'easeInOutQuad'}
324+
],
325+
opacity: [
326+
{value:0.5,duration:150,easing:'easeOutSine'},
327+
{value:1,duration:250,easing:'easeInOutQuad'}
328+
],
329+
delay: anime.stagger(10, {grid:[this.gridSize,this.gridSize],from:'center'}),
330+
complete: resolve
331+
});
332+
});
333+
}
334+
}
335+
336+
document.addEventListener('DOMContentLoaded', () => {
337+
const game = new ColorFlood();
338+
});
339+
</script>
340+
<script src="../logo.js"></script>
341+
</body>
342+
</html>

0 commit comments

Comments
 (0)