1 ! Copyright (C) 2017 John Benediktsson
2 ! See http://factorcode.org/license.txt for BSD license
4 USING: accessors arrays assocs calendar circular colors
5 combinators combinators.short-circuit combinators.smart
6 destructors formatting images.loader kernel math math.order
7 math.parser namespaces opengl opengl.textures random sequences
8 timers ui ui.commands ui.gadgets ui.gadgets.toolbar
9 ui.gadgets.tracks ui.gadgets.worlds ui.gestures ui.pens.solid
10 ui.render ui.tools.browser words ;
15 { -1 -1 } { -1 0 } { -1 1 }
17 { 1 -1 } { 1 0 } { 1 1 }
20 SYMBOLS: +flagged+ +question+ +clicked+ ;
22 TUPLE: cell #adjacent mined? state ;
24 : make-cells ( rows cols -- cells )
25 '[ _ [ cell new ] replicate ] replicate ;
27 :: cell-at ( cells row col -- cell/f )
28 row cells ?nth [ col swap ?nth ] [ f ] if* ;
30 : cells-dim ( cells -- rows cols )
31 [ length ] [ first length ] bi ;
33 : #mines ( cells -- n )
34 [ [ mined?>> ] count ] map-sum ;
36 : #flagged ( cells -- n )
37 [ [ state>> +flagged+ = ] count ] map-sum ;
39 : #mines-remaining ( cells -- n )
40 [ #mines ] [ #flagged ] bi - ;
42 : unmined-cell ( cells -- cell )
43 '[ _ random random dup mined?>> ] smart-loop ;
45 : place-mines ( cells n -- cells )
46 [ dup unmined-cell t >>mined? drop ] times ;
48 :: count-neighbors ( cells row col quot: ( cell -- ? ) -- n )
50 first2 [ row + ] [ col + ] bi* cell-at quot [ f ] if*
53 : adjacent-mines ( cells row col -- #mines )
54 [ mined?>> ] count-neighbors ;
56 : adjacent-flags ( cells row col -- #flags )
57 [ state>> +flagged+ = ] count-neighbors ;
59 :: each-cell ( ... cells quot: ( ... row col cell -- ... ) -- ... )
61 [| cell col | row col cell quot call ] each-index
64 :: update-counts ( cells -- cells )
65 cells [| row col cell |
66 cells row col adjacent-mines cell #adjacent<<
69 : reset-cells ( cells -- cells )
70 [ cells-dim make-cells ] [ #mines place-mines ] bi update-counts ;
73 [ [ { [ state>> +clicked+ = ] [ mined?>> ] } 1|| ] all? ] all? ;
75 : lost? ( cells -- ? )
76 [ [ { [ state>> +clicked+ = ] [ mined?>> ] } 1&& ] any? ] any? ;
78 : game-over? ( cells -- ? )
79 { [ lost? ] [ won? ] } 1|| ;
81 : new-game? ( cells -- ? )
82 [ [ state>> +clicked+ = ] any? ] none? ;
86 :: click-cells-around ( cells row col -- )
88 first2 [ row + ] [ col + ] bi* :> ( row' col' )
89 cells row' col' cell-at [
90 cells row' col' click-cell-at drop
94 :: click-cell-at ( cells row col -- ? )
95 cells row col cell-at [
97 ! first click shouldn't be a mine
99 cells unmined-cell t >>mined? drop f >>mined?
100 cells update-counts drop
103 dup state>> { +clicked+ +flagged+ } member? [ drop f ] [
105 { [ mined?>> not ] [ #adjacent>> 0 = ] } 1&& [
106 cells row col click-cells-around
111 :: mark-cell-at ( cells row col -- ? )
112 cells row col cell-at [
114 { +clicked+ [ +clicked+ ] }
115 { +flagged+ [ +question+ ] }
118 } case >>state drop t
121 :: open-cell-at ( cells row col -- ? )
122 cells row col cell-at [
123 state>> +clicked+ = [
124 cells row col [ adjacent-flags ] [ adjacent-mines ] 3bi = [
125 cells row col click-cells-around
130 TUPLE: grid-gadget < gadget cells timer textures start end hint? ;
132 :: <grid-gadget> ( rows cols mines -- gadget )
135 mines place-mines update-counts >>cells
136 H{ } clone >>textures
137 dup '[ _ relayout-1 ] f 1 seconds <timer> >>timer
138 COLOR: gray <solid> >>interior
139 "12345" <circular> >>hint? ;
141 M: grid-gadget graft*
142 [ timer>> start-timer ] [ call-next-method ] bi ;
144 M: grid-gadget ungraft*
147 [ values dispose-each H{ } clone ] change-textures
149 ] [ call-next-method ] bi ;
151 M: grid-gadget pref-dim*
152 cells>> cells-dim [ 32 * ] bi@ swap 58 + 2array ;
154 :: cell-image-path ( cell won? lost? -- image-path )
155 won? lost? or cell mined?>> and [
157 { +flagged+ [ "flagged.gif" ] }
158 { +clicked+ [ "mineclicked.gif" ] }
159 [ drop won? "flagged.gif" "mine.gif" ? ]
163 { +question+ [ "question.gif" ] }
164 { +flagged+ [ lost? "misflagged.gif" "flagged.gif" ? ] }
166 cell #adjacent>> 0 or number>string
167 "open" ".gif" surround ] }
168 { f [ "blank.gif" ] }
170 ] if "vocab:minesweeper/_resources/" prepend ;
172 : digit-image-path ( ch -- image-path )
173 "vocab:minesweeper/_resources/digit%c.gif" sprintf ;
175 :: smiley-image-path ( won? lost? clicking? -- image-path )
177 { [ lost? ] [ "vocab:minesweeper/_resources/smileylost.gif" ] }
178 { [ won? ] [ "vocab:minesweeper/_resources/smileywon.gif" ] }
179 { [ clicking? ] [ "vocab:minesweeper/_resources/smileyuhoh.gif" ] }
180 [ "vocab:minesweeper/_resources/smiley.gif" ]
183 : draw-cached-texture ( path gadget -- )
184 textures>> [ load-image { 0 0 } <texture> ] cache
185 [ dim>> [ 2 /i ] map ] [ draw-scaled-texture ] bi ;
187 :: draw-hint ( gadget -- )
188 gadget hint?>> "xyzzy" sequence= [
189 gadget hand-rel first2 :> ( w h )
191 h 58 - w [ 32 /i ] bi@ :> ( row col )
192 gadget cells>> row col cell-at [
193 mined?>> COLOR: black COLOR: white ? gl-color
194 { 0 0 } { 1 1 } gl-fill-rect
199 :: draw-mines ( n gadget -- )
200 gadget cells>> won? 0 n ? "%03d" sprintf [
202 digit-image-path gadget draw-cached-texture
206 :: draw-smiley ( gadget -- )
207 gadget pref-dim first :> width
208 width 2/ 26 - 3 2array [
209 gadget cells>> [ won? ] [ lost? ] bi
210 hand-buttons get-global empty? not
211 gadget hand-click-rel [ second 58 >= ] [ f ] if* and
212 smiley-image-path gadget draw-cached-texture
215 :: draw-timer ( n gadget -- )
216 gadget pref-dim first :> width
217 n 999 min "%03d" sprintf [
218 3 swap - 26 * width swap - 3 - 6 2array [
219 digit-image-path gadget draw-cached-texture
223 :: draw-cells ( gadget -- )
224 gadget cells>> [ won? ] [ lost? ] bi :> ( won? lost? )
225 gadget cells>> [| row col cell |
226 col row [ 32 * ] bi@ 58 + 2array [
227 cell won? lost? cell-image-path
228 gadget draw-cached-texture
232 :: elapsed-time ( gadget -- n )
234 gadget end>> now or swap time- duration>seconds
237 M: grid-gadget handle-gesture
239 [ key-down? ] [ sym>> length 1 = ] [ sym>> " " = not ]
241 2dup [ sym>> first ] [ hint?>> ] bi* circular-push
242 ] when call-next-method ;
244 M: grid-gadget draw-gadget*
247 [ cells>> #mines-remaining ]
255 :: on-grid ( gadget quot: ( cells row col -- ? ) -- )
256 gadget hand-rel first2 :> ( w h )
258 h 58 - w [ 32 /i ] bi@ :> ( row col )
259 gadget cells>> :> cells
261 cells row col quot call [
262 gadget start>> [ now gadget start<< ] unless
263 cells game-over? [ now gadget end<< ] when
266 ] when gadget relayout-1 ; inline
268 :: on-click ( gadget -- )
269 gadget hand-rel first2 :> ( w h )
272 gadget pref-dim first 2/ w - abs 26 < and [
273 gadget [ reset-cells ] change-cells
274 f >>start f >>end drop
276 ] when gadget [ click-cell-at ] on-grid ;
278 : on-mark ( gadget -- ) [ mark-cell-at ] on-grid ;
280 : on-open ( gadget -- ) [ open-cell-at ] on-grid ;
282 : new-game ( gadget rows cols mines -- )
283 [ make-cells ] dip place-mines update-counts >>cells
284 f >>start f >>end relayout-window ;
286 : com-easy ( gadget -- ) 8 8 10 new-game ;
288 : com-medium ( gadget -- ) 16 16 40 new-game ;
290 : com-hard ( gadget -- ) 16 30 99 new-game ;
292 : com-help ( gadget -- ) drop "minesweeper" com-browse ;
294 grid-gadget "toolbar" f {
295 { T{ key-down { sym "1" } } com-easy }
296 { T{ key-down { sym "2" } } com-medium }
297 { T{ key-down { sym "3" } } com-hard }
298 { T{ key-down { sym "?" } } com-help }
301 grid-gadget "gestures" [
303 { T{ button-down { # 1 } } [ relayout-1 ] }
304 { T{ button-up { # 1 } } [ on-click ] }
305 { T{ button-up { # 3 } } [ on-mark ] }
306 { T{ button-up { # 2 } } [ on-open ] }
307 { T{ key-down { sym " " } } [ on-mark ] }
308 { motion [ relayout-1 ] }
312 TUPLE: minesweeper-gadget < track ;
314 : <minesweeper-gadget> ( -- gadget )
315 vertical minesweeper-gadget new-track
317 [ <toolbar> format-toolbar f track-add ]
320 M: minesweeper-gadget focusable-child* children>> second ;
322 MAIN-WINDOW: run-minesweeper {
323 { title "Minesweeper" }
325 { normal-title-bar close-button minimize-button } }
326 } <minesweeper-gadget> >>gadgets ;