Initial commit

main
copygirl 5 months ago
commit de3182744b
  1. 2
      .gitignore
  2. 12
      .vscode/launch.json
  3. 16
      .vscode/tasks.json
  4. 38
      build.zig
  5. 18
      build.zig.zon
  6. BIN
      gfx/sprite_map.png
  7. BIN
      gfx/sprite_map.xcf
  8. 102
      src/canvas.zig
  9. 51
      src/grid.zig
  10. 112
      src/keys.zig
  11. 92
      src/main.zig
  12. 105
      src/window.zig

2
.gitignore vendored

@ -0,0 +1,2 @@
/zig-cache/
/zig-out/

@ -0,0 +1,12 @@
{
"version": "0.2.0",
"configurations": [{
"name": "Debug",
"type": "lldb",
"preLaunchTask": "build",
"request": "launch",
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/zig-out/bin/gridstep",
"args": [],
}]
}

16
.vscode/tasks.json vendored

@ -0,0 +1,16 @@
{
"version": "2.0.0",
"tasks": [{
"label": "build",
"group": { "kind": "build", "isDefault": true },
"type": "shell",
"command": "zig build",
"problemMatcher": []
},{
"label": "test",
"group": { "kind": "test", "isDefault": true },
"type": "shell",
"command": "zig build test",
"problemMatcher": []
}]
}

