use fmt; use math::random; use raylib; use strings; use time; // Types type cell = struct { is_mine: bool, is_hidden: bool, is_flag: bool, near_mines: int, near_flags: int }; type board = struct { cells: []cell, last_clicked: (int | void), width: int, height: int, nmines: int, nfreed: int, finished: bool, }; // Gameplay constants const BOARD_WIDTH = 8; const BOARD_HEIGHT = 8; const NMINES = 10; // Graphics-related constants const WINDOW_WIDTH = 1000; const WINDOW_HEIGHT = 800; const WINDOW_TITLE = "Minesweeper"; const FLAG_SIZE = 40; const CELL_SIZE = 80; const HALFGAP_SIZE = 4; const BOTTOM_BAR_THICKNESS = 40; const COLOR_BACKGROUND = raylib::color { r = 230, g = 230, b = 230, a = 255 }; const COLOR_FREE = raylib::color { r = 200, g = 200, b = 200, a = 255 }; const COLOR_HIDDEN = raylib::color { r = 100, g = 100, b = 100, a = 255 }; const COLOR_FLAG = raylib::color { r = 30, g = 30, b = 30, a = 255 }; const COLOR_MINE = raylib::color { r = 255, g = 0, b = 0, a = 255 }; const COLOR_CLICKED_MINE = raylib::color { r = 255, g = 100, b = 100, a = 255 }; const COLOR_NR: [9]raylib::color = [ // TODO, maybe: different color for each number raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 0, unused raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 1 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 2 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 3 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 4 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 5 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 6 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 7 raylib::color { r = 0, g = 0, b = 0, a = 255 }, // 8 ]; let bottom_left_text = "Playing..."; fn draw_bottom_bar() void = { const color_text = raylib::color { r = 200, g = 0, b = 0, a = 255 }; const text_gap = 4; let h = raylib::get_screen_height(); let w = raylib::get_screen_width(); let tl = raylib::vector2 { x = 0.0, y = (h - BOTTOM_BAR_THICKNESS) : f32 }; let sz = raylib::vector2 { x = w: f32, y = h: f32 }; raylib::draw_rectangle_v(tl, sz, COLOR_BACKGROUND); raylib::draw_text(bottom_left_text, text_gap, (h - BOTTOM_BAR_THICKNESS + text_gap), (BOTTOM_BAR_THICKNESS - 2 * text_gap), color_text); }; fn foreach_neighbor( b: *board, ii: int, f: *fn(b: *board, jj: int) void ) void = { let i = ii / b.width; let j = ii % b.width; const neighbors: [8](int, int) = [ (i-1, j-1), (i-1, j), (i-1, j+1), (i, j-1), (i, j+1), (i+1, j-1), (i+1, j), (i+1, j+1) ]; for (let p .. neighbors) if (p.0 >= 0 && p.0 < b.height && p.1 >= 0 && p.1 < b.width) f(b, p.0*b.width + p.1); }; fn inc_near_mines(b: *board, ii: int) void = b.cells[ii].near_mines += 1; fn inc_near_flags(b: *board, ii: int) void = b.cells[ii].near_flags += 1; fn dec_near_flags(b: *board, ii: int) void = b.cells[ii].near_flags -= 1; fn new_board(w: int, h: int, n: int) *board = { let empty_cell = cell { is_mine = false, is_hidden = true, is_flag = false, near_mines = 0, near_flags = 0 }; let b: *board = alloc(board { cells = alloc([empty_cell ...], (w*h): size)!, last_clicked = void, width = w, height = h, nmines = n, nfreed = 0, finished = false })!; const seed: u64 = time::now(time::clock::REALTIME).sec : u64; const rand: math::random::random = math::random::init(seed); for (let i = 0; i < n; i += 1) { let j = math::random::u32n(&rand, (w*h-i): u32): int; for (let k = 0; k <= j; k += 1) if (b.cells[k].is_mine) j += 1; b.cells[j].is_mine = true; foreach_neighbor(b, j, &inc_near_mines); }; return b; }; fn delete_board(b: *board) void = { free(b.cells); free(b); }; fn game_lost(b: *board) void = { for (let c &.. b.cells) c.is_hidden = false; bottom_left_text = "You lost! Press n for a new game"; b.finished = true; }; fn game_won(b: *board) void = { for (let c &.. b.cells) c.is_hidden = false; bottom_left_text = "You won! Press n for a new game"; b.finished = true; }; fn leftclick_if_hidden_noflag(b: *board, i: int) void = { if (b.cells[i].is_hidden && ! b.cells[i].is_flag) leftclick_cell(b, i); }; fn leftclick_cell(b: *board, i: int) void = { let c = &(b.cells[i]); if (c.is_hidden) { if (c.is_flag) return; b.last_clicked = i; c.is_hidden = false; if (c.is_mine) { game_lost(b); } else { b.nfreed += 1; if (b.nfreed == len(b.cells): int - b.nmines) game_won(b); }; if (c.near_mines == 0 && c.near_flags == 0) foreach_neighbor(b, i, &leftclick_if_hidden_noflag); } else { if (c.near_flags == c.near_mines) foreach_neighbor(b, i, &leftclick_if_hidden_noflag); }; }; fn rightclick_cell(b: *board, i: int) void = { let c = &(b.cells[i]); if (c.is_hidden) { c.is_flag = !c.is_flag; const f = if (c.is_flag) &inc_near_flags else &dec_near_flags; foreach_neighbor(b, i, f); } else { return; }; }; // Return true if the user started a new game fn handle_interaction(b: *board) bool = { const p = raylib::get_mouse_position(); if (raylib::is_mouse_button_pressed(raylib::MOUSE_BUTTON_LEFT)) { match (get_cell_index(b, p)) { case void => return false; case let i: int => leftclick_cell(b, i); }; }; if (raylib::is_mouse_button_pressed(raylib::MOUSE_BUTTON_RIGHT)) { match (get_cell_index(b, p)) { case void => return false; case let i: int => rightclick_cell(b, i); }; }; if (raylib::is_key_pressed(raylib::KEY_N) && b.finished) return true; return false; }; fn top_gap() int = { let h = raylib::get_screen_height(); return (h - BOTTOM_BAR_THICKNESS - BOARD_HEIGHT * CELL_SIZE) / 2; }; fn side_gap() int = { let w = raylib::get_screen_width(); return (w - BOARD_WIDTH * CELL_SIZE) / 2; }; fn cell_pos(b: *board, i: int) raylib::vector2 = { return raylib::vector2 { x = ((i % b.width) * CELL_SIZE + HALFGAP_SIZE + side_gap()) : f32, y = ((i / b.width) * CELL_SIZE + HALFGAP_SIZE + top_gap()) : f32 }; }; fn flag_pos(b: *board, i: int) raylib::vector2 = { const shift = (CELL_SIZE - FLAG_SIZE) / 2; return raylib::vector2 { x = ((i % b.width) * CELL_SIZE + shift + side_gap()) : f32, y = ((i / b.width) * CELL_SIZE + shift + top_gap()) : f32 }; }; fn get_cell_index(b: *board, p: raylib::vector2) (int | void) = { const i = (p.y: int - top_gap()) / CELL_SIZE; const j: int = (p.x: int - side_gap()) / CELL_SIZE; if (i < b.height && j < b.width) return i * b.width + j; }; fn cell_drawsize() raylib::vector2 = raylib::vector2 { x = (CELL_SIZE - 2*HALFGAP_SIZE) : f32, y = (CELL_SIZE - 2*HALFGAP_SIZE) : f32 }; fn flag_drawsize() raylib::vector2 = raylib::vector2 { x = FLAG_SIZE : f32, y = FLAG_SIZE : f32 }; fn draw_flag(b: *board, i: int) void = raylib::draw_rectangle_v(flag_pos(b, i), flag_drawsize(), COLOR_FLAG); fn draw_cell_hidden(b: *board, i: int) void = { raylib::draw_rectangle_v(cell_pos(b, i), cell_drawsize(), COLOR_HIDDEN); if (b.cells[i].is_flag) draw_flag(b, i); }; fn draw_cell_mine(b: *board, i: int) void = { const c = match (b.last_clicked) { case void => yield COLOR_MINE; case let l: int => yield if (l == i) COLOR_CLICKED_MINE else COLOR_MINE; }; raylib::draw_rectangle_v(cell_pos(b, i), cell_drawsize(), c); }; fn draw_cell_free(b: *board, i: int) void = { let pos = cell_pos(b, i); let sz = cell_drawsize(); raylib::draw_rectangle_v(pos, cell_drawsize(), COLOR_FREE); const n = b.cells[i].near_mines: u8; const s = strings::fromutf8([n + '0']) as str; const w = raylib::measure_text(s, sz.y: int); const shift_x = (CELL_SIZE: f32 / 2.0) - (w: f32 / 2.0) - (HALFGAP_SIZE: f32); const shift_y = (CELL_SIZE: f32 / 2.0) - (sz.y / 2.0); let text_x = (pos.x + shift_x): int; let text_y = (pos.y + shift_y): int; raylib::draw_text(s, text_x, text_y, sz.y: int, COLOR_NR[n]); }; fn draw_cell(b: *board, i: int) void = { const c = b.cells[i]; if (c.is_hidden) { draw_cell_hidden(b, i); } else if (c.is_mine) { draw_cell_mine(b, i); } else { draw_cell_free(b, i); }; }; fn draw_board(b: *board) void = { for (let i = 0; i < b.height; i += 1) for (let j = 0; j < b.width; j += 1) draw_cell(b, i*b.width + j); }; export fn main() void = { let game_board = new_board(BOARD_WIDTH, BOARD_HEIGHT, NMINES); defer delete_board(game_board); raylib::set_config_flags(raylib::FLAG_WINDOW_RESIZABLE); raylib::init_window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE); for (!raylib::window_should_close()) { raylib::begin_drawing(); raylib::clear_background(COLOR_BACKGROUND); draw_board(game_board); draw_bottom_bar(); const restart = handle_interaction(game_board); if (restart) { delete_board(game_board); game_board = new_board(BOARD_WIDTH, BOARD_HEIGHT, NMINES); }; raylib::end_drawing(); }; };