v2 / examples / gg / minesweeper.v
220 lines · 206 sloc · 4.64 KB · 8e35f4d9848f7ad35d857a187dddbfd2eca5e19d
Raw
1module main
2
3import gg
4import rand
5import os.asset
6
7const header_size = 30
8
9enum Cell {
10 empty
11 n1
12 n2
13 n3
14 n4
15 n5
16 n6
17 n7
18 n8
19 mine
20}
21
22@[heap]
23struct Game {
24mut:
25 ctx &gg.Context = unsafe { nil }
26 grid [][]Cell
27 flags [][]bool
28 revealed [][]bool
29 size int = 10 // in cells
30 csize int = 30 // in pixels
31 game_over bool
32 first_click bool = true
33 mines int = 10
34 mines_flagged int
35}
36
37@[inline]
38fn (mut g Game) in_grid(cy int, cx int) bool {
39 return cx >= 0 && cx < g.size && cy >= 0 && cy < g.size
40}
41
42fn (mut g Game) generate_mines(first_y int, first_x int) {
43 mut mines_placed := 0
44 for mines_placed < g.mines {
45 x, y := rand.intn(g.size) or { 0 }, rand.intn(g.size) or { 0 }
46 // avoid placing mines at the position of the first click
47 if (x == first_x && y == first_y) || g.grid[y][x] == .mine {
48 continue
49 }
50 g.grid[y][x] = .mine
51 mines_placed++
52 }
53 for y in 0 .. g.size {
54 for x in 0 .. g.size {
55 if g.grid[y][x] == .mine {
56 continue
57 }
58 mut count := 0
59 for dy in -1 .. 2 {
60 for dx in -1 .. 2 {
61 cy, cx := y + dy, x + dx
62 if g.in_grid(cy, cx) {
63 if g.grid[cy][cx] == .mine {
64 count++
65 }
66 }
67 }
68 }
69 g.grid[y][x] = unsafe { Cell(count) }
70 }
71 }
72}
73
74fn (mut g Game) reveal(y int, x int) {
75 if !g.in_grid(y, x) {
76 return
77 }
78 if g.revealed[y][x] {
79 return
80 }
81 g.revealed[y][x] = true
82 if g.grid[y][x] == .mine {
83 g.game_over = true
84 return
85 }
86 if g.grid[y][x] == .empty {
87 for dy in -1 .. 2 {
88 for dx in -1 .. 2 {
89 g.reveal(y + dy, x + dx)
90 }
91 }
92 }
93}
94
95fn (mut g Game) restart() {
96 g.grid = [][]Cell{len: g.size, init: []Cell{len: g.size}}
97 g.flags = [][]bool{len: g.size, init: []bool{len: g.size}}
98 g.revealed = [][]bool{len: g.size, init: []bool{len: g.size}}
99 g.game_over = false
100 g.first_click = true
101 g.mines_flagged = 0
102}
103
104fn on_event(e &gg.Event, mut g Game) {
105 if e.typ == .key_down {
106 match e.key_code {
107 .escape { g.ctx.quit() }
108 .r { g.restart() }
109 else {}
110 }
111
112 return
113 }
114 if g.game_over {
115 return
116 }
117 if e.typ != .mouse_down {
118 return
119 }
120 x := int(e.mouse_x / g.csize)
121 y := int((e.mouse_y - header_size) / g.csize)
122 if e.mouse_button == .left {
123 if g.first_click {
124 g.generate_mines(y, x)
125 g.first_click = false
126 }
127 g.reveal(y, x)
128 }
129 if e.mouse_button == .right {
130 if !g.revealed[y][x] {
131 old := g.flags[y][x]
132 g.flags[y][x] = !old
133 g.mines_flagged += if old { -1 } else { 1 }
134 }
135 }
136 if e.mouse_button == .middle {
137 if g.revealed[y][x] {
138 count := g.act_on_neighbors(y, x, fn (mut g Game, cy int, cx int) int {
139 if g.in_grid(cy, cx) {
140 return int(g.flags[cy][cx])
141 }
142 return 0
143 })
144 if int(g.grid[y][x]) == count {
145 g.act_on_neighbors(y, x, fn (mut g Game, cy int, cx int) int {
146 if g.in_grid(cy, cx) && !g.flags[cy][cx] {
147 g.reveal(cy, cx)
148 }
149 return 0
150 })
151 }
152 }
153 }
154}
155
156fn (mut g Game) act_on_neighbors(y int, x int, f fn (mut g Game, cy int, cx int) int) int {
157 mut count := 0
158 for dy in -1 .. 2 {
159 for dx in -1 .. 2 {
160 if dy == 0 && dx == 0 {
161 continue
162 }
163 cy, cx := y + dy, x + dx
164 count += f(mut g, cy, cx)
165 }
166 }
167 return count
168}
169
170fn (mut g Game) draw_cell(y int, x int) {
171 o := header_size
172 rect_x, rect_y := x * g.csize, y * g.csize
173 if g.revealed[y][x] {
174 if g.grid[y][x] == .mine {
175 g.ctx.draw_rect_filled(rect_x, o + rect_y, g.csize, g.csize, gg.red)
176 g.ctx.draw_text(rect_x + 10, o + rect_y + 5, '*', color: gg.black)
177 } else if int(g.grid[y][x]) > 0 {
178 g.ctx.draw_rect_filled(rect_x, o + rect_y, g.csize, g.csize, gg.light_gray)
179 n := int(g.grid[y][x]).str()
180 g.ctx.draw_text(rect_x + 10, o + rect_y + 5, n, color: gg.black)
181 } else {
182 c := gg.rgb(240, 240, 240)
183 g.ctx.draw_rect_filled(rect_x, o + rect_y, g.csize, g.csize, c)
184 }
185 } else if g.flags[y][x] {
186 g.ctx.draw_rect_filled(rect_x, o + rect_y, g.csize, g.csize, gg.gray)
187 g.ctx.draw_text(rect_x + 10, o + rect_y + 5, 'F', color: gg.white)
188 } else {
189 g.ctx.draw_rect_filled(rect_x, o + rect_y, g.csize, g.csize, gg.gray)
190 }
191 g.ctx.draw_rect_empty(rect_x, o + rect_y, g.csize, g.csize, gg.black)
192}
193
194fn on_frame(mut g Game) {
195 g.ctx.begin()
196 for y in 0 .. g.size {
197 for x in 0 .. g.size {
198 g.draw_cell(y, x)
199 }
200 }
201 message := 'Flagged: ${g.mines_flagged:02}/${g.mines:02} (r)estart (ESC)ape'
202 g.ctx.draw_text(5, 7, message, color: gg.green)
203 g.ctx.end()
204}
205
206fn main() {
207 mut g := &Game{}
208 g.restart()
209 g.ctx = gg.new_context(
210 bg_color: gg.black
211 width: g.size * g.csize
212 height: header_size + g.size * g.csize
213 window_title: 'V Minesweeper'
214 user_data: g
215 frame_fn: on_frame
216 event_fn: on_event
217 font_path: asset.get_path('../assets', 'fonts/RobotoMono-Regular.ttf')
218 )
219 g.ctx.run()
220}
221