From 20ca6cd8919ecfd6a616f06428709180fe109709 Mon Sep 17 00:00:00 2001 From: Simen Kirkvik Date: Sun, 8 Mar 2026 22:56:13 +0100 Subject: [PATCH] Add tile snapping --- src/root.zig | 168 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 155 insertions(+), 13 deletions(-) diff --git a/src/root.zig b/src/root.zig index 716cb1f..014f01d 100644 --- a/src/root.zig +++ b/src/root.zig @@ -70,6 +70,11 @@ const V2 = struct { } }; +const Rect = struct { + pos: V2, + size: V2, +}; + const BBox = struct { min: V2, max: V2, @@ -103,7 +108,42 @@ const Board = struct { pos: V2, width: u32, height: u32, + tile_size: V2, tiles: [7][7]BoardTile, + + fn getTilePos(self: Board, i: usize, j: usize) V2 { + const tile_pos = V2{ + .x = @as(f32, @floatFromInt(j)) * self.tile_size.x, + .y = @as(f32, @floatFromInt(i)) * self.tile_size.y, + }; + const result = tile_pos.add(self.pos); + + return result; + } +}; + +const BoardIndex = struct { + i: usize, + j: usize, + + fn init(i: usize, j: usize) BoardIndex { + return .{ + .i = i, + .j = j, + }; + } + + fn max() BoardIndex { + return .{ + .i = std.math.maxInt(usize), + .j = std.math.maxInt(usize), + }; + } +}; + +const BrickTile = struct { + id: u32, + board_index: ?BoardIndex, }; const Brick = struct { @@ -111,6 +151,7 @@ const Brick = struct { pos: V2, color: u32, rotating_time: f64, + board_indices: [4][4]?BoardIndex, tiles: [4][4]u32, fn transpose(self: Brick) [4][4]u32 { @@ -203,6 +244,43 @@ fn tilesIntersects(r1: V2, r2: V2) bool { return result; } +fn rectsIntersects(r1: V2, r1_size: V2, r2: V2, r2_size: V2) bool { + var result = false; + + const r2a: V2 = V2.init(r2.x, r2.y); + const r2b: V2 = V2.init(r2.x, r2.y + r2_size.y); + const r2c: V2 = V2.init(r2.x + r2_size.x, r2.y); + const r2d: V2 = V2.init(r2.x + r2_size.x, r2.y + r2_size.y); + + const a = pointInsideRect(r2a, r1, r1_size); + const b = pointInsideRect(r2b, r1, r1_size); + const c = pointInsideRect(r2c, r1, r1_size); + const d = pointInsideRect(r2d, r1, r1_size); + + result = a or b or c or d; + + return result; +} + +fn brickHasValidPos(brick: *const Brick) bool { + var valid_pos = true; + + for (brick.board_indices, 0..) |row, i| { + for (row, 0..) |opt, j| { + const tile = brick.tiles[i][j]; + if (tile > 0) { + if (opt != null) { + valid_pos = valid_pos and true; + } else { + valid_pos = false; + } + } + } + } + + return valid_pos; +} + fn findBrickBoundingBox(brick: *const Brick) BBox { var result = BBox{ .min = V2.max, .max = V2.zero, .center = V2.zero }; @@ -231,6 +309,24 @@ fn findBrickBoundingBox(brick: *const Brick) BBox { return result; } +fn findBrickMinBoardIdx(brick: *const Brick) BoardIndex { + var result = BoardIndex.max(); + + for (brick.board_indices, 0..) |row, i| { + for (row, 0..) |opt, j| { + const tile = brick.tiles[i][j]; + if (tile > 0) { + if (opt) |index| { + result.i = if (index.i < result.i) index.i else result.i; + result.j = if (index.j < result.j) index.j else result.j; + } + } + } + } + + return result; +} + fn drawRect(buffer: platform.OffscreenBuffer, pos: V2, size: V2, color: u32, line_width: i32) void { var pixels: []u32 = @ptrCast(@alignCast(buffer.data)); @@ -332,11 +428,19 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu state.grabbed_brick_index = null; + const indices = [4][4]?BoardIndex{ + .{ null, null, null, null }, + .{ null, null, null, null }, + .{ null, null, null, null }, + .{ null, null, null, null }, + }; + state.bricks[0] = Brick{ .rotating = false, .rotating_time = 0, .pos = V2{ .x = 0, .y = 50 }, .color = 0xFF586F52, + .board_indices = indices, .tiles = [4][4]u32{ .{ 1, 1, 1, 1 }, .{ 1, 0, 0, 0 }, @@ -350,6 +454,7 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu .rotating_time = 0, .pos = V2{ .x = 256, .y = 50 }, .color = 0xFF8299B2, + .board_indices = indices, .tiles = [4][4]u32{ .{ 2, 2, 2, 0 }, .{ 2, 2, 0, 0 }, @@ -363,6 +468,7 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu .rotating_time = 0, .pos = V2{ .x = 384, .y = 50 }, .color = 0xFF602925, + .board_indices = indices, .tiles = [4][4]u32{ .{ 3, 3, 3, 0 }, .{ 0, 0, 3, 3 }, @@ -376,6 +482,7 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu .rotating_time = 0, .pos = V2{ .x = 512, .y = 50 }, .color = 0xFFDA983C, + .board_indices = indices, .tiles = [4][4]u32{ .{ 4, 0, 0, 0 }, .{ 4, 4, 4, 0 }, @@ -389,6 +496,7 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu .rotating_time = 0, .pos = V2{ .x = 384, .y = 150 }, .color = 0xFF3A5E7A, + .board_indices = indices, .tiles = [4][4]u32{ .{ 5, 5, 5, 5 }, .{ 0, 5, 0, 0 }, @@ -402,6 +510,7 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu .rotating_time = 0, .pos = V2{ .x = 160, .y = 250 }, .color = 0xFFD69EB0, + .board_indices = indices, .tiles = [4][4]u32{ .{ 6, 6, 6, 0 }, .{ 6, 0, 0, 0 }, @@ -415,6 +524,7 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu .rotating_time = 0, .pos = V2{ .x = 192, .y = 350 }, .color = 0xFF9AAB89, + .board_indices = indices, .tiles = [4][4]u32{ .{ 7, 7, 7, 0 }, .{ 7, 7, 7, 0 }, @@ -428,6 +538,7 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu .rotating_time = 0, .pos = V2{ .x = 224, .y = 450 }, .color = 0xFFBA4816, + .board_indices = indices, .tiles = [4][4]u32{ .{ 8, 8, 8, 0 }, .{ 8, 0, 8, 0 }, @@ -462,6 +573,7 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu .pos = mid, .width = 7, .height = 7, + .tile_size = V2.init(32, 32), .tiles = tiles, }; @@ -490,7 +602,7 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu for (&state.bricks, 0..) |*brick, brick_index| { if (brick.rotating) { brick.rotating_time += input.delta_time; - if (brick.rotating_time > 0.25) { + if (brick.rotating_time > 0.05) { brick.rotating = false; brick.rotating_time = 0.0; } @@ -500,6 +612,10 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu for (row, 0..) |tile, j| { if (tile > 0) { const pos = getTilePos(i, j).add(brick.pos); + const tile_mid = pos.add(tile_size.mul(0.5)); + + const tile_board_index = &brick.board_indices[i][j]; + tile_board_index.* = null; if (input.mouse_left_down) { if (pointInsideRect(mouse_world_pos, pos, tile_size)) { @@ -511,10 +627,16 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu for (&state.board.tiles, 0..) |*board_row, k| { for (board_row, 0..) |*board_tile, l| { - const board_tile_pos = getTilePos(k, l).add(state.board.pos); + const board_tile_pos = state.board.getTilePos(k, l); + + const in_tile = pointInsideRect(tile_mid, board_tile_pos, state.board.tile_size); + if (in_tile and !board_tile.blocked and !board_tile.taken) { + tile_board_index.* = BoardIndex.init(k, l); + // board_tile.taken = true; + } if (!board_tile.hovering) { - board_tile.hovering = tilesIntersects(board_tile_pos, pos); + board_tile.hovering = in_tile; } } } @@ -547,6 +669,19 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu brick.pos = mouse_world_pos.add(center); } } + + if (!input.mouse_left_down) { + const valid_pos = brickHasValidPos(brick); + + if (valid_pos) { + // TODO: Snap to board + const min = findBrickMinBoardIdx(brick); + const board_tile_pos = state.board.getTilePos(min.i, min.j); + const bbox = findBrickBoundingBox(brick); + const diff = brick.pos.sub(bbox.min); + brick.pos = board_tile_pos.add(diff); + } + } } if (input.mouse_left_down and state.grabbed_brick_index == null) { @@ -559,6 +694,7 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu // Render fillRect(buffer, 0, 0, buffer.width, buffer.height, 0xFFFFFFFF); + // Draw board { const board = state.board; // const board_size = V2.init_int( @@ -567,17 +703,14 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu // ); // const half = board_size.mul(0.5); - const board_camera_pos = board.pos.add(state.camera.pos); - for (board.tiles, 0..) |row, i| { for (row, 0..) |tile, j| { - const tile_pos = V2{ - .x = @floatFromInt(j * TILE_SIZE), - .y = @floatFromInt(i * TILE_SIZE), - }; - const camera_pos = board_camera_pos.add(tile_pos); + const tile_pos = board.getTilePos(i, j); + const camera_pos = tile_pos.add(state.camera.pos); const pos_x: i32 = @intFromFloat(camera_pos.x); const pos_y: i32 = @intFromFloat(camera_pos.y); + const width: i32 = @intFromFloat(board.tile_size.x); + const height: i32 = @intFromFloat(board.tile_size.y); var color: u32 = 0xFF000000; if (tile.blocked) { @@ -586,8 +719,8 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu color = 0xFF00FF00; } - fillSquare(buffer, pos_x, pos_y, TILE_SIZE, color); - drawRect(buffer, camera_pos, tile_size, 0xFFFFFFFF, 2); + fillRect(buffer, pos_x, pos_y, width, height, color); + drawRect(buffer, camera_pos, board.tile_size, 0xFFFFFFFF, 2); } } } @@ -600,12 +733,21 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu if (tile > 0) { const pos = getTilePos(i, j).add(brick.pos); const camera_pos = pos.add(state.camera.pos); + var color: u32 = if (brick.rotating) 0xFF007800 else brick.color; + + if (comptime builtin.mode == std.builtin.OptimizeMode.Debug) { + const board_index = brick.board_indices[i][j]; + if (board_index != null) { + color = 0xFFFF0000; + } + } + fillSquare( buffer, @intFromFloat(camera_pos.x), @intFromFloat(camera_pos.y), 30, - if (brick.rotating) 0xFF00FF00 else brick.color, + color, ); } }