1+ /**
2+ * 7.10 Minesweeper: Design and implement a text-based Minesweeper game. Minesweeper is the classic single-player computer game where an NxN grid has B mines (or bombs) hidden across the grid. The remaining cells are either blank or have a number behind them. The numbers reflect the number of bombs in the surrounding eight cells.The user then uncovers a cell. If it is a bomb, the player loses. If it is a number, the number is exposed. If it is a blank cell, this cell and all adjacent blank cells (up to and including the surrounding numeric cells) are exposed. The player wins when all non-bomb cells are exposed. The player can also flag certain places as potential bombs. This doesn't affect game play, other than to block the user from accidentally clicking a cell that is thought to have a bomb. (Tip for the reader: if you're not familiar with this game, please playa few rounds online first.)
3+ */
4+ class Minesweeper {
5+ constructor ( rows , columns , mines ) {
6+ this . board = new Board ( rows , columns , mines ) ;
7+ }
8+ }
9+
10+ class Board {
11+ constructor ( rows , columns , mines ) {
12+ this . rows = rows ;
13+ this . columns = columns ;
14+ this . board = [ ] ;
15+ this . uncovered = 0 ;
16+
17+ for ( let column = 0 ; column < columns ; column ++ ) {
18+ this . board [ column ] = [ ] ;
19+
20+ for ( let row = 0 ; row < rows ; row ++ ) {
21+ this . board [ column ] [ row ] = new Square ( column , row ) ;
22+ }
23+ }
24+
25+ this . setMines ( mines ) ;
26+ }
27+
28+ setMines ( mines ) {
29+ this . mines = 0 ;
30+ let minesCoordinates = [ ] ;
31+
32+ if ( Array . isArray ( mines ) ) {
33+ minesCoordinates = mines ;
34+ } else {
35+ for ( let bomb = 0 ; bomb < parseInt ( mines ) ; bomb ++ ) {
36+ const row = parseInt ( Math . random ( ) * this . rows ) ;
37+ const col = parseInt ( Math . random ( ) * this . columns ) ;
38+ minesCoordinates . push ( [ col , row ] ) ;
39+ }
40+ }
41+
42+ minesCoordinates . forEach ( ( v ) => {
43+ const col = v [ 0 ] ;
44+ const row = v [ 1 ] ;
45+ const square = this . getSquare ( col , row ) ;
46+
47+ if ( ! square ) return ;
48+
49+ square . setBomb ( ) ;
50+ this . mines ++ ;
51+
52+ // increase adjacent numbers
53+ this . getAdjacents ( col , row ) . forEach ( ( square ) => square . increaseNumber ( ) ) ;
54+ } ) ;
55+ }
56+
57+ getMinesCount ( ) {
58+ return Array . isArray ( this . mines ) ? this . mines . length : this . mines ;
59+ }
60+
61+ getSquare ( col , row ) {
62+ if ( col < 0 || col >= this . columns || row < 0 || row >= this . rows ) {
63+ return ;
64+ } else {
65+ return this . board [ col ] [ row ] ;
66+ }
67+ }
68+
69+ getAdjacents ( col , row ) {
70+ const adjacents = [ ] ;
71+
72+ for ( let y = - 1 ; y < 2 ; y ++ ) {
73+ for ( let x = - 1 ; x < 2 ; x ++ ) {
74+ if ( x === y && x === 0 ) {
75+ continue ;
76+ }
77+ const square = this . getSquare ( col + y , row + x ) ;
78+ if ( square ) {
79+ adjacents . push ( square ) ;
80+ }
81+ }
82+ }
83+
84+ return adjacents ;
85+ }
86+
87+ revealNumbers ( col , row ) {
88+ this . getAdjacents ( col , row ) . forEach ( ( square ) => {
89+ if ( ! square . isHidden ) { return ; }
90+
91+ this . uncovered ++ ;
92+ square . discover ( ) ;
93+
94+ if ( square . isEmpty ( ) ) {
95+ this . revealNumbers ( square . col , square . row ) ;
96+ }
97+ } ) ;
98+ }
99+
100+ /**
101+ *
102+ * @param col
103+ * @param row
104+ * @param action discover or flag
105+ * @returns {* }
106+ */
107+ play ( col , row , action = 'discover' ) {
108+ const square = this . getSquare ( col , row ) ;
109+ const value = square [ action ] ( ) ;
110+ if ( action === 'flag' ) { return ; }
111+
112+ this . uncovered ++ ;
113+
114+ if ( square . hasBomb ( ) ) {
115+ throw 'Game Over' ;
116+ }
117+
118+ if ( square . isEmpty ( ) ) {
119+ this . revealNumbers ( col , row ) ;
120+ }
121+
122+ if ( this . uncovered === ( this . rows * this . columns - this . mines ) ) {
123+ throw 'You won!' ;
124+ }
125+
126+ return value ;
127+ }
128+
129+ hasWin ( ) {
130+ return this . board . reduce ( ( acc , col ) => acc && col . reduce ( ( acc , row ) => {
131+ return row . isHidden ? row . hasBomb ( ) : false ;
132+ } , true ) , true ) ;
133+ }
134+
135+ toString ( fn ) {
136+ let string = '' ;
137+
138+
139+ for ( let column = 0 ; column < this . board . length ; column ++ ) {
140+ string += '\n' ;
141+ for ( let row = 0 ; row < this . board [ column ] . length ; row ++ ) {
142+ const square = this . board [ column ] [ row ] ;
143+ const val = fn ? ( fn instanceof Function ? fn ( square ) : square [ fn ] ) : square . getValue ( ) ;
144+ string += `\t ${ val } ` ;
145+ }
146+ }
147+ return string ;
148+ }
149+ }
150+
151+ class Square {
152+ constructor ( col , row , value = Square . EMPTY ) {
153+ this . value = value ;
154+ this . col = col ;
155+ this . row = row ;
156+ this . isFlagged = false ;
157+ this . isHidden = true ;
158+ }
159+
160+ setBomb ( ) {
161+ this . value = Square . BOMB ;
162+ }
163+
164+ increaseNumber ( ) {
165+ if ( this . value === Square . BOMB ) {
166+ return ;
167+ }
168+
169+ if ( Number . isInteger ( this . value ) ) {
170+ ++ this . value ;
171+ } else {
172+ this . value = 1 ;
173+ }
174+
175+ return this . value ;
176+ }
177+
178+ hasBomb ( ) {
179+ return this . value === Square . BOMB
180+ }
181+
182+ isEmpty ( ) {
183+ return this . value === Square . EMPTY ;
184+ }
185+
186+ discover ( ) {
187+ this . isHidden = false ;
188+ return this . value ;
189+ }
190+
191+ flag ( ) {
192+ this . isFlagged = true ;
193+ }
194+
195+ getValue ( ) {
196+ return this . isFlagged ? 'F' : ( this . isHidden ? '?' : this . value ) ;
197+ }
198+ }
199+
200+ Square . EMPTY = '.' ;
201+ Square . BOMB = '*' ;
202+
203+ module . exports = Minesweeper ;
0 commit comments