building up the minesweeper example
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 17 Nov 2021 16:50:25 +0000 (10:50 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 17 Nov 2021 16:50:25 +0000 (10:50 -0600)
include/gfx.h
misc/onyx/heartbreak_graphics.onyx
misc/onyx/heartbreak_system.onyx
misc/onyx/heartbreak_window.onyx
src/gfx.c
src/heartbreak_graphics.c
src/heartbreak_system.c
tests/minesweeper.onyx

index 7a198a2af36bb29a5938a5a1147eef61ee64b562..ceaac4ddcbe61e2e3fdb4973fd591a11d0700645 100644 (file)
@@ -122,4 +122,5 @@ Font* gfx_font_load(ImmediateRenderer *ir, char* font_path, i32 font_size);
 void  gfx_font_destroy(ImmediateRenderer *ir, Font *font);
 void  gfx_font_use(ImmediateRenderer *ir, Font *font);
 f32   gfx_font_get_text_width(ImmediateRenderer *ir, char* msgptr, i32 msglen);
+f32   gfx_font_get_text_height(ImmediateRenderer *ir, char* msgptr, i32 msglen, f32 max_width);
 #endif
index dfb3721f474897ba2f51829bb68d7a32a60cf8a1..724dde15938951fa9935f4469a5985434b0cef78 100644 (file)
@@ -77,9 +77,10 @@ getImageDimensions :: (img: Image) => getImageProperty(img, .Width), getImagePro
 //
 Font :: #type i64
 
-newFont      :: (path: str, size: i32) -> Font #foreign "heartbreak" "graphics_font_load" ---
-setFont      :: (font: Font = 0) -> void #foreign "heartbreak" "graphics_font_set" ---
-getTextWidth :: (text: str) -> f32 #foreign "heartbreak" "graphics_get_text_width" ---
+newFont       :: (path: str, size: i32) -> Font #foreign "heartbreak" "graphics_font_load" ---
+setFont       :: (font: Font = 0) -> void #foreign "heartbreak" "graphics_font_set" ---
+getTextWidth  :: (text: str) -> f32 #foreign "heartbreak" "graphics_get_text_width" ---
+getTextHeight :: (text: str, max_width: f32 = 0) -> f32 #foreign "heartbreak" "graphics_get_text_height" ---
 
 //
 // Meshes
index 17af27971a327f331131d3a0fd5a8494a7fb6d97..e85fc90b0f2568aa83159138b472493df8259ff9 100644 (file)
@@ -3,3 +3,12 @@ package heartbreak.system
 init      :: () -> bool #foreign "heartbreak" "system_init" ---
 destroy   :: () -> void #foreign "heartbreak" "system_destroy" ---
 end_frame :: () -> bool #foreign "heartbreak" "system_end_frame" ---
+
+ConfigOption :: enum {
+    Wait_For_Events :: 1;
+}
+
+config :: #match {
+    (option: ConfigOption, value: i32)  -> void #foreign "heartbreak" "system_config" ---   ,
+    (option: ConfigOption, value: bool) -> void #foreign "heartbreak" "system_config" ---   ,
+}
index 09d5141c595fce0d7c5923038a39bb6dfb1ce811..94414c741a801c1f1aec9bc10f6466bd6e4a982a 100644 (file)
@@ -4,3 +4,5 @@ getWidth       :: () -> i32 #foreign "heartbreak" "window_get_width" ---
 getHeight      :: () -> i32 #foreign "heartbreak" "window_get_height" ---
 setDimensions  :: (width, height: i32) -> void #foreign "heartbreak" "window_set_dimensions" ---
 setShouldClose :: (close: bool) -> void #foreign "heartbreak" "window_set_should_close" ---
