feature dump; working on minesweeper
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 17 Nov 2021 02:55:44 +0000 (20:55 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 17 Nov 2021 02:55:44 +0000 (20:55 -0600)
12 files changed:
include/gfx.h
include/utils.h
misc/onyx/heartbreak_graphics.onyx
misc/onyx/heartbreak_input.onyx
misc/onyx/qhb.onyx
src/gfx.c
src/heartbreak.c
src/heartbreak_graphics.c
src/heartbreak_system.c
src/utils.c
tests/minesweeper.onyx [new file with mode: 0644]
tests/simp.onyx

index 44075f057bedc810efcfc151a3f6cf921bdbc95d..abef4ee5266bd4e89640fb0a8427b13a6b6d73d5 100644 (file)
@@ -83,6 +83,7 @@ typedef struct ImmediateRenderer {
 
     Image*       active_image;
     Font*        active_font;
+    Font*        default_font;
 
     bh_arr(mat3) world_transforms;
     b32          world_transform_dirty;
@@ -117,5 +118,7 @@ void   gfx_image_bind(ImmediateRenderer *ir, Image *img, u32 texture_unit);
 void   gfx_image_unbind(ImmediateRenderer *ir);
 
 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);
 #endif
index 2efe859f747ec6cc87496bd459be69c6c2492812..289139398a10e13524e4cc87aa5595c0509cd059 100644 (file)
@@ -27,4 +27,15 @@ typedef struct mat3 {
 void mat3_identity(mat3 *out);
 void mat3_mul(mat3 *out, mat3 a, mat3 b);
 
-#endif
\ No newline at end of file
+// Logging
+typedef enum LogLevel {
+    DEBUG, INFO, WARNING, ERROR,
+    Log_Level_Count
+} LogLevel;
+extern LogLevel log_level;
+extern char *log_level_strings[Log_Level_Count];
+
+void log_level_set(LogLevel level);
+void logprint(LogLevel level, char *format, ...);
+
+#endif
index eef9531bf3d385a95a5010875b7112e7121a21ee..dfb3721f474897ba2f51829bb68d7a32a60cf8a1 100644 (file)
@@ -3,7 +3,8 @@ package heartbreak.graphics
 // State
 
 setClearColor :: (r, g, b: f32, a: f32 = 1) -> void #foreign "heartbreak" "graphics_set_clear_color" ---
-setColor  :: (r, g, b: f32, a: f32 = 1) -> void        #foreign "heartbreak" "graphics_set_color" ---
+setColor      :: (r, g, b: f32, a: f32 = 1) -> void        #foreign "heartbreak" "graphics_set_color" ---
+setLineWidth  :: (width: i32) -> void #foreign "heartbreak" "graphics_set_line_width" ---
 
 //
 // Drawing
@@ -33,14 +34,14 @@ push      :: ()           -> void #foreign "heartbreak" "graphics_push" ---
 pop       :: ()           -> void #foreign "heartbreak" "graphics_pop" ---
 
 //
-// Objects
+// Images
 //
 Image :: #type i64
 
 newImage  :: (path: str) -> Image #foreign "heartbreak" "graphics_image_load" ---
 drawImage :: (img: Image, x, y: f32, w: f32 = -1, h: f32 = -1) -> void #foreign "heartbreak" "graphics_image_draw" ---
 
-#private_file imageParam :: (img: Image, param: i32, value: i32) -> bool #foreign "heartbreak" "graphics_image_param" ---
+#local imageParam :: (img: Image, param: i32, value: i32) -> bool #foreign "heartbreak" "graphics_image_param" ---
 
 ImageFilter :: enum {
     Nearest :: 0x01;
@@ -71,5 +72,20 @@ ImageProperty :: enum {
 getImageProperty :: (img: Image, prop: ImageProperty) -> i32 #foreign "heartbreak" "graphics_image_property" ---
 getImageDimensions :: (img: Image) => getImageProperty(img, .Width), getImageProperty(img, .Height);
 
+//
+// Fonts
+//
+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" ---
+
+//
+// Meshes
+//
+
+
+//
+// Shaders
+//
index e31afa072071f11595e58ebaaffb7c301850b9e6..ce48a3a0d4da5cc011ee03b8b151a4674f4ff6c5 100644 (file)
@@ -124,7 +124,7 @@ KeyConstant :: enum {
     Right_Alt ::   346;
     Right_Super ::   347;
     Menu ::   348;
-    Last ::   348;
+    Last ::   Menu;
 }
 
 ButtonConstant :: enum {
@@ -137,9 +137,9 @@ ButtonConstant :: enum {
     Button_7 :: 6;
     Button_8 :: 7;
 
-    Left   :: 0;
-    Right  :: 1;
-    Middle :: 2;
+    Left   :: Button_1;
+    Right  :: Button_2;
+    Middle :: Button_3;
 }
 
 keyIsDown :: (key: KeyConstant) -> bool #foreign "heartbreak" "input_key_is_down" ---
index d592b2fe998f7bf33e97841daccc73d37d3b4e75..2236bff40a54f952725eac34ed85f6e133418270 100644 (file)
@@ -6,21 +6,9 @@ package main
 #load "core/std"
 #load "./heartbreak"
 
-#private hb :: package heartbreak
+#package hb :: package heartbreak
 
-#private_file symbol_source :: package main
-
-#if !#defined(symbol_source.load) {
-    load :: () {}
-}
-
-#if !#defined(symbol_source.update) {
-    update :: (dt: f32) {}
-}
-
-#if !#defined(symbol_source.draw) {
-    draw :: () {}
-}
+#local symbol_source :: package main
 
 // This is one place where top-level macros would be nice.
 #if #defined(symbol_source.keydown)    { #export "__heartbreak_keydown"    symbol_source.keydown    }
index 3a36e16fbcd1a1a421a4b3f8e1870d6d6c914f09..d78d971a640c50728f5ac0627638943abdbf8d73 100644 (file)
--- a/src/gfx.c
+++ b/src/gfx.c
@@ -260,7 +260,9 @@ void gfx_immediate_renderer_init(ImmediateRenderer *ir, bh_allocator allocator)
     ir->current_color = (ImmediateColor) { 1, 1, 1, 1 };
     ir->world_transform_dirty = 1;
 
-    ir->active_font = gfx_font_load(ir, "/usr/share/fonts/nerd-fonts-complete/TTF/mononoki-Regular Nerd Font Complete Mono.ttf", 32.0f);
+    // @TODO Use a font that is within this project
+    ir->default_font = gfx_font_load(ir, "/usr/share/fonts/nerd-fonts-complete/TTF/mononoki-Regular Nerd Font Complete Mono.ttf", 32.0f);
+    ir->active_font  = ir->default_font;
 }
 
 void gfx_immediate_renderer_flush(ImmediateRenderer *ir) {
@@ -580,6 +582,24 @@ Font* gfx_font_load(ImmediateRenderer *ir, char* font_path, i32 font_size) {
     return font;
 }
 
+void gfx_font_destroy(ImmediateRenderer *ir, Font *font) {
+    bh_free(ir->allocator, font->name);
+    glDeleteTextures(1, &font->texture);
+    bh_free(ir->allocator, font->char_data);
+    bh_free(ir->allocator, font);
+}
+
+void gfx_font_use(ImmediateRenderer *ir, Font *font) {
+    // If the desired image is already bound, then just keep it bound.
+    if (ir->active_font == font) return;
+
+    if (font == NULL) {
+        ir->active_font = ir->default_font;
+    } else {
+        ir->active_font = font;
+    }
+}
+
 f32 gfx_font_get_text_width(ImmediateRenderer *ir, char* msgptr, i32 msglen) {
     stbtt_aligned_quad quad;
 
index 11ea7f68084a89f9ba6fb861b8b87c2db40eb53e..c3711164638e170f738285f504609fdabfe2fb09 100644 (file)
@@ -186,6 +186,7 @@ void run_wasm_file(bh_buffer wasm_bytes) {
     wasm_val_vec_t results;
     wasm_val_vec_new_uninitialized(&args, 0);
 
+    logprint(INFO, "Calling _start in WASM module");
     wasm_trap_t* result = wasm_func_call(start_func, &args, &results);
 
     if (result != NULL) {
@@ -197,7 +198,7 @@ void run_wasm_file(bh_buffer wasm_bytes) {
     goto cleanup;
 
 error_handling:
-    bh_printf("An error occured trying to run the WASM module...\n");
+    logprint(ERROR, "An error occured trying to run the WASM module...\n");
 
 cleanup:
     if (wasm_instance) wasm_instance_delete(wasm_instance);
@@ -208,6 +209,9 @@ cleanup:
 }
 
 int main(int argc, char* argv[]) {
+    log_level_set(WARNING);
+    logprint(INFO, "Heartbreak version " VERSION);
+
     if (argc < 2) {
         bh_printf("Expected a .wasm file to run.\n");
         return 1;
@@ -218,8 +222,10 @@ int main(int argc, char* argv[]) {
         return 1;
     }
 
+    logprint(DEBUG, "Loading file %s", argv[1]);
     bh_allocator heap_allocator = bh_heap_allocator();
     bh_file_contents wasm_file = bh_file_read_contents_direct(heap_allocator, argv[1]);
+    logprint(INFO, "Loaded file %s", argv[1]);
 
     run_wasm_file((bh_buffer) {
         .data   = wasm_file.data,
index 3212b41bc1a4381b9b03529a0a55d22a74d41062..9563ab33a0ac0e56b4bbc99db4ad6478cfbfa865 100644 (file)
@@ -31,6 +31,11 @@ HEARTBREAK_DEF(set_color, (WASM_F32,WASM_F32,WASM_F32,WASM_F32), ()) {
     return NULL;
 }
 
+HEARTBREAK_DEF(set_line_width, (WASM_I32), ()) {
+    glLineWidth(params->data[0].of.i32);
+    return NULL;
+}
+
 HEARTBREAK_DEF(rectangle, (WASM_I32,WASM_F32,WASM_F32,WASM_F32,WASM_F32), ()) {
     gfx_image_unbind(&renderer);
 
@@ -304,7 +309,7 @@ HEARTBREAK_DEF(image_load, (WASM_I32, WASM_I32), (WASM_I64)) {
 
     bh_printf("Loading image: %b\n", str_data, str_size);
 
-    u8 filename[512];
+    char* filename = alloca(str_size + 1);
     strncpy(filename, str_data, str_size);
     filename[str_size] = '\0';
 
@@ -359,35 +364,15 @@ HEARTBREAK_DEF(image_param, (WASM_I64, WASM_I32, WASM_I32), (WASM_I32)) {
 
     i32 param = params->data[1].of.i32;
     i32 value = params->data[2].of.i32;
-    i32 filter;
+
+    static const GLint filters[] = { -1, GL_NEAREST, GL_LINEAR };
+    static const GLint wraps[]   = { -1, GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT, GL_REPEAT };
 
     switch (param) {
-        case 1: 
-            filter = -1;
-            if (value == 1) filter = GL_NEAREST;
-            if (value == 2) filter = GL_LINEAR;
-            if (filter > 0) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
-            break;
-        case 2:
-            filter = -1;
-            if (value == 1) filter = GL_NEAREST;
-            if (value == 2) filter = GL_LINEAR;
-            if (filter > 0) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
-            break;
-        case 3: 
-            filter = -1;
-            if (value == 1) filter = GL_CLAMP_TO_EDGE;
-            if (value == 2) filter = GL_MIRRORED_REPEAT;
-            if (value == 3) filter = GL_REPEAT;
-            if (filter > 0) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, filter);
-            break;
-        case 4:
-            filter = -1;
-            if (value == 1) filter = GL_CLAMP_TO_EDGE;
-            if (value == 2) filter = GL_MIRRORED_REPEAT;
-            if (value == 3) filter = GL_REPEAT;
-            if (filter > 0) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, filter);
-            break;
+        case 1: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filters[value]); break;
+        case 2: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filters[value]); break;
+        case 3: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wraps[value]); break;
+        case 4: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wraps[value]); break;
     }
 
     glBindTexture(GL_TEXTURE_2D, -1);
@@ -411,6 +396,37 @@ HEARTBREAK_DEF(image_property, (WASM_I64, WASM_I32), (WASM_I32)) {
     return NULL;
 }
 
+HEARTBREAK_DEF(font_load, (WASM_I32, WASM_I32, WASM_I32), (WASM_I64)) {
+    char* str_data = wasm_memory_data(wasm_memory) + params->data[0].of.i32;
+    u32   str_size = params->data[1].of.i32;
+
+    char *font_name = alloca(str_size + 1);
+    strncpy(font_name, str_data, str_size);
+    font_name[str_size] = '\0';
+
+    Font *new_font = gfx_font_load(&renderer, font_name, params->data[2].of.i32);
+    wasm_val_init_ptr(&results->data[0], new_font);
+    return NULL;
+}
+
+HEARTBREAK_DEF(font_destroy, (WASM_I64), ()) {
+    Font *font = (Font *) params->data[0].of.i64;
+    assert(font->magic_number == 0xbeefbeefbeefbeef);
+
+    gfx_font_destroy(&renderer, font);
+
+    return NULL;
+}
+
+HEARTBREAK_DEF(font_set, (WASM_I64), ()) {
+    Font *font = (Font *) params->data[0].of.i64;
+    if (font) assert(font->magic_number == 0xbeefbeefbeefbeef);
+
+    gfx_font_use(&renderer, font);
+
+    return NULL;
+}
+
 HEARTBREAK_DEF(get_text_width, (WASM_I32, WASM_I32), (WASM_F32)) {
     char* str_data = wasm_memory_data(wasm_memory) + params->data[0].of.i32;
     u32   str_size = params->data[1].of.i32;
@@ -422,8 +438,10 @@ HEARTBREAK_DEF(get_text_width, (WASM_I32, WASM_I32), (WASM_F32)) {
 
 HEARTBREAK_MODULE {
     HEARTBREAK_FUNC(set_clear_color)
-    HEARTBREAK_FUNC(clear)
     HEARTBREAK_FUNC(set_color)
+    HEARTBREAK_FUNC(set_line_width)
+
+    HEARTBREAK_FUNC(clear)
     HEARTBREAK_FUNC(rectangle)
     HEARTBREAK_FUNC(circle)
     HEARTBREAK_FUNC(ellipse)
@@ -444,6 +462,9 @@ HEARTBREAK_MODULE {
     HEARTBREAK_FUNC(image_param)
     HEARTBREAK_FUNC(image_property)
 
+    HEARTBREAK_FUNC(font_load)
+    HEARTBREAK_FUNC(font_destroy)
+    HEARTBREAK_FUNC(font_set)
     HEARTBREAK_FUNC(get_text_width)
 
     { NULL }
index 5465e62a3c80e69aff929d1237ce6f3de66aefa9..02a3ba115f506d0c4bbd454c1585a41be57c4d64 100644 (file)
@@ -113,6 +113,7 @@ HEARTBREAK_DEF(init, (), (WASM_I32)) {
     glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
     glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
     glfwMakeContextCurrent(glfw_window);
+
     glfwSetWindowSizeCallback(glfw_window, __glfw_window_size_callback);
     glfwSetKeyCallback(glfw_window, __glfw_key_callback);
     glfwSetMouseButtonCallback(glfw_window, __glfw_mouse_button_callback);
@@ -122,7 +123,6 @@ HEARTBREAK_DEF(init, (), (WASM_I32)) {
 
     glfwSwapInterval(1);
 
-    // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
     glEnable(GL_BLEND);
     glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
 
index 6b0020735e673360152784be1198ae0ca42c3f2e..b1c145b5b38e7c552751ff07cf506ef5ed1447a5 100644 (file)
@@ -60,3 +60,29 @@ void mat3_mul(mat3 *out, mat3 a, mat3 b) {
         }
     }
 }
+
+LogLevel log_level = INFO;
+char *log_level_strings[Log_Level_Count] = {
+" DEBUG ",
+" INFO  ",
+"WARNING",
+" ERROR ",
+};
+
+void log_level_set(LogLevel level) {
+    log_level = level;
+}
+
+void logprint(LogLevel level, char *format, ...) {
+    if (level < log_level) return;
+
+    va_list args;
+    va_start(args, format);
+
+    char buf[2048];
+    vsnprintf(buf, 2048, format, args);
+
+    va_end(args);
+
+    printf("[%s] %s\n", log_level_strings[level], buf);
+}
diff --git a/tests/minesweeper.onyx b/tests/minesweeper.onyx
new file mode 100644 (file)
index 0000000..14f2ba0
--- /dev/null
@@ -0,0 +1,225 @@
+#load "./../misc/onyx/qhb"
+
+use package core
+use package core.intrinsics.onyx
+
+Pos :: struct { x, y: i32; }
+
+Board :: struct {
+    Cell :: enum #flags {
+        Empty :: 0;
+        Covered :: 1;
+
+        Mine :: 2;
+        Flag :: 3;
+
+        Is_Number :: 0x10;
+        Number1 :: Is_Number | 1;
+        Number2 :: Is_Number | 2;
+        Number3 :: Is_Number | 3;
+        Number4 :: Is_Number | 4;
+        Number5 :: Is_Number | 5;
+        Number6 :: Is_Number | 6;
+        Number7 :: Is_Number | 7;
+        Number8 :: Is_Number | 8;
+    }
+
+    width, height : i32;
+    visible_cells : [] Cell;
+    true_cells    : [] Cell;
+}
+
+init_board :: (use this: ^Board, w, h: i32) {
+    width = w;
+    height = h;
+    memory.alloc_slice(^visible_cells, w * h);
+    memory.alloc_slice(^true_cells, w * h);
+}
+
+generate_board :: (use this: ^Board, mine_count: i32) {
+    // Assumes an initialized board
+
+    for ^cell: visible_cells do *cell = .Covered;
+    for ^cell: true_cells    do *cell = .Empty;
+
+    mine_positions := array.make(Pos);
+    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};
+                break;
+            }
+        }
+    }
+
+    for ^mine_pos: mine_positions {
+        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;
+            elseif cell&.Is_Number do cell += 1;
+
+            set_true_cell(this, neighbor.x, neighbor.y, cell);
+        }
+    }
+}
+
+neighbors :: (board: ^Board, x, y: i32, include_corners := true) -> Iterator(Pos) {
+    Context :: struct {
+        board: ^Board;
+        cx, cy: i32;
+        dx, dy: i32;
+
+        include_corners: bool;
+    }
+
+    context := new(Context);
+    *context = .{ board, x, y, -1, -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{
+                dx += 1;
+                if dx > 1 { dx = -1; dy += 1; }
+            }
+        }
+
+        while true {
+            if dy > 1 do break;
+            if dx == 0 && dy == 0 { step(); continue; }
+
+            rx, ry := cx + dx, cy + dy;
+            step();
+
+            if rx < 0 || rx >= board.width || ry < 0 || ry >= board.height do continue;
+            return .{rx, ry}, true;
+        }
+
+        return __zero_value(Pos), false;
+    }
+
+    return Iterator(Pos).{ context, next, cfree };
+}
+
+get_visible_cell :: (use this: ^Board, x, y: i32) => {
+    if x >= 0 && x < width && y >= 0 && y < height do return visible_cells[x + y * width];
+    return .Empty;
+}
+
+set_visible_cell :: (use this: ^Board, x, y: i32, cell: Board.Cell) => {
+    if x >= 0 && x < width && y >= 0 && y < height do visible_cells[x + y * width] = cell;
+}
+
+get_true_cell :: (use this: ^Board, x, y: i32) => {
+    if x >= 0 && x < width && y >= 0 && y < height do return true_cells[x + y * width];
+    return .Empty;
+}
+
+set_true_cell :: (use this: ^Board, x, y: i32, cell: Board.Cell) => {
+    if x >= 0 && x < width && y >= 0 && y < height do true_cells[x + y * width] = cell;
+}
+
+reveal_cell :: (use this: ^Board, x, y: i32) => {
+    set_visible_cell(this, x, y, get_true_cell(this, x, y));
+}
+
+flood_reveal :: (use this: ^Board, x, y: i32) => {
+    positions := array.make(Pos);
+    positions << .{x, y};
+
+    while positions.count != 0 {
+        p := positions[0];
+        array.delete(^positions, 0);
+
+        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) {
+            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 {
+                positions << n;
+            }
+        }
+    }
+}
+
+cell_size :: 32.0f
+border    :: 2.0f
+
+board: Board;
+
+load :: () {
+    random.set_seed(clock.time());
+    init_board(^board, 20, 20);
+    generate_board(^board, 5);
+}
+
+update :: (dt: f32) {
+    if hb.input.keyIsDown(.Escape) {
+        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);
+        }
+    }
+    last_mouse_down = mouse_down;
+}
+
+draw :: () {
+    size :: cell_size
+
+    hb.graphics.setClearColor(0.05, 0.05, 0.05, 1);
+    hb.graphics.clear();
+
+    for y: board.height {
+        for x: board.width {
+            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);
+            }
+
+            if cell == .Covered {
+                hb.graphics.setColor(0.6, 0.6, 0.6);
+                hb.graphics.rectangle(.Fill, ~~x * size, ~~y * size, size - border, 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.setColor(1, 0, 0);
+                hb.graphics.circle(.Fill, ~~x * size + 0.5 * size, ~~y * size + 0.5 * size, (size - 2 * border) / 2);
+            }
+
+            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);
+
+                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);
+            }
+        }
+    }
+
+    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 {
+        hb.graphics.setColor(1, 1, 1, 0.3);
+        hb.graphics.rectangle(.Fill, ~~mx, ~~my, size - border, size - border);
+    }
+}
index 4c8020b9bd65fa9223b6dd5e94787e09e9feb59e..164c70bae81de33d264f8d6ab06381eaebf196c8 100644 (file)
@@ -26,14 +26,19 @@ load :: () {
 
     printf("Width is: {}\n", hb.graphics.getTextWidth("Hello, World!"));
 
+    better_font := hb.graphics.newFont("/usr/share/fonts/TTF/InputSerif-Bold.ttf", 32);
+    assert(better_font != 0, "Failed to load font!");
+    hb.graphics.setFont(better_font);
+    // hb.graphics.setFont();
+
     // hb.window.setDimensions(1200, 900);
 }
 
-keydown :: (key, mods: i32) {
+keydown :: (key: hb.input.KeyConstant, mods: i32) {
     printf("Key down! {} {}\n", key, mods);
 }
 
-keyup :: (key: i32) {
+keyup :: (key: hb.input.KeyConstant) {
     printf("Key up! {}\n", key);
 }
 
@@ -109,8 +114,10 @@ draw :: () {
     hb.graphics.arc(.Line, 300, 300, 100, math.PI / 4, math.PI * 7 / 4);
 
     hb.graphics.setColor(0, 1, 1);
+    hb.graphics.rectangle(.Fill, 400, 90 - 32, 200 + math.sin(t) * 100, 10);
     hb.graphics.print("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+`~[{}]\|<,>./?'\";:", 400, 100, 200 + math.sin(t) * 100);
 
+    hb.graphics.setLineWidth(3);
     hb.graphics.setColor(1, 1, 1);
-    hb.graphics.circle(.Line, 200, 500, 100, 100);
+    hb.graphics.circle(.Line, 200, 500, 50, 100);
 }