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 "> ×</ 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 "> ×</ 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