+
+getDimensions :: () => getWidth(), getHeight();
index d78d971a640c50728f5ac0627638943abdbf8d73..a4af0d9ec92c6f9ef1082f63c037bac269623610 100644 (file)
--- a/src/gfx.c
+++ b/src/gfx.c
@@ -615,3 +615,31 @@ f32 gfx_font_get_text_width(ImmediateRenderer *ir, char* msgptr, i32 msglen) {
 
     return x;
 }
+
+f32 gfx_font_get_text_height(ImmediateRenderer *ir, char* msgptr, i32 msglen, f32 max_width) {
+    stbtt_aligned_quad quad;
+
+    f32 x = 0;
+    f32 y = 0;
+
+    while (msglen--) {
+        stbtt_GetPackedQuad(ir->active_font->char_data,
+                            FONT_INTERNAL_IMAGE_SIZE, FONT_INTERNAL_IMAGE_SIZE,
+                            *msgptr - ir->active_font->first_character,
+                            &x, &y, &quad, 0);
+
+        if (max_width > 0 && x >= max_width) {
+            x = 0;
+            y += ir->active_font->size;
+
+            stbtt_GetPackedQuad(ir->active_font->char_data,
+                                FONT_INTERNAL_IMAGE_SIZE, FONT_INTERNAL_IMAGE_SIZE,
+                                *msgptr - ir->active_font->first_character,
+                                &x, &y, &quad, 0);
+        }
+
+        msgptr++;
+    }
+
+    return quad.y1 - quad.y0;
+}
index c7718abcefe15663397079f68152336533cdbe2f..db2a48ae2d82d50b5e2c55e9b9e89f89d49b661e 100644 (file)
@@ -436,6 +436,15 @@ HEARTBREAK_DEF(get_text_width, (WASM_I32, WASM_I32), (WASM_F32)) {
     return NULL;
 }
 
