refactored snake example
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Fri, 26 Nov 2021 04:08:48 +0000 (22:08 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Fri, 26 Nov 2021 04:08:48 +0000 (22:08 -0600)
misc/onyx/heartbreak.onyx
misc/onyx/heartbreak_graphics.onyx
misc/onyx/heartbreak_utils.onyx [new file with mode: 0644]
src/heartbreak_graphics.c
tests/snake.onyx

index d512831a40acbc1b1e5acbd6f6111bd5878d0a71..ad2cfb2db5e16de8fd571b422b96ac878c77aafb 100644 (file)
@@ -6,6 +6,7 @@ package heartbreak
 #load "./heartbreak_thread_runtime"
 #load "./heartbreak_timer"
 #load "./heartbreak_window"
+#load "./heartbreak_utils"
 
 use package core {println}
 
index 77cac2addfa5bb2f26a6ccc49a0e898ab53d2c51..f652e61acf4ec0a88435840bcebebda76115bbf0 100644 (file)
@@ -4,6 +4,7 @@ package heartbreak.graphics
 
 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" ---
+getColor      :: (r, g, b, a: ^f32) -> void         #foreign "heartbreak" "graphics_get_color" ---
 setLineWidth  :: (width: i32) -> void #foreign "heartbreak" "graphics_set_line_width" ---
 
 //
diff --git a/misc/onyx/heartbreak_utils.onyx b/misc/onyx/heartbreak_utils.onyx
new file mode 100644 (file)
index 0000000..5b2c2dd
--- /dev/null
@@ -0,0 +1,61 @@
+package heartbreak.utils
+
+use package core
+#local hb :: package heartbreak
+
+load_assets :: (assets: any) {
+    ptr_asset_info := cast(^type_info.Type_Info_Pointer) type_info.get_type_info(assets.type);
+    assert(ptr_asset_info.kind == .Pointer, "Loading assets only works for pointers to structures.");
+    asset_info := cast(^type_info.Type_Info_Struct) type_info.get_type_info(ptr_asset_info.to);
+    assert(asset_info.kind == .Struct, "Loading assets only works for pointers to structures.");
+
+    set_member :: macro (a, T, m, v) => {
+        *cast(^T) ((cast(^u8) *cast(^rawptr) a.data) + m.offset) = v;
+    }
+
+    for ^asset: asset_info.members {
+        switch asset.type {
+            case hb.graphics.Image {
+                path := *(cast(^str) array.first(asset.tags, (t) => t.type == str).data);
+                img := hb.graphics.newImage(path);
+
+                if img == 0 {
+                    println("Failed to load image");
+                    continue;
+                }
+
+                @TODO // Add settings like scaling, filters, etc.
+                filter_ptr := array.first(asset.tags, (t) => t.type == hb.graphics.ImageFilter);
+                if filter_ptr != null {
+                    filter := *cast(^hb.graphics.ImageFilter) filter_ptr.data;
+                    hb.graphics.setImageFilters(img, filter, filter);
+                }
+
+                wrapping_ptr := array.first(asset.tags, (t) => t.type == hb.graphics.ImageWrapping);
+                if wrapping_ptr != null {
+                    wrapping := *cast(^hb.graphics.ImageWrapping) wrapping_ptr.data;
+                    hb.graphics.setImageWrapping(img, wrapping, wrapping);
+                }
+
+                set_member(assets, hb.graphics.Image, asset, img);
+            }
+
+            case hb.graphics.Font {
+                path := *(cast(^str) array.first(asset.tags, (t) => t.type == str).data);
+                size := *(cast(^i32) array.first(asset.tags, (t) => t.type == i32).data);
+                font := hb.graphics.newFont(path, ~~size);
+
+                if font == 0 {
+                    println("Failed to load font");
+                    continue;
+                }
+
+                set_member(assets, hb.graphics.Font, asset, font);
+            }
+
+            case #default {
+                printf("Cannot load something of type '{}'.\n", asset.type);
+            }
+        }
+    }
+}
index 101dd91d7c7de2949af5f0ea599890a2aee38670..a131a6fa2aa559bcdd844f892e3da057cbc9f3f4 100644 (file)
@@ -31,6 +31,16 @@ HEARTBREAK_DEF(set_color, (WASM_F32,WASM_F32,WASM_F32,WASM_F32), ()) {
     return NULL;
 }
 
