//! By convention, root.zig is the root source file when making a library. const std = @import("std"); const builtin = @import("builtin"); pub const platform = @import("platform.zig"); const TILE_SIZE = 64; const V2 = struct { x: f32, y: f32, const max = V2{ .x = std.math.floatMax(f32), .y = std.math.floatMax(f32) }; const zero = V2{ .x = 0.0, .y = 0.0 }; fn init(x: f32, y: f32) V2 { return .{ .x = x, .y = y, }; } fn initI32(x: i32, y: i32) V2 { return .{ .x = @floatFromInt(x), .y = @floatFromInt(y), }; } fn initUSize(x: usize, y: usize) V2 { return .{ .x = @floatFromInt(x), .y = @floatFromInt(y), }; } fn add(self: V2, other: V2) V2 { return .{ .x = self.x + other.x, .y = self.y + other.y, }; } fn sub(self: V2, other: V2) V2 { return .{ .x = self.x - other.x, .y = self.y - other.y, }; } fn mul(self: V2, s: f32) V2 { return .{ .x = self.x * s, .y = self.y * s, }; } fn inner(self: V2, other: V2) f32 { const result = self.x * other.x + self.y * other.y; return result; } fn hadamard(self: V2, other: V2) V2 { var result = V2.zero; result.x = self.x * other.x; result.y = self.y * other.y; return result; } }; const Rect = struct { pos: V2, size: V2, }; const BBox = struct { min: V2, max: V2, center: V2, }; const Camera = struct { pos: V2, offset: V2, }; const Image = struct { width: i32, height: i32, stride: i32, components: i32, data: []u8, }; const BoardTile = struct { blocked: bool, taken: bool, hovering: bool, label: []const u8, fn init(label: []const u8, blocked: bool) BoardTile { const result: BoardTile = .{ .blocked = blocked, .taken = false, .hovering = false, .label = label, }; return result; } }; 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 { rotating: bool, pos: V2, color: u32, rotating_time: f64, board_indices: [4][4]?BoardIndex, tiles: [4][4]u32, fn transpose(self: Brick) [4][4]u32 { var result = [4][4]u32{ .{ 0, 0, 0, 0 }, .{ 0, 0, 0, 0 }, .{ 0, 0, 0, 0 }, .{ 0, 0, 0, 0 }, }; for (0..self.tiles.len) |i| { const row = self.tiles[i]; for (0..row.len) |j| { result[i][j] = self.tiles[j][i]; } } return result; } fn rotate(self: Brick) [4][4]u32 { var result = [4][4]u32{ .{ 0, 0, 0, 0 }, .{ 0, 0, 0, 0 }, .{ 0, 0, 0, 0 }, .{ 0, 0, 0, 0 }, }; for (0..self.tiles.len) |i| { const row = self.tiles[i]; for (0..row.len) |j| { result[j][row.len - i - 1] = self.tiles[i][j]; } } return result; } }; const State = struct { frame_count: i32, mouse_pos: V2, camera: Camera, font_image: Image, draw_bounding_rects: bool, grabbed_brick_index: ?usize, bricks: [8]Brick, board: Board, }; const tile_size = V2{ .x = TILE_SIZE, .y = TILE_SIZE, }; fn getTilePos(i: usize, j: usize) V2 { return V2{ .x = @floatFromInt(j * TILE_SIZE), .y = @floatFromInt(i * TILE_SIZE), }; } fn pointInsideRect(p: V2, rect_start: V2, rect_size: V2) bool { var result: bool = false; const rect_end: V2 = rect_start.add(rect_size); const inside_x = rect_start.x < p.x and p.x < rect_end.x; const inside_y = rect_start.y < p.y and p.y < rect_end.y; result = inside_x and inside_y; return result; } fn tilesIntersects(r1: V2, r2: V2) bool { var result = false; const r2a: V2 = V2.init(r2.x, r2.y); const r2b: V2 = V2.init(r2.x, r2.y + tile_size.y); const r2c: V2 = V2.init(r2.x + tile_size.x, r2.y); const r2d: V2 = V2.init(r2.x + tile_size.x, r2.y + tile_size.y); const a = pointInsideRect(r2a, r1, tile_size); const b = pointInsideRect(r2b, r1, tile_size); const c = pointInsideRect(r2c, r1, tile_size); const d = pointInsideRect(r2d, r1, tile_size); result = a or b or c or d; 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 }; for (brick.tiles, 0..) |row, i| { for (row, 0..) |tile, j| { if (tile > 0) { const tile_start = getTilePos(i, j); const tile_end = tile_start.add(tile_size); const start = brick.pos.add(tile_start); const end = brick.pos.add(tile_end); if (start.x < result.min.x) result.min.x = start.x; if (start.y < result.min.y) result.min.y = start.y; if (result.max.x < end.x) result.max.x = end.x; if (result.max.y < end.y) result.max.y = end.y; } } } result.center = .{ .x = (result.min.x + result.max.x) / 2.0, .y = (result.min.y + result.max.y) / 2.0, }; 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)); const start_x: i32 = if (pos.x < 0) 0 else @intFromFloat(pos.x); const start_y: i32 = if (pos.y < 0) 0 else @intFromFloat(pos.y); const end = pos.add(size); const end_x: i32 = if (end.x < 0) 0 else @intFromFloat(end.x); const end_y: i32 = if (end.y < 0) 0 else @intFromFloat(end.y); const min_x: usize = @intCast(if (buffer.width < start_x) buffer.width else start_x); const min_y: usize = @intCast(if (buffer.height < start_y) buffer.height else start_y); const max_x: usize = @intCast(if (buffer.width < end_x) buffer.width else end_x); const max_y: usize = @intCast(if (buffer.height < end_y) buffer.height else end_y); for (min_y..max_y) |y| { for (min_x..max_x) |x| { const left = x < start_x + line_width; const top = y < start_y + line_width; const right = end_x - line_width - 1 < x; const bottom = end_y - line_width - 1 < y; if (left or top or right or bottom) { const idx: usize = y * @as(usize, @intCast(buffer.width)) + x; pixels[idx] = color; } } } } fn fillRect( buffer: platform.OffscreenBuffer, pos_x: i32, pos_y: i32, width: i32, height: i32, color: u32, ) void { var pixels: []u32 = @ptrCast(@alignCast(buffer.data)); var start_x: i32 = pos_x; var start_y: i32 = pos_y; var end_x: i32 = pos_x + width; var end_y: i32 = pos_y + height; if (start_x < 0) start_x = 0; if (start_y < 0) start_y = 0; if (buffer.width < start_x) start_x = buffer.width; if (buffer.height < start_y) start_y = buffer.height; if (end_x < 0) end_x = 0; if (end_y < 0) end_y = 0; if (buffer.width < end_x) end_x = buffer.width; if (buffer.height < end_y) end_y = buffer.height; for (@intCast(start_y)..@intCast(end_y)) |y| { for (@intCast(start_x)..@intCast(end_x)) |x| { const idx: usize = y * @as(usize, @intCast(buffer.width)) + x; pixels[idx] = color; } } } fn fillSquare( buffer: platform.OffscreenBuffer, x: i32, y: i32, size: i32, color: u32, ) void { fillRect(buffer, x, y, size, size, color); } fn drawImage(buffer: platform.OffscreenBuffer, image: Image, pos: V2, scale: f32) void { const pixels: []u32 = @ptrCast(@alignCast(buffer.data)); const scale_factor: f32 = 1.0 / scale; const buffer_width: usize = @intCast(buffer.width); const image_width: f32 = @floatFromInt(image.width); const image_height: f32 = @floatFromInt(image.height); var min_x: i32 = @intFromFloat(pos.x); var min_y: i32 = @intFromFloat(pos.y); var max_x: i32 = @intFromFloat(pos.x + scale * @as(f32, @floatFromInt(image.width))); var max_y: i32 = @intFromFloat(pos.y + scale * @as(f32, @floatFromInt(image.height))); var image_offset_x: i32 = 0; var image_offset_y: i32 = 0; if (min_x < 0) { image_offset_x = -min_x; min_x = 0; } if (min_y < 0) { image_offset_y = -min_y; min_y = 0; } if (buffer.width < max_x) max_x = buffer.width; if (buffer.height < max_y) max_y = buffer.height; const start_x: usize = @intCast(min_x); const start_y: usize = @intCast(min_y); const end_x: usize = @intCast(max_x); const end_y: usize = @intCast(max_y); var image_x: i32 = image_offset_x * image.components; var image_y: i32 = image_offset_y; for (start_y..end_y) |y| { for (start_x..end_x) |x| { const idx: usize = y * buffer_width + x; const current: u32 = pixels[idx]; const current_alpha = (0xFF000000 & current) >> 24; // const current_red = (0x00FF0000 & current) >> 16; // const current_green = (0x0000FF00 & current) >> 8; // const current_blue = (0x000000FF & current) >> 0; const x_f: f32 = @as(f32, @floatFromInt(image_x)) / @as(f32, @floatFromInt(image.width)); const xff: f32 = scale_factor * x_f; const new_x: i32 = @as(i32, @intFromFloat(image_width * xff)); const y_f: f32 = @as(f32, @floatFromInt(image_y)) / @as(f32, @floatFromInt(image.height)); const yff: f32 = scale_factor * y_f; const new_y: i32 = @as(i32, @intFromFloat(image_height * yff)); const image_idx: usize = @intCast(new_y * image.stride + new_x); const image_pixel: [*]u8 = image.data.ptr + image_idx; var image_alpha: u32 = 0xFF; const image_red: u32 = image_pixel[0]; const image_green: u32 = image_pixel[1]; const image_blue: u32 = image_pixel[2]; if (image.components > 3) { image_alpha = image_pixel[3]; } if (image.components == 3 and image_red == 0 and image_green == 0 and image_blue == 0) { image_alpha = 0; } // u32 red = lerp_color(image_alpha, current_red, image_red); // u32 green = lerp_color(image_alpha, current_green, image_green); // u32 blue = lerp_color(image_alpha, current_blue, image_blue); const red = image_red; const green = image_green; const blue = image_blue; pixels[idx] = (current_alpha << 24) | (red << 16) | (green << 8) | blue; image_x += image.components; } image_x = image_offset_x * image.components; image_y += 1; } } fn indexFont(font: Image, c: u8) V2 { var result = V2.zero; _ = font; var i: i32 = 0; var j: i32 = 0; switch (c) { ' ' => { i = 15; j = 1; }, '(' => { i = 7; j = 2; }, ')' => { i = 8; j = 2; }, ',' => { i = 11; j = 2; }, '-' => { i = 12; j = 2; }, '.' => { i = 13; j = 2; }, '0' => { i = 15; j = 2; }, '1' => { i = 0; j = 3; }, '2' => { i = 1; j = 3; }, '3' => { i = 2; j = 3; }, '4' => { i = 3; j = 3; }, '5' => { i = 4; j = 3; }, '6' => { i = 5; j = 3; }, '7' => { i = 6; j = 3; }, '8' => { i = 7; j = 3; }, '9' => { i = 8; j = 3; }, ':' => { i = 9; j = 3; }, 'V' => { i = 5; j = 5; }, 'a' => { i = 1; j = 6; }, 'b' => { i = 2; j = 6; }, 'c' => { i = 3; j = 6; }, 'd' => { i = 4; j = 6; }, 'e' => { i = 5; j = 6; }, 'f' => { i = 6; j = 6; }, 'g' => { i = 7; j = 6; }, 'h' => { i = 8; j = 6; }, 'i' => { i = 9; j = 6; }, 'j' => { i = 10; j = 6; }, 'k' => { i = 11; j = 6; }, 'l' => { i = 12; j = 6; }, 'm' => { i = 13; j = 6; }, 'n' => { i = 14; j = 6; }, 'o' => { i = 15; j = 6; }, 'p' => { i = 0; j = 7; }, 'q' => { i = 1; j = 7; }, 'r' => { i = 2; j = 7; }, 's' => { i = 3; j = 7; }, 't' => { i = 4; j = 7; }, 'u' => { i = 5; j = 7; }, 'v' => { i = 6; j = 7; }, 'w' => { i = 7; j = 7; }, 'x' => { i = 8; j = 7; }, 'y' => { i = 9; j = 7; }, 'z' => { i = 10; j = 7; }, else => { }, } result.x = @floatFromInt(i * 32); result.y = @floatFromInt(j * 32); return result; } fn drawString(buffer: platform.OffscreenBuffer, image: Image, pos: V2, str: []const u8) void { var current_pos = pos; const font_size = 32; const scale = 0.5; const font_width_scaled = scale * font_size; for (str) |c| { const char_image_pos = indexFont(image, c); const pos_x: i32 = @intFromFloat(char_image_pos.x); const pos_y: i32 = @intFromFloat(char_image_pos.y); const idx: usize = @intCast(pos_y * image.stride + (pos_x * image.components)); const ptr = image.data.ptr + idx; const size: usize = @intCast(font_size * image.stride); const char_image: Image = .{ .width = font_size, .height = font_size, .stride = image.stride, .components = image.components, .data = ptr[0..size], }; drawImage(buffer, char_image, current_pos, scale); current_pos.x += font_width_scaled; } } fn keyPressed(key: *platform.Key) bool { var result = false; if (key.down) { if (key.frame_count == 0) { result = true; } key.frame_count += 1; } else { key.frame_count = 0; } return result; } fn loadPng(image_path: []const u8) ?Image { var x: c_int = undefined; var y: c_int = undefined; var n: c_int = undefined; const image_data: [*c]platform.c.stbi_uc = platform.c.stbi_load(@ptrCast(image_path), &x, &y, &n, 0); if (image_data != 0) { const stride = n * x; const size: usize = @intCast(y * stride); const ptr: []u8 = image_data[0..size]; return .{ .width = x, .height = y, .stride = stride, .components = n, .data = ptr, }; } else { return null; } } pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBuffer, input: *platform.AppInput) void { var state: *State = @ptrCast(@alignCast(memory.permanent_storage)); const mid = V2{ .x = @floatFromInt(@divTrunc(buffer.width, 2)), .y = @floatFromInt(@divTrunc(buffer.height, 2)), }; if (!memory.initialized) { state.camera.offset = mid; state.grabbed_brick_index = null; const font_image = loadPng("assets/font.png"); if (font_image) |img| { state.font_image = img; } 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 }, .{ 0, 0, 0, 0 }, .{ 0, 0, 0, 0 }, }, }; state.bricks[1] = Brick{ .rotating = false, .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 }, .{ 0, 0, 0, 0 }, .{ 0, 0, 0, 0 }, }, }; state.bricks[2] = Brick{ .rotating = false, .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 }, .{ 0, 0, 0, 0 }, .{ 0, 0, 0, 0 }, }, }; state.bricks[3] = Brick{ .rotating = false, .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 }, .{ 0, 0, 4, 0 }, .{ 0, 0, 0, 0 }, }, }; state.bricks[4] = Brick{ .rotating = false, .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 }, .{ 0, 0, 0, 0 }, .{ 0, 0, 0, 0 }, }, }; state.bricks[5] = Brick{ .rotating = false, .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 }, .{ 6, 0, 0, 0 }, .{ 0, 0, 0, 0 }, }, }; state.bricks[6] = Brick{ .rotating = false, .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 }, .{ 0, 0, 0, 0 }, .{ 0, 0, 0, 0 }, }, }; state.bricks[7] = Brick{ .rotating = false, .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 }, .{ 0, 0, 0, 0 }, .{ 0, 0, 0, 0 }, }, }; const tile_names = [7][7]?[]const u8{ .{ "jan", "feb", "mar", "apr", "may", "jun", null }, .{ "jul", "aug", "sep", "oct", "nov", "dec", null }, .{ "1", "2", "3", "4", "5", "6", "7" }, .{ "8", "9", "10", "11", "12", "13", "14" }, .{ "15", "16", "17", "18", "19", "20", "21" }, .{ "22", "23", "24", "25", "26", "27", "28" }, .{ "29", "30", "31", null, null, null, null }, }; var tiles: [7][7]BoardTile = undefined; for (tile_names, 0..) |row, i| { for (row, 0..) |opt, j| { if (opt) |tile| { tiles[i][j] = BoardTile.init(tile, false); } else { tiles[i][j] = BoardTile.init("", true); } } } state.board = .{ .pos = mid, .width = 7, .height = 7, .tile_size = V2.init(TILE_SIZE, TILE_SIZE), .tiles = tiles, }; const board_size = V2.initI32( @intCast(state.board.width * TILE_SIZE), @intCast(state.board.height * TILE_SIZE), ); const half = board_size.mul(0.5); state.board.pos = state.board.pos.sub(half); memory.initialized = true; } // Update const mouse_pos = V2{ .x = @floatCast(input.mouse_x), .y = @floatCast(input.mouse_y) }; const mouse_world_pos = mouse_pos.sub(state.camera.pos); if (keyPressed(&input.key.f1)) { state.draw_bounding_rects = !state.draw_bounding_rects; } if (!input.mouse_left_down) { state.grabbed_brick_index = null; } for (&state.board.tiles) |*board_row| { for (board_row) |*board_tile| { board_tile.hovering = false; } } for (&state.bricks, 0..) |*brick, brick_index| { if (brick.rotating) { brick.rotating_time += input.delta_time; if (brick.rotating_time > 0.05) { brick.rotating = false; brick.rotating_time = 0.0; } } for (brick.tiles, 0..) |row, i| { 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)) { if (state.grabbed_brick_index == null) { state.grabbed_brick_index = brick_index; } } } for (&state.board.tiles, 0..) |*board_row, k| { for (board_row, 0..) |*board_tile, l| { 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 = in_tile; } } } } } } if (brick_index == state.grabbed_brick_index) { const diff = mouse_pos.sub(state.mouse_pos); brick.pos = brick.pos.add(diff); if (keyPressed(&input.key.e) or keyPressed(&input.key.f)) { brick.rotating = true; const rotated = brick.rotate(); brick.tiles = rotated; const bbox = findBrickBoundingBox(brick); const center = brick.pos.sub(bbox.center); brick.pos = mouse_world_pos.add(center); } // Flip if (keyPressed(&input.key.w)) { brick.rotating = true; const flipped = brick.transpose(); brick.tiles = flipped; const bbox = findBrickBoundingBox(brick); const center = brick.pos.sub(bbox.center); 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) { const diff = mouse_pos.sub(state.mouse_pos); state.camera.pos = state.camera.pos.add(diff); } state.mouse_pos = mouse_pos; // Render fillRect(buffer, 0, 0, buffer.width, buffer.height, 0xFFFFFFFF); // Draw board { const board = state.board; for (board.tiles, 0..) |row, i| { for (row, 0..) |tile, j| { const tile_pos = board.getTilePos(i, j); const width: i32 = @intFromFloat(board.tile_size.x); const height: i32 = @intFromFloat(board.tile_size.y); const str_width = @divFloor(TILE_SIZE * tile.label.len, 4); const str_height = @divFloor(32, 4); const str_size = V2.initUSize(str_width, str_height); const string_pos = tile_pos.add(board.tile_size.mul(0.5)).sub(str_size.mul(0.5)); var color: u32 = 0xFF000000; if (tile.blocked) { color = 0xFFFFFFFF; } else if (tile.hovering) { color = 0xFF00FF00; } 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); fillRect(buffer, pos_x, pos_y, width, height, color); drawRect(buffer, camera_pos, board.tile_size, 0xFFFFFFFF, 2); const string_camera_pos = string_pos.add(state.camera.pos); drawString(buffer, state.font_image, string_camera_pos, tile.label); } } } for (state.bricks) |brick| { const bounding_box = findBrickBoundingBox(&brick); for (brick.tiles, 0..) |row, i| { for (row, 0..) |tile, j| { 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), TILE_SIZE, color, ); } } } if (comptime builtin.mode == std.builtin.OptimizeMode.Debug) { if (state.draw_bounding_rects) { { const camera_pos = brick.pos.add(state.camera.pos); drawRect(buffer, camera_pos, .{ .x = 4 * TILE_SIZE, .y = 4 * TILE_SIZE }, 0xFFFF0000, 1); } { const bbox_size = bounding_box.max.sub(bounding_box.min); const camera_pos = bounding_box.min.add(state.camera.pos); drawRect(buffer, camera_pos, bbox_size, 0xFFFF0000, 1); } { const size = 8; const half = @divTrunc(size, 2); const camera_pos = bounding_box.center.add(state.camera.pos); const x: i32 = @intFromFloat(camera_pos.x); const y: i32 = @intFromFloat(camera_pos.y); fillSquare(buffer, x - half, y - half, 8, 0xFFFF0000); } } } } state.frame_count += 1; }