minesweeper.ha (8711B)
1 use fmt; 2 use math::random; 3 use raylib; 4 use strings; 5 use time; 6 7 // Types 8 type cell = struct { 9 is_mine: bool, 10 is_hidden: bool, 11 is_flag: bool, 12 near_mines: int, 13 near_flags: int 14 }; 15 16 type 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 27 const BOARD_WIDTH = 8; 28 const BOARD_HEIGHT = 8; 29 const NMINES = 10; 30 31 // Graphics-related constants 32 const WINDOW_WIDTH = 1000; 33 const WINDOW_HEIGHT = 800; 34 const WINDOW_TITLE = "Minesweeper"; 35 36 const FLAG_SIZE = 40; 37 const CELL_SIZE = 80; 38 const HALFGAP_SIZE = 4; 39 const BOTTOM_BAR_THICKNESS = 40; 40 41 const COLOR_BACKGROUND = raylib::color { r = 230, g = 230, b = 230, a = 255 }; 42 const COLOR_FREE = raylib::color { r = 200, g = 200, b = 200, a = 255 }; 43 const COLOR_HIDDEN = raylib::color { r = 100, g = 100, b = 100, a = 255 }; 44 const COLOR_FLAG = raylib::color { r = 30, g = 30, b = 30, a = 255 }; 45 const COLOR_MINE = raylib::color { r = 255, g = 0, b = 0, a = 255 }; 46 const COLOR_CLICKED_MINE = raylib::color { r = 255, g = 100, b = 100, a = 255 }; 47 const 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 60 let bottom_left_text = "Playing..."; 61 62 fn 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 76 fn 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 95 fn inc_near_mines(b: *board, ii: int) void = b.cells[ii].near_mines += 1; 96 fn inc_near_flags(b: *board, ii: int) void = b.cells[ii].near_flags += 1; 97 fn dec_near_flags(b: *board, ii: int) void = b.cells[ii].near_flags -= 1; 98 99 fn 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 130 fn delete_board(b: *board) void = { 131 free(b.cells); 132 free(b); 133 }; 134 135 fn 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 142 fn 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 149 fn 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 154 fn 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 176 fn 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 188 fn 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 207 fn top_gap() int = { 208 let h = raylib::get_screen_height(); 209 return (h - BOTTOM_BAR_THICKNESS - BOARD_HEIGHT * CELL_SIZE) / 2; 210 }; 211 212 fn side_gap() int = { 213 let w = raylib::get_screen_width(); 214 return (w - BOARD_WIDTH * CELL_SIZE) / 2; 215 }; 216 217 fn 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 224 fn 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 232 fn 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 239 fn cell_drawsize() raylib::vector2 = raylib::vector2 { 240 x = (CELL_SIZE - 2*HALFGAP_SIZE) : f32, 241 y = (CELL_SIZE - 2*HALFGAP_SIZE) : f32 242 }; 243 244 fn flag_drawsize() raylib::vector2 = raylib::vector2 { 245 x = FLAG_SIZE : f32, 246 y = FLAG_SIZE : f32 247 }; 248 249 fn draw_flag(b: *board, i: int) void = 250 raylib::draw_rectangle_v(flag_pos(b, i), flag_drawsize(), COLOR_FLAG); 251 252 fn 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 258 fn 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 266 fn 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 281 fn 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 292 fn 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 298 export 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 };