const std = @import("std"); const rl = @cImport(@cInclude("raylib.h")); const context = @import("context.zig"); const Animation = @import("Animation.zig"); const camera_move_speed: f32 = 10.0; const grid_size: usize = 45; const grid_central_row = grid_size / 2 + 1; pub const MyRect = struct { rect: rl.Rectangle, color: rl.Color, }; const dude_radius: f32 = 50.0; var dude_rotation: f32 = 45.0; const hex_radius: f32 = 100.0; const hex_rotation = 30.0; pub const HexCoord = struct { q: i32, r: i32, pub inline fn fracS(q: f32, r: f32) f32 { return -q - r; } pub inline fn index(self: HexCoord) usize { return self.r / grid_size + self.q % grid_size; } pub inline fn toWorld(self: HexCoord) rl.Vector2 { return qrToWorld(self.q, self.r); } pub inline fn qrToWorld(q: i32, r: i32) rl.Vector2 { return .{ .x = hex_radius * (@sqrt(3.0) * @as(f32, @floatFromInt(q)) + @sqrt(3.0) / 2.0 * @as(f32, @floatFromInt(r))), .y = hex_radius * (3.0 / 2.0 * @as(f32, @floatFromInt(r))), }; } pub inline fn worldToQr(point: rl.Vector2) HexCoord { const q = (@sqrt(3.0) / 3.0 * point.x - 1.0 / 3.0 * point.y) / hex_radius; const r = (2.0 / 3.0 * point.y) / hex_radius; return axialRound(.{ .x = q, .y = r }); } pub inline fn axialRound(frac: rl.Vector2) HexCoord { return cubeRound(rl.Vector3{ .x = frac.x, .y = frac.y, .z = fracS(frac.x, frac.y) }); } pub inline fn fakeDistance(from: HexCoord, to: HexCoord) u32 { return @abs(from.q - to.q) + @abs(from.r - to.r); } pub inline fn cubeRound(frac: rl.Vector3) HexCoord { var q = @round(frac.x); var r = @round(frac.y); var s = @round(frac.z); const q_diff = @abs(q - frac.x); const r_diff = @abs(r - frac.y); const s_diff = @abs(s - frac.z); if (q_diff > r_diff and q_diff > s_diff) { q = -r - s; } else if (r_diff > s_diff) { r = -q - s; } else { s = -q - r; } return .{ .q = @as(i32, @intFromFloat(q)), .r = @as(i32, @intFromFloat(r)), }; } }; pub const Hex = struct { // todo perhaps worth having a sentiental value somewhere to state if a hex is alive or perhaps make the list optinals? color: rl.Color, }; pub fn setup() !void { const target = HexCoord.qrToWorld(grid_central_row, grid_central_row); context.camera = rl.Camera2D{ .target = target, .offset = rl.Vector2{ .x = 0, .y = 0 }, .rotation = 0, .zoom = 1 }; context.hex_grid = try context.Grid.init(context.gpa, grid_size); context.main_dude = .{ .world_coords = .{ .x = 0.0, .y = 0.0 }, .hex_coords = .{ .q = (grid_size / 2) + 1, .r = (grid_size / 2) + 1 }, .target_coords = .{ .q = (grid_size / 2) + 1, .r = (grid_size / 2) + 1 }, .animation = Animation.init(.{ .x = 0.0, .y = 0.0 }, .{ .x = 0.0, .y = 0.0 }, 1.0), }; context.main_dude.animation.active = false; context.main_dude.world_coords = HexCoord.qrToWorld(@intCast(context.main_dude.hex_coords.q), @intCast(context.main_dude.hex_coords.r)); // TODO think what it means to populate a hex grid for (0..grid_size) |_| { for (0..grid_size) |_| { try context.hex_grid.initPush(Hex{ // TODO real colors .color = rl.DARKGREEN, }); } } } pub fn update() !void { const mouse_pos = rl.GetMousePosition(); const mouse_world_pos = rl.GetScreenToWorld2D(mouse_pos, context.camera); var main_dude = &context.main_dude; context.hovered_coords = HexCoord.worldToQr(mouse_world_pos); const zoom_scale = context.camera.zoom; if (rl.IsKeyDown(rl.KEY_D)) { context.camera.target.x += camera_move_speed / zoom_scale; } if (rl.IsKeyDown(rl.KEY_A)) { context.camera.target.x -= camera_move_speed / zoom_scale; } if (rl.IsKeyDown(rl.KEY_W)) { context.camera.target.y -= camera_move_speed / zoom_scale; } if (rl.IsKeyDown(rl.KEY_S)) { context.camera.target.y += camera_move_speed / zoom_scale; } // TODO FIXME laptop dev doesnt allow middle mouse and mouse move to happen // at same time and im not yak shavin this if (rl.IsMouseButtonDown(rl.MOUSE_BUTTON_RIGHT) or rl.IsMouseButtonDown(rl.MOUSE_BUTTON_MIDDLE)) { const delta = rl.GetMouseDelta(); var scale = @log2(@abs(delta.x) + @abs(delta.y)); if (delta.x == 0.0 and delta.y == 0.0) { scale = 1.0; } context.camera.target.x -= delta.x / zoom_scale; context.camera.target.y -= delta.y / zoom_scale; } if (main_dude.animation.active) { main_dude.world_coords = main_dude.animation.next(); main_dude.hex_coords = HexCoord.worldToQr(main_dude.world_coords); } if (rl.IsMouseButtonDown(rl.MOUSE_BUTTON_LEFT)) { if (!(main_dude.animation.world_to.x == context.hovered_coords.toWorld().x and main_dude.animation.world_to.y == context.hovered_coords.toWorld().y)) { main_dude.animation = Animation.init(main_dude.world_coords, context.hovered_coords.toWorld(), 1); std.debug.print("{}\n", .{main_dude.animation}); main_dude.target_coords = context.hovered_coords; } } const wm = rl.GetMouseWheelMove(); if (wm != 0.0) { context.camera.zoom += wm * 0.05; context.camera.target = mouse_world_pos; context.camera.offset = mouse_pos; if (context.camera.zoom > 3.0) context.camera.zoom = 3.0; if (context.camera.zoom < 0.1) context.camera.zoom = 0.1; } } pub fn draw() !void { rl.BeginDrawing(); rl.ClearBackground(rl.SKYBLUE); rl.BeginMode2D(context.camera); const slice = context.hex_grid.buffer.slice(); const colors = slice.items(.color); for (0..grid_size) |r| { var start: usize = 0; var end: usize = 0; if (r < grid_central_row) { start = grid_central_row - r; end = grid_size; } else { start = 0; end = grid_size - (r - grid_central_row); } for (start..end) |q| { const idx = r * grid_size + q; const center = HexCoord.qrToWorld(@intCast(q), @intCast(r)); rl.DrawPoly(center, 6, hex_radius, hex_rotation, colors[idx]); rl.DrawPolyLines(center, 6, hex_radius, hex_rotation, rl.BLACK); } } const center = context.hovered_coords.toWorld(); rl.DrawPolyLines(center, 6, hex_radius, hex_rotation, rl.WHITE); const dude_center = context.main_dude.world_coords; rl.DrawPoly(dude_center, 4, dude_radius, dude_rotation, rl.RED); rl.EndMode2D(); rl.EndDrawing(); }