1 ! Copyright (C) 2017 John Benediktsson
2 ! See http://factorcode.org/license.txt for BSD license
4 USING: accessors arrays assocs calendar circular
5 colors.constants combinators combinators.short-circuit
6 combinators.smart destructors formatting fry images.loader
7 kernel locals math math.order math.parser namespaces opengl
8 opengl.textures random sequences timers ui ui.commands
9 ui.gadgets ui.gadgets.toolbar ui.gadgets.tracks
10 ui.gadgets.worlds ui.gestures ui.pens.solid ui.render
11 ui.tools.browser words ;
16 { -1 -1 } { -1 0 } { -1 1 }
18 { 1 -1 } { 1 0 } { 1 1 }
21 SYMBOLS: +flagged+ +question+ +clicked+ ;
23 TUPLE: cell #adjacent mined? state ;
25 : make-cells ( rows cols -- cells )
26 '[ _ [ cell new ] replicate ] replicate ;
28 :: cell-at ( cells row col -- cell/f )
29 row cells ?nth [ col swap ?nth ] [ f ] if* ;
31 : cells-dim ( cells -- rows cols )
32 [ length ] [ first length ] bi ;
34 : #mines ( cells -- n )
35 [ [ mined?>> ] count ] map-sum ;
37 : #flagged ( cells -- n )
38 [ [ state>> +flagged+ = ] count ] map-sum ;
40 : #mines-remaining ( cells -- n )
41 [ #mines ] [ #flagged ] bi - ;
43 : unmined-cell ( cells -- cell )
44 '[ _ random random dup mined?>> ] smart-loop ;
46 : place-mines ( cells n -- cells )
47 [ dup unmined-cell t >>mined? drop ] times ;
49 :: count-neighbors ( cells row col quot: ( cell -- ? ) -- n )
51 first2 [ row + ] [ col + ] bi* cell-at quot [ f ] if*
54 : adjacent-mines ( cells row col -- #mines )
55 [ mined?>> ] count-neighbors ;
57 : adjacent-flags ( cells row col -- #flags )
58 [ state>> +flagged+ = ] count-neighbors ;
60 :: each-cell ( ... cells quot: ( ... row col cell -- ... ) -- ... )
62 [| cell col | row col cell quot call ] each-index
65 :: update-counts ( cells -- cells )
66 cells [| row col cell |
67 cells row col adjacent-mines cell #adjacent<<
70 : reset-cells ( cells -- cells )
71 [ cells-dim make-cells ] [ #mines place-mines ] bi update-counts ;
74 [ [ { [ state>> +clicked+ = ] [ mined?>> ] } 1|| ] all? ] all? ;
76 : lost? ( cells -- ? )
77 [ [ { [ state>> +clicked+ = ] [ mined?>> ] } 1&& ] any? ] any? ;
79 : game-over? ( cells -- ? )
80 { [ lost? ] [ won? ] } 1|| ;
82 : new-game? ( cells -- ? )
83 [ [ state>> +clicked+ = ] any? ] none? ;
87 :: click-cells-around ( cells row col -- )
89 first2 [ row + ] [ col + ] bi* :> ( row' col' )
90 cells row' col' cell-at [
91 cells row' col' click-cell-at drop
95 :: click-cell-at ( cells row col -- ? )
96 cells row col cell-at [
98 ! first click shouldn't be a mine
100 cells unmined-cell t >>mined? drop f >>mined?
101 cells update-counts drop
104 dup state>> { +clicked+ +flagged+ } member? [ drop f ] [
106 { [ mined?>> not ] [ #adjacent>> 0 = ] } 1&& [
107 cells row col click-cells-around
112 :: mark-cell-at ( cells row col -- ? )
113 cells row col cell-at [
115 { +clicked+ [ +clicked+ ] }
116 { +flagged+ [ +question+ ] }
119 } case >>state drop t
122 :: open-cell-at ( cells row col -- ? )
123 cells row col cell-at [
124 state>> +clicked+ = [
125 cells row col [ adjacent-flags ] [ adjacent-mines ] 3bi = [
126 cells row col click-cells-around
131 TUPLE: grid-gadget < gadget cells timer textures start end hint? ;
133 :: <grid-gadget> ( rows cols mines -- gadget )
136 mines place-mines update-counts >>cells
137 H{ } clone >>textures
138 dup '[ _ relayout-1 ] f 1 seconds <timer> >>timer
139 COLOR: gray <solid> >>interior
140 "12345" <circular> >>hint? ;
142 M: grid-gadget graft*
143 [ timer>> start-timer ] [ call-next-method ] bi ;
145 M: grid-gadget ungraft*
148 [ values dispose-each H{ } clone ] change-textures
150 ] [ call-next-method ] bi ;
152 M: grid-gadget pref-dim*
153 cells>> cells-dim [ 32 * ] bi@ swap 58 + 2array ;
155 :: cell-image-path ( cell won? lost? -- image-path )
156 won? lost? or cell mined?>> and [
158 { +flagged+ [ "flagged.gif" ] }
159 { +clicked+ [ "mineclicked.gif" ] }
160 [ drop won? "flagged.gif" "mine.gif" ? ]
164 { +question+ [ "question.gif" ] }
165 { +flagged+ [ lost? "misflagged.gif" "flagged.gif" ? ] }
167 cell #adjacent>> 0 or number>string
168 "open" ".gif" surround ] }
169 { f [ "blank.gif" ] }
171 ] if "vocab:minesweeper/_resources/" prepend ;
173 : digit-image-path ( ch -- image-path )
174 "vocab:minesweeper/_resources/digit%c.gif" sprintf ;
176 :: smiley-image-path ( won? lost? clicking? -- image-path )
178 { [ lost? ] [ "vocab:minesweeper/_resources/smileylost.gif" ] }
179 { [ won? ] [ "vocab:minesweeper/_resources/smileywon.gif" ] }
180 { [ clicking? ] [ "vocab:minesweeper/_resources/smileyuhoh.gif" ] }
181 [ "vocab:minesweeper/_resources/smiley.gif" ]
184 : draw-cached-texture ( path gadget -- )
185 textures>> [ load-image { 0 0 } <texture> ] cache
186 [ dim>> [ 2 /i ] map ] [ draw-scaled-texture ] bi ;
188 :: draw-hint ( gadget -- )
189 gadget hint?>> "xyzzy" sequence= [
190 gadget hand-rel first2 :> ( w h )
192 h 58 - w [ 32 /i ] bi@ :> ( row col )
193 gadget cells>> row col cell-at [
194 mined?>> COLOR: black COLOR: white ? gl-color
195 { 0 0 } { 1 1 } gl-fill-rect
200 :: draw-mines ( n gadget -- )
201 gadget cells>> won? 0 n ? "%03d" sprintf [
203 digit-image-path gadget draw-cached-texture
207 :: draw-smiley ( gadget -- )
208 gadget pref-dim first :> width
209 width 2/ 26 - 3 2array [
210 gadget cells>> [ won? ] [ lost? ] bi
211 hand-buttons get-global empty? not
212 gadget hand-click-rel [ second 58 >= ] [ f ] if* and
213 smiley-image-path gadget draw-cached-texture
216 :: draw-timer ( n gadget -- )
217 gadget pref-dim first :> width
218 n 999 min "%03d" sprintf [
219 3 swap - 26 * width swap - 3 - 6 2array [
220 digit-image-path gadget draw-cached-texture
224 :: draw-cells ( gadget -- )
225 gadget cells>> [ won? ] [ lost? ] bi :> ( won? lost? )
226 gadget cells>> [| row col cell |
227 col row [ 32 * ] bi@ 58 + 2array [
228 cell won? lost? cell-image-path
229 gadget draw-cached-texture
233 :: elapsed-time ( gadget -- n )
235 gadget end>> now or swap time- duration>seconds
238 M: grid-gadget handle-gesture
240 [ key-down? ] [ sym>> length 1 = ] [ sym>> " " = not ]
242 2dup [ sym>> first ] [ hint?>> ] bi* circular-push
243 ] when call-next-method ;
245 M: grid-gadget draw-gadget*
248 [ cells>> #mines-remaining ]
256 :: on-grid ( gadget quot: ( cells row col -- ? ) -- )
257 gadget hand-rel first2 :> ( w h )
259 h 58 - w [ 32 /i ] bi@ :> ( row col )
260 gadget cells>> :> cells
262 cells row col quot call [
263 gadget start>> [ now gadget start<< ] unless
264 cells game-over? [ now gadget end<< ] when
267 ] when gadget relayout-1 ; inline
269 :: on-click ( gadget -- )
270 gadget hand-rel first2 :> ( w h )
273 gadget pref-dim first 2/ w - abs 26 < and [
274 gadget [ reset-cells ] change-cells
275 f >>start f >>end drop
277 ] when gadget [ click-cell-at ] on-grid ;
279 : on-mark ( gadget -- ) [ mark-cell-at ] on-grid ;
281 : on-open ( gadget -- ) [ open-cell-at ] on-grid ;
283 : new-game ( gadget rows cols mines -- )
284 [ make-cells ] dip place-mines update-counts >>cells
285 f >>start f >>end relayout-window ;
287 : com-easy ( gadget -- ) 8 8 10 new-game ;
289 : com-medium ( gadget -- ) 16 16 40 new-game ;
291 : com-hard ( gadget -- ) 16 30 99 new-game ;
293 : com-help ( gadget -- ) drop "minesweeper" com-browse ;
295 grid-gadget "toolbar" f {
296 { T{ key-down { sym "1" } } com-easy }
297 { T{ key-down { sym "2" } } com-medium }
298 { T{ key-down { sym "3" } } com-hard }
299 { T{ key-down { sym "?" } } com-help }
302 grid-gadget "gestures" [
304 { T{ button-down { # 1 } } [ relayout-1 ] }
305 { T{ button-up { # 1 } } [ on-click ] }
306 { T{ button-up { # 3 } } [ on-mark ] }
307 { T{ button-up { # 2 } } [ on-open ] }
308 { T{ key-down { sym " " } } [ on-mark ] }
309 { motion [ relayout-1 ] }
313 TUPLE: minesweeper-gadget < track ;
315 : <minesweeper-gadget> ( -- gadget )
316 vertical minesweeper-gadget new-track
318 [ <toolbar> format-toolbar f track-add ]
321 M: minesweeper-gadget focusable-child* children>> second ;
323 MAIN-WINDOW: run-minesweeper {
324 { title "Minesweeper" }
326 { normal-title-bar close-button minimize-button } }
327 } <minesweeper-gadget> >>gadgets ;