commit
de3182744b
12 changed files with 548 additions and 0 deletions
@ -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": [], |
||||
}] |
||||
} |
@ -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", |
||||
}, |
||||
} |
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…
Reference in new issue