c29af146dd
And make work with colemak hehe
1137 lines
30 KiB
Zig
1137 lines
30 KiB
Zig
//! 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;
|
|
}
|