diff --git a/src/assets/images/exit_portal.png b/src/assets/images/exit_portal.png new file mode 100644 index 0000000..da94d05 Binary files /dev/null and b/src/assets/images/exit_portal.png differ diff --git a/src/engine.zig b/src/engine.zig index 8828591..c50dca4 100644 --- a/src/engine.zig +++ b/src/engine.zig @@ -7,6 +7,8 @@ pub const Object = @import("engine/object.zig").Object; pub const OpaquePtr = @import("engine/opaque_ptr.zig").OpaquePtr; pub const Point = @import("engine/point.zig").Point; pub const PointF = @import("engine/point_f.zig").PointF; +pub const pt = Point.init; +pub const ptF = PointF.init; pub const Rectangle = @import("engine/rectangle.zig").Rectangle; pub const RectangleF = @import("engine/rectangle_f.zig").RectangleF; pub const Scene = @import("engine/scene.zig").Scene; diff --git a/src/engine/point.zig b/src/engine/point.zig index 6991f34..f9a62f2 100644 --- a/src/engine/point.zig +++ b/src/engine/point.zig @@ -5,6 +5,7 @@ pub const Point = struct { x: i64, y: i64, + pub const One = Point{ .x = 1, .y = 1 }; pub const Zero = Point{ .x = 0, .y = 0 }; pub fn init(x: i64, y: i64) Point { diff --git a/src/engine/point_f.zig b/src/engine/point_f.zig index e606395..bb81350 100644 --- a/src/engine/point_f.zig +++ b/src/engine/point_f.zig @@ -6,6 +6,8 @@ pub const PointF = struct { x: f32, y: f32, + pub const Half = PointF{ .x = 0.5, .y = 0.5 }; + pub const One = PointF{ .x = 1, .y = 1 }; pub const Zero = PointF{ .x = 0, .y = 0 }; pub fn init(x: f32, y: f32) PointF { diff --git a/src/game/game.zig b/src/game/game.zig index cc40398..f3cadbe 100644 --- a/src/game/game.zig +++ b/src/game/game.zig @@ -12,12 +12,18 @@ pub const Game = struct { Right, }; + pub const State = enum { + InProgress, + Over, + Completed, + }; + pub const TileSize = 32; prng: std.rand.DefaultPrng = std.rand.DefaultPrng.init(0), level: Level, - isOver: bool = false, + state: State = .InProgress, starsCollected: usize = 0, starsTotal: usize, @@ -70,7 +76,7 @@ pub const Game = struct { } pub fn drawSpriteFrame(self: *Game, spriteName: []const u8, frame: usize, x: i64, y: i64) void { - self.drawSpriteFrameP(spriteName, frame, engine.Point.init(x, y).float()); + self.drawSpriteFrameP(spriteName, frame, engine.pt(x, y).float()); } pub fn drawSpriteFrameP(self: *Game, spriteName: []const u8, frame: usize, position: engine.PointF) void { @@ -87,13 +93,13 @@ pub const Game = struct { } pub fn movePlayer(self: *Game, dt: f32) void { - if (self.isOver) return; + if (self.state != .InProgress) return; const to = self.playerPosition.add(self.playerVelocity.multiply(dt)); self.playerPosition = to; if (self.playerPosition.y > 21.5) { - self.isOver = true; + self.state = .Over; return; } @@ -124,21 +130,22 @@ pub const Game = struct { } self.playerDirection = if (self.playerVelocity.x == 0) self.playerDirection else if (self.playerVelocity.x > 0) Direction.Right else Direction.Left; - const playerCenter = self.playerPosition.add(engine.PointF.init(0.5, 0.5)).floor().integer(); + const playerCenter = self.playerPosition.add(engine.PointF.Half).floor().integer(); var dx: i64 = -1; while (dx <= 1) : (dx += 1) { var dy: i64 = -1; while (dy <= 1) : (dy += 1) { const x = playerCenter.x + dx; const y = playerCenter.y + dy; - const collectablePosition = engine.Point.init(x, y); + const collectablePosition = engine.pt(x, y); if (self.level.collectables.get(x, y)) |collectable| { if (self.collected.hasValue(x, y, true)) continue; if (self.playerPosition.distance2(collectablePosition.float()) > 1.5) continue; switch (collectable) { - Level.Collectable.Star => { + .Exit => self.state = .Completed, + .Star => { self.starsCollected += 1; self.collected.set(x, y, true) catch {}; }, @@ -177,25 +184,25 @@ pub const Game = struct { // return values are in view coordinates pub fn tile(self: Game, x: f32, y: f32) engine.PointF { - return self.tileP(engine.PointF.init(x, y)); + return self.tileP(engine.ptF(x, y)); } pub fn tileCenter(self: Game, x: i64, y: i64) engine.PointF { - return self.tileCenterP(engine.Point.init(x, y)); + return self.tileCenterP(engine.pt(x, y)); } pub fn tileCenterP(self: Game, position: engine.Point) engine.PointF { - const center = engine.PointF.init(0.5, 0.5); + const center = engine.PointF.Half; return self.tileP(position.float().add(center)); } pub fn tileP(self: Game, position: engine.PointF) engine.PointF { const relative = position.subtract(self.boundsInTiles.min); - return engine.PointF.init(relative.x / self.boundsInTiles.width(), relative.y / self.boundsInTiles.height()); + return engine.ptF(relative.x / self.boundsInTiles.width(), relative.y / self.boundsInTiles.height()); } pub fn tileTopLeft(self: Game, x: i64, y: i64) engine.PointF { - return self.tileTopLeftP(engine.Point.init(x, y)); + return self.tileTopLeftP(engine.pt(x, y)); } pub fn tileTopLeftP(self: Game, p: engine.Point) engine.PointF { diff --git a/src/game/level.zig b/src/game/level.zig index eacafbe..20ca36e 100644 --- a/src/game/level.zig +++ b/src/game/level.zig @@ -8,6 +8,7 @@ pub const Level = struct { }; pub const Collectable = enum { + Exit, Star, }; @@ -44,9 +45,10 @@ pub const Level = struct { for (line, 0..) |tile, column| { var x = @intCast(i64, column); switch (tile) { - 'P' => character = engine.Point.init(x, y), - 'S' => try collectables.set(x, y, Collectable.Star), - 'x' => try tiles.set(x, y, Tile.Grass), + 'P' => character = engine.pt(x, y), + 'S' => try collectables.set(x, y, .Star), + 'E' => try collectables.set(x, y, .Exit), + 'x' => try tiles.set(x, y, .Grass), else => {}, } } diff --git a/src/game_scene.zig b/src/game_scene.zig index ad85c63..d6b822f 100644 --- a/src/game_scene.zig +++ b/src/game_scene.zig @@ -24,7 +24,7 @@ pub const GameScene = struct { } pub fn handle(self: *GameScene, ctx: *Context, event: *allegro.Event) !void { - if (self.game.isOver) { + if (self.game.state != .InProgress) { switch (event.type) { allegro.c.ALLEGRO_EVENT_KEY_DOWN => { switch (event.keyboard.keycode) { @@ -50,7 +50,7 @@ pub const GameScene = struct { const maxFallVelocity: f32 = 23; const maxHorizontalWalkVelocity: f32 = 7; // tiles/s - if (self.game.isOver) { + if (self.game.state != .InProgress) { self.game.playerIdleAnimation.tick(t); return; } @@ -116,13 +116,16 @@ pub const GameScene = struct { const randomDirtOffset = self.game.randomTileOffset(x, 99, 3); self.game.drawSpriteFrame("tiles_dirt_32", 0 + randomDirtOffset, x, 19); self.game.drawSpriteFrame("tiles_dirt_32", 3 + randomDirtOffset, x, 20); + } - var y = tileBounds.min.y; - while (y <= tileBounds.max.y) : (y += 1) { + var y = tileBounds.min.y; + while (y <= tileBounds.max.y) : (y += 1) { + x = tileBounds.min.x; + while (x < tileBounds.max.x) : (x += 1) { if (tiles.get(x, y)) |tile| { switch (tile) { - game.Level.Tile.Grass => { - const ordinals = tiles.getOrdinals(x, y, game.Level.Tile.Grass); + .Grass => { + const ordinals = tiles.getOrdinals(x, y, .Grass); var offset: usize = 17; switch (ordinals.count()) { 1 => { @@ -156,10 +159,14 @@ pub const GameScene = struct { if (self.game.collected.hasValue(x, y, true)) continue; switch (collectable) { - game.Level.Collectable.Star => { - const distanceToPlayer = engine.Point.init(x, y).float().distance(self.game.playerPosition); + .Star => { + const distanceToPlayer = engine.pt(x, y).float().distance(self.game.playerPosition); self.game.drawSpriteFrame("item_star_32", self.game.starAnimation.currentOffset(@floatToInt(usize, @mod(distanceToPlayer * 0.54, 1) * 16)), x, y); }, + .Exit => { + const position = self.game.tileP(engine.pt(x, y).float().subtract(engine.ptF(0.5, 1))); + renderer.drawTextureV("exit_portal", position.x, position.y); + }, } } } @@ -167,11 +174,11 @@ pub const GameScene = struct { const playerDirectionFrameOffset: usize = if (self.game.playerDirection == game.Game.Direction.Left) 0 else 12; if (self.game.playerVelocity.y != 0) { - self.game.drawSpriteFrameP("character_lion_48", self.game.playerFallingAnimation.current + playerDirectionFrameOffset, self.game.playerPosition.add(engine.PointF.init(-0.25, -0.25))); + self.game.drawSpriteFrameP("character_lion_48", self.game.playerFallingAnimation.current + playerDirectionFrameOffset, self.game.playerPosition.add(engine.ptF(-0.25, -0.25))); } else if (self.game.playerVelocity.x != 0 and self.game.playerVelocity.y == 0) { - self.game.drawSpriteFrameP("character_lion_48", self.game.playerWalkingAnimation.current + playerDirectionFrameOffset, self.game.playerPosition.add(engine.PointF.init(-0.25, -0.25))); + self.game.drawSpriteFrameP("character_lion_48", self.game.playerWalkingAnimation.current + playerDirectionFrameOffset, self.game.playerPosition.add(engine.ptF(-0.25, -0.25))); } else { - self.game.drawSpriteFrameP("character_lion_48", self.game.playerIdleAnimation.current + playerDirectionFrameOffset, self.game.playerPosition.add(engine.PointF.init(-0.25, -0.25))); + self.game.drawSpriteFrameP("character_lion_48", self.game.playerIdleAnimation.current + playerDirectionFrameOffset, self.game.playerPosition.add(engine.ptF(-0.25, -0.25))); } ctx.renderer.printTextV("default", ctx.palette.background.text, 0.01, 0.01, .Left, "Stars collected: {d}", .{self.game.starsCollected}); @@ -181,15 +188,25 @@ pub const GameScene = struct { ctx.renderer.drawTintedSpriteFrameV("pie_charts_24", percentage - 1, ctx.palette.background.text, 0.97, 0.01); } - if (self.game.isOver) { - const textColor = allegro.mapRgb(0x48, 0x91, 0x00); + const textColor = allegro.mapRgb(0x48, 0x91, 0x00); + + if (self.game.state != .InProgress) { ctx.renderer.textures.get("opaque").?.drawTintedScaled(allegro.mapRgba(0, 0, 0, 191), 0, 0, @intToFloat(f32, ctx.renderer.display.width()), @intToFloat(f32, ctx.renderer.display.height())); - ctx.renderer.drawTextV("default", textColor, 0.5, 0.2, .Center, "Game over"); - ctx.renderer.drawSpriteFrameV("text_balloons", 0, 0.48, 0.40); - ctx.renderer.drawTextV("small", ctx.palette.background.text, 0.547, 0.436, .Center, "Do you want to"); - ctx.renderer.drawTextV("small", ctx.palette.background.text, 0.547, 0.465, .Center, "try again?"); - ctx.renderer.drawSpriteFrameV("character_lion_48", self.game.playerIdleAnimation.current + 12, 0.44, 0.57); - ctx.renderer.drawTextV("small", textColor, 0.5, 0.7, .Center, "[enter] try again | [escape] quit"); + + if (self.game.state == .Over) { + ctx.renderer.drawTextV("default", textColor, 0.5, 0.2, .Center, "Game over"); + } + if (self.game.state == .Completed) { + ctx.renderer.drawTextV("default", textColor, 0.5, 0.2, .Center, "Level completed"); + } + + if (self.game.starsCollected < self.game.starsTotal) { + ctx.renderer.drawSpriteFrameV("text_balloons", 0, 0.45, 0.40); + ctx.renderer.drawTextV("small", ctx.palette.background.text, 0.517, 0.436, .Center, "Do you want to"); + ctx.renderer.drawTextV("small", ctx.palette.background.text, 0.517, 0.465, .Center, "try again?"); + ctx.renderer.drawSpriteFrameV("character_lion_48", self.game.playerIdleAnimation.current + 12, 0.41, 0.57); + ctx.renderer.drawTextV("small", textColor, 0.5, 0.7, .Center, "[enter] try again | [escape] quit"); + } } if (ctx.showDebug) { diff --git a/src/main.zig b/src/main.zig index c0153c7..8c303ae 100644 --- a/src/main.zig +++ b/src/main.zig @@ -44,6 +44,7 @@ pub fn main() !void { allegro.setNewBitmapFlags(allegro.NewBitmapFlags{ .MIN_LINEAR = false, .MAG_LINEAR = false }); try renderer.textures.addFromFile("character_lion_48", paths.AssetsDir ++ "/images/character_lion_48.png"); + try renderer.textures.addFromFile("exit_portal", paths.AssetsDir ++ "/images/exit_portal.png"); try renderer.textures.addFromFile("item_star_32", paths.AssetsDir ++ "/images/item_star_32.png"); try renderer.textures.addFromFile("pie_charts_24", paths.AssetsDir ++ "/images/pie_charts_24.png"); try renderer.textures.addFromFile("text_balloons", paths.AssetsDir ++ "/images/text_balloons.png"); diff --git a/src/renderer.zig b/src/renderer.zig index e15d6e0..8d9fadb 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -62,6 +62,17 @@ pub const Renderer = struct { } } + pub fn drawTexture(self: Renderer, textureName: []const u8, x: f32, y: f32) void { + if (self.textures.get(textureName)) |texture| { + texture.drawScaledUniform(x, y, self.viewport.scale); + } + } + + pub fn drawTextureV(self: Renderer, textureName: []const u8, x: f32, y: f32) void { + const screen = self.viewport.viewToScreen(x, y); + self.drawTexture(textureName, screen.x, screen.y); + } + pub fn drawTextV(self: Renderer, fontName: []const u8, color: allegro.Color, x: f32, y: f32, alignment: TextAlignment, text: [*:0]const u8) void { const screen = self.viewport.viewToScreen(x, y); self.drawText(fontName, color, screen.x, screen.y, alignment, text);