@ -0,0 +1,38 @@
const std = @import("std");
const sdl = @import("sdl");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const sdl_sdk = sdl.init(b, null);
const exe = b.addExecutable(.{
.name = "gridstep",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
sdl_sdk.link(exe, .dynamic);
exe.linkSystemLibrary("sdl2_image");
exe.linkSystemLibrary("libpng");
exe.root_module.addImport("sdl", sdl_sdk.getNativeModule());
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const unit_tests = b.addTest(.{
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
const run_unit_tests = b.addRunArtifact(unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
}

@ -0,0 +1,18 @@
.{
.name = "gridstep",
.version = "0.0.0",
.minimum_zig_version = "0.11.0",
.dependencies = .{
.sdl = .{
.url = "https://github.com/MasterQ32/SDL.zig/archive/39fb8355cccb45a241a891c4848ab925af20fee4.tar.gz",
.hash = "12203537fc1357c4efce1c7a5ae3b407b9070490d83acd69aa3363b6b8e8a2b27870",
},
},
.paths = .{
"src",
"build.zig",
"build.zig.zon",
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

@ -0,0 +1,102 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const sdl = @import("sdl");
const Window = @import("./window.zig");
const makeSdlError = Window.makeSdlError;
const Canvas = @This();
allocator: Allocator,
surface: *sdl.SDL_Surface,
pixels: []Pixel,
width: usize,
height: usize,
pub fn init(allocator: Allocator, width: usize, height: usize) !*Canvas {
const surface = sdl.SDL_CreateRGBSurfaceWithFormat(
0,
@intCast(width),
@intCast(height),
0,
sdl.SDL_PIXELFORMAT_ABGR8888,
) orelse
return makeSdlError();
const pixels_ptr: [*]Pixel = @alignCast(@ptrCast(surface.pixels));
const pixels_len = width * height;
const pixels = pixels_ptr[0..pixels_len];
const result = try allocator.create(Canvas);
result.* = .{
.allocator = allocator,
.surface = surface,
.pixels = pixels,
.width = width,
.height = height,
};
return result;
}
pub fn deinit(self: *Canvas) void {
sdl.SDL_FreeSurface(self.surface);
self.allocator.destroy(self);
}
pub fn getUnsafe(self: *Canvas, x: usize, y: usize) *Pixel {
const index = x + y * self.width;
return &self.pixels[index];
}
pub fn get(self: *Canvas, x: usize, y: usize) *Pixel {
std.debug.assert(x < self.width);
std.debug.assert(y < self.height);
return self.getUnsafe(x, y);
}
pub fn put(self: *Canvas, x: usize, y: usize, value: Pixel) void {
self.get(x, y).* = value;
}
pub fn putRect(self: *Canvas, x: usize, y: usize, w: usize, h: usize, value: Pixel) void {
std.debug.assert(x + w <= self.width);
std.debug.assert(y + h <= self.height);
const start_index = x + y * self.width;
for (0..h) |yo| {
const row_index = start_index + yo * self.width;
@memset(self.pixels[row_index..][0..w], value);
}
}
pub fn putSprite(self: *Canvas, x: usize, y: usize, sprite: *const Sprite) void {
std.debug.assert(x + 16 <= self.width);
std.debug.assert(y + 16 <= self.height);
for (0..16) |yo|
for (0..16) |xo| {
const value = sprite[yo][xo];
if (value.a == 0x00) continue;
const dest = self.getUnsafe(x + xo, y + yo);
if (value.a == 0xFF)
dest.* = value
else
@panic("Not supported");
};
}
pub fn clear(self: *Canvas, value: Pixel) void {
@memset(self.pixels, value);
}
pub const Pixel = packed struct {
r: u8,
g: u8,
b: u8,
a: u8,
pub const transparent = Pixel{ .r = 0x00, .g = 0x00, .b = 0x00, .a = 0x00 };
pub const black = Pixel{ .r = 0x00, .g = 0x00, .b = 0x00, .a = 0xFF };
pub const white = Pixel{ .r = 0xFF, .g = 0xFF, .b = 0xFF, .a = 0xFF };
};
pub const Sprite = [16][16]Pixel;

@ -0,0 +1,51 @@
const std = @import("std");
const Grid = @This();
pub const size = 16;
array: [size][size][size]Tile = .{.{.{0} ** size} ** size} ** size,
pub fn get(self: *Grid, pos: Pos) Ref {
return .{ .grid = self, .pos = pos };
}
pub fn layerAs(self: *Grid, z: i4, comptime T: type) []T {
const bytes = std.mem.sliceAsBytes(&self.array[@as(u4, @bitCast(z))]);
return std.mem.bytesAsSlice(T, bytes);
}
pub const Tile = u8;
pub const Pos = packed struct {
x: i4,
y: i4,
z: i4,
pub fn init(x: i4, y: i4, z: i4) Pos {
return .{ .x = x, .y = y, .z = z };
}
pub fn initTruncate(x: anytype, y: anytype, z: anytype) Pos {
const sign = @typeInfo(@TypeOf(x, y, z)).Int.signedness;
const T = @Type(.{ .Int = .{ .signedness = sign, .bits = 4 } });
return .{
.x = @bitCast(@as(T, @truncate(x))),
.y = @bitCast(@as(T, @truncate(y))),
.z = @bitCast(@as(T, @truncate(z))),
};
}
};
pub const Ref = struct {
grid: *Grid,
pos: Pos,
pub fn as(self: Ref, comptime T: type) *T {
std.debug.assert(@sizeOf(T) == @sizeOf(Tile));
const x: usize = @as(u4, @bitCast(self.pos.x));
const y: usize = @as(u4, @bitCast(self.pos.y));
const z: usize = @as(u4, @bitCast(self.pos.z));
return @ptrCast(&self.grid.array[z][y][x]);
}
};

@ -0,0 +1,112 @@
const std = @import("std");
const sdl = @import("sdl");
pub const PhysicalKey = enum(u8) {
// zig fmt: off
backspace = 0x08,
tab = '\t',
enter = '\r',
escape = 0x1B,
// Numbers
_0 = '0', _1 = '1', _2 = '2', _3 = '3', _4 = '4',
_5 = '5', _6 = '6', _7 = '7', _8 = '8', _9 = '9',
// Letters
a = 'A', b = 'B', c = 'C', d = 'D', e = 'E', f = 'F', g = 'G',
h = 'H', i = 'I', j = 'J', k = 'K', l = 'L', m = 'M', n = 'N',
o = 'O', p = 'P', q = 'Q', r = 'R', s = 'S', t = 'T', u = 'U',
v = 'V', w = 'W', x = 'X', y = 'Y', z = 'Z',
// Visual
space = ' ',
grave = '`',
minus = '-',
equals = '=',
left_bracket = '[',
right_bracket = ']',
semicolon = ';',
apostrophe = '\'',
backslash = '\\',
comma = ',',
period = '.',
slash = '/',
// Nagivation
insert = 0x8D,
delete = 0x7F,
home = 0x8E,
end = 0x9E,
page_up = 0x8F,
page_down = 0x9F,
up = 0x8C,
down = 0x9C,
left = 0x9B,
right = 0x9D,
// Modifiers
ctrl = 0x88,
alt = 0x89,
shift = 0x8A,
caps = 0x8B,
_,
// zig fmt: on
};
pub const lookup = blk: {
const size = sdl.SDL_NUM_SCANCODES;
var result: [size]?PhysicalKey = .{null} ** size;
result[sdl.SDL_SCANCODE_BACKSPACE] = PhysicalKey.backspace;
result[sdl.SDL_SCANCODE_TAB] = PhysicalKey.tab;
result[sdl.SDL_SCANCODE_RETURN] = PhysicalKey.enter;
result[sdl.SDL_SCANCODE_ESCAPE] = PhysicalKey.escape;
for ('0'..'9' + 1) |i| {
const scancode = @field(sdl, "SDL_SCANCODE_" ++ .{i});
const physical_key = @field(PhysicalKey, "_" ++ .{i});
result[scancode] = physical_key;
}
for ('A'..'Z' + 1) |i| {
const scancode = @field(sdl, "SDL_SCANCODE_" ++ .{i});
const physical_key = @field(PhysicalKey, &.{std.ascii.toLower(i)});
result[scancode] = physical_key;
}
result[sdl.SDL_SCANCODE_GRAVE] = PhysicalKey.grave;
result[sdl.SDL_SCANCODE_MINUS] = PhysicalKey.minus;
result[sdl.SDL_SCANCODE_EQUALS] = PhysicalKey.equals;
result[sdl.SDL_SCANCODE_LEFTBRACKET] = PhysicalKey.left_bracket;
result[sdl.SDL_SCANCODE_RIGHTBRACKET] = PhysicalKey.right_bracket;
result[sdl.SDL_SCANCODE_SEMICOLON] = PhysicalKey.semicolon;
result[sdl.SDL_SCANCODE_APOSTROPHE] = PhysicalKey.apostrophe;
result[sdl.SDL_SCANCODE_BACKSLASH] = PhysicalKey.backslash;
result[sdl.SDL_SCANCODE_COMMA] = PhysicalKey.comma;
result[sdl.SDL_SCANCODE_PERIOD] = PhysicalKey.period;
result[sdl.SDL_SCANCODE_SLASH] = PhysicalKey.slash;
result[sdl.SDL_SCANCODE_SPACE] = PhysicalKey.space;
result[sdl.SDL_SCANCODE_INSERT] = PhysicalKey.insert;
result[sdl.SDL_SCANCODE_DELETE] = PhysicalKey.delete;
result[sdl.SDL_SCANCODE_HOME] = PhysicalKey.home;
result[sdl.SDL_SCANCODE_END] = PhysicalKey.end;
result[sdl.SDL_SCANCODE_PAGEUP] = PhysicalKey.page_up;
result[sdl.SDL_SCANCODE_PAGEDOWN] = PhysicalKey.page_down;
result[sdl.SDL_SCANCODE_UP] = PhysicalKey.up;
result[sdl.SDL_SCANCODE_DOWN] = PhysicalKey.down;
result[sdl.SDL_SCANCODE_LEFT] = PhysicalKey.left;
result[sdl.SDL_SCANCODE_RIGHT] = PhysicalKey.right;
result[sdl.SDL_SCANCODE_LCTRL] = PhysicalKey.ctrl;
result[sdl.SDL_SCANCODE_RCTRL] = PhysicalKey.ctrl;
result[sdl.SDL_SCANCODE_LALT] = PhysicalKey.alt;
result[sdl.SDL_SCANCODE_RALT] = PhysicalKey.alt;
result[sdl.SDL_SCANCODE_LSHIFT] = PhysicalKey.shift;
result[sdl.SDL_SCANCODE_RSHIFT] = PhysicalKey.shift;
result[sdl.SDL_SCANCODE_CAPSLOCK] = PhysicalKey.caps;
break :blk result;
};

@ -0,0 +1,92 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const sdl = @import("sdl");
const Window = @import("./window.zig");
const makeSdlError = Window.makeSdlError;
const Canvas = @import("./canvas.zig");
const Pixel = Canvas.Pixel;
const Sprite = Canvas.Sprite;
const Grid = @import("./grid.zig");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var prng = std.Random.DefaultPrng.init(0);
const random = prng.random();
var grid = Grid{};
const key_layer = grid.layerAs(0, u8);
var sprite_lookup: [256]Sprite = undefined;
try loadSprites(&sprite_lookup);
const size = Grid.size * Grid.size;
const scale = 3;
const window = try Window.init(allocator, size * scale, size * scale, key_layer);
defer window.deinit();
const canvas = try Canvas.init(allocator, size, size);
defer canvas.deinit();
while (window.running) {
window.pollEvents();
grid.get(Grid.Pos.init(-5, -4, 0)).as(u8).* +%= 1;
grid.get(Grid.Pos.init(-4, -4, 0)).as(u8).* = random.int(u8);
render(&grid, canvas, &sprite_lookup);
try window.updateSurface(canvas);
}
}
fn render(grid: *Grid, canvas: *Canvas, sprite_lookup: *[256]Sprite) void {
canvas.clear(Pixel.black);
for (0..Grid.size) |vx|
for (0..Grid.size) |vy|
for (0..Grid.size) |vz| {
const x = @as(isize, @intCast(vx)) - Grid.size / 2;
const y = @as(isize, @intCast(vy)) - Grid.size / 2;
const z = @as(isize, @intCast(vz)) - Grid.size / 2;
const pos = Grid.Pos.initTruncate(x, y, z);
const value = grid.get(pos).as(u8).*;
const sprite = &sprite_lookup[value];
canvas.putSprite(vx * Grid.size, vy * Grid.size, sprite);
};
}
fn loadSprites(sprite_lookup: *[256]Sprite) !void {
const png_surface = sdl.IMG_Load("./gfx/sprite_map.png") orelse
return makeSdlError();
defer sdl.SDL_FreeSurface(png_surface);
std.debug.assert(png_surface.w == 256);
std.debug.assert(png_surface.h == 256);
const pixels_surface = sdl.SDL_ConvertSurfaceFormat(
png_surface,
sdl.SDL_PIXELFORMAT_ABGR8888,
0,
) orelse
return makeSdlError();
defer sdl.SDL_FreeSurface(pixels_surface);
const pixels_ptr: [*]Pixel = @alignCast(@ptrCast(pixels_surface.pixels));
const pixels_len: usize = @intCast(pixels_surface.w * pixels_surface.h);
const pixels = pixels_ptr[0..pixels_len];
for (sprite_lookup, 0..) |*sprite, i| {
const x = i % 16;
const y = i / 16;
for (0..16) |yo| {
const dst_index = x * 16 + (y * 16 + yo) * 256;
@memcpy(&sprite[yo], pixels[dst_index..][0..16]);
}
}
}

@ -0,0 +1,105 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const sdl = @import("sdl");
const keys = @import("./keys.zig");
const PhysicalKey = keys.PhysicalKey;
const Canvas = @import("./canvas.zig");
const Window = @This();
allocator: Allocator,
handle: *sdl.SDL_Window,
surface: ?*sdl.SDL_Surface = null,
key_status: []u8,
running: bool = true,
pub fn init(allocator: Allocator, width: usize, height: usize, key_status: []u8) !*Window {
if (sdl.SDL_Init(sdl.SDL_INIT_AUDIO | sdl.SDL_INIT_VIDEO | sdl.SDL_INIT_EVENTS) < 0)
return makeSdlError();
const window = sdl.SDL_CreateWindow(
"Gridstep",
sdl.SDL_WINDOWPOS_UNDEFINED,
sdl.SDL_WINDOWPOS_UNDEFINED,
@intCast(width),
@intCast(height),
sdl.SDL_WINDOW_SHOWN | sdl.SDL_WINDOW_RESIZABLE,
) orelse
return makeSdlError();
const result = try allocator.create(Window);
result.* = .{
.allocator = allocator,
.handle = window,
.key_status = key_status,
};
return result;
}
pub fn deinit(self: *Window) void {
sdl.SDL_DestroyWindow(self.handle);
sdl.SDL_Quit();
self.allocator.destroy(self);
}
pub fn pollEvents(self: *Window) void {
{
// Special handling for the `.caps` key.
const i = @intFromEnum(PhysicalKey.caps);
const on = (sdl.SDL_GetModState() & sdl.KMOD_CAPS) != 0;
self.key_status[i] = if (on) i else 0;
}
var ev = std.mem.zeroes(sdl.SDL_Event);
while (sdl.SDL_PollEvent(&ev) != 0) {
switch (ev.type) {
sdl.SDL_QUIT => {
self.running = false;
},
sdl.SDL_WINDOWEVENT => {
switch (ev.window.event) {
sdl.SDL_WINDOWEVENT_RESIZED => {
// Invalidate surface when window is resized.
self.surface = null;
},
else => {},
}
},
sdl.SDL_KEYDOWN, sdl.SDL_KEYUP => {
const key = ev.key.keysym;
const down = (ev.type == sdl.SDL_KEYDOWN);
if (keys.lookup[key.scancode]) |k| {
if (k == .caps) break;
const i = @intFromEnum(k);
self.key_status[i] = if (down) i else 0;
}
},
else => {},
}
}
}
pub fn updateSurface(self: *Window, canvas: *Canvas) !void {
// If surface has not yet been created or invalidated due
// to window being resized, reaquire the window's surface.
if (self.surface == null)
self.surface = sdl.SDL_GetWindowSurface(self.handle) orelse
return makeSdlError();
const w: c_int = @intCast(canvas.width);
const h: c_int = @intCast(canvas.height);
const src_rect = sdl.SDL_Rect{ .x = 0, .y = 0, .w = w, .h = h };
var dest_rect = sdl.SDL_Rect{ .x = 0, .y = 0, .w = self.surface.?.w, .h = self.surface.?.h };
if (sdl.SDL_BlitScaled(canvas.surface, &src_rect, self.surface, &dest_rect) < 0)
return makeSdlError();
if (sdl.SDL_UpdateWindowSurface(self.handle) < 0)
return makeSdlError();
}
pub fn makeSdlError() error{SdlError} {
std.debug.print("{s}\n", .{sdl.SDL_GetError()});
return error.SdlError;
}
Loading…
Cancel
Save