diff --git a/assets/tiled/map.tmx b/assets/tiled/map.tmx
index bea0985..6ce0160 100644
--- a/assets/tiled/map.tmx
+++ b/assets/tiled/map.tmx
@@ -239,7 +239,7 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,98,98,98,98,98,98,0,0,0,0,0,0,0,0,
0,0,98,0,0,0,0,98,98,98,98,98,98,0,0,0,
-0,0,98,0,0,0,0,0,0,0,0,0,98,98,0,0
+0,0,98,0,0,8,42,0,12,0,0,0,98,98,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@@ -332,7 +332,7 @@
0,0,0,99,0,98,98,0,0,0,0,0,0,0,0,0
-0,0,98,0,72,0,7,0,0,0,0,40,0,98,0,0,
+0,0,98,0,72,0,7,7,0,0,0,40,0,98,0,0,
0,0,98,0,0,0,0,0,0,0,0,0,98,98,0,0,
0,0,98,98,98,98,98,98,98,98,98,98,98,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@@ -350,7 +350,7 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
-0,0,98,0,72,0,0,7,0,0,0,75,0,98,0,0,
+0,0,98,0,72,0,0,7,0,0,0,40,0,98,0,0,
0,0,98,0,0,0,0,7,0,0,0,0,98,98,0,0,
0,0,98,98,98,98,98,98,98,98,98,98,98,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@@ -368,7 +368,7 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
-0,98,0,72,0,0,7,0,0,0,75,0,98,0,0,98,
+0,98,0,72,0,0,7,0,0,0,40,0,98,0,0,98,
0,98,0,0,0,0,7,0,0,99,0,98,98,0,0,98,
0,98,98,98,98,98,98,98,98,98,98,98,0,0,0,98,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@@ -386,7 +386,7 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
-0,72,0,0,0,0,0,12,75,0,98,0,0,98,0,72,
+0,72,0,0,0,0,0,12,40,0,98,0,0,98,0,72,
0,0,0,0,0,0,0,99,0,98,98,0,0,98,0,0,
98,98,98,98,98,98,98,98,98,98,0,0,0,98,98,98,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@@ -404,7 +404,7 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
-0,7,7,0,12,12,75,0,98,0,0,98,0,72,0,0,
+0,7,7,0,12,12,40,0,98,0,0,98,0,72,0,0,
0,0,0,0,99,99,0,98,98,0,0,98,0,0,0,0,
98,98,98,98,98,98,98,98,0,0,0,98,98,98,98,98,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@@ -422,7 +422,7 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
-0,0,0,12,75,0,98,0,0,0,0,0,0,0,0,0,
+0,0,0,12,40,0,98,0,0,0,0,0,0,0,0,0,
7,0,0,99,0,98,98,0,0,0,0,0,0,0,0,0,
98,98,98,98,98,98,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
diff --git a/assets/tiled/tileset.tsx b/assets/tiled/tileset.tsx
index 2267c09..5b3d03e 100644
--- a/assets/tiled/tileset.tsx
+++ b/assets/tiled/tileset.tsx
@@ -3,26 +3,31 @@
+
+
+
+
+
@@ -31,11 +36,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -48,34 +65,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -91,6 +125,12 @@
+
+
+
+
+
+
diff --git a/src/app.zig b/src/app.zig
index d81e85c..137cb3f 100644
--- a/src/app.zig
+++ b/src/app.zig
@@ -21,6 +21,23 @@ const Tilesheet = @import("./tilesheet.zig");
const App = @This();
+const BoxColor = enum {
+ none,
+ brown,
+ red,
+ blue,
+ green,
+ gray,
+
+ var string_mapping = std.StaticStringMap(BoxColor).initComptime(.{
+ .{ "brown", .brown },
+ .{ "red", .red },
+ .{ "blue", .blue },
+ .{ "green", .green },
+ .{ "gray", .gray },
+ });
+};
+
const Direction = enum {
up,
down,
@@ -71,6 +88,7 @@ const Entity = struct {
pos: Point = .{ .x = -1, .y = -1 },
box_hole_id: Entity.Id = .nil,
+ color: BoxColor = .none,
const Type = enum {
nil,
@@ -139,26 +157,28 @@ const UndoFrame = struct {
};
const World = struct {
+ entities_buffer: [256]Entity.SlotMap.Slot,
entities: Entity.SlotMap,
width: u32,
height: u32,
+ level_number: i32,
- pub fn clone(self: World, gpa: Allocator) !World {
- var entity_slots = try self.entities.slots.clone(gpa);
- errdefer entity_slots.deinit(gpa);
-
- var entities = self.entities;
- entities.slots = entity_slots;
-
- return World{
- .width = self.width,
- .height = self.height,
- .entities = entities,
+ pub fn init(self: *World) void {
+ self.* = World{
+ .entities_buffer = undefined,
+ .entities = .initBuffer(&self.entities_buffer),
+ .width = 0,
+ .height = 0,
+ .level_number = 0
};
}
- pub fn deinit(self: *World, gpa: Allocator) void {
- self.entities.deinit(gpa);
+ pub fn copy(self: *World, other: World) void {
+ self.* = other;
+
+ const self_slots = self.entities_buffer[0..other.entities.slots.items.len];
+ self.entities.slots.items = self_slots;
+ @memcpy(self_slots, other.entities.slots.items);
}
fn spawnEntity(self: *World, opts: Entity) Entity.Id {
@@ -301,21 +321,28 @@ const TickContext = struct {
}
};
-const Level = struct {
- number: i32,
- world: World,
+const BoxSprites = struct {
+ regular: Gfx.Sprite.Id,
+ in_hole: Gfx.Sprite.Id,
+ darker: Gfx.Sprite.Id,
+
+ pub const nil = BoxSprites{
+ .regular = .nil,
+ .in_hole = .nil,
+ .darker = .nil,
+ };
};
world: World,
undo_stack: std.ArrayList(UndoFrame) = .empty,
current_level: u32 = 0,
-levels: std.ArrayList(Level),
+levels: std.ArrayList(World),
roboto_font: Font,
tilesheet: Tilesheet,
player_sprites: PlayerSprites,
-hole_with_box_sprite: Gfx.Sprite.Id,
+box_sprites_by_color: std.EnumArray(BoxColor, BoxSprites),
animation_timer: Platform.Nanoseconds,
footstep_buffer: Audio.BufferId,
@@ -386,19 +413,46 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
.animation_timer = 0,
.tilesheet = tilesheet,
.footstep_buffer = footstep_sound,
- .hole_with_box_sprite = tilesheet.get(6, 4),
+ .box_sprites_by_color = .init(.{
+ .none = .nil,
+ .brown = .{
+ .regular = tilesheet.get(6, 0),
+ .darker = tilesheet.get(6, 1),
+ .in_hole = tilesheet.get(6, 4),
+ },
+ .red = .{
+ .regular = tilesheet.get(7, 0),
+ .darker = tilesheet.get(7, 1),
+ .in_hole = tilesheet.get(7, 4),
+ },
+ .blue = .{
+ .regular = tilesheet.get(8, 0),
+ .darker = tilesheet.get(8, 1),
+ .in_hole = tilesheet.get(8, 4),
+ },
+ .green = .{
+ .regular = tilesheet.get(9, 0),
+ .darker = tilesheet.get(9, 1),
+ .in_hole = tilesheet.get(9, 4),
+ },
+ .gray = .{
+ .regular = tilesheet.get(10, 0),
+ .darker = tilesheet.get(10, 1),
+ .in_hole = tilesheet.get(10, 4),
+ }
+ }),
.sfx_bus = Audio.addBus(.{ .label = "sfx" }),
.world = undefined,
.levels = .empty,
};
self.levels = try self.loadLevels(plt.gpa, plt.assets);
- self.world = try self.levels.items[0].world.clone(plt.gpa);
+ self.world.copy(self.levels.items[0]);
return null;
}
-fn loadLevels(self: *App, gpa: Allocator, assets: *Platform.Assets) !std.ArrayList(Level) {
+fn loadLevels(self: *App, gpa: Allocator, assets: *Platform.Assets) !std.ArrayList(World) {
var xml_parser = try Tiled.xml.Parser.initCapacity(gpa, .{});
defer xml_parser.deinit(gpa);
@@ -418,9 +472,11 @@ fn loadLevels(self: *App, gpa: Allocator, assets: *Platform.Assets) !std.ArrayLi
.root
));
- var levels: std.ArrayList(Level) = .empty;
+ var levels: std.ArrayList(World) = .empty;
const level_bounds = tilemap.getLayer("Level bounds").?;
+ try levels.ensureTotalCapacity(gpa, level_bounds.variant.object.items.len);
+
for (level_bounds.variant.object.items) |obj| {
if (obj.shape != .rectangle) {
continue;
@@ -445,37 +501,32 @@ fn loadLevels(self: *App, gpa: Allocator, assets: *Platform.Assets) !std.ArrayLi
};
level_bound = tilemap.trimBoundsByEmptyTiles(level_bound);
- var level = try self.loadLevel(gpa, tilemap, tilesets, level_bound);
- level.number = level_index;
- try levels.append(gpa, level);
+ var level = levels.addOneAssumeCapacity();
+ try self.loadLevel(level, tilemap, tilesets, level_bound);
+ level.level_number = level_index;
}
const Context = struct {
- fn lessThan(_: @This(), lhs: Level, rhs: Level) bool {
- return lhs.number < rhs.number;
+ fn lessThan(_: @This(), lhs: World, rhs: World) bool {
+ return lhs.level_number < rhs.level_number;
}
};
- std.mem.sort(Level, levels.items, Context{}, Context.lessThan);
+ std.mem.sort(World, levels.items, Context{}, Context.lessThan);
return levels;
}
fn loadLevel(
self: *App,
- gpa: Allocator,
+ world: *World,
tilemap: Tiled.Tilemap,
tilesets: Tiled.Tileset.List,
bounds: Tiled.Tilemap.Bounds
-) !Level {
- var level = Level{
- .number = 0,
- .world = .{
- .width = bounds.width(),
- .height = bounds.height(),
- .entities = try .initCapacity(gpa, 256)
- }
- };
+) !void {
+ world.init();
+ world.width = bounds.width();
+ world.height = bounds.height();
const ground = tilemap.getLayer("Ground").?;
const foreground = tilemap.getLayer("Foreground").?;
@@ -487,7 +538,7 @@ fn loadLevel(
if (tilemap.getTile(ground, tilesets, x, y)) |tiled_tile| {
const pos = tiled_tile.getPosition();
- _ = level.world.spawnEntity(Entity{
+ _ = world.spawnEntity(Entity{
.type = .nil,
.visual_layer = .ground,
.pos = .{
@@ -502,29 +553,34 @@ fn loadLevel(
const pos = tiled_tile.getPosition();
const props = tiled_tile.getProperties();
const props_type = props.getString("type") orelse "";
+ const props_color = props.getString("color") orelse "";
var entity_type: ?Entity.Type = null;
var layer: Entity.Layer = .surface;
+ var color: BoxColor = .none;
if (std.mem.eql(u8, props_type, "wall")) {
entity_type = .wall;
} else if (std.mem.eql(u8, props_type, "box")) {
entity_type = .box;
+ color = BoxColor.string_mapping.get(props_color) orelse return error.UnknownBoxColor;
} else if (std.mem.eql(u8, props_type, "player")) {
entity_type = .player;
} else if (std.mem.eql(u8, props_type, "coin")) {
entity_type = .coin;
} else if (std.mem.eql(u8, props_type, "box_hole")) {
entity_type = .box_hole;
+ color = BoxColor.string_mapping.get(props_color) orelse .none;
layer = .on_ground;
} else if (std.mem.eql(u8, props_type, "box_goal")) {
entity_type = .box_goal;
layer = .on_ground;
+ color = BoxColor.string_mapping.get(props_color) orelse .none;
} else {
log.warn("Unknown tile type: '{s}' at {} {}", .{props_type, x, y});
}
if (entity_type != null) {
- _ = level.world.spawnEntity(Entity{
+ _ = world.spawnEntity(Entity{
.type = entity_type.?,
.visual_layer = layer,
.pos = .{
@@ -532,25 +588,26 @@ fn loadLevel(
.y = @intCast(oy),
},
.visual_pos = .initFromInt(usize, ox, oy),
- .sprite = self.tilesheet.get(pos.x, pos.y)
+ .sprite = self.tilesheet.get(pos.x, pos.y),
+ .color = color
});
}
}
}
}
-
- return level;
}
const PlayerInput = struct {
move_dir: Point,
- undo: bool
+ undo: bool,
+ restart: bool
};
fn getPlayerInput(input: *Platform.Input) PlayerInput {
var result: PlayerInput = .{
.move_dir = .{ .x = 0, .y = 0 },
- .undo = false
+ .undo = false,
+ .restart = false
};
if (input.isKeyPressedOrRepeat(.S)) {
@@ -570,6 +627,7 @@ fn getPlayerInput(input: *Platform.Input) PlayerInput {
}
result.undo = input.isKeyPressedOrRepeat(.Z);
+ result.restart = input.isKeyPressed(.R);
return result;
}
@@ -596,12 +654,14 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
}
}
+ if (input.restart) {
+ try self.setWorldByLevel(self.current_level);
+ }
+
var tick_context: TickContext = .{
.world = &self.world
};
- var goal_count: u32 = 0;
- var box_at_goal_count: u32 = 0;
{
var entity_iter = self.world.entities.iterator();
while (entity_iter.next()) |entity_id| {
@@ -620,12 +680,6 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
self.footstep_sound = Audio.play(.{ .buffer = self.footstep_buffer, .bus = self.sfx_bus });
}
- } else if (entity.type == .box_goal) {
- goal_count += 1;
-
- if (self.world.entityExists(.{ .pos = entity.pos, .type = .box })) {
- box_at_goal_count += 1;
- }
} else if (entity.type == .box) {
interpolate_pos = true;
}
@@ -662,6 +716,28 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
try self.undo_stack.append(plt.gpa, tick_context.undo_frame);
}
+ var goal_count: u32 = 0;
+ var box_at_goal_count: u32 = 0;
+ {
+ var goal_iter = self.world.iterEntities(.{ .type = .box_goal });
+ while (goal_iter.next()) |goal_id| {
+ const goal = self.world.entities.getAssumeExists(goal_id);
+ goal_count += 1;
+
+ if (self.world.findEntity(.{ .pos = goal.pos, .type = .box })) |box| {
+ var has_correct_color = true;
+ if (goal.color != .none) {
+ has_correct_color = goal.color == box.color;
+ }
+
+ const is_on_top = box.visual_pos.distance(goal.visual_pos) < 0.05;
+ if (is_on_top and has_correct_color) {
+ box_at_goal_count += 1;
+ }
+ }
+ }
+ }
+
inline for (.{ Entity.Layer.ground, Entity.Layer.on_ground, Entity.Layer.surface }) |layer| {
var entity_iter = self.world.entities.iterator();
while (entity_iter.next()) |entity_id| {
@@ -672,7 +748,7 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
var sprite = entity.sprite;
- const box_hole_margin = 0.2;
+ const box_hole_margin = 0.1;
if (entity.type == .box) {
const box_hole_id = self.world.findEntityId(.{ .type = .box_hole, .pos = entity.pos });
if (self.world.entities.get(box_hole_id)) |box_hole| {
@@ -680,12 +756,25 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
sprite = null;
}
}
+
+ if (self.world.findEntity(.{ .type = .box_goal, .pos = entity.pos })) |box_goal| {
+ if (box_goal.visual_pos.distance(entity.visual_pos) < 0.2) {
+ var is_correct = true;
+ if (box_goal.color != .none) {
+ is_correct = box_goal.color == entity.color;
+ }
+
+ if (is_correct) {
+ sprite = self.box_sprites_by_color.get(entity.color).darker;
+ }
+ }
+ }
} else if (entity.type == .box_hole) {
var box_iter = self.world.iterEntities(.{ .type = .box, .pos = entity.pos });
while (box_iter.next()) |box_id| {
const box = self.world.entities.getAssumeExists(box_id);
if (box.visual_pos.distance(entity.visual_pos) < box_hole_margin) {
- sprite = self.hole_with_box_sprite;
+ sprite = self.box_sprites_by_color.get(box.color).in_hole;
}
}
}
@@ -703,21 +792,19 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
if (goal_count > 0 and goal_count == box_at_goal_count and self.current_level+1 < self.levels.items.len) {
self.current_level += 1;
- self.world.deinit(plt.gpa);
- self.world = try self.levels.items[self.current_level].world.clone(plt.gpa);
- self.undo_stack.clearRetainingCapacity();
+ try self.setWorldByLevel(self.current_level);
}
}
+pub fn setWorldByLevel(self: *App, index: u32) !void {
+ self.world.copy(self.levels.items[index]);
+ self.undo_stack.clearRetainingCapacity();
+}
+
pub fn deinit(self: *App, plt: Platform.Deinit) void {
self.roboto_font.deinit(plt.gpa);
Gfx.deinitSpritesheet(self.roboto_font.spritesheet);
- self.world.deinit(plt.gpa);
-
- for (self.levels.items) |*level| {
- level.world.deinit(plt.gpa);
- }
self.levels.deinit(plt.gpa);
self.undo_stack.deinit(plt.gpa);
}