+HEARTBREAK_DEF(get_color, (WASM_I32,WASM_I32,WASM_I32,WASM_I32), ()) {
+    char* mem = wasm_memory_data(wasm_memory);
+
+    *(f32 *) (mem + params->data[0].of.i32) = renderer.current_color.r;
+    *(f32 *) (mem + params->data[1].of.i32) = renderer.current_color.g;
+    *(f32 *) (mem + params->data[2].of.i32) = renderer.current_color.b;
+    *(f32 *) (mem + params->data[3].of.i32) = renderer.current_color.a;
+    return NULL;
+}
+
 HEARTBREAK_DEF(set_line_width, (WASM_I32), ()) {
     glLineWidth(params->data[0].of.i32);
     return NULL;
@@ -480,6 +490,7 @@ HEARTBREAK_DEF(get_text_height, (WASM_I32, WASM_I32, WASM_F32), (WASM_F32)) {
 HEARTBREAK_MODULE {
     HEARTBREAK_FUNC(set_clear_color)
     HEARTBREAK_FUNC(set_color)
+    HEARTBREAK_FUNC(get_color)
     HEARTBREAK_FUNC(set_line_width)
 
     HEARTBREAK_FUNC(clear)
index 1ca8c7bed61502e1d9a22395420a52b030b4fba4..7bea9a6e9135bb4fb6ad44dfe85e12d83a5533f4 100644 (file)
@@ -2,72 +2,6 @@
 
 use package core
 
-assets : struct {
-    ["./tests/player.png"]
-    [hb.graphics.ImageFilter.Nearest]
-    player: hb.graphics.Image;
-
-    ["./assets/fonts/mononoki-Regular Nerd Font Complete Mono.ttf", 72]
-    title_font: hb.graphics.Font;
-}
-
-load_assets :: (assets: any) {
-    ptr_asset_info := cast(^type_info.Type_Info_Pointer) type_info.get_type_info(assets.type);
-    assert(ptr_asset_info.kind == .Pointer, "Loading assets only works for pointers to strucutres.");
-    asset_info := cast(^type_info.Type_Info_Struct) type_info.get_type_info(ptr_asset_info.to);
-    assert(asset_info.kind == .Struct, "Loading assets only works for pointers to strucutres.");
-
-    set_member :: macro (a, T, m, v) => {
-        *cast(^T) ((cast(^u8) *cast(^rawptr) a.data) + m.offset) = v;
-    }
-
-    for ^asset: asset_info.members {
-        switch asset.type {
-            case hb.graphics.Image {
-                path := *(cast(^str) array.first(asset.tags, (t) => t.type == str).data);
-                img := hb.graphics.newImage(path);
-
-                if img == 0 {
-                    println("Failed to load image");
-                    continue;
-                }
-
-                @TODO // Add settings like scaling, filters, etc.
-                filter_ptr := array.first(asset.tags, (t) => t.type == hb.graphics.ImageFilter);
-                if filter_ptr != null {
-                    filter := *cast(^hb.graphics.ImageFilter) filter_ptr.data;
-                    hb.graphics.setImageFilters(img, filter, filter);
-                }
-
-                wrapping_ptr := array.first(asset.tags, (t) => t.type == hb.graphics.ImageWrapping);
-                if wrapping_ptr != null {
-                    wrapping := *cast(^hb.graphics.ImageWrapping) wrapping_ptr.data;
-                    hb.graphics.setImageWrapping(img, wrapping, wrapping);
-                }
-
-                set_member(assets, hb.graphics.Image, asset, img);
-            }
-
-            case hb.graphics.Font {
-                path := *(cast(^str) array.first(asset.tags, (t) => t.type == str).data);
-                size := *(cast(^i32) array.first(asset.tags, (t) => t.type == i32).data);
-                font := hb.graphics.newFont(path, ~~size);
-
-                if font == 0 {
-                    println("Failed to load font");
-                    continue;
-                }
-
-                set_member(assets, hb.graphics.Font, asset, font);
-            }
-
-            case #default {
-                printf("Cannot load something of type '{}'.\n", asset.type);
-            }
-        }
-    }
-}
-
 Vec2 :: struct (T: type_expr) {
     x, y: T;
 
@@ -145,13 +79,43 @@ state := GameState.MainMenu;
 the_snake: Snake;
 the_food: Vec2i;
 
+assets : struct {
+    ["./tests/player.png", hb.graphics.ImageFilter.Nearest]
+    player: hb.graphics.Image;
+
+    ["./assets/fonts/mononoki-Regular Nerd Font Complete Mono.ttf", 72]
+    title_font: hb.graphics.Font;
+}
+
+Menu :: struct {
+    options: type_expr;
+    selected_option := 0;
+}
+
+Main_Menu_Options :: struct {
+    ["Play game"]
+    option_1 := () {
+        active_menu = .{void};
+        state = .Playing;
+    };
+
+    ["Quit"]
+    option_2 := () {
+        hb.window.setShouldClose(true);
+    };
+}
+
+active_menu: Menu;
+
 load :: () {
     hb.window.setTitle("Snake 🐍");
 
-    load_assets(^assets);
+    hb.utils.load_assets(^assets);
 
     the_snake = snake_make(.{ 0, 0 });
     the_food = .{ 10, 10 };
+
+    active_menu = .{ Main_Menu_Options };
 }
 
 cell_contains_snake_body :: (p: Vec2i) => {
@@ -166,14 +130,36 @@ update :: (dt: f32) {
         hb.window.setShouldClose(true);
     }
 
-    if state == .MainMenu {
+    if state != .Playing {
+        if active_menu.options == void do return;
+
+        #persist last_up_down := false;
+        #persist last_down_down := false;
+        up_down := hb.input.keyIsDown(.Up);
+        down_down := hb.input.keyIsDown(.Down);
+        defer {
+            last_up_down = up_down;
+            last_down_down = down_down;
+        }
+
+        if !up_down && last_up_down { active_menu.selected_option -= 1; }
+        if !down_down && last_down_down { active_menu.selected_option += 1; }
+
+        option_count := (cast(^type_info.Type_Info_Struct) type_info.get_type_info(active_menu.options)).members.count;
+
+        if active_menu.selected_option < 0 do active_menu.selected_option = 0;
+        if active_menu.selected_option >= option_count do active_menu.selected_option = option_count - 1;
+
         if hb.input.keyIsDown(.Space) {
-            state = .Playing;
-            return;
+            option := (cast(^type_info.Type_Info_Struct) type_info.get_type_info(active_menu.options)).members[active_menu.selected_option];
+            if option.type == #type () -> void {
+                func := *(cast(^() -> void) option.default);
+                func();
+            }
         }
-    }
 
-    if state != .Playing do return;
+        return;
+    }
 
     #persist last_left_down := false;
     #persist last_right_down := false;
@@ -211,26 +197,24 @@ update :: (dt: f32) {
     }
 }
 
+#local centered_text :: macro (text: str, y: f32) {
+    width := hb.graphics.getTextWidth(text);
+    hb.graphics.print(text, (~~ww - width) / 2, y);
+}
+
 draw :: () {
     hb.graphics.setClearColor(0.9, 0.9, 0.9);
     hb.graphics.clear();
 
     ww, wh := hb.window.getDimensions();
 
-    centered_text :: macro (text: str, y: f32) {
-        width := hb.graphics.getTextWidth(text);
-        hb.graphics.print(text, (~~ww - width) / 2, y);
-    }
-
     switch state {
         case .MainMenu {
             hb.graphics.setFont(assets.title_font);
             hb.graphics.setColor(0.2, 0.2, 0.2);
             centered_text("Snake!", 100);
 
-            hb.graphics.setFont();
-            centered_text("Option 1", 200);
-            centered_text("Option 2", 240);
+            draw_menu(200);
         }
 
         case .Playing {
@@ -255,10 +239,9 @@ draw :: () {
             hb.graphics.setColor(0, 0, 0, 0.4);
             hb.graphics.rectangle(.Fill, 0, 0, ~~ww, ~~wh);
 
-            message := "You Lost!";
-            message_width := hb.graphics.getTextWidth(message);
+            hb.graphics.setFont(assets.title_font);
             hb.graphics.setColor(1, 1, 1);
-            hb.graphics.print(message, (~~ww - message_width) / 2, 100);
+            centered_text("You Lost!", 100);
         }
     }
 }
@@ -273,4 +256,28 @@ draw_game :: () {
     hb.graphics.setLineWidth(2);
     hb.graphics.setColor(0.2, 0.25, 0.03);
     hb.graphics.circle(.Line, cell_size * ~~the_food.x + cell_size / 2, cell_size * ~~the_food.y + cell_size / 2, cell_size / 2);
-}
\ No newline at end of file
+}
+
+draw_menu :: (y: f32) {
+    ww, wh := hb.window.getDimensions();
+    hb.graphics.setFont();
+
+    menu_info := cast(^type_info.Type_Info_Struct) type_info.get_type_info(active_menu.options);
+    if menu_info.kind != .Struct do return;
+
+    for it: iter.enumerate(menu_info.members) {
+        text := *(cast(^str) array.first(it.value.tags, (t) => t.type == str).data);
+        height := hb.graphics.getTextHeight(text);
+
+        if it.index == active_menu.selected_option {
+            r, g, b, a: f32;
+            hb.graphics.getColor(^r, ^g, ^b, ^a);
+            hb.graphics.setColor(0.2, 0.2, 0.2, 0.2);
+            hb.graphics.rectangle(.Fill, 0, y - height - 8, ~~ww, height + 16);
+            hb.graphics.setColor(r, g, b, a);
+        }
+
+        centered_text(text, ~~y);
+        y += 40;
+    }
+}