aboutsummaryrefslogtreecommitdiff
path: root/minesweeper.ha
diff options
context:
space:
mode:
Diffstat (limited to 'minesweeper.ha')
-rw-r--r--minesweeper.ha318
1 files changed, 318 insertions, 0 deletions
diff --git a/minesweeper.ha b/minesweeper.ha
new file mode 100644
index 0000000..4567c7b
--- /dev/null
+++ b/minesweeper.ha
@@ -0,0 +1,318 @@
1use fmt;
2use math::random;
3use raylib;
4use strings;
5use time;
6
7// Types
8type cell = struct {
9 is_mine: bool,
10 is_hidden: bool,
11 is_flag: bool,
12 near_mines: int,
13 near_flags: int
14};
15
16type board = struct {
17 cells: []cell,
18 last_clicked: (int | void),
19 width: int,
20 height: int,
21 nmines: int,
22 nfreed: int,
23 finished: bool,
24};
25
26// Gameplay constants
27const BOARD_WIDTH = 8;
28const BOARD_HEIGHT = 8;
29const NMINES = 10;
30
31// Graphics-related constants
32const WINDOW_WIDTH = 1000;
33const WINDOW_HEIGHT = 800;
34const WINDOW_TITLE = "Minesweeper";
35
36const FLAG_SIZE = 40;
37const CELL_SIZE = 80;
38const HALFGAP_SIZE = 4;
39const BOTTOM_BAR_THICKNESS = 40;
40
41const COLOR_BACKGROUND = raylib::color { r = 230, g = 230, b = 230, a = 255 };
42const COLOR_FREE = raylib::color { r = 200, g = 200, b = 200, a = 255 };
43const COLOR_HIDDEN = raylib::color { r = 100, g = 100, b = 100, a = 255 };
44const COLOR_FLAG = raylib::color { r = 30, g = 30, b = 30, a = 255 };
45const COLOR_MINE = raylib::color { r = 255, g = 0, b = 0, a = 255 };
46const COLOR_CLICKED_MINE = raylib::color { r = 255, g = 100, b = 100, a = 255 };
47const COLOR_NR: [9]raylib::color = [
48 // TODO, maybe: different color for each number
49 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 0, unused
50 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 1
51 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 2
52 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 3
53 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 4
54 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 5
55 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 6
56 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 7
57 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 8
58];
59
60let bottom_left_text = "Playing...";
61
62fn draw_bottom_bar() void = {
63 const color_text = raylib::color { r = 200, g = 0, b = 0, a = 255 };
64 const text_gap = 4;
65 let h = raylib::get_screen_height();
66 let w = raylib::get_screen_width();
67 let tl = raylib::vector2 { x = 0.0, y = (h - BOTTOM_BAR_THICKNESS) : f32 };
68 let sz = raylib::vector2 { x = w: f32, y = h: f32 };
69
70 raylib::draw_rectangle_v(tl, sz, COLOR_BACKGROUND);
71 raylib::draw_text(bottom_left_text, text_gap,
72 (h - BOTTOM_BAR_THICKNESS + text_gap),
73 (BOTTOM_BAR_THICKNESS - 2 * text_gap), color_text);
74};
75
76fn foreach_neighbor(
77 b: *board,
78 ii: int,
79 f: *fn(b: *board, jj: int) void
80) void = {
81 let i = ii / b.width;
82 let j = ii % b.width;
83
84 const neighbors: [8](int, int) = [
85 (i-1, j-1), (i-1, j), (i-1, j+1),
86 (i, j-1), (i, j+1),
87 (i+1, j-1), (i+1, j), (i+1, j+1)
88 ];
89 for (let p .. neighbors)
90 if (p.0 >= 0 && p.0 < b.height &&
91 p.1 >= 0 && p.1 < b.width)
92 f(b, p.0*b.width + p.1);
93};
94
95fn inc_near_mines(b: *board, ii: int) void = b.cells[ii].near_mines += 1;
96fn inc_near_flags(b: *board, ii: int) void = b.cells[ii].near_flags += 1;
97fn dec_near_flags(b: *board, ii: int) void = b.cells[ii].near_flags -= 1;
98
99fn new_board(w: int, h: int, n: int) *board = {
100 let empty_cell = cell {
101 is_mine = false,
102 is_hidden = true,
103 is_flag = false,
104 near_mines = 0,
105 near_flags = 0
106 };
107 let b: *board = alloc(board {
108 cells = alloc([empty_cell ...], (w*h): size)!,
109 last_clicked = void,
110 width = w,
111 height = h,
112 nmines = n,
113 nfreed = 0,
114 finished = false
115 })!;
116
117 const seed: u64 = time::now(time::clock::REALTIME).sec : u64;
118 const rand: math::random::random = math::random::init(seed);
119 for (let i = 0; i < n; i += 1) {
120 let j = math::random::u32n(&rand, (w*h-i): u32): int;
121 for (let k = 0; k <= j; k += 1)
122 if (b.cells[k].is_mine)
123 j += 1;
124 b.cells[j].is_mine = true;
125 foreach_neighbor(b, j, &inc_near_mines);
126 };
127 return b;
128};
129
130fn delete_board(b: *board) void = {
131 free(b.cells);
132 free(b);
133};
134
135fn game_lost(b: *board) void = {
136 for (let c &.. b.cells)
137 c.is_hidden = false;
138 bottom_left_text = "You lost! Press n for a new game";
139 b.finished = true;
140};
141
142fn game_won(b: *board) void = {
143 for (let c &.. b.cells)
144 c.is_hidden = false;
145 bottom_left_text = "You won! Press n for a new game";
146 b.finished = true;
147};
148
149fn leftclick_if_hidden_noflag(b: *board, i: int) void = {
150 if (b.cells[i].is_hidden && ! b.cells[i].is_flag)
151 leftclick_cell(b, i);
152};
153
154fn leftclick_cell(b: *board, i: int) void = {
155 let c = &(b.cells[i]);
156 if (c.is_hidden) {
157 if (c.is_flag)
158 return;
159 b.last_clicked = i;
160 c.is_hidden = false;
161 if (c.is_mine) {
162 game_lost(b);
163 } else {
164 b.nfreed += 1;
165 if (b.nfreed == len(b.cells): int - b.nmines)
166 game_won(b);
167 };
168 if (c.near_mines == 0 && c.near_flags == 0)
169 foreach_neighbor(b, i, &leftclick_if_hidden_noflag);
170 } else {
171 if (c.near_flags == c.near_mines)
172 foreach_neighbor(b, i, &leftclick_if_hidden_noflag);
173 };
174};
175
176fn rightclick_cell(b: *board, i: int) void = {
177 let c = &(b.cells[i]);
178 if (c.is_hidden) {
179 c.is_flag = !c.is_flag;
180 const f = if (c.is_flag) &inc_near_flags else &dec_near_flags;
181 foreach_neighbor(b, i, f);
182 } else {
183 return;
184 };
185};
186
187// Return true if the user started a new game
188fn handle_interaction(b: *board) bool = {
189 const p = raylib::get_mouse_position();
190 if (raylib::is_mouse_button_pressed(raylib::MOUSE_BUTTON_LEFT)) {
191 match (get_cell_index(b, p)) {
192 case void => return false;
193 case let i: int => leftclick_cell(b, i);
194 };
195 };
196 if (raylib::is_mouse_button_pressed(raylib::MOUSE_BUTTON_RIGHT)) {
197 match (get_cell_index(b, p)) {
198 case void => return false;
199 case let i: int => rightclick_cell(b, i);
200 };
201 };
202 if (raylib::is_key_pressed(raylib::KEY_N) && b.finished)
203 return true;
204 return false;
205};
206
207fn top_gap() int = {
208 let h = raylib::get_screen_height();
209 return (h - BOTTOM_BAR_THICKNESS - BOARD_HEIGHT * CELL_SIZE) / 2;
210};
211
212fn side_gap() int = {
213 let w = raylib::get_screen_width();
214 return (w - BOARD_WIDTH * CELL_SIZE) / 2;
215};
216
217fn cell_pos(b: *board, i: int) raylib::vector2 = {
218 return raylib::vector2 {
219 x = ((i % b.width) * CELL_SIZE + HALFGAP_SIZE + side_gap()) : f32,
220 y = ((i / b.width) * CELL_SIZE + HALFGAP_SIZE + top_gap()) : f32
221 };
222};
223
224fn flag_pos(b: *board, i: int) raylib::vector2 = {
225 const shift = (CELL_SIZE - FLAG_SIZE) / 2;
226 return raylib::vector2 {
227 x = ((i % b.width) * CELL_SIZE + shift + side_gap()) : f32,
228 y = ((i / b.width) * CELL_SIZE + shift + top_gap()) : f32
229 };
230};
231
232fn get_cell_index(b: *board, p: raylib::vector2) (int | void) = {
233 const i = (p.y: int - top_gap()) / CELL_SIZE;
234 const j: int = (p.x: int - side_gap()) / CELL_SIZE;
235 if (i < b.height && j < b.width)
236 return i * b.width + j;
237};
238
239fn cell_drawsize() raylib::vector2 = raylib::vector2 {
240 x = (CELL_SIZE - 2*HALFGAP_SIZE) : f32,
241 y = (CELL_SIZE - 2*HALFGAP_SIZE) : f32
242};
243
244fn flag_drawsize() raylib::vector2 = raylib::vector2 {
245 x = FLAG_SIZE : f32,
246 y = FLAG_SIZE : f32
247};
248
249fn draw_flag(b: *board, i: int) void =
250 raylib::draw_rectangle_v(flag_pos(b, i), flag_drawsize(), COLOR_FLAG);
251
252fn draw_cell_hidden(b: *board, i: int) void = {
253 raylib::draw_rectangle_v(cell_pos(b, i), cell_drawsize(), COLOR_HIDDEN);
254 if (b.cells[i].is_flag)
255 draw_flag(b, i);
256};
257
258fn draw_cell_mine(b: *board, i: int) void = {
259 const c = match (b.last_clicked) {
260 case void => yield COLOR_MINE;
261 case let l: int => yield if (l == i) COLOR_CLICKED_MINE else COLOR_MINE;
262 };
263 raylib::draw_rectangle_v(cell_pos(b, i), cell_drawsize(), c);
264};
265
266fn draw_cell_free(b: *board, i: int) void = {
267 let pos = cell_pos(b, i);
268 let sz = cell_drawsize();
269 raylib::draw_rectangle_v(pos, cell_drawsize(), COLOR_FREE);
270
271 const n = b.cells[i].near_mines: u8;
272 const s = strings::fromutf8([n + '0']) as str;
273 const w = raylib::measure_text(s, sz.y: int);
274 const shift_x = (CELL_SIZE: f32 / 2.0) - (w: f32 / 2.0) - (HALFGAP_SIZE: f32);
275 const shift_y = (CELL_SIZE: f32 / 2.0) - (sz.y / 2.0);
276 let text_x = (pos.x + shift_x): int;
277 let text_y = (pos.y + shift_y): int;
278 raylib::draw_text(s, text_x, text_y, sz.y: int, COLOR_NR[n]);
279};
280
281fn draw_cell(b: *board, i: int) void = {
282 const c = b.cells[i];
283 if (c.is_hidden) {
284 draw_cell_hidden(b, i);
285 } else if (c.is_mine) {
286 draw_cell_mine(b, i);
287 } else {
288 draw_cell_free(b, i);
289 };
290};
291
292fn draw_board(b: *board) void = {
293 for (let i = 0; i < b.height; i += 1)
294 for (let j = 0; j < b.width; j += 1)
295 draw_cell(b, i*b.width + j);
296};
297
298export fn main() void = {
299 let game_board = new_board(BOARD_WIDTH, BOARD_HEIGHT, NMINES);
300 defer delete_board(game_board);
301
302 raylib::set_config_flags(raylib::FLAG_WINDOW_RESIZABLE);
303 raylib::init_window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE);
304 for (!raylib::window_should_close()) {
305 raylib::begin_drawing();
306
307 raylib::clear_background(COLOR_BACKGROUND);
308 draw_board(game_board);
309 draw_bottom_bar();
310 const restart = handle_interaction(game_board);
311 if (restart) {
312 delete_board(game_board);
313 game_board = new_board(BOARD_WIDTH, BOARD_HEIGHT, NMINES);
314 };
315
316 raylib::end_drawing();
317 };
318};

Generated with cgit - Back to sebastiano.tronto.net