Compare commits

...

4 Commits

8 changed files with 172 additions and 19 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
zig-cache/
sample_*.kble

View File

@ -42,6 +42,11 @@ Verbs:
* `-`: decrease scale (dezoom)
* `=`: reset scale
File (read/write):
* `e`: load level contained in `sample.kble`
* `w`: write level to `sample.kble`
*See `kbleformat.md` for technical informations.*
Modes:
* `<return>`: normal mode, default
* `i`: free selection mode

11
gen_sample.lua Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/lua
local out = io.open("sample.kble", "wb")
out:write(string.char(
2,
0, 2,
0, 2,
0, 1,
0, 0,
0, 0,
1, 1))
out:close()

View File

@ -7,6 +7,6 @@ Encoding:
* first byte indicates how big is a chunk of data (in bytes)
* second and third bytes indicates level width
* fourth and fifth bytes indicates level height
* 7th => (7 + data_size * width * height) contains level data
* 6th => (6 + data_size * width * height) contains level data
If the encoding is incorrect, return an error or crash the program.

Binary file not shown.

View File

@ -19,6 +19,7 @@ pub const ActionCat = enum {
movement, // move and change selection
verb, // do stuff with selection
scale, // change draw scaling
file, // load/save, file related stuff
mode, // change mode
};
@ -28,6 +29,7 @@ pub const Action = struct {
function_move: fn (*Vec2, conf.arg_type) movement.SelectionUpdate = movement.move_left,
function_verb: fn (*Level, conf.arg_type) void = verbs.delete,
function_scale: fn (scaling.scale_type) scaling.scale_type = scaling.scale_reset,
function_file: fn (*Level, *std.mem.Allocator, []const u8) void = Level.action_write,
next_mode: Mode = Mode.normal,
};
@ -110,6 +112,16 @@ pub const ActionsDef = .{
.function_scale = scaling.scale_down,
},
// File
.file_read = Action{
.category = ActionCat.file,
.function_file = Level.action_read,
},
.file_write = Action{
.category = ActionCat.file,
.function_file = Level.action_write,
},
// Mode change.
.mode_normal = Action{
.category = ActionCat.mode,

View File

@ -9,6 +9,7 @@ const ray = @cImport({
});
const std = @import("std");
const expect = std.testing.expect;
const assert = std.debug.assert;
const Vec2 = @import("vec2.zig");
const Rect = @import("rect.zig");
@ -17,6 +18,7 @@ const SelectionUpdate = @import("movement.zig").SelectionUpdate;
const Self = @This();
pub const cell_type = u16;
const expected_bytes_per_cell = @sizeOf(cell_type);
width: u16,
height: u16,
@ -48,15 +50,16 @@ pub fn init(allocator: *std.mem.Allocator, width: u16, height: u16) !Self {
return self;
}
/// Load level content from given absolute path. Expect the KBLE file format (see
/// `kbleformat.md` for more details.
pub fn init_load(allocator: *std.mem.Allocator, kble_file_path: []const u8) !Self {
var self = Self{
.width = undefined,
.height = undefined,
.content = undefined,
.selection = undefined,
};
/// Free the level content.
pub fn deinit(self: *Self, allocator: *std.mem.Allocator) void {
allocator.free(self.selection);
allocator.free(self.content);
}
/// Load level content from given absolute or relative path.
/// Expect the KBLE file format (see `kbleformat.md` for more details).
pub fn init_read(allocator: *std.mem.Allocator, kble_file_path: []const u8) !Self {
var self: Self = undefined;
// Open directory.
var dir: std.fs.Dir = std.fs.cwd();
@ -66,18 +69,111 @@ pub fn init_load(allocator: *std.mem.Allocator, kble_file_path: []const u8) !Sel
.write = false,
});
defer file.close();
// Get reader.
var reader: std.fs.File.Reader = file.reader();
// Read first byte and check than the value matches the size of cell_type.
{
const cell_spec_byte = try reader.readByte();
assert(cell_spec_byte == expected_bytes_per_cell);
}
// Read four bytes and use them for width and height. Two bytes each.
{
const width_byte1: u8 = try reader.readByte();
const width_byte2: u8 = try reader.readByte();
const height_byte1: u8 = try reader.readByte();
const height_byte2: u8 = try reader.readByte();
const width: u16 = @intCast(u16, width_byte1) * 256 + @intCast(u16, width_byte2);
const height: u16 = @intCast(u16, height_byte1) * 256 + @intCast(u16, height_byte2);
std.log.info("Cell size: {}\nWidth/height: {}·{} ; {}·{} => {} ; {}", .{
expected_bytes_per_cell,
width_byte1,
width_byte2,
height_byte1,
height_byte2,
width,
height,
});
// Non-null width and height.
assert(width > 0);
assert(height > 0);
self = try Self.init(allocator, width, height);
}
// Read the rest of the file and assign to .content accordingly.
{
var i: u32 = 0;
while (i < self.width * self.height) : (i += 1) {
const byte1 = try reader.readByte();
const byte2 = try reader.readByte();
const cell = @intCast(u16, byte1) * 256 + @intCast(u16, byte2);
self.content[i] = cell;
}
}
return self;
}
/// Free the level content.
pub fn deinit(self: *Self, allocator: *std.mem.Allocator) void {
allocator.free(self.selection);
allocator.free(self.content);
/// Write header and level content to given absolute or relative path.
/// Uses the KBLE file format (see `kbleformat.md` for more details).
pub fn write(self: Self, kble_file_path: []const u8) !void {
// Open directory.
var dir: std.fs.Dir = std.fs.cwd();
// Open file in write mode.
const file: std.fs.File = try dir.createFile(kble_file_path, std.fs.File.CreateFlags{});
defer file.close();
// Get writer.
var writer: std.fs.File.Writer = file.writer();
// Write first byte, indicates cell size in bytes.
try writer.writeByte(expected_bytes_per_cell);
// Write level height and level width.
{
const width_byte1: u8 = @intCast(u8, self.width / 256);
const width_byte2: u8 = @intCast(u8, self.width % 256);
const height_byte1: u8 = @intCast(u8, self.height / 256);
const height_byte2: u8 = @intCast(u8, self.height % 256);
std.log.info("{}·{} ; {}·{}", .{ width_byte1, width_byte2, height_byte1, height_byte2 });
try writer.writeByte(width_byte1);
try writer.writeByte(width_byte2);
try writer.writeByte(height_byte1);
try writer.writeByte(height_byte2);
}
// Write level content.
{
var i: u32 = 0;
while (i < self.width * self.height) : (i += 1) {
const cell: u16 = self.content[i];
const byte1: u8 = @intCast(u8, cell / 256);
const byte2: u8 = @intCast(u8, cell % 256);
try writer.writeByte(byte1);
try writer.writeByte(byte2);
}
}
}
/// Wrapper around `init_read`.
pub fn action_read(self: *Self, allocator: *std.mem.Allocator, kble_file_path: []const u8) void {
self.deinit(allocator);
self.* = Self.init_read(allocator, kble_file_path) catch unreachable;
}
/// Wrapper around `write`.
pub fn action_write(self: *Self, allocator: *std.mem.Allocator, kble_file_path: []const u8) void {
self.write(kble_file_path) catch unreachable;
}
/// Draw level tiles from `offset` to fill the window.
pub fn draw(self: *Self, scale: u16, offset: Vec2) void {
pub fn draw(self: Self, scale: u16, offset: Vec2) void {
// Pixel position (were we draw).
var x: Vec2.int_type = 0;
var y: Vec2.int_type = 0;
@ -103,7 +199,7 @@ pub fn draw(self: *Self, scale: u16, offset: Vec2) void {
}
/// Draw selection from `offset` to fill the window.
pub fn draw_selection(self: *Self, scale: u16, offset: Vec2) void {
pub fn draw_selection(self: Self, scale: u16, offset: Vec2) void {
// Pixel position (were we draw).
var x: Vec2.int_type = 0;
var y: Vec2.int_type = 0;
@ -217,11 +313,30 @@ test "select rectangle area" {
expect(!level.selection[245]);
}
test "load level from existing file" {
test "load level from sample file and save it" {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = &arena.allocator;
var level: Self = try Self.init_load(allocator, "sample.kble");
//defer level.deinit(allocator);
var level: Self = try Self.init_read(allocator, "sample.kble");
defer level.deinit(allocator);
try level.write("sample_out.kble");
}
test "write large level to file and load it back" {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = &arena.allocator;
var level: Self = try Self.init(allocator, 300, 1);
try level.write("sample_large.kble");
level.deinit(allocator);
level = try Self.init_read(allocator, "sample_large.kble");
defer level.deinit(allocator);
expect(level.width == 300);
expect(level.height == 1);
}

View File

@ -26,6 +26,9 @@ const Mode = @import("modes.zig").Mode;
const char_range = 255;
// TODO: make this a command line parameter.
const level_path = "sample.kble";
pub fn main() void {
// Create allocator
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
@ -83,6 +86,9 @@ pub fn main() void {
bindings['='] = &ActionsDef.scale_reset;
bindings['+'] = &ActionsDef.scale_up;
bindings['-'] = &ActionsDef.scale_down;
// File.
bindings['e'] = &ActionsDef.file_read;
bindings['w'] = &ActionsDef.file_write;
// Mode.
bindings['\n'] = &ActionsDef.mode_normal;
bindings['i'] = &ActionsDef.mode_select;
@ -154,6 +160,9 @@ pub fn main() void {
.scale => {
scale = action.function_scale(scale);
},
.file => {
action.function_file(&level, allocator, level_path);
},
.mode => {
// Rectangle selection!
if (mode == Mode.rectangle) {