minesweeper

A minewseeper implementation to play around with Hare and Raylib
git clone https://git.tronto.net/minesweeper
Download | Log | Files | Refs | README | LICENSE

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 };