+HEARTBREAK_DEF(get_text_height, (WASM_I32, WASM_I32, WASM_F32), (WASM_F32)) {
+    char* str_data = wasm_memory_data(wasm_memory) + params->data[0].of.i32;
+    u32   str_size = params->data[1].of.i32;
+
+    f32 res = gfx_font_get_text_height(&renderer, str_data, str_size, params->data[2].of.f32);
+    results->data[0] = WASM_F32_VAL(res);
+    return NULL;
+}
+
 HEARTBREAK_MODULE {
     HEARTBREAK_FUNC(set_clear_color)
     HEARTBREAK_FUNC(set_color)
@@ -466,6 +475,7 @@ HEARTBREAK_MODULE {
     HEARTBREAK_FUNC(font_destroy)
     HEARTBREAK_FUNC(font_set)
     HEARTBREAK_FUNC(get_text_width)
+    HEARTBREAK_FUNC(get_text_height)
 
     NULL
 };
index 829d355113fb3a8aad2831ac14f0938c45baa951..56556acb18ce6bbfa3527d1cde5839bbf361483e 100644 (file)
@@ -3,6 +3,9 @@
 
 #define HEARTBREAK_MODULE_NAME system
 
+static b32 wait_for_events = 0;
+
+
 #define HEARTBREAK_EVENTS   \
         HB_EVENT(keydown,    (WASM_I32, WASM_I32))  \
         HB_EVENT(keyup,      (WASM_I32))            \
@@ -166,16 +169,37 @@ HEARTBREAK_DEF(end_frame, (), (WASM_I32)) {
     gfx_immediate_renderer_flush(&renderer);
 
     glfwSwapBuffers(glfw_window);
-    glfwPollEvents();
+
+    if (wait_for_events) {
+        glfwWaitEvents();
+    } else {
+        glfwPollEvents();
+    }
 
     results->data[0] = WASM_I32_VAL(!glfwWindowShouldClose(glfw_window));
     return NULL;
 }
 
+HEARTBREAK_DEF(config, (WASM_I32, WASM_I32), ()) {
+    i32 option = params->data[0].of.i32;
+    i32 value  = params->data[1].of.i32;
+
+    switch (option) {
+        case 1:
+            if (value == 0) wait_for_events = 0;
+            if (value == 1) wait_for_events = 1;
+            break;
+    }
+
+    return NULL;
+}
+
 HEARTBREAK_MODULE {
     HEARTBREAK_FUNC(init)
     HEARTBREAK_FUNC(destroy)
     HEARTBREAK_FUNC(end_frame)
 
+    HEARTBREAK_FUNC(config)
+
     NULL
 };
index 14f2ba030a393262567b0fcd31ea962e2b70cb7a..195ec0f1da0107681dd4001bd2426d698a636740 100644 (file)
@@ -4,6 +4,8 @@ use package core
 use package core.intrinsics.onyx
 
 Pos :: struct { x, y: i32; }
+#match    hash.to_u32 (a: Pos) => 13 * hash.to_u32(a.x) + 17 * hash.to_u32(a.y);
+#operator == (a, b: Pos) => a.x == b.x && a.y == b.y;
 
 Board :: struct {
     Cell :: enum #flags {
@@ -27,6 +29,8 @@ Board :: struct {
     width, height : i32;
     visible_cells : [] Cell;
     true_cells    : [] Cell;
+
+    mine_locations : [] Pos;
 }
 
 init_board :: (use this: ^Board, w, h: i32) {
@@ -42,19 +46,19 @@ generate_board :: (use this: ^Board, mine_count: i32) {
     for ^cell: visible_cells do *cell = .Covered;
     for ^cell: true_cells    do *cell = .Empty;
 
-    mine_positions := array.make(Pos);
+    memory.alloc_slice(^mine_locations, mine_count);
     for i: mine_count {
         while true {
             mx, my := random.between(0, width - 1), random.between(0, height - 1);
             if get_true_cell(this, mx, my) != .Mine {
                 set_true_cell(this, mx, my, .Mine);
-                mine_positions << .{mx, my};
+                mine_locations[i] = .{mx, my};
                 break;
             }
         }
     }
 
-    for ^mine_pos: mine_positions {
+    for ^mine_pos: mine_locations {
         for neighbor: neighbors(this, mine_pos.x, mine_pos.y) {
             cell := get_true_cell(this, neighbor.x, neighbor.y);
             if     cell == .Empty  do cell = .Number1;
@@ -75,14 +79,14 @@ neighbors :: (board: ^Board, x, y: i32, include_corners := true) -> Iterator(Pos
     }
 
     context := new(Context);
-    *context = .{ board, x, y, -1, -1, include_corners };
+    *context = .{ board, x, y, -1 if include_corners else 0, -1, include_corners };
 
     next :: (use _: ^Context) -> (Pos, bool) {
         step :: macro () {
             @Gross @Cleanup
             dx += 1;
             if dx > 1 { dx = -1; dy += 1; }
-            if math.abs(dx) + math.abs(dy) == 2 && !include_corners{
+            if math.abs(dx) + math.abs(dy) == 2 && !include_corners {
                 dx += 1;
                 if dx > 1 { dx = -1; dy += 1; }
             }
@@ -128,17 +132,18 @@ reveal_cell :: (use this: ^Board, x, y: i32) => {
 }
 
 flood_reveal :: (use this: ^Board, x, y: i32) => {
-    positions := array.make(Pos);
+
+    positions := set.make(Pos);
     positions << .{x, y};
 
-    while positions.count != 0 {
-        p := positions[0];
-        array.delete(^positions, 0);
+    while !positions->empty() {
+        p := positions.entries[0].value;
+        positions->remove(p);
 
         reveal_cell(this, p.x, p.y);
         if get_true_cell(this, p.x, p.y) & .Is_Number do continue;
 
-        for n: neighbors(this, p.x, p.y, include_corners=false) {
+        for n: neighbors(this, p.x, p.y, include_corners=true) {
             true_cell    := get_true_cell(this, n.x, n.y);
             visible_cell := get_visible_cell(this, n.x, n.y);
             if true_cell != .Mine && visible_cell == .Covered {
@@ -148,15 +153,32 @@ flood_reveal :: (use this: ^Board, x, y: i32) => {
     }
 }
 
-cell_size :: 32.0f
-border    :: 2.0f
+reveal_mines :: (use this: ^Board) {
+    for ^mine: mine_locations {
+        reveal_cell(this, mine.x, mine.y);
+    }
+}
+
+toggle_flag :: (use this: ^Board, x, y: i32) {
+    cell := get_visible_cell(this, x, y);
+    switch cell {
+        case .Flag do set_visible_cell(this, x, y, .Covered);
+        case .Covered do set_visible_cell(this, x, y, .Flag);
+    }
+}
+
+cell_size := 32.0f
+border    := 2.0f
 
 board: Board;
 
 load :: () {
     random.set_seed(clock.time());
-    init_board(^board, 20, 20);
-    generate_board(^board, 5);
+
+    hb.system.config(.Wait_For_Events, true);
+
+    init_board(^board, 30, 20);
+    generate_board(^board, 80);
 }
 
 update :: (dt: f32) {
@@ -164,20 +186,41 @@ update :: (dt: f32) {
         hb.window.setShouldClose(true);
     }
 
-    #persist last_mouse_down := false;
-    mouse_down := hb.input.mouseIsDown(.Left);
-    if last_mouse_down && !mouse_down {
-        mx, my := hb.input.mouseGetPos();
-        cx, cy := cast(i32) math.floor(mx / cell_size), cast(i32) math.floor(my / cell_size);
-        if cx < board.width && cy < board.height {
-            flood_reveal(^board, cx, cy);
+    mx, my := hb.input.mouseGetPos();
+    cx, cy := cast(i32) math.floor(mx / cell_size), cast(i32) math.floor(my / cell_size);
+
+    { // Left mouse clicking
+        #persist last_mouse_down := false;
+        mouse_down := hb.input.mouseIsDown(.Left);
+        defer last_mouse_down = mouse_down;
+
+        if !last_mouse_down || mouse_down do break;
+        if cx >= board.width || cy >= board.height do break;
+
+        if get_visible_cell(^board, cx, cy) == .Covered {
+            if array.contains(board.mine_locations, .{ cx, cy }) {
+                reveal_mines(^board);
+            } else {
+                flood_reveal(^board, cx, cy);
+            }
         }
     }
-    last_mouse_down = mouse_down;
+
+    { // Right mouse clicking
+        #persist last_mouse_down := false;
+        mouse_down := hb.input.mouseIsDown(.Right);
+        defer last_mouse_down = mouse_down;
+
+        if !last_mouse_down || mouse_down do break;
+        if cx >= board.width || cy >= board.height do break;
+
+        toggle_flag(^board, cx, cy);
+    }
 }
 
 draw :: () {
-    size :: cell_size
+    window_width, window_height := hb.window.getDimensions();
+    cell_size = ~~math.min(window_width / board.width, window_height / board.height);
 
     hb.graphics.setClearColor(0.05, 0.05, 0.05, 1);
     hb.graphics.clear();
@@ -187,39 +230,61 @@ draw :: () {
             cell := get_visible_cell(^board, x, y);
             if cell == .Empty {
                 hb.graphics.setColor(0.1, 0.1, 0.1);
-                hb.graphics.rectangle(.Fill, ~~x * size, ~~y * size, size - border, size - border);
+                hb.graphics.rectangle(.Fill, ~~x * cell_size, ~~y * cell_size, cell_size - border, cell_size - border);
             }
 
             if cell == .Covered {
                 hb.graphics.setColor(0.6, 0.6, 0.6);
-                hb.graphics.rectangle(.Fill, ~~x * size, ~~y * size, size - border, size - border);
+                hb.graphics.rectangle(.Fill, ~~x * cell_size, ~~y * cell_size, cell_size - border, cell_size - border);
             }
 
             if cell == .Mine {
                 hb.graphics.setColor(0.1, 0.1, 0.1);
-                hb.graphics.rectangle(.Fill, ~~x * size, ~~y * size, size - border, size - border);
+                hb.graphics.rectangle(.Fill, ~~x * cell_size, ~~y * cell_size, cell_size - border, cell_size - border);
                 hb.graphics.setColor(1, 0, 0);
-                hb.graphics.circle(.Fill, ~~x * size + 0.5 * size, ~~y * size + 0.5 * size, (size - 2 * border) / 2);
+                hb.graphics.circle(.Fill, ~~x * cell_size + 0.5 * cell_size, ~~y * cell_size + 0.5 * cell_size, (cell_size - 2 * border) / 2);
+            }
+
+            if cell == .Flag {
+                hb.graphics.setColor(0.6, 0.6, 0.6);
+                hb.graphics.rectangle(.Fill, ~~x * cell_size, ~~y * cell_size, cell_size - border, cell_size - border);
+
+                hb.graphics.setColor(0.3, 0.2, 0.1);
+                hb.graphics.rectangle(.Fill, ~~x * cell_size + 0.2 * cell_size, ~~y * cell_size + 0.2 * cell_size, 0.1 * cell_size, 0.6 * cell_size);
+                hb.graphics.rectangle(.Fill, ~~x * cell_size + 0.2 * cell_size, ~~y * cell_size + 0.2 * cell_size, 0.6 * cell_size, 0.35 * cell_size);
             }
 
             if cell & .Is_Number {
                 buf: [2] u8;
-                @Bug // Unary bitwise-not does not work on enum values
                 s := conv.str_format(buf, "{}", cast(i32) (cell - .Is_Number));
                 w := hb.graphics.getTextWidth(s);
+                h := hb.graphics.getTextHeight(s, cell_size);
+
+                #persist number_colors := ([3] f32).[
+                    f32.[ 0,   0,   1 ],
+                    f32.[ 0,   1,   0 ],
+                    f32.[ 0,   0.5, 0.5 ],
+                    f32.[ 0.5, 0,   0 ],
+                    f32.[ 0.7, 0,   0 ],
+                    f32.[ 1.0, 0,   0 ],
+                    f32.[ 1.0, 0,   1.0 ],
+                    f32.[ 0,   0,   0 ],
+                ];
+
+                hb.graphics.setColor(0.1, 0.1, 0.1);
+                hb.graphics.rectangle(.Fill, ~~x * cell_size, ~~y * cell_size, cell_size - border, cell_size - border);
 
-                hb.graphics.setColor(0.3, 0.3, 0.3);
-                hb.graphics.rectangle(.Fill, ~~x * size, ~~y * size, size - border, size - border);
-                hb.graphics.setColor(0.7, 0.5, 0.5);
-                hb.graphics.print(s, ~~x * size + (size - w) / 2, ~~(y + 1) * size - 3 * border);
+                col := number_colors[cast(i32) (cell - .Is_Number) - 1];
+                hb.graphics.setColor(col[0], col[1], col[2]);
+                hb.graphics.print(s, ~~x * cell_size + (cell_size - w) / 2, ~~y * cell_size + (cell_size - h) / 2 + h);
             }
         }
     }
 
     mx, my := hb.input.mouseGetPos();
-    mx, my = size * math.floor(mx / size), size * math.floor(my / size);
-    if mx < ~~board.width * size && my < ~~board.height * size {
+    mx, my = cell_size * math.floor(mx / cell_size), cell_size * math.floor(my / cell_size);
+    if mx < ~~board.width * cell_size && my < ~~board.height * cell_size {
         hb.graphics.setColor(1, 1, 1, 0.3);
-        hb.graphics.rectangle(.Fill, ~~mx, ~~my, size - border, size - border);
+        hb.graphics.rectangle(.Fill, ~~mx, ~~my, cell_size - border, cell_size - border);
     }
 }