From 648098ed27caf488fe5140f6c39d97fc9ab95e90 Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Wed, 17 Nov 2021 10:50:25 -0600 Subject: [PATCH] building up the minesweeper example --- include/gfx.h | 1 + misc/onyx/heartbreak_graphics.onyx | 7 +- misc/onyx/heartbreak_system.onyx | 9 ++ misc/onyx/heartbreak_window.onyx | 2 + src/gfx.c | 28 ++++++ src/heartbreak_graphics.c | 10 +++ src/heartbreak_system.c | 26 +++++- tests/minesweeper.onyx | 135 +++++++++++++++++++++-------- 8 files changed, 179 insertions(+), 39 deletions(-) diff --git a/include/gfx.h b/include/gfx.h index 7a198a2..ceaac4d 100644 --- a/include/gfx.h +++ b/include/gfx.h @@ -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 diff --git a/misc/onyx/heartbreak_graphics.onyx b/misc/onyx/heartbreak_graphics.onyx index dfb3721..724dde1 100644 --- a/misc/onyx/heartbreak_graphics.onyx +++ b/misc/onyx/heartbreak_graphics.onyx @@ -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 diff --git a/misc/onyx/heartbreak_system.onyx b/misc/onyx/heartbreak_system.onyx index 17af279..e85fc90 100644 --- a/misc/onyx/heartbreak_system.onyx +++ b/misc/onyx/heartbreak_system.onyx @@ -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" --- , +} diff --git a/misc/onyx/heartbreak_window.onyx b/misc/onyx/heartbreak_window.onyx index 09d5141..94414c7 100644 --- a/misc/onyx/heartbreak_window.onyx +++ b/misc/onyx/heartbreak_window.onyx @@ -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(); diff --git a/src/gfx.c b/src/gfx.c index d78d971..a4af0d9 100644 --- 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; +} diff --git a/src/heartbreak_graphics.c b/src/heartbreak_graphics.c index c7718ab..db2a48a 100644 --- a/src/heartbreak_graphics.c +++ b/src/heartbreak_graphics.c @@ -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 }; diff --git a/src/heartbreak_system.c b/src/heartbreak_system.c index 829d355..56556ac 100644 --- a/src/heartbreak_system.c +++ b/src/heartbreak_system.c @@ -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 }; diff --git a/tests/minesweeper.onyx b/tests/minesweeper.onyx index 14f2ba0..195ec0f 100644 --- a/tests/minesweeper.onyx +++ b/tests/minesweeper.onyx @@ -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); } } -- 2.25.1