Files
zig-a-puzzle-a-day/src/root.zig
T
2026-03-07 14:29:17 +01:00

319 lines
8.6 KiB
Zig

//! By convention, root.zig is the root source file when making a library.
const std = @import("std");
pub const platform = @import("platform.zig");
const TILE_SIZE = 32;
const V2 = struct {
x: f32,
y: f32,
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,
};
}
};
const Camera = struct {
pos: V2,
offset: V2,
};
const Brick = struct {
rotating: bool,
pos: V2,
color: u32,
tiles: [4][4]u32,
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,
bricks: [8]Brick,
};
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 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 < 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);
}
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.bricks[0] = Brick{
.rotating = false,
.pos = V2{ .x = 0, .y = 50 },
.color = 0xFFFF0000,
.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,
.pos = V2{ .x = 32, .y = 50 },
.color = 0xFFEF0000,
.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,
.pos = V2{ .x = 64, .y = 50 },
.color = 0xFFDF0000,
.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,
.pos = V2{ .x = 96, .y = 50 },
.color = 0xFFCF0000,
.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,
.pos = V2{ .x = 128, .y = 150 },
.color = 0xFFBF0000,
.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,
.pos = V2{ .x = 160, .y = 250 },
.color = 0xFFAF0000,
.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,
.pos = V2{ .x = 192, .y = 350 },
.color = 0xFF9F0000,
.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,
.pos = V2{ .x = 224, .y = 450 },
.color = 0xFF8F0000,
.tiles = [4][4]u32{
.{ 8, 8, 8, 0 },
.{ 8, 0, 8, 0 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
},
};
memory.initialized = true;
}
// Update
const mouse_pos = V2{ .x = @floatCast(input.mouse_x), .y = @floatCast(input.mouse_y) };
const tile_size = V2{
.x = TILE_SIZE,
.y = TILE_SIZE,
};
var mouse_on_brick = false;
for (&state.bricks) |*brick| {
brick.rotating = false;
for (brick.tiles, 0..) |row, i| {
for (row, 0..) |tile, j| {
if (tile > 0) {
const tile_pos = V2{
.x = @floatFromInt(j * TILE_SIZE),
.y = @floatFromInt(i * TILE_SIZE),
};
const pos = tile_pos.add(brick.pos);
const camera_pos = pos.add(state.camera.pos);
if (pointInsideRect(state.mouse_pos, camera_pos, tile_size)) {
if (input.mouse_left_down) {
const diff = mouse_pos.sub(state.mouse_pos);
brick.pos = brick.pos.add(diff);
if (input.key.space.down and !brick.rotating) {
// TODO: Rotate
brick.rotating = true;
const rotated = brick.rotate();
brick.tiles = rotated;
}
mouse_on_brick = true;
}
}
}
}
}
}
if (input.mouse_left_down and !mouse_on_brick) {
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);
const board_pos = mid;
for (0..7) |i| {
for (0..7) |j| {
if (i == 0 and j == 6) continue;
if (i == 1 and j == 6) continue;
if (i == 6 and j == 3) continue;
if (i == 6 and j == 4) continue;
if (i == 6 and j == 5) continue;
if (i == 6 and j == 6) continue;
const tile_pos = V2{
.x = @floatFromInt(j * TILE_SIZE),
.y = @floatFromInt(i * TILE_SIZE),
};
const camera_pos = board_pos.add(tile_pos).add(state.camera.pos);
fillSquare(buffer, @intFromFloat(camera_pos.x), @intFromFloat(camera_pos.y), 30, 0xFF000000);
}
}
for (state.bricks) |brick| {
for (brick.tiles, 0..) |row, i| {
for (row, 0..) |tile, j| {
if (tile > 0) {
const pos = V2{
.x = @floatFromInt(j * TILE_SIZE),
.y = @floatFromInt(i * TILE_SIZE),
};
const camera_pos = brick.pos.add(pos).add(state.camera.pos);
fillSquare(
buffer,
@intFromFloat(camera_pos.x),
@intFromFloat(camera_pos.y),
30,
if (brick.rotating) 0xFF00FF00 else brick.color ,
);
}
}
}
}
state.frame_count += 1;
}