├── .gitignore ├── assets └── title_screen_bg.png ├── wapm.toml ├── LICENSE.md ├── src ├── main_wasm4.zig ├── sfx.zig ├── platform_wasm4.zig ├── bresenham.zig ├── util.zig ├── wasm4.zig ├── world.zig ├── data.zig ├── game.zig └── gfx.zig ├── README.md ├── flake.nix └── flake.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /.zig-cache 2 | /zig-out 3 | /node_modules -------------------------------------------------------------------------------- /assets/title_screen_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hazeycode/escape-guldur/HEAD/assets/title_screen_bg.png -------------------------------------------------------------------------------- /wapm.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hazeycode/escape-guldur" 3 | version = "0.3.0-dev" 4 | description = "Retro action-rpg game written in Zig" 5 | readme = "README.md" 6 | repository = "https://github.com/hazeycode/escape-guldur/" 7 | 8 | [[module]] 9 | name = "cart" 10 | source = "./zig-out/bin/cart_opt.wasm" 11 | abi = "wasm4" 12 | interfaces = { wasm4 = "2.7.1" } 13 | 14 | [[command]] 15 | runner = "wasm4@2.7.1" 16 | name = "play" 17 | module = "cart" 18 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Chris Heyes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main_wasm4.zig: -------------------------------------------------------------------------------- 1 | const w4 = @import("platform").w4; 2 | 3 | const game = @import("game.zig"); 4 | 5 | var prev_gamepad: u8 = 0; 6 | 7 | export fn start() void { 8 | game.init(); 9 | } 10 | 11 | export fn update() void { 12 | const gamepad = w4.GAMEPAD1.*; 13 | const pressed = gamepad & (gamepad ^ prev_gamepad); 14 | prev_gamepad = gamepad; 15 | 16 | game.update( 17 | game.ButtonState{ 18 | .left = (gamepad & w4.BUTTON_LEFT > 0), 19 | .right = (gamepad & w4.BUTTON_RIGHT > 0), 20 | .up = (gamepad & w4.BUTTON_UP > 0), 21 | .down = (gamepad & w4.BUTTON_DOWN > 0), 22 | .action_1 = (gamepad & w4.BUTTON_1 > 0), 23 | .action_2 = (gamepad & w4.BUTTON_2 > 0), 24 | }, 25 | game.ButtonsPressed{ 26 | .left = (pressed & w4.BUTTON_LEFT > 0), 27 | .right = (pressed & w4.BUTTON_RIGHT > 0), 28 | .up = (pressed & w4.BUTTON_UP > 0), 29 | .down = (pressed & w4.BUTTON_DOWN > 0), 30 | .action_1 = (pressed & w4.BUTTON_1 > 0), 31 | .action_2 = (pressed & w4.BUTTON_2 > 0), 32 | }, 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Escape Guldur 2 | 3 | A minimalistic "retro action RPG" for the [WASM-4](https://wasm4.org/) fantasy console. 4 | 5 | Originally made for [WASM-4 Jam #2](https://itch.io/jam/wasm4-v2). 6 | 7 | [Play on itch.io!](https://hazeycode.itch.io/escape-guldur) 8 | 9 |

10 | screenshot 11 | screenshot 12 |

13 | 14 | ## Building 15 | 16 | To start a dev shell using [Nix](https://nixos.org), just type: 17 | ```shell 18 | nix develop 19 | ``` 20 | 21 | Build and run a native (debug) executable: 22 | ```shell 23 | zig build run-native 24 | ``` 25 | 26 | Produce a size-optimised release build (zig-out/lib/opt.wasm): 27 | ```shell 28 | zig build release -Doptimize=ReleaseSmall 29 | ``` 30 | 31 | Load and run in your browser: 32 | ```shell 33 | w4 run ./zig-out/bin/cart_opt.wasm 34 | ``` 35 | 36 | ## Distribution 37 | Bundle into an HTML file for publishing: 38 | ```shell 39 | w4 bundle ./zig-out/bin/cart_opt.wasm --title "Escape Guldur" --html ./escape_guldur.html 40 | ``` 41 | 42 | To publish to wasmer.io, first update wapm.toml. Then remember your username and password and use `wapm`: 43 | ```shell 44 | wapm login 45 | wapm publish 46 | ``` 47 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Ludutra"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | zig.url = "github:mitchellh/zig-overlay"; 8 | }; 9 | 10 | outputs = { self, nixpkgs, flake-utils, zig }: 11 | flake-utils.lib.eachDefaultSystem (system: 12 | let 13 | pkgs = import nixpkgs { 14 | inherit system; 15 | overlays = [ 16 | zig.overlays.default 17 | ]; 18 | }; 19 | in { 20 | devShell = pkgs.mkShell { 21 | buildInputs = with pkgs; [ 22 | zigpkgs."0.14.1" 23 | nodejs 24 | binaryen 25 | wapm 26 | ]; 27 | shellHook = '' 28 | # Install w4 using npm if it's not already installed 29 | if [ ! -d "node_modules/.bin" ] || [ ! -f "node_modules/.bin/w4" ]; then 30 | echo "Installing wasm4 CLI..." 31 | npm install --no-save wasm4@2.7.1 32 | fi 33 | 34 | export PATH="$PWD/node_modules/.bin:$PATH" 35 | 36 | echo "" 37 | echo "Welcome to the Escape Guldur dev shell..." 38 | echo "" 39 | echo " zig $(zig version)" 40 | echo " w4 $(w4 --version)" 41 | echo " $(wasm-opt --version)" 42 | echo " $(wapm --version)" 43 | echo "" 44 | ''; 45 | }; 46 | } 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/sfx.zig: -------------------------------------------------------------------------------- 1 | const play_tone = @import("platform").AudioPlayback.play_tone; 2 | 3 | pub fn walk() void { 4 | play_tone(.{ 5 | .channel = .triangle, 6 | .freq1 = 100, 7 | .freq2 = 230, 8 | .sustain = 2, 9 | .release = 4, 10 | .volume_sustain = 90, 11 | .volume_peak = 90, 12 | }); 13 | } 14 | 15 | pub fn pickup() void { 16 | play_tone(.{ 17 | .channel = .pulse1, 18 | .duty_cycle = .half, 19 | .freq1 = 200, 20 | .freq2 = 4400, 21 | .attack = 2, 22 | .decay = 1, 23 | .sustain = 8, 24 | .release = 4, 25 | .volume_sustain = 50, 26 | .volume_peak = 90, 27 | }); 28 | } 29 | 30 | pub fn deal_damage() void { 31 | play_tone(.{ 32 | .channel = .pulse1, 33 | .freq1 = 600, 34 | .freq2 = 220, 35 | .sustain = 2, 36 | .release = 4, 37 | }); 38 | play_tone(.{ 39 | .channel = .noise, 40 | .freq1 = 200, 41 | .freq2 = 200, 42 | .sustain = 2, 43 | .volume_sustain = 70, 44 | .volume_peak = 70, 45 | }); 46 | } 47 | 48 | pub fn receive_damage() void { 49 | play_tone(.{ 50 | .channel = .noise, 51 | .freq1 = 300, 52 | .freq2 = 300, 53 | .sustain = 2, 54 | .release = 4, 55 | .volume_sustain = 80, 56 | .volume_peak = 80, 57 | }); 58 | play_tone(.{ 59 | .channel = .triangle, 60 | .freq1 = 300, 61 | .freq2 = 100, 62 | .sustain = 2, 63 | .release = 4, 64 | }); 65 | } 66 | 67 | pub fn destroy_wall() void { 68 | play_tone(.{ 69 | .channel = .pulse2, 70 | .duty_cycle = .quarter, 71 | .freq1 = 70, 72 | .freq2 = 70, 73 | .sustain = 8, 74 | .release = 4, 75 | .volume_sustain = 50, 76 | .volume_peak = 50, 77 | }); 78 | play_tone(.{ 79 | .channel = .noise, 80 | .freq1 = 200, 81 | .freq2 = 200, 82 | .sustain = 8, 83 | .release = 4, 84 | .volume_sustain = 80, 85 | .volume_peak = 80, 86 | }); 87 | play_tone(.{ 88 | .channel = .pulse1, 89 | .freq1 = 90, 90 | .freq2 = 90, 91 | .sustain = 8, 92 | .release = 4, 93 | .volume_sustain = 70, 94 | .volume_peak = 70, 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /src/platform_wasm4.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const w4 = @import("wasm4.zig"); 4 | 5 | pub const screen_width = w4.SCREEN_SIZE; 6 | pub const screen_height = w4.SCREEN_SIZE; 7 | 8 | pub inline fn trace(str: []const u8) void { 9 | w4.trace(str); 10 | } 11 | 12 | pub inline fn tracef(template: [*:0]const u8, args: anytype) void { 13 | w4.tracef(template, args); 14 | } 15 | 16 | pub inline fn set_palette(palette: [4]u32) void { 17 | w4.PALETTE.* = palette; 18 | } 19 | 20 | pub inline fn set_draw_colours(colours: u16) void { 21 | w4.DRAW_COLORS.* = colours; 22 | } 23 | 24 | pub inline fn text(str: []const u8, x: i32, y: i32) void { 25 | w4.text(str, x, y); 26 | } 27 | 28 | pub inline fn line(x0: i32, y0: i32, x1: i32, y1: i32) void { 29 | w4.line(x0, y0, x1, y1); 30 | } 31 | 32 | pub inline fn hline(x: i32, y: i32, len: u32) void { 33 | w4.hline(x, y, len); 34 | } 35 | 36 | pub inline fn vline(x: i32, y: i32, len: u32) void { 37 | w4.vline(x, y, len); 38 | } 39 | 40 | pub inline fn rect(x: i32, y: i32, width: u32, height: u32) void { 41 | w4.rect(x, y, width, height); 42 | } 43 | 44 | pub inline fn oval(x: i32, y: i32, width: u32, height: u32) void { 45 | w4.oval(x, y, width, height); 46 | } 47 | 48 | pub fn blit( 49 | texture_bytes: []const u8, 50 | x: i32, 51 | y: i32, 52 | width: u32, 53 | height: u32, 54 | bpp: u8, 55 | flip_x: bool, 56 | ) void { 57 | var flags: u32 = 0; 58 | switch (bpp) { 59 | 1 => { 60 | flags |= w4.BLIT_1BPP; 61 | }, 62 | 2 => { 63 | flags |= w4.BLIT_2BPP; 64 | }, 65 | else => { 66 | std.debug.assert(false); 67 | }, 68 | } 69 | if (flip_x) flags |= w4.BLIT_FLIP_X; 70 | w4.blit(@ptrCast(texture_bytes), x, y, width, height, flags); 71 | } 72 | 73 | pub const AudioPlayback = struct { 74 | pub fn play_tone(args: struct { 75 | channel: enum { pulse1, pulse2, triangle, noise }, 76 | duty_cycle: enum { eighth, quarter, half, three_quarter } = .eighth, 77 | freq1: u32, 78 | freq2: u32, 79 | attack: u32 = 0, 80 | decay: u32 = 0, 81 | sustain: u32 = 0, 82 | release: u32 = 0, 83 | volume_sustain: u32 = 100, 84 | volume_peak: u32 = 100, 85 | }) void { 86 | var flags: u32 = switch (args.channel) { 87 | .pulse1 => w4.TONE_PULSE1, 88 | .pulse2 => w4.TONE_PULSE2, 89 | .triangle => w4.TONE_TRIANGLE, 90 | .noise => w4.TONE_NOISE, 91 | }; 92 | flags |= switch (args.duty_cycle) { 93 | .eighth => w4.TONE_MODE1, 94 | .quarter => w4.TONE_MODE2, 95 | .half => w4.TONE_MODE3, 96 | .three_quarter => w4.TONE_MODE4, 97 | }; 98 | w4.tone( 99 | args.freq1 | (args.freq2 << 16), 100 | (args.attack << 24) | (args.decay << 16) | (args.sustain) | (args.release << 8), 101 | args.volume_sustain | (args.volume_peak << 16), 102 | flags, 103 | ); 104 | } 105 | }; 106 | -------------------------------------------------------------------------------- /src/bresenham.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | /// A piece-wise implementaion of Bresenham's line algorithm (https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm) 4 | /// `plotter` should be something defining a `plot(x, y) bool` member function that returns false if plotting should stop 5 | pub fn line(x0: i32, y0: i32, x1: i32, y1: i32, plotter: anytype) void { 6 | var x = x0; 7 | var y = y0; 8 | const dx = abs(x1 - x0); 9 | const sx: i32 = if (x0 < x1) 1 else -1; 10 | const dy = -abs(y1 - y0); 11 | const sy: i32 = if (y0 < y1) 1 else -1; 12 | var err = dx + dy; 13 | 14 | while (true) { 15 | if (plotter.plot(x, y) == false) { 16 | return; 17 | } 18 | 19 | if (x0 == x1 and y0 == y1) break; 20 | 21 | const e2 = 2 * err; 22 | 23 | if (e2 >= dy) { 24 | if (x == x1) break; 25 | err = err + dy; 26 | x = x + sx; 27 | } 28 | 29 | if (e2 < dx) { 30 | if (y == y1) break; 31 | err = err + dx; 32 | y = y + sy; 33 | } 34 | } 35 | } 36 | 37 | fn abs(x: anytype) @TypeOf(x) { 38 | return if (x < 0) -x else x; 39 | } 40 | 41 | const testing = std.testing; 42 | 43 | test "bresenham.line" { 44 | const Point = struct { x: i32, y: i32 }; 45 | 46 | var plotter = struct { 47 | points: std.ArrayList(Point), 48 | 49 | pub fn plot(self: *@This(), x: i32, y: i32) bool { 50 | self.points.append(.{ .x = x, .y = y }) catch @panic("out of memory"); 51 | return true; 52 | } 53 | }{ 54 | .points = std.ArrayList(Point).init(testing.allocator), 55 | }; 56 | defer plotter.points.deinit(); 57 | 58 | { 59 | line(0, 0, 2, -1, &plotter); 60 | defer plotter.points.clearRetainingCapacity(); 61 | 62 | try testing.expectEqualSlices( 63 | Point, 64 | &.{ 65 | Point{ .x = 0, .y = 0 }, 66 | Point{ .x = 1, .y = 0 }, 67 | Point{ .x = 2, .y = -1 }, 68 | }, 69 | plotter.points.items, 70 | ); 71 | } 72 | 73 | { 74 | line(0, 0, -2, -2, &plotter); 75 | defer plotter.points.clearRetainingCapacity(); 76 | 77 | try testing.expectEqualSlices( 78 | Point, 79 | &.{ 80 | Point{ .x = 0, .y = 0 }, 81 | Point{ .x = -1, .y = -1 }, 82 | Point{ .x = -2, .y = -2 }, 83 | }, 84 | plotter.points.items, 85 | ); 86 | } 87 | 88 | { 89 | line(0, 0, -2, 3, &plotter); 90 | defer plotter.points.clearRetainingCapacity(); 91 | 92 | try testing.expectEqualSlices( 93 | Point, 94 | &.{ 95 | Point{ .x = 0, .y = 0 }, 96 | Point{ .x = -1, .y = 1 }, 97 | Point{ .x = -1, .y = 2 }, 98 | Point{ .x = -2, .y = 3 }, 99 | }, 100 | plotter.points.items, 101 | ); 102 | } 103 | 104 | { 105 | line(0, 0, 3, 5, &plotter); 106 | defer plotter.points.clearRetainingCapacity(); 107 | 108 | try testing.expectEqualSlices( 109 | Point, 110 | &.{ 111 | Point{ .x = 0, .y = 0 }, 112 | Point{ .x = 1, .y = 1 }, 113 | Point{ .x = 1, .y = 2 }, 114 | Point{ .x = 2, .y = 3 }, 115 | Point{ .x = 2, .y = 4 }, 116 | Point{ .x = 3, .y = 5 }, 117 | }, 118 | plotter.points.items, 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/util.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn quicksort( 4 | values: anytype, 5 | low: isize, 6 | high: isize, 7 | comparitor: anytype, 8 | ) void { 9 | if (low >= high) return; 10 | const p = partition(values, low, high, comparitor); 11 | quicksort(values, low, p - 1, comparitor); 12 | quicksort(values, p + 1, high, comparitor); 13 | } 14 | 15 | fn partition( 16 | values: anytype, 17 | low: isize, 18 | high: isize, 19 | comparitor: anytype, 20 | ) isize { 21 | const pivot = values[@intCast(high)]; 22 | var i = low - 1; 23 | var j: usize = @intCast(low); 24 | while (j < high) : (j += 1) { 25 | if (comparitor.compare(values[j], pivot)) { 26 | i += 1; 27 | swap(values, @intCast(i), j); 28 | } 29 | } 30 | i += 1; 31 | swap(values, @intCast(i), @intCast(high)); 32 | return i; 33 | } 34 | 35 | fn swap(values: anytype, i: usize, j: usize) void { 36 | const temp = values[i]; 37 | values[i] = values[j]; 38 | values[j] = temp; 39 | } 40 | 41 | pub fn NumberDigitIterator(comptime T: type) type { 42 | return struct { 43 | number: T, 44 | n: T, 45 | m: T, 46 | i: usize, 47 | 48 | pub fn init(number: T) @This() { 49 | return .{ 50 | .number = number, 51 | .n = number, 52 | .m = 0, 53 | .i = count_digits_fast(number), 54 | }; 55 | } 56 | 57 | pub fn next(self: *@This()) ?u8 { 58 | if (self.i == 0) return null; 59 | defer self.i -= 1; 60 | 61 | self.n = @divTrunc( 62 | self.number - self.m, 63 | std.math.pow(T, 10, @as(T, @intCast(self.i)) - 1), 64 | ); 65 | 66 | self.m += self.n * std.math.pow(T, 10, @as(T, @intCast(self.i)) - 1); 67 | 68 | return @as(u8, @truncate(self.n)); 69 | } 70 | }; 71 | } 72 | 73 | pub fn count_digits_fast(number: anytype) usize { 74 | const n = if (number < 0) -number else number; 75 | return @as(usize, switch (n) { 76 | 0...9 => 1, 77 | 10...99 => 2, 78 | 100...999 => 3, 79 | 1000...9999 => 4, 80 | 10000...99999 => 5, 81 | else => unreachable, 82 | }) + @as(usize, if (number < 0) 1 else 0); 83 | } 84 | 85 | // 86 | 87 | const testing = std.testing; 88 | 89 | test "quicksort" { 90 | const comparitor = struct { 91 | pub fn compare(_: @This(), a: u32, b: u32) bool { 92 | return a < b; 93 | } 94 | }{}; 95 | 96 | { 97 | var values = [_]u32{ 3, 7, 4, 234, 4, 19, 19 }; 98 | quicksort(&values, 0, values.len - 1, comparitor); 99 | 100 | const expected = [_]u32{ 3, 4, 4, 7, 19, 19, 234 }; 101 | 102 | try testing.expectEqualSlices(u32, &expected, &values); 103 | } 104 | { 105 | var values = [_]u32{ 3, 3 }; 106 | quicksort(&values, 0, values.len - 1, comparitor); 107 | 108 | const expected = [_]u32{ 3, 3 }; 109 | 110 | try testing.expectEqualSlices(u32, &expected, &values); 111 | } 112 | } 113 | 114 | test "NumberDigitIterator" { 115 | var digit_iter = NumberDigitIterator(u32).init(1234); 116 | var i: usize = 0; 117 | while (digit_iter.next()) |digit| { 118 | switch (i) { 119 | 0 => try testing.expectEqual(@as(u8, 1), digit), 120 | 1 => try testing.expectEqual(@as(u8, 2), digit), 121 | 2 => try testing.expectEqual(@as(u8, 3), digit), 122 | 3 => try testing.expectEqual(@as(u8, 4), digit), 123 | else => unreachable, 124 | } 125 | i += 1; 126 | } 127 | try testing.expectEqual(@as(usize, 4), i); 128 | } 129 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1696426674, 7 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 8 | "owner": "edolstra", 9 | "repo": "flake-compat", 10 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "edolstra", 15 | "repo": "flake-compat", 16 | "type": "github" 17 | } 18 | }, 19 | "flake-utils": { 20 | "inputs": { 21 | "systems": "systems" 22 | }, 23 | "locked": { 24 | "lastModified": 1731533236, 25 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 26 | "owner": "numtide", 27 | "repo": "flake-utils", 28 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 29 | "type": "github" 30 | }, 31 | "original": { 32 | "owner": "numtide", 33 | "repo": "flake-utils", 34 | "type": "github" 35 | } 36 | }, 37 | "flake-utils_2": { 38 | "inputs": { 39 | "systems": "systems_2" 40 | }, 41 | "locked": { 42 | "lastModified": 1705309234, 43 | "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", 44 | "owner": "numtide", 45 | "repo": "flake-utils", 46 | "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", 47 | "type": "github" 48 | }, 49 | "original": { 50 | "owner": "numtide", 51 | "repo": "flake-utils", 52 | "type": "github" 53 | } 54 | }, 55 | "nixpkgs": { 56 | "locked": { 57 | "lastModified": 1764560356, 58 | "narHash": "sha256-M5aFEFPppI4UhdOxwdmceJ9bDJC4T6C6CzCK1E2FZyo=", 59 | "owner": "nixos", 60 | "repo": "nixpkgs", 61 | "rev": "6c8f0cca84510cc79e09ea99a299c9bc17d03cb6", 62 | "type": "github" 63 | }, 64 | "original": { 65 | "owner": "nixos", 66 | "ref": "nixos-25.05", 67 | "repo": "nixpkgs", 68 | "type": "github" 69 | } 70 | }, 71 | "nixpkgs_2": { 72 | "locked": { 73 | "lastModified": 1708161998, 74 | "narHash": "sha256-6KnemmUorCvlcAvGziFosAVkrlWZGIc6UNT9GUYr0jQ=", 75 | "owner": "NixOS", 76 | "repo": "nixpkgs", 77 | "rev": "84d981bae8b5e783b3b548de505b22880559515f", 78 | "type": "github" 79 | }, 80 | "original": { 81 | "owner": "NixOS", 82 | "ref": "nixos-23.11", 83 | "repo": "nixpkgs", 84 | "type": "github" 85 | } 86 | }, 87 | "root": { 88 | "inputs": { 89 | "flake-utils": "flake-utils", 90 | "nixpkgs": "nixpkgs", 91 | "zig": "zig" 92 | } 93 | }, 94 | "systems": { 95 | "locked": { 96 | "lastModified": 1681028828, 97 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 98 | "owner": "nix-systems", 99 | "repo": "default", 100 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 101 | "type": "github" 102 | }, 103 | "original": { 104 | "owner": "nix-systems", 105 | "repo": "default", 106 | "type": "github" 107 | } 108 | }, 109 | "systems_2": { 110 | "locked": { 111 | "lastModified": 1681028828, 112 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 113 | "owner": "nix-systems", 114 | "repo": "default", 115 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 116 | "type": "github" 117 | }, 118 | "original": { 119 | "owner": "nix-systems", 120 | "repo": "default", 121 | "type": "github" 122 | } 123 | }, 124 | "zig": { 125 | "inputs": { 126 | "flake-compat": "flake-compat", 127 | "flake-utils": "flake-utils_2", 128 | "nixpkgs": "nixpkgs_2" 129 | }, 130 | "locked": { 131 | "lastModified": 1764203689, 132 | "narHash": "sha256-ivb0SqCptyIxx5g8ryRrUL0xrJhLrJPlvZbZjxVaui0=", 133 | "owner": "mitchellh", 134 | "repo": "zig-overlay", 135 | "rev": "8f7347545dea59b75e40247cc1ed55a42f64dbbf", 136 | "type": "github" 137 | }, 138 | "original": { 139 | "owner": "mitchellh", 140 | "repo": "zig-overlay", 141 | "type": "github" 142 | } 143 | } 144 | }, 145 | "root": "root", 146 | "version": 7 147 | } 148 | -------------------------------------------------------------------------------- /src/wasm4.zig: -------------------------------------------------------------------------------- 1 | // 2 | // WASM-4: https://wasm4.org/docs 3 | 4 | // ┌───────────────────────────────────────────────────────────────────────────┐ 5 | // │ │ 6 | // │ Platform Constants │ 7 | // │ │ 8 | // └───────────────────────────────────────────────────────────────────────────┘ 9 | 10 | pub const SCREEN_SIZE: u32 = 160; 11 | pub const FONT_SIZE: u32 = 8; 12 | 13 | // ┌───────────────────────────────────────────────────────────────────────────┐ 14 | // │ │ 15 | // │ Memory Addresses │ 16 | // │ │ 17 | // └───────────────────────────────────────────────────────────────────────────┘ 18 | 19 | pub const PALETTE: *[4]u32 = @ptrFromInt(0x04); 20 | pub const DRAW_COLORS: *u16 = @ptrFromInt(0x14); 21 | pub const GAMEPAD1: *const u8 = @ptrFromInt(0x16); 22 | pub const GAMEPAD2: *const u8 = @ptrFromInt(0x17); 23 | pub const GAMEPAD3: *const u8 = @ptrFromInt(0x18); 24 | pub const GAMEPAD4: *const u8 = @ptrFromInt(0x19); 25 | pub const MOUSE_X: *const i16 = @ptrFromInt(0x1a); 26 | pub const MOUSE_Y: *const i16 = @ptrFromInt(0x1c); 27 | pub const MOUSE_BUTTONS: *const u8 = @ptrFromInt(0x1e); 28 | pub const SYSTEM_FLAGS: *u8 = @ptrFromInt(0x1f); 29 | pub const NETPLAY: *const u8 = @ptrFromInt(0x20); 30 | pub const FRAMEBUFFER: *[6400]u8 = @ptrFromInt(0xA0); 31 | 32 | pub const BUTTON_1: u8 = 1; 33 | pub const BUTTON_2: u8 = 2; 34 | pub const BUTTON_LEFT: u8 = 16; 35 | pub const BUTTON_RIGHT: u8 = 32; 36 | pub const BUTTON_UP: u8 = 64; 37 | pub const BUTTON_DOWN: u8 = 128; 38 | 39 | pub const MOUSE_LEFT: u8 = 1; 40 | pub const MOUSE_RIGHT: u8 = 2; 41 | pub const MOUSE_MIDDLE: u8 = 4; 42 | 43 | pub const SYSTEM_PRESERVE_FRAMEBUFFER: u8 = 1; 44 | pub const SYSTEM_HIDE_GAMEPAD_OVERLAY: u8 = 2; 45 | 46 | // ┌───────────────────────────────────────────────────────────────────────────┐ 47 | // │ │ 48 | // │ Drawing Functions │ 49 | // │ │ 50 | // └───────────────────────────────────────────────────────────────────────────┘ 51 | 52 | /// Copies pixels to the framebuffer. 53 | pub extern fn blit(sprite: [*]const u8, x: i32, y: i32, width: u32, height: u32, flags: u32) void; 54 | 55 | /// Copies a subregion within a larger sprite atlas to the framebuffer. 56 | pub extern fn blitSub(sprite: [*]const u8, x: i32, y: i32, width: u32, height: u32, src_x: u32, src_y: u32, stride: u32, flags: u32) void; 57 | 58 | pub const BLIT_2BPP: u32 = 1; 59 | pub const BLIT_1BPP: u32 = 0; 60 | pub const BLIT_FLIP_X: u32 = 2; 61 | pub const BLIT_FLIP_Y: u32 = 4; 62 | pub const BLIT_ROTATE: u32 = 8; 63 | 64 | /// Draws a line between two points. 65 | pub extern fn line(x1: i32, y1: i32, x2: i32, y2: i32) void; 66 | 67 | /// Draws an oval (or circle). 68 | pub extern fn oval(x: i32, y: i32, width: u32, height: u32) void; 69 | 70 | /// Draws a rectangle. 71 | pub extern fn rect(x: i32, y: i32, width: u32, height: u32) void; 72 | 73 | /// Draws text using the built-in system font. 74 | pub fn text(str: []const u8, x: i32, y: i32) void { 75 | textUtf8(str.ptr, str.len, x, y); 76 | } 77 | extern fn textUtf8(strPtr: [*]const u8, strLen: usize, x: i32, y: i32) void; 78 | 79 | /// Draws a vertical line 80 | pub extern fn vline(x: i32, y: i32, len: u32) void; 81 | 82 | /// Draws a horizontal line 83 | pub extern fn hline(x: i32, y: i32, len: u32) void; 84 | 85 | // ┌───────────────────────────────────────────────────────────────────────────┐ 86 | // │ │ 87 | // │ Sound Functions │ 88 | // │ │ 89 | // └───────────────────────────────────────────────────────────────────────────┘ 90 | 91 | /// Plays a sound tone. 92 | pub extern fn tone(frequency: u32, duration: u32, volume: u32, flags: u32) void; 93 | 94 | pub const TONE_PULSE1: u32 = 0; 95 | pub const TONE_PULSE2: u32 = 1; 96 | pub const TONE_TRIANGLE: u32 = 2; 97 | pub const TONE_NOISE: u32 = 3; 98 | pub const TONE_MODE1: u32 = 0; 99 | pub const TONE_MODE2: u32 = 4; 100 | pub const TONE_MODE3: u32 = 8; 101 | pub const TONE_MODE4: u32 = 12; 102 | pub const TONE_PAN_LEFT: u32 = 16; 103 | pub const TONE_PAN_RIGHT: u32 = 32; 104 | pub const TONE_NOTE_MODE: u32 = 64; 105 | 106 | // ┌───────────────────────────────────────────────────────────────────────────┐ 107 | // │ │ 108 | // │ Storage Functions │ 109 | // │ │ 110 | // └───────────────────────────────────────────────────────────────────────────┘ 111 | 112 | /// Reads up to `size` bytes from persistent storage into the pointer `dest`. 113 | pub extern fn diskr(dest: [*]u8, size: u32) u32; 114 | 115 | /// Writes up to `size` bytes from the pointer `src` into persistent storage. 116 | pub extern fn diskw(src: [*]const u8, size: u32) u32; 117 | 118 | // ┌───────────────────────────────────────────────────────────────────────────┐ 119 | // │ │ 120 | // │ Other Functions │ 121 | // │ │ 122 | // └───────────────────────────────────────────────────────────────────────────┘ 123 | 124 | /// Prints a message to the debug console. 125 | pub fn trace(x: []const u8) void { 126 | traceUtf8(x.ptr, x.len); 127 | } 128 | extern fn traceUtf8(strPtr: [*]const u8, strLen: usize) void; 129 | 130 | /// Use with caution, as there's no compile-time type checking. 131 | /// 132 | /// * %c, %d, and %x expect 32-bit integers. 133 | /// * %f expects 64-bit floats. 134 | /// * %s expects a *zero-terminated* string pointer. 135 | /// 136 | /// See https://github.com/aduros/wasm4/issues/244 for discussion and type-safe 137 | /// alternatives. 138 | pub extern fn tracef(x: [*:0]const u8, ...) void; 139 | -------------------------------------------------------------------------------- /src/world.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const util = @import("util"); 4 | 5 | const bresenham_line = @import("bresenham").line; 6 | 7 | pub const map_columns = 20; 8 | pub const map_rows = 20; 9 | const max_map_distance = map_columns + map_rows; 10 | 11 | pub const map_world_scale = 10; 12 | 13 | pub const Position = struct { 14 | x: i32, 15 | y: i32, 16 | z: i32, 17 | 18 | pub inline fn add(self: @This(), other: @This()) @This() { 19 | return .{ 20 | .x = self.x + other.x, 21 | .y = self.y + other.y, 22 | .z = self.z + other.z, 23 | }; 24 | } 25 | 26 | pub inline fn sub(self: @This(), other: @This()) @This() { 27 | return .{ 28 | .x = self.x - other.x, 29 | .y = self.y - other.y, 30 | .z = self.z - other.z, 31 | }; 32 | } 33 | 34 | pub fn from_map_location(map_location: MapLocation, z: i32) @This() { 35 | return .{ 36 | .x = map_location.x * map_world_scale + map_world_scale / 2, 37 | .y = map_location.y * map_world_scale + map_world_scale / 2, 38 | .z = z, 39 | }; 40 | } 41 | 42 | pub fn to_map_location(self: @This()) MapLocation { 43 | return .{ 44 | .x = @as(i16, @intCast(@divTrunc(self.x, map_world_scale))), 45 | .y = @as(i16, @intCast(@divTrunc(self.y, map_world_scale))), 46 | }; 47 | } 48 | 49 | pub fn lerp_to(self: @This(), to: @This(), frame: usize, total_frames: usize) @This() { 50 | var res = self; 51 | const dt = @as(f32, @floatFromInt(frame)) / @as(f32, @floatFromInt(total_frames)); 52 | res.x += @as(i32, @intFromFloat(@as(f32, @floatFromInt(to.x - self.x)) * dt)); 53 | res.y += @as(i32, @intFromFloat(@as(f32, @floatFromInt(to.y - self.y)) * dt)); 54 | res.z += @as(i32, @intFromFloat(@as(f32, @floatFromInt(to.z - self.z)) * dt)); 55 | return res; 56 | } 57 | }; 58 | 59 | pub fn Map(comptime columns: u8, comptime rows: u8) type { 60 | return [columns][rows]u4; 61 | } 62 | 63 | pub const MapTileKind = enum(u4) { 64 | floor = 0, 65 | wall = 1, 66 | breakable_wall = 2, 67 | door = 3, 68 | secret_path = 4, 69 | sword_pickup = 5, 70 | small_axe_pickup = 6, 71 | health_pickup = 7, 72 | player_spawn = 10, 73 | monster_spawn = 11, 74 | fire_monster_spawn = 12, 75 | charge_monster_spawn = 13, 76 | }; 77 | 78 | pub const Direction = enum(u3) { 79 | north, 80 | north_east, 81 | east, 82 | south_east, 83 | south, 84 | south_west, 85 | west, 86 | north_west, 87 | 88 | pub const all = [_]@This(){ 89 | @This().north, 90 | @This().north_east, 91 | @This().east, 92 | @This().south_east, 93 | @This().south, 94 | @This().south_west, 95 | @This().west, 96 | @This().north_west, 97 | }; 98 | 99 | pub const all_orthogonal = [_]@This(){ 100 | @This().north, 101 | @This().east, 102 | @This().south, 103 | @This().west, 104 | }; 105 | }; 106 | 107 | pub const Path = std.BoundedArray(MapLocation, max_map_distance); 108 | 109 | pub const MapLocation = struct { 110 | x: i16, 111 | y: i16, 112 | 113 | pub fn walk(self: @This(), direction: Direction, distance: u8) MapLocation { 114 | return switch (direction) { 115 | .north => self.north(distance), 116 | .north_east => self.north_east(distance), 117 | .east => self.east(distance), 118 | .south_east => self.south_east(distance), 119 | .south => self.south(distance), 120 | .south_west => self.south_west(distance), 121 | .west => self.west(distance), 122 | .north_west => self.north_west(distance), 123 | }; 124 | } 125 | 126 | pub fn eql(self: @This(), other: @This()) bool { 127 | return self.x == other.x and self.y == other.y; 128 | } 129 | 130 | pub fn manhattan_to(self: MapLocation, other: MapLocation) u8 { 131 | var dx = @as(i32, @intCast(other.x)) - @as(i32, @intCast(self.x)); 132 | var dy = @as(i32, @intCast(other.y)) - @as(i32, @intCast(self.y)); 133 | 134 | if (dx < 0) dx = -dx; 135 | if (dy < 0) dy = -dy; 136 | 137 | return @as(u8, @intCast(dx + dy)); 138 | } 139 | 140 | pub inline fn north(self: MapLocation, distance: u8) MapLocation { 141 | return MapLocation{ .x = self.x, .y = self.y - distance }; 142 | } 143 | 144 | pub inline fn north_east(self: MapLocation, distance: u8) MapLocation { 145 | return MapLocation{ .x = self.x + distance, .y = self.y - distance }; 146 | } 147 | 148 | pub inline fn east(self: MapLocation, distance: u8) MapLocation { 149 | return MapLocation{ .x = self.x + distance, .y = self.y }; 150 | } 151 | 152 | pub inline fn south_east(self: MapLocation, distance: u8) MapLocation { 153 | return MapLocation{ .x = self.x + distance, .y = self.y + distance }; 154 | } 155 | 156 | pub inline fn south(self: MapLocation, distance: u8) MapLocation { 157 | return MapLocation{ .x = self.x, .y = self.y + distance }; 158 | } 159 | 160 | pub inline fn south_west(self: MapLocation, distance: u8) MapLocation { 161 | return MapLocation{ .x = self.x - distance, .y = self.y + distance }; 162 | } 163 | 164 | pub inline fn west(self: MapLocation, distance: u8) MapLocation { 165 | return MapLocation{ .x = self.x - distance, .y = self.y }; 166 | } 167 | 168 | pub inline fn north_west(self: MapLocation, distance: u8) MapLocation { 169 | return MapLocation{ .x = self.x - distance, .y = self.y - distance }; 170 | } 171 | }; 172 | 173 | pub const LineOfSightResult = struct { 174 | path: Path = .{}, 175 | hit_target: bool = false, 176 | }; 177 | 178 | pub fn check_line_of_sight( 179 | comptime MapType: type, 180 | world_map: MapType, 181 | origin: MapLocation, 182 | target: MapLocation, 183 | ) LineOfSightResult { 184 | var plotter = struct { 185 | world_map: MapType, 186 | target: MapLocation, 187 | result: LineOfSightResult = .{}, 188 | 189 | pub fn plot(self: *@This(), x: i32, y: i32) bool { 190 | const location = MapLocation{ .x = @intCast(x), .y = @intCast(y) }; 191 | 192 | switch (map_get_tile_kind(self.world_map, location)) { 193 | .wall, .breakable_wall => return false, 194 | else => {}, 195 | } 196 | 197 | self.result.path.append(location) catch { 198 | return false; 199 | }; 200 | 201 | if (location.eql(self.target)) { 202 | self.result.hit_target = true; 203 | return false; 204 | } 205 | 206 | return true; 207 | } 208 | }{ 209 | .world_map = world_map, 210 | .target = target, 211 | }; 212 | 213 | bresenham_line( 214 | @as(i32, @intCast(origin.x)), 215 | @as(i32, @intCast(origin.y)), 216 | @as(i32, @intCast(target.x)), 217 | @as(i32, @intCast(target.y)), 218 | &plotter, 219 | ); 220 | 221 | return plotter.result; 222 | } 223 | 224 | pub fn map_set_tile(world: anytype, location: MapLocation, value: u4) void { 225 | if (location.x < 0 or location.x > map_columns or location.y < 0 or location.y > map_rows) { 226 | return; 227 | } 228 | world[@intCast(location.y)][@intCast(location.x)] = value; 229 | } 230 | 231 | pub fn map_get_tile(world: anytype, location: MapLocation) u4 { 232 | if (location.x < 0 or location.x > map_columns or location.y < 0 or location.y > map_rows) { 233 | return 0; 234 | } 235 | return world[@intCast(location.y)][@intCast(location.x)]; 236 | } 237 | 238 | pub fn map_get_tile_kind(world: anytype, location: MapLocation) MapTileKind { 239 | if (location.x < 0 or location.x > map_columns or location.y < 0 or location.y > map_rows) { 240 | return .wall; 241 | } 242 | return @enumFromInt( 243 | world[@intCast(location.y)][@intCast(location.x)], 244 | ); 245 | } 246 | 247 | const testing = std.testing; 248 | 249 | test { 250 | _ = testing.refAllDecls(@This()); 251 | } 252 | 253 | test "world.MapLocation.manhattan_to" { 254 | { 255 | const a = MapLocation{ .x = 5, .y = 16 }; 256 | const b = MapLocation{ .x = 11, .y = 10 }; 257 | try testing.expectEqual(@as(i16, 12), a.manhattan_to(b)); 258 | } 259 | { 260 | const a = MapLocation{ .x = 25, .y = 11 }; 261 | const b = MapLocation{ .x = 13, .y = 5 }; 262 | try testing.expectEqual(@as(i16, 18), a.manhattan_to(b)); 263 | } 264 | { 265 | const a = MapLocation{ .x = 9, .y = 9 }; 266 | const b = MapLocation{ .x = 3, .y = 18 }; 267 | try testing.expectEqual(@as(i16, 15), a.manhattan_to(b)); 268 | } 269 | { 270 | const a = MapLocation{ .x = 0, .y = 10 }; 271 | const b = MapLocation{ .x = 12, .y = 17 }; 272 | try testing.expectEqual(@as(i16, 19), a.manhattan_to(b)); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/data.zig: -------------------------------------------------------------------------------- 1 | pub const levels = [_][20 * 20]u4{ 2 | [_]u4{ // LEVEL 1 3 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 11 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 12 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 13 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 14 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 15 | 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 16 | 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 17 | 1, 1, 0, 10, 0, 4, 4, 4, 0, 0, 0, 0, 0, 0, 7, 0, 1, 1, 1, 1, 18 | 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 19 | 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 20 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 21 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 22 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23 | }, 24 | 25 | [_]u4{ // LEVEL 2 26 | 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27 | 1, 1, 1, 0, 12, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 28 | 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 1, 29 | 1, 4, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 30 | 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 31 | 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 32 | 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 33 | 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 34 | 1, 4, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 35 | 1, 4, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 36 | 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 37 | 1, 0, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 4, 1, 1, 1, 1, 38 | 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 11, 0, 0, 0, 0, 1, 1, 1, 1, 1, 39 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 40 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 41 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 42 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 43 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 44 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 10, 0, 1, 1, 1, 1, 1, 1, 1, 45 | 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 46 | }, 47 | 48 | [_]u4{ // LEVEL 3 49 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 50 | 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 51 | 1, 1, 11, 0, 0, 0, 0, 0, 0, 11, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 52 | 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 53 | 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 54 | 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 55 | 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 56 | 1, 0, 0, 0, 1, 0, 0, 0, 0, 12, 0, 0, 0, 0, 1, 0, 11, 0, 1, 1, 57 | 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 58 | 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 59 | 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 60 | 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 61 | 1, 0, 0, 0, 1, 0, 0, 11, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 62 | 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 1, 63 | 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 64 | 1, 0, 0, 0, 1, 1, 1, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 11, 1, 1, 65 | 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 1, 1, 66 | 1, 0, 10, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1, 1, 67 | 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 68 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 69 | }, 70 | 71 | [_]u4{ // LEVEL 4 72 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 73 | 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 74 | 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 75 | 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 76 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 77 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 78 | 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 79 | 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 80 | 1, 1, 1, 1, 1, 0, 1, 3, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 81 | 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 82 | 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 83 | 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 84 | 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 85 | 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 2, 0, 0, 1, 86 | 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 87 | 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 7, 0, 88 | 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 89 | 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 90 | 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 91 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 92 | }, 93 | 94 | [_]u4{ // LEVEL 5 95 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 96 | 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 11, 0, 0, 0, 0, 1, 97 | 1, 0, 11, 0, 0, 0, 0, 1, 12, 11, 0, 1, 1, 1, 0, 0, 13, 12, 0, 1, 98 | 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 2, 0, 0, 2, 0, 0, 1, 99 | 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 100 | 1, 0, 12, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 101 | 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 0, 1, 0, 0, 0, 0, 1, 102 | 1, 0, 0, 0, 0, 0, 0, 12, 0, 12, 0, 0, 12, 0, 1, 0, 11, 0, 0, 1, 103 | 1, 0, 0, 11, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 104 | 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 0, 0, 0, 0, 1, 105 | 1, 0, 0, 0, 1, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 106 | 1, 0, 13, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 107 | 1, 0, 0, 0, 1, 0, 0, 11, 0, 13, 1, 2, 2, 1, 0, 0, 0, 1, 1, 1, 108 | 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 109 | 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 1, 110 | 1, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 111 | 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 12, 0, 1, 112 | 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 113 | 1, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 114 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 115 | }, 116 | 117 | [_]u4{ // LEVEL 6 118 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 119 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 120 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 121 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 122 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 123 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 124 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 125 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 126 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 127 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 128 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 129 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 130 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 131 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 132 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 133 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 134 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 135 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 136 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 137 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 138 | }, 139 | }; 140 | -------------------------------------------------------------------------------- /src/game.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const sqrt = std.math.sqrt; 3 | var rng: std.Random.DefaultPrng = undefined; 4 | 5 | const platform = @import("platform"); 6 | const gfx = @import("gfx"); 7 | const sfx = @import("sfx"); 8 | const data = @import("data"); 9 | 10 | const util = @import("util"); 11 | const quicksort = util.quicksort; 12 | 13 | const world = @import("world"); 14 | const WorldMap = world.Map(world.map_columns, world.map_rows); 15 | 16 | const bresenham_line = @import("bresenham").line; 17 | 18 | const level_debug_override: ?u8 = if (false) 3 else null; 19 | 20 | const starting_player_health = 5; 21 | 22 | var screen: Screen = .title; 23 | var menu_option: u8 = 0; 24 | var player_level_starting_health: [6]i8 = .{ starting_player_health, 0, 0, 0, 0, 0 }; 25 | var player_level_starting_items: [6]u8 = .{ 0b1, 0, 0, 0, 0, 0 }; 26 | var player_level_starting_active_item: [6]Player.Item = .{.fists} ** 6; 27 | var game_state: State = .{}; 28 | 29 | pub var button_press_ev_queue = struct { 30 | buf: [8]ButtonsPressed = undefined, 31 | count: usize = 0, 32 | read_cursor: usize = 0, 33 | write_cursor: usize = 0, 34 | 35 | pub fn push(self: *@This(), input: ButtonsPressed) void { 36 | self.buf[self.write_cursor] = input; 37 | self.write_cursor = @mod( 38 | self.write_cursor + 1, 39 | self.buf.len, 40 | ); 41 | if (self.count == self.buf.len) { 42 | platform.trace("warning: input queue overflow"); 43 | self.read_cursor = @mod( 44 | self.read_cursor + 1, 45 | self.buf.len, 46 | ); 47 | } else { 48 | self.count += 1; 49 | } 50 | } 51 | 52 | pub fn pop(self: *@This()) ?ButtonsPressed { 53 | if (self.count == 0) { 54 | return null; 55 | } 56 | defer { 57 | self.read_cursor = @mod(self.read_cursor + 1, self.buf.len); 58 | self.count -= 1; 59 | } 60 | return self.buf[self.read_cursor]; 61 | } 62 | 63 | pub fn clear(self: *@This()) void { 64 | self.count = 0; 65 | self.read_cursor = 0; 66 | self.write_cursor = 0; 67 | } 68 | }{}; 69 | 70 | pub const ButtonState = packed struct { 71 | left: bool, 72 | right: bool, 73 | up: bool, 74 | down: bool, 75 | action_1: bool, 76 | action_2: bool, 77 | 78 | pub fn anyPressed(self: @This()) bool { 79 | return (self.left or 80 | self.right or 81 | self.up or 82 | self.down or 83 | self.action_1 or 84 | self.action_2); 85 | } 86 | }; 87 | 88 | pub const ButtonsPressed = ButtonState; 89 | 90 | pub const Screen = enum { title, controls, game, reload, win }; 91 | 92 | const Entity = struct { 93 | location: world.MapLocation = world.MapLocation{ .x = 0, .y = 0 }, 94 | target_location: world.MapLocation = world.MapLocation{ .x = 0, .y = 0 }, 95 | cooldown: u8 = 0, 96 | health: i8, 97 | pending_damage: u4 = 0, 98 | did_receive_damage: bool = false, 99 | state: enum { idle, walk, melee_attack, charge } = .idle, 100 | look_direction: world.Direction, 101 | 102 | pub fn set_location(self: *@This(), location: world.MapLocation) void { 103 | self.location = location; 104 | self.target_location = location; 105 | } 106 | }; 107 | 108 | const Player = struct { 109 | entity: Entity, 110 | items: u8, 111 | active_item: Item, 112 | 113 | pub const Item = enum(u8) { fists, sword, small_axe }; 114 | 115 | pub fn has_item(self: Player, item: Item) bool { 116 | return (self.items & (@as(u8, 1) << @intCast(@intFromEnum(item)))) > 0; 117 | } 118 | 119 | pub fn give_item(self: *Player, item: Item) void { 120 | self.items |= (@as(u8, 1) << @intCast(@intFromEnum(item))); 121 | self.active_item = item; 122 | } 123 | 124 | pub fn remove_item(self: *Player, item: Item) void { 125 | self.items &= ~(@as(u8, 1) << @intCast(@intFromEnum(item))); 126 | if (self.active_item == item) { 127 | self.active_item = if (self.has_item(.sword)) .sword else .fists; 128 | } 129 | } 130 | 131 | pub fn get_damage(self: Player) u4 { 132 | return switch (self.active_item) { 133 | .fists => 1, 134 | .sword => 3, 135 | .small_axe => 2, 136 | }; 137 | } 138 | }; 139 | 140 | const Enemy = struct { 141 | entity: Entity, 142 | path: world.Path = .{}, 143 | }; 144 | 145 | const Pickup = struct { 146 | entity: Entity, 147 | kind: enum { health, sword, small_axe }, 148 | }; 149 | 150 | pub const State = struct { 151 | // timer: std.time.Timer = undefined, 152 | game_elapsed_ns: u64 = 0, 153 | turn_state: enum { ready, aim, commit, response, dead } = .ready, 154 | turn: u32 = 0, 155 | level: u8 = 0, 156 | action_target: u8 = 0, 157 | action_targets: std.BoundedArray(world.MapLocation, 16) = .{}, 158 | world_map: WorldMap = undefined, 159 | world_vis_map: WorldMap = undefined, 160 | player: Player = undefined, 161 | monsters: [16]Enemy = undefined, 162 | fire_monsters: [8]Enemy = undefined, 163 | charge_monsters: [4]Enemy = undefined, 164 | fire: [16]Enemy = undefined, 165 | pickups: [8]Pickup = undefined, 166 | 167 | pub fn reset(self: *@This()) void { 168 | platform.trace("reset"); 169 | // self.timer = std.time.Timer.start() catch @panic("Failed to start timer"); 170 | self.turn_state = .ready; 171 | self.turn = 0; 172 | self.player.entity.health = starting_player_health; 173 | self.player.active_item = .fists; 174 | self.player.items = 0b1; 175 | self.action_targets.clear(); 176 | } 177 | 178 | pub fn load_level(self: *@This(), level: u8) void { 179 | platform.trace("load_level"); 180 | 181 | button_press_ev_queue.clear(); 182 | 183 | self.turn_state = .ready; 184 | self.action_targets.clear(); 185 | 186 | self.level = level_debug_override orelse level; 187 | self.world_map = @bitCast(data.levels[self.level]); 188 | 189 | // reset entity pools 190 | for (&self.monsters) |*monster| monster.entity.health = 0; 191 | for (&self.fire_monsters) |*fire_monster| fire_monster.entity.health = 0; 192 | for (&self.charge_monsters) |*charge_monster| charge_monster.entity.health = 0; 193 | for (&self.fire) |*fire| fire.entity.health = 0; 194 | for (&self.pickups) |*pickup| pickup.entity.health = 0; 195 | 196 | // find spawners on level map and spawn things at those locations 197 | var location: world.MapLocation = .{ .x = 0, .y = 0 }; 198 | while (location.x < world.map_columns) : (location.x += 1) { 199 | defer location.y = 0; 200 | while (location.y < world.map_rows) : (location.y += 1) { 201 | switch (world.map_get_tile_kind(self.world_map, location)) { 202 | .player_spawn => { 203 | self.player.entity.set_location(location); 204 | }, 205 | .monster_spawn => spawn_enemy(&self.monsters, location, 2), 206 | .fire_monster_spawn => spawn_enemy(&self.fire_monsters, location, 3), 207 | .charge_monster_spawn => spawn_enemy(&self.charge_monsters, location, 7), 208 | .health_pickup => spawn_pickup(self, location, .health), 209 | .sword_pickup => spawn_pickup(self, location, .sword), 210 | .small_axe_pickup => spawn_pickup(self, location, .small_axe), 211 | else => {}, 212 | } 213 | } 214 | } 215 | 216 | update_world_visibilty(self); 217 | } 218 | }; 219 | 220 | fn spawn_enemy(pool: anytype, location: world.MapLocation, health: u4) void { 221 | for (pool) |*enemy| { 222 | if (enemy.entity.health <= 0) { 223 | enemy.entity.set_location(location); 224 | enemy.entity.health = health; 225 | enemy.entity.state = .idle; 226 | enemy.path.clear(); 227 | platform.trace("spawned enemy"); 228 | return; 229 | } 230 | } 231 | platform.trace("warning: enemy not spawned. no free space"); 232 | } 233 | 234 | fn spawn_pickup(state: *State, location: world.MapLocation, kind: anytype) void { 235 | for (&state.pickups) |*pickup| { 236 | if (pickup.entity.health <= 0) { 237 | pickup.entity.set_location(location); 238 | pickup.entity.health = 1; 239 | pickup.kind = kind; 240 | platform.trace("spawned pickup"); 241 | return; 242 | } 243 | } 244 | platform.trace("warning: pickup not spawned. no free space"); 245 | } 246 | 247 | fn spawn_fire(state: *State, _path: world.Path) void { 248 | var path = _path; 249 | if (path.pop()) |location| { 250 | for (&state.fire) |*fire| { 251 | if (fire.entity.health <= 0) { 252 | fire.entity.health = 1; 253 | fire.entity.state = .walk; 254 | fire.entity.set_location(location); 255 | if (path.pop()) |next_location| { 256 | fire.entity.target_location = next_location; 257 | fire.path.append(next_location) catch { 258 | platform.trace("error: failed to queue fire path. no space left"); 259 | return; 260 | }; 261 | } 262 | while (path.pop()) |future_location| { 263 | fire.path.append(future_location) catch { 264 | platform.trace("error: failed to queue fire path. no space left"); 265 | return; 266 | }; 267 | } 268 | platform.trace("spawned fire"); 269 | return; 270 | } 271 | } 272 | platform.trace("warning: fire not spawned. no free space"); 273 | } 274 | platform.trace("warning: fire not spawned. empty path"); 275 | } 276 | 277 | fn try_move(state: *State, location: world.MapLocation) void { 278 | switch (world.map_get_tile_kind(state.world_map, location)) { 279 | .wall, .breakable_wall => { 280 | // you shall not pass! 281 | return; 282 | }, 283 | else => find_move: { 284 | for (&state.fire) |*fire| { 285 | if (fire.entity.health > 0 and 286 | fire.entity.location.eql(location)) 287 | { 288 | state.player.entity.health -= 1; 289 | sfx.receive_damage(); 290 | commit_move(state); 291 | break :find_move; 292 | } 293 | } 294 | 295 | if (try_hit_enemy(state, location)) { 296 | state.player.entity.state = .melee_attack; 297 | commit_move(state); 298 | break :find_move; 299 | } 300 | 301 | for (&state.pickups) |*pickup| { 302 | if (pickup.entity.health > 0 and 303 | pickup.entity.location.eql(location)) 304 | { 305 | switch (pickup.kind) { 306 | .health => state.player.entity.health += 2, 307 | .sword => state.player.give_item(.sword), 308 | .small_axe => state.player.give_item(.small_axe), 309 | } 310 | pickup.entity.health = 0; 311 | sfx.pickup(); 312 | break; 313 | } 314 | } 315 | 316 | state.player.entity.target_location = location; 317 | state.player.entity.state = .walk; 318 | sfx.walk(); 319 | commit_move(state); 320 | }, 321 | } 322 | } 323 | 324 | fn try_hit_enemy(state: *State, location: world.MapLocation) bool { 325 | if (entities_try_hit(&state.monsters, location) orelse 326 | entities_try_hit(&state.fire_monsters, location) orelse 327 | entities_try_hit(&state.charge_monsters, location)) |entity| 328 | { 329 | state.player.entity.target_location = entity.location; 330 | entity.pending_damage += state.player.get_damage(); 331 | return true; 332 | } 333 | return false; 334 | } 335 | 336 | fn entities_try_hit(entities: anytype, location: world.MapLocation) ?*Entity { 337 | for (entities) |*e| { 338 | if (e.entity.health > 0 and 339 | e.entity.location.eql(location)) 340 | { 341 | return &e.entity; 342 | } 343 | } 344 | return null; 345 | } 346 | 347 | fn entities_test_will_collide(entities: anytype, location: world.MapLocation) ?*Entity { 348 | for (entities) |*e| { 349 | if (e.entity.health > 0 and 350 | e.entity.target_location.eql(location)) 351 | { 352 | return &e.entity; 353 | } 354 | } 355 | return null; 356 | } 357 | 358 | fn try_cycle_item(state: *State) void { 359 | platform.trace("cycle item"); 360 | 361 | sfx.walk(); 362 | 363 | const T = @TypeOf(state.player.items); 364 | const max = std.meta.fields(Player.Item).len; 365 | var item = @intFromEnum(state.player.active_item); 366 | var i: T = 0; 367 | while (i < max) : (i += 1) { 368 | item = @mod(item + 1, @as(u8, max)); 369 | if (state.player.has_item(@enumFromInt(item))) { 370 | state.player.active_item = @enumFromInt(item); 371 | break; 372 | } 373 | platform.trace("dont possess item"); 374 | } 375 | } 376 | 377 | fn find_action_targets(state: *State) !void { 378 | state.action_targets.clear(); 379 | 380 | switch (state.player.active_item) { 381 | .fists => try find_melee_targets(state, world.Direction.all_orthogonal), 382 | .sword => try find_melee_targets(state, world.Direction.all), 383 | .small_axe => { // ranged attack 384 | try find_ranged_targets(state, state.monsters); 385 | try find_ranged_targets(state, state.fire_monsters); 386 | try find_ranged_targets(state, state.charge_monsters); 387 | 388 | if (state.action_targets.len > 1) { 389 | const targets = state.action_targets.slice(); 390 | const distance_comparitor = struct { 391 | state: *State, 392 | pub fn compare( 393 | self: @This(), 394 | a: world.MapLocation, 395 | b: world.MapLocation, 396 | ) bool { 397 | const da = self.state.player.entity.location.manhattan_to(a); 398 | const db = self.state.player.entity.location.manhattan_to(b); 399 | return da < db; 400 | } 401 | }{ .state = state }; 402 | quicksort( 403 | targets, 404 | 0, 405 | @intCast(targets.len - 1), 406 | distance_comparitor, 407 | ); 408 | } 409 | }, 410 | } 411 | } 412 | 413 | fn find_melee_targets(state: *State, directions: anytype) !void { 414 | for (directions) |dir| { 415 | const location = state.player.entity.location.walk(dir, 1); 416 | if (entities_try_hit(&state.monsters, location) orelse 417 | entities_try_hit(&state.fire_monsters, location) orelse 418 | entities_try_hit(&state.charge_monsters, location)) |entity| 419 | { 420 | try state.action_targets.append(entity.location); 421 | } 422 | } 423 | } 424 | 425 | fn find_ranged_targets(state: *State, potential_targets: anytype) !void { 426 | for (&potential_targets) |*target| { 427 | if (target.entity.health > 0) { 428 | if (test_can_ranged_attack(state, target.entity.location)) { 429 | try state.action_targets.append(target.entity.location); 430 | } 431 | } 432 | } 433 | } 434 | 435 | fn test_can_ranged_attack(state: *State, location: world.MapLocation) bool { 436 | const d = state.player.entity.location.manhattan_to(location); 437 | if (d < 9) { 438 | const res = world.check_line_of_sight( 439 | WorldMap, 440 | state.world_map, 441 | state.player.entity.location, 442 | location, 443 | ); 444 | if (res.hit_target) { 445 | return true; 446 | } 447 | } 448 | return false; 449 | } 450 | 451 | fn commit_move(state: *State) void { 452 | platform.trace("commit move"); 453 | 454 | gfx.move_anim_start_frame = gfx.frame_counter; 455 | state.turn_state = .commit; 456 | } 457 | 458 | fn respond_to_move(state: *State) void { 459 | platform.trace("responding to move..."); 460 | 461 | if (world.map_get_tile_kind(state.world_map, state.player.entity.location) == .door) { 462 | state.level += 1; 463 | if (state.level < data.levels.len) { 464 | platform.trace("load next level"); 465 | state.load_level(state.level); 466 | player_level_starting_health[state.level] = state.player.entity.health; 467 | player_level_starting_items[state.level] = state.player.items; 468 | player_level_starting_active_item[state.level] = state.player.active_item; 469 | } 470 | return; 471 | } 472 | 473 | update_enemies(&state.fire, state, update_fire); 474 | update_enemies(&state.monsters, state, update_monster); 475 | update_enemies(&state.fire_monsters, state, update_fire_monster); 476 | update_enemies(&state.charge_monsters, state, update_charge_monster); 477 | } 478 | 479 | /// Returns true if the location is not occupied by a blocking tile or blocking entity 480 | fn test_walkable(state: *State, location: world.MapLocation) bool { 481 | platform.trace("test location walkable..."); 482 | 483 | const kind = world.map_get_tile_kind(state.world_map, location); 484 | 485 | switch (kind) { 486 | .wall, .breakable_wall, .secret_path => { 487 | return false; 488 | }, 489 | else => { 490 | if (state.player.entity.health > 0 and state.player.entity.location.eql(location)) { 491 | return false; 492 | } 493 | 494 | if (entities_test_will_collide(&state.monsters, location) orelse 495 | entities_test_will_collide(&state.fire_monsters, location) orelse 496 | entities_test_will_collide(&state.charge_monsters, location) orelse 497 | entities_test_will_collide(&state.pickups, location)) |_| 498 | { 499 | return false; 500 | } 501 | }, 502 | } 503 | 504 | return true; 505 | } 506 | 507 | fn update_enemies( 508 | enemies: anytype, 509 | state: *State, 510 | comptime update_fn: fn (*State, *Enemy) void, 511 | ) void { 512 | for (enemies) |*enemy| { 513 | if (enemy.entity.cooldown > 0) { 514 | enemy.entity.cooldown -= 1; 515 | } else if (enemy.entity.health > 0) { 516 | update_fn(state, enemy); 517 | } 518 | } 519 | } 520 | 521 | fn update_monster(state: *State, monster: *Enemy) void { 522 | platform.trace("monster: begin move..."); 523 | defer platform.trace("monster: move complete"); 524 | 525 | const dx = state.player.entity.location.x - monster.entity.location.x; 526 | const dy = state.player.entity.location.y - monster.entity.location.y; 527 | const manhattan_dist: u8 = @intCast((if (dx < 0) -dx else dx) + if (dy < 0) -dy else dy); 528 | 529 | if (manhattan_dist == 1) { 530 | platform.trace("monster: hit player!"); 531 | state.player.entity.pending_damage += 2; 532 | monster.entity.state = .melee_attack; 533 | return; 534 | } else if (manhattan_dist <= 3) { 535 | platform.trace("monster: approach player"); 536 | 537 | var possible_location = monster.entity.location; 538 | 539 | if (dx == 0 or dy == 0) { 540 | platform.trace("monster: orthogonal, step closer"); 541 | if (dx != 0) { 542 | possible_location.x += @divTrunc(dx, dx); 543 | } else if (dy != 0) { 544 | possible_location.y += @divTrunc(dy, dy); 545 | } 546 | } else { 547 | platform.trace("monster: on diagonal, roll dice"); 548 | switch (rng.random().int(u1)) { 549 | 0 => possible_location.x += @divTrunc(dx, dx), 550 | 1 => possible_location.y += @divTrunc(dy, dy), 551 | } 552 | } 553 | 554 | if (test_walkable(state, possible_location)) { 555 | monster.entity.target_location = possible_location; 556 | monster.entity.state = .walk; 557 | return; 558 | } 559 | } else if (manhattan_dist < 10) { 560 | const res = world.check_line_of_sight( 561 | WorldMap, 562 | state.world_map, 563 | monster.entity.location, 564 | state.player.entity.location, 565 | ); 566 | if (res.hit_target) { 567 | platform.trace("monster: chase player!"); 568 | 569 | { // find a walkable tile that gets closer to player 570 | for (world.Direction.all_orthogonal) |dir| { 571 | const loc = monster.entity.location.walk(dir, 1); 572 | if (test_walkable(state, loc)) { 573 | if (loc.manhattan_to(state.player.entity.location) < manhattan_dist) { 574 | monster.entity.target_location = loc; 575 | monster.entity.state = .walk; 576 | break; 577 | } 578 | } 579 | } 580 | } 581 | return; 582 | } 583 | } 584 | 585 | platform.trace("monster: random walk"); 586 | random_walk(state, &monster.entity); 587 | } 588 | 589 | fn update_fire_monster(state: *State, monster: *Enemy) void { 590 | platform.trace("fire_monster: begin move..."); 591 | defer platform.trace("fire_monster: move complete"); 592 | 593 | const d = monster.entity.location.manhattan_to(state.player.entity.location); 594 | 595 | if (d > 3 and d < 20) { 596 | const res = world.check_line_of_sight( 597 | WorldMap, 598 | state.world_map, 599 | monster.entity.location, 600 | state.player.entity.location, 601 | ); 602 | if (res.hit_target) { 603 | platform.trace("fire_monster: spit at player"); 604 | spawn_fire(state, res.path); 605 | monster.entity.cooldown = 2; 606 | return; 607 | } 608 | } 609 | 610 | platform.trace("fire_monster: random walk"); 611 | random_walk(state, &monster.entity); 612 | } 613 | 614 | fn update_charge_monster(state: *State, monster: *Enemy) void { 615 | platform.trace("charge_monster: begin move..."); 616 | defer platform.trace("charge_monster: move complete"); 617 | 618 | switch (monster.entity.state) { 619 | .idle, .walk => { 620 | const vertically_aligned = monster.entity.location.x == state.player.entity.location.x; 621 | const horizontally_aligned = monster.entity.location.y == state.player.entity.location.y; 622 | 623 | if (vertically_aligned or horizontally_aligned) { 624 | const d = monster.entity.location.manhattan_to(state.player.entity.location); 625 | if (d < 13) { 626 | platform.trace("charge_monster: spotted player"); 627 | monster.entity.cooldown = 1; 628 | if (vertically_aligned) { 629 | const dy = state.player.entity.location.y - monster.entity.location.y; 630 | const dir: world.Direction = if (dy < 0) .north else .south; 631 | charge_monster_begin_charge(monster, dir, @as(u16, @intCast(if (dy < 0) -dy else dy)) + 14); 632 | } else if (horizontally_aligned) { 633 | const dx = state.player.entity.location.x - monster.entity.location.x; 634 | const dir: world.Direction = if (dx < 0) .west else .east; 635 | charge_monster_begin_charge(monster, dir, @as(u16, @intCast(if (dx < 0) -dx else dx)) + 14); 636 | } 637 | return; 638 | } 639 | } 640 | }, 641 | .charge => { 642 | platform.trace("charge_monster: charge"); 643 | if (monster.path.pop()) |next_location| { 644 | var plotter = struct { 645 | game_state: *State, 646 | last_passable: ?world.MapLocation = null, 647 | hit_impassable: bool = false, 648 | player_hit: bool = false, 649 | 650 | pub fn plot(self: *@This(), x: i32, y: i32) bool { 651 | const location = world.MapLocation{ .x = @intCast(x), .y = @intCast(y) }; 652 | switch (world.map_get_tile_kind(self.game_state.world_map, location)) { 653 | .wall => { 654 | self.hit_impassable = true; 655 | return false; 656 | }, 657 | .breakable_wall => { 658 | world.map_set_tile(&self.game_state.world_map, location, 0); 659 | sfx.destroy_wall(); 660 | self.last_passable = location; 661 | }, 662 | else => { 663 | self.last_passable = location; 664 | }, 665 | } 666 | if (location.eql(self.game_state.player.entity.location) or 667 | location.eql(self.game_state.player.entity.target_location)) 668 | { 669 | self.player_hit = true; 670 | } 671 | 672 | return true; 673 | } 674 | }{ 675 | .game_state = state, 676 | }; 677 | 678 | bresenham_line( 679 | @as(i32, @intCast(monster.entity.location.x)), 680 | @as(i32, @intCast(monster.entity.location.y)), 681 | @as(i32, @intCast(next_location.x)), 682 | @as(i32, @intCast(next_location.y)), 683 | &plotter, 684 | ); 685 | 686 | monster.entity.target_location = plotter.last_passable orelse next_location; 687 | 688 | if (plotter.player_hit) { 689 | const player = &state.player; 690 | player.entity.pending_damage += 1; 691 | // try push player 692 | var new_player_location = monster.entity.target_location.walk(monster.entity.look_direction, 1); 693 | switch (world.map_get_tile_kind(state.world_map, new_player_location)) { 694 | .wall, .breakable_wall => { 695 | player.entity.pending_damage += 1; 696 | player.entity.state = .idle; 697 | const location_behind_monster = monster.entity.target_location.walk( 698 | switch (monster.entity.look_direction) { 699 | .north => .south, 700 | .south => .north, 701 | .east => .west, 702 | .west => .east, 703 | else => { 704 | platform.trace("error: invalid charge direction"); 705 | return; // Return instead of unreachable 706 | }, 707 | }, 708 | 1, 709 | ); 710 | // TODO(hazeycode): pick a random available location instead of trying in arbitary order? 711 | // TODO(hazeycode): test collision with other enemies and push them out of the way? 712 | for (world.Direction.all_orthogonal) |possible_dir| { 713 | const possible_location = monster.entity.target_location.walk(possible_dir, 1); 714 | if (possible_location.eql(location_behind_monster)) { 715 | continue; 716 | } 717 | if (test_walkable(state, possible_location)) { 718 | new_player_location = possible_location; 719 | player.entity.state = .walk; 720 | break; 721 | } 722 | } 723 | if (player.entity.state == .idle) { 724 | player.entity.pending_damage = std.math.maxInt( 725 | @TypeOf(player.entity.pending_damage), 726 | ); 727 | } 728 | }, 729 | else => {}, 730 | } 731 | platform.trace("charge_monster pushed player"); 732 | player.entity.location = new_player_location; 733 | } 734 | 735 | if (plotter.hit_impassable) { 736 | monster.path.clear(); 737 | monster.entity.state = .idle; 738 | platform.trace("charge_monster: end charge"); 739 | } 740 | } else { 741 | monster.path.clear(); 742 | monster.entity.state = .idle; 743 | platform.trace("charge_monster: end charge"); 744 | } 745 | return; 746 | }, 747 | else => {}, 748 | } 749 | 750 | platform.trace("charge_monster: random walk"); 751 | random_walk(state, &monster.entity); 752 | } 753 | 754 | fn charge_monster_begin_charge(monster: *Enemy, dir: world.Direction, dist: u16) void { 755 | platform.trace("charge_monster: begin charge"); 756 | const speed = 2; 757 | var next_location = monster.entity.location; 758 | var moved: u16 = 0; 759 | while (moved <= dist) : (moved += speed) { 760 | monster.path.append(next_location) catch { 761 | platform.trace("error: failed to append to path. out of space"); 762 | return; // Return instead of unreachable 763 | }; 764 | next_location = next_location.walk(dir, speed); 765 | } 766 | monster.entity.state = .charge; 767 | monster.entity.look_direction = dir; 768 | } 769 | 770 | fn update_fire(state: *State, fire: *Enemy) void { 771 | if (fire.path.pop()) |next_location| { 772 | fire.entity.target_location = next_location; 773 | fire.entity.state = .walk; 774 | 775 | if (fire.entity.target_location.eql(state.player.entity.location)) { 776 | platform.trace("fire hit player!"); 777 | state.player.entity.pending_damage += 1; 778 | } 779 | } else { 780 | fire.entity.state = .idle; 781 | fire.entity.health = 0; 782 | platform.trace("fire extinguished"); 783 | } 784 | } 785 | 786 | /// finds walkable adjacent tile or ramains still (random walk) 787 | fn random_walk(state: *State, entity: *Entity) void { 788 | const ortho_dirs = world.Direction.all_orthogonal; 789 | const random_index = @mod(rng.random().int(usize), 4); 790 | const location = entity.location.walk(ortho_dirs[random_index], 1); 791 | if (test_walkable(state, location)) { 792 | entity.target_location = location; 793 | entity.state = .walk; 794 | } else { 795 | entity.state = .idle; 796 | } 797 | } 798 | 799 | fn update_world_visibilty(state: *State) void { 800 | platform.trace("update world visibilty"); 801 | defer platform.trace("world visibilty updated"); 802 | 803 | var location: world.MapLocation = .{ .x = 0, .y = 0 }; 804 | while (location.x < world.map_columns) : (location.x += 1) { 805 | defer location.y = 0; 806 | while (location.y < world.map_rows) : (location.y += 1) { 807 | world.map_set_tile(&state.world_vis_map, location, 0); 808 | if (state.player.entity.location.manhattan_to(location) < 9 and 809 | world.check_line_of_sight( 810 | WorldMap, 811 | state.world_map, 812 | state.player.entity.location, 813 | location, 814 | ).hit_target) 815 | { 816 | world.map_set_tile(&state.world_vis_map, location, 1); 817 | } 818 | } 819 | } 820 | } 821 | 822 | fn cancel_aim(state: *State) void { 823 | platform.trace("cancel item"); 824 | state.turn_state = .ready; 825 | state.action_target = 0; 826 | state.action_targets.clear(); 827 | } 828 | 829 | fn entities_apply_pending_damage(entities: anytype) void { 830 | for (entities) |*e| { 831 | if (e.entity.pending_damage > 0) { 832 | e.entity.health -= e.entity.pending_damage; 833 | e.entity.pending_damage = 0; 834 | e.entity.did_receive_damage = true; 835 | sfx.deal_damage(); 836 | } 837 | } 838 | } 839 | 840 | fn entities_complete_move(entities: anytype) void { 841 | for (entities) |*e| { 842 | if (e.entity.health > 0) { 843 | switch (e.entity.state) { 844 | .melee_attack => { 845 | e.entity.target_location = e.entity.location; 846 | }, 847 | else => { 848 | e.entity.location = e.entity.target_location; 849 | }, 850 | } 851 | } else { 852 | e.entity.location = .{ .x = 0, .y = 0 }; 853 | e.entity.target_location = e.entity.location; 854 | e.entity.state = .idle; 855 | } 856 | } 857 | } 858 | 859 | pub fn init() void { 860 | rng = std.Random.DefaultPrng.init(42); 861 | gfx.init(); 862 | } 863 | 864 | pub fn update(buttons: ButtonState, pressed: ButtonsPressed) void { 865 | switch (screen) { 866 | .title => screenTitle(&game_state, pressed), 867 | .controls => screenControls(pressed), 868 | .game => screenGame(&game_state, buttons, pressed), 869 | .reload => screenReload(&game_state, pressed), 870 | .win => screenStats(&game_state, pressed, "YOU ESCAPED", .title, null), 871 | } 872 | gfx.frame_counter += 1; 873 | } 874 | 875 | fn screenGame(state: anytype, buttons: ButtonState, pressed: ButtonsPressed) void { 876 | _ = buttons; 877 | 878 | if (state.level == data.levels.len) { 879 | screen = .win; 880 | menu_option = 0; 881 | // state.game_elapsed_ns = state.timer.read(); 882 | return; 883 | } 884 | 885 | if (pressed.anyPressed()) { 886 | button_press_ev_queue.push(pressed); 887 | } 888 | 889 | switch (state.turn_state) { 890 | .ready => { 891 | if (button_press_ev_queue.pop()) |input| { 892 | if (input.action_1) { 893 | platform.trace("aim item"); 894 | find_action_targets(state) catch { 895 | platform.trace("error: failed to find action targets"); 896 | }; 897 | state.action_target = 0; 898 | state.turn_state = .aim; 899 | gfx.move_anim_start_frame = gfx.frame_counter; 900 | } else if (input.action_2) { 901 | try_cycle_item(state); 902 | } else if (input.up) { 903 | try_move(state, state.player.entity.location.walk(.north, 1)); 904 | } else if (input.right) { 905 | gfx.flip_player_sprite = false; 906 | try_move(state, state.player.entity.location.walk(.east, 1)); 907 | } else if (input.down) { 908 | try_move(state, state.player.entity.location.walk(.south, 1)); 909 | } else if (input.left) { 910 | gfx.flip_player_sprite = true; 911 | try_move(state, state.player.entity.location.walk(.west, 1)); 912 | } 913 | } 914 | }, 915 | .aim => { 916 | if (button_press_ev_queue.pop()) |input| { 917 | if (input.action_1) { 918 | if (state.action_targets.len == 0) { 919 | cancel_aim(state); 920 | } else { 921 | platform.trace("commit action"); 922 | const target_location = state.action_targets.get(state.action_target); 923 | switch (state.player.active_item) { 924 | .fists, .sword => try_move(state, target_location), 925 | .small_axe => if (try_hit_enemy(state, target_location)) { 926 | state.player.remove_item(.small_axe); 927 | spawn_pickup(state, target_location, .small_axe); 928 | if (target_location.x < state.player.entity.location.x) { 929 | gfx.flip_player_sprite = true; 930 | } else if (target_location.x > state.player.entity.location.x) { 931 | gfx.flip_player_sprite = false; 932 | } 933 | commit_move(state); 934 | }, 935 | } 936 | } 937 | } else if (input.action_2) { 938 | cancel_aim(state); 939 | } else if (state.action_targets.len > 0) { 940 | if (input.up or input.right) { 941 | sfx.walk(); 942 | state.action_target = @as(u8, @intCast( 943 | if (state.action_target == state.action_targets.len - 1) 0 else state.action_target + 1, 944 | )); 945 | gfx.move_anim_start_frame = gfx.frame_counter; 946 | } else if (input.down or input.left) { 947 | sfx.walk(); 948 | state.action_target = @as(u8, @intCast( 949 | if (state.action_target == 0) state.action_targets.len - 1 else state.action_target - 1, 950 | )); 951 | gfx.move_anim_start_frame = gfx.frame_counter; 952 | } 953 | } 954 | } 955 | }, 956 | .commit => { 957 | if (gfx.frame_counter > gfx.move_anim_start_frame + gfx.move_animation_length) { 958 | switch (state.player.entity.state) { 959 | .walk => { 960 | state.player.entity.location = state.player.entity.target_location; 961 | }, 962 | else => { 963 | state.player.entity.target_location = state.player.entity.location; 964 | }, 965 | } 966 | 967 | entities_apply_pending_damage(&state.monsters); 968 | entities_apply_pending_damage(&state.fire_monsters); 969 | entities_apply_pending_damage(&state.charge_monsters); 970 | 971 | respond_to_move(state); 972 | update_world_visibilty(state); 973 | state.player.entity.state = .idle; 974 | gfx.move_anim_start_frame = gfx.frame_counter; 975 | state.turn_state = .response; 976 | } 977 | }, 978 | .response => { 979 | if (gfx.frame_counter > gfx.move_anim_start_frame + gfx.move_animation_length) { 980 | entities_complete_move(&state.monsters); 981 | entities_complete_move(&state.fire_monsters); 982 | entities_complete_move(&state.charge_monsters); 983 | entities_complete_move(&state.fire); 984 | 985 | state.player.entity.target_location = state.player.entity.location; 986 | 987 | if (state.player.entity.pending_damage > 0) { 988 | state.player.entity.health -= state.player.entity.pending_damage; 989 | state.player.entity.pending_damage = 0; 990 | state.player.entity.did_receive_damage = true; 991 | sfx.receive_damage(); 992 | } 993 | 994 | if (state.player.entity.health <= 0) { 995 | platform.trace("player died"); 996 | state.turn_state = .dead; 997 | // state.game_elapsed_ns = state.timer.read(); 998 | } else { 999 | state.turn_state = .ready; 1000 | } 1001 | 1002 | state.turn += 1; 1003 | } 1004 | }, 1005 | .dead => {}, 1006 | } 1007 | 1008 | defer { 1009 | state.player.entity.did_receive_damage = false; 1010 | for (&state.monsters) |*monster| { 1011 | monster.entity.did_receive_damage = false; 1012 | } 1013 | for (&state.fire_monsters) |*monster| { 1014 | monster.entity.did_receive_damage = false; 1015 | } 1016 | for (&state.charge_monsters) |*monster| { 1017 | monster.entity.did_receive_damage = false; 1018 | } 1019 | } 1020 | 1021 | gfx.draw_game(state); 1022 | 1023 | if (state.turn_state == .dead) { 1024 | gfx.draw_transparent_overlay(); 1025 | screenStats(state, pressed, "YOU DIED", .reload, null); 1026 | menu_option = state.level; 1027 | } else { 1028 | gfx.draw_hud(state); 1029 | } 1030 | } 1031 | 1032 | fn screenTitle(state: anytype, input: anytype) void { 1033 | gfx.draw_title_menu(); 1034 | 1035 | if (input.action_1) { 1036 | sfx.walk(); 1037 | screen = .game; 1038 | menu_option = 0; 1039 | state.reset(); 1040 | state.load_level(0); 1041 | platform.trace("start game"); 1042 | } else if (input.action_2) { 1043 | sfx.walk(); 1044 | screen = .controls; 1045 | menu_option = 0; 1046 | platform.trace("show controls"); 1047 | } 1048 | } 1049 | 1050 | fn screenControls(input: anytype) void { 1051 | gfx.draw_controls(); 1052 | 1053 | if (input.action_1 or input.action_2) { 1054 | sfx.walk(); 1055 | screen = .title; 1056 | menu_option = 0; 1057 | platform.trace("return to title screen"); 1058 | } 1059 | } 1060 | 1061 | fn screenStats( 1062 | state: anytype, 1063 | input: anytype, 1064 | title_text: []const u8, 1065 | advance_screen: Screen, 1066 | maybe_retreat_screen: ?Screen, 1067 | ) void { 1068 | gfx.draw_screen_title(title_text); 1069 | 1070 | // const total_elasped_sec = @divTrunc(state.game_elapsed_ns, 1_000_000_000); 1071 | // const elapsed_minutes = @divTrunc(total_elasped_sec, 60); 1072 | // const elapsed_seconds = total_elasped_sec - (elapsed_minutes + 60); 1073 | gfx.draw_stats(.{ 1074 | .turns_taken = state.turn, 1075 | // .elapsed_m = elapsed_minutes, 1076 | // .elapsed_s = elapsed_seconds, 1077 | }); 1078 | 1079 | if (input.action_1) { 1080 | sfx.walk(); 1081 | screen = advance_screen; 1082 | return; 1083 | } 1084 | 1085 | if (maybe_retreat_screen) |retreat_screen| { 1086 | if (input.action_1) { 1087 | sfx.walk(); 1088 | screen = retreat_screen; 1089 | } 1090 | } 1091 | } 1092 | 1093 | fn screenReload(state: anytype, input: anytype) void { 1094 | switch (state.level) { 1095 | 0 => { 1096 | screen = .game; 1097 | menu_option = 0; 1098 | state.load_level(0); 1099 | }, 1100 | else => { 1101 | if (input.action_1) { 1102 | screen = .game; 1103 | defer menu_option = 0; 1104 | state.load_level(menu_option); 1105 | state.player.entity.health = player_level_starting_health[menu_option]; 1106 | state.player.items = player_level_starting_items[menu_option]; 1107 | state.player.active_item = player_level_starting_active_item[menu_option]; 1108 | } 1109 | 1110 | if (input.up or input.right) { 1111 | menu_option = if (menu_option == state.level) 0 else menu_option + 1; 1112 | } else if (input.down or input.left) { 1113 | menu_option = if (menu_option == 0) state.level else menu_option - 1; 1114 | } 1115 | 1116 | gfx.draw_reload_screen(state, &menu_option); 1117 | }, 1118 | } 1119 | } 1120 | 1121 | test { 1122 | std.testing.refAllDecls(@This()); 1123 | } 1124 | -------------------------------------------------------------------------------- /src/gfx.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const platform = @import("platform"); 4 | 5 | const util = @import("util"); 6 | const count_digits_fast = util.count_digits_fast; 7 | const NumberDigitIterator = util.NumberDigitIterator; 8 | 9 | const world = @import("world"); 10 | 11 | pub const max_sprites = 64; 12 | pub const move_animation_length = 3; 13 | pub const camera_height = 0; 14 | 15 | pub var frame_counter: usize = 0; 16 | pub var move_anim_start_frame: usize = 0; 17 | pub var camera_world_pos = world.Position{ .x = 0, .y = 0, .z = 0 }; 18 | pub var flip_player_sprite = false; 19 | 20 | pub const ScreenPosition = struct { 21 | x: i32, 22 | y: i32, 23 | 24 | pub inline fn add(self: @This(), other: @This()) @This() { 25 | return .{ 26 | .x = self.x + other.x, 27 | .y = self.y + other.y, 28 | }; 29 | } 30 | 31 | pub inline fn sub(self: @This(), other: @This()) @This() { 32 | return .{ 33 | .x = self.x - other.x, 34 | .y = self.y - other.y, 35 | }; 36 | } 37 | 38 | pub inline fn from_world_position( 39 | world_position: world.Position, 40 | camera_position: world.Position, 41 | ) @This() { 42 | return .{ 43 | .x = world_position.x - camera_position.x + platform.screen_width / 2, 44 | .y = world_position.y - camera_position.y + platform.screen_height / 2, 45 | }; 46 | } 47 | 48 | pub inline fn to_world_position( 49 | self: @This(), 50 | camera_position: world.Position, 51 | ) world.Position { 52 | return .{ 53 | .x = self.x + camera_position.x - platform.screen_width / 2, 54 | .y = self.y + camera_position.y - platform.screen_height / 2, 55 | }; 56 | } 57 | }; 58 | 59 | pub fn init() void { 60 | platform.set_palette([_]u32{ 61 | 0x211e20, 62 | 0x494f5b, 63 | 0x808070, 64 | 0xe9efec, 65 | }); 66 | } 67 | 68 | const Sprite = struct { 69 | texture: Texture, 70 | draw_colours: u16, 71 | world_position: world.Position, 72 | target_world_position: world.Position, 73 | flip_x: bool = false, 74 | casts_shadow: bool = false, 75 | decoration_texture: ?Texture = null, 76 | }; 77 | 78 | const SpriteList = std.BoundedArray(Sprite, max_sprites); 79 | 80 | fn sprite_list_draw(sprite_list: *SpriteList, visibilty_map: anytype, anim_frame: u32) void { 81 | for (sprite_list.constSlice()) |*sprite| { 82 | const actual_world_position = sprite.world_position.lerp_to( 83 | sprite.target_world_position, 84 | anim_frame, 85 | move_animation_length, 86 | ); 87 | if (world.map_get_tile(visibilty_map, actual_world_position.to_map_location()) > 0) { 88 | const screen_pos = ScreenPosition.from_world_position( 89 | actual_world_position, 90 | camera_world_pos, 91 | ); 92 | platform.set_draw_colours(sprite.draw_colours); 93 | platform.blit( 94 | sprite.texture.bytes, 95 | screen_pos.x - sprite.texture.width / 2, 96 | screen_pos.y - sprite.texture.height / 2 - world.map_world_scale / 2, 97 | sprite.texture.width, 98 | sprite.texture.height, 99 | 1, 100 | sprite.flip_x, 101 | ); 102 | } 103 | } 104 | } 105 | 106 | fn sprite_list_draw_shadows(sprite_list: *SpriteList, visibilty_map: anytype, anim_frame: u32) void { 107 | for (sprite_list.constSlice()) |*sprite| { 108 | if (sprite.casts_shadow) { 109 | const actual_world_position = sprite.world_position.lerp_to( 110 | sprite.target_world_position, 111 | anim_frame, 112 | move_animation_length, 113 | ); 114 | if (world.map_get_tile(visibilty_map, actual_world_position.to_map_location()) > 0) { 115 | const screen_pos = ScreenPosition.from_world_position( 116 | actual_world_position, 117 | camera_world_pos, 118 | ); 119 | platform.set_draw_colours(0x11); 120 | platform.oval( 121 | screen_pos.x - 3, 122 | screen_pos.y - 1, 123 | 7, 124 | 3, 125 | ); 126 | } 127 | } 128 | } 129 | } 130 | 131 | fn sprite_list_draw_decorations( 132 | sprite_list: *SpriteList, 133 | visibilty_map: anytype, 134 | anim_frame: u32, 135 | ) void { 136 | for (sprite_list.constSlice()) |*sprite| { 137 | if (sprite.decoration_texture) |*decoration_texture| { 138 | const actual_world_position = sprite.world_position.lerp_to( 139 | sprite.target_world_position, 140 | anim_frame, 141 | move_animation_length, 142 | ); 143 | if (world.map_get_tile(visibilty_map, actual_world_position.to_map_location()) > 0) { 144 | const screen_pos = ScreenPosition.from_world_position( 145 | actual_world_position, 146 | camera_world_pos, 147 | ); 148 | platform.set_draw_colours(0x40); 149 | platform.blit( 150 | decoration_texture.bytes, 151 | screen_pos.x + world.map_world_scale / 2 - decoration_texture.width, 152 | screen_pos.y - world.map_world_scale - decoration_texture.height / 2, 153 | decoration_texture.width, 154 | decoration_texture.height, 155 | 1, 156 | false, 157 | ); 158 | } 159 | } 160 | } 161 | } 162 | 163 | fn sprite_list_push_enemy_sprites( 164 | sprite_list: *SpriteList, 165 | kind: enum { monster, fire_monster, charge_monster }, 166 | enemies: anytype, 167 | ) void { 168 | for (&enemies) |*enemy| { 169 | if (enemy.entity.health > 0) { 170 | sprite_list.append(.{ 171 | .texture = switch (kind) { 172 | .monster => Texture.monster, 173 | .fire_monster => Texture.fire_monster, 174 | .charge_monster => Texture.charge_monster, 175 | }, 176 | .draw_colours = if (enemy.entity.did_receive_damage) 0x40 else 0x20, 177 | .world_position = world.Position.from_map_location(enemy.entity.location, 0), 178 | .target_world_position = world.Position.from_map_location( 179 | enemy.entity.target_location, 180 | 0, 181 | ), 182 | .casts_shadow = true, 183 | .decoration_texture = switch (kind) { 184 | .charge_monster => switch (enemy.entity.state) { 185 | .charge => Texture.alert_marker, 186 | else => null, 187 | }, 188 | else => null, 189 | }, 190 | }) catch { 191 | platform.trace("warning: failed to push sprite. no space left"); 192 | }; 193 | } 194 | } 195 | } 196 | 197 | pub fn draw_game(state: anytype) void { 198 | const move_animation_frame = switch (state.turn_state) { 199 | .aim, .commit, .response => frame_counter - move_anim_start_frame, 200 | else => 0, 201 | }; 202 | 203 | { // update camera 204 | if (state.turn_state == .aim and state.action_targets.len > 0) { 205 | // move to aim target 206 | if (move_animation_frame <= move_animation_length) { 207 | const target_location = state.action_targets.get(state.action_target); 208 | camera_world_pos = camera_world_pos.lerp_to( 209 | world.Position.from_map_location(target_location, 0), 210 | move_animation_frame, 211 | move_animation_length, 212 | ); 213 | } else { 214 | const camera_location = state.action_targets.get(state.action_target); 215 | camera_world_pos = world.Position.from_map_location(camera_location, 0); 216 | } 217 | } else { 218 | // follow player 219 | camera_world_pos = world.Position.from_map_location(state.player.entity.location, 0).lerp_to( 220 | world.Position.from_map_location(state.player.entity.target_location, 0), 221 | move_animation_frame, 222 | move_animation_length, 223 | ); 224 | } 225 | } 226 | 227 | var sprite_list = SpriteList{}; 228 | 229 | sprite_list_push_enemy_sprites(&sprite_list, .monster, state.monsters); 230 | sprite_list_push_enemy_sprites(&sprite_list, .fire_monster, state.fire_monsters); 231 | sprite_list_push_enemy_sprites(&sprite_list, .charge_monster, state.charge_monsters); 232 | 233 | // push pickup sprites 234 | for (&state.pickups) |*pickup| { 235 | if (pickup.entity.health > 0) { 236 | sprite_list.append(.{ 237 | .texture = switch (pickup.kind) { 238 | .health => Texture.heart, 239 | .sword => Texture.sword, 240 | .small_axe => Texture.small_axe, 241 | }, 242 | .draw_colours = 0x40, 243 | .world_position = world.Position.from_map_location(pickup.entity.location, 0), 244 | .target_world_position = world.Position.from_map_location( 245 | pickup.entity.target_location, 246 | 0, 247 | ), 248 | .casts_shadow = true, 249 | }) catch { 250 | platform.trace("warning: failed to push sprite. no space left"); 251 | }; 252 | } 253 | } 254 | 255 | { // push player sprite 256 | sprite_list.append(.{ 257 | .texture = Texture.player, 258 | .draw_colours = if (state.player.entity.did_receive_damage) 0x40 else 0x20, 259 | .world_position = world.Position.from_map_location(state.player.entity.location, 0), 260 | .target_world_position = world.Position.from_map_location( 261 | state.player.entity.target_location, 262 | 0, 263 | ), 264 | .flip_x = flip_player_sprite, 265 | .casts_shadow = true, 266 | }) catch { 267 | platform.trace("warning: failed to push sprite. no space left"); 268 | }; 269 | } 270 | 271 | // push fire sprites 272 | for (&state.fire) |*fire| { 273 | if (fire.entity.health <= 0) { 274 | continue; 275 | } 276 | 277 | sprite_list.append(.{ 278 | .texture = Texture.fire_big, 279 | .draw_colours = 0x40, 280 | .world_position = world.Position.from_map_location(fire.entity.location, 0), 281 | .target_world_position = world.Position.from_map_location( 282 | fire.entity.location, 283 | 0, 284 | ), 285 | }) catch { 286 | platform.trace("warning: failed to push sprite. no space left"); 287 | }; 288 | 289 | if (fire.path.len > 0) { 290 | const next_location = fire.path.slice()[if (fire.path.len > 1) 1 else 0]; 291 | sprite_list.append(.{ 292 | .texture = Texture.fire_small, 293 | .draw_colours = 0x40, 294 | .world_position = world.Position.from_map_location(next_location, 0), 295 | .target_world_position = world.Position.from_map_location(next_location, 0), 296 | }) catch { 297 | platform.trace("warning: failed to push sprite. no space left"); 298 | }; 299 | } 300 | } 301 | 302 | { // draw world 303 | var location: world.MapLocation = .{ .x = 0, .y = 0 }; 304 | while (location.x < world.map_columns) : (location.x += 1) { 305 | defer location.y = 0; 306 | while (location.y < world.map_rows) : (location.y += 1) { 307 | switch (world.map_get_tile_kind(state.world_map, location)) { 308 | .wall, .secret_path => {}, 309 | .door => { 310 | platform.set_draw_colours(0x30); 311 | const screen_pos = ScreenPosition.from_world_position( 312 | world.Position.from_map_location(location, 0).sub(.{ 313 | .x = world.map_world_scale / 2, 314 | .y = world.map_world_scale / 2, 315 | .z = 0, 316 | }), 317 | camera_world_pos, 318 | ); 319 | platform.blit( 320 | Texture.door.bytes, 321 | screen_pos.x + (world.map_world_scale - Texture.door.width) / 2, 322 | screen_pos.y + (world.map_world_scale - Texture.door.height) / 2, 323 | Texture.door.width, 324 | Texture.door.height, 325 | 1, 326 | false, 327 | ); 328 | }, 329 | else => { 330 | if (world.map_get_tile(state.world_vis_map, location) > 0) { 331 | // TODO(hazeycode): optimise floor drawing by deferring and rendering contiguous blocks 332 | platform.set_draw_colours(0x33); 333 | const screen_pos = ScreenPosition.from_world_position( 334 | world.Position.from_map_location(location, 0).sub(.{ 335 | .x = world.map_world_scale / 2, 336 | .y = world.map_world_scale / 2, 337 | .z = 0, 338 | }), 339 | camera_world_pos, 340 | ); 341 | platform.rect( 342 | screen_pos.x, 343 | screen_pos.y, 344 | world.map_world_scale, 345 | world.map_world_scale, 346 | ); 347 | } 348 | }, 349 | } 350 | } 351 | } 352 | } 353 | 354 | sprite_list_draw_shadows(&sprite_list, state.world_vis_map, move_animation_frame); 355 | 356 | if (state.turn_state == .aim) { 357 | draw_tile_markers(state); 358 | } 359 | 360 | sprite_list_draw(&sprite_list, state.world_vis_map, move_animation_frame); 361 | 362 | sprite_list_draw_decorations(&sprite_list, state.world_vis_map, move_animation_frame); 363 | } 364 | 365 | pub fn draw_tile_markers(state: anytype) void { 366 | var i: usize = 0; 367 | while (i < state.action_targets.len) : (i += 1) { 368 | const target = state.action_targets.get(i); 369 | const screen_pos = ScreenPosition.from_world_position( 370 | world.Position.from_map_location(target, 0), 371 | camera_world_pos, 372 | ); 373 | 374 | platform.set_draw_colours(0x4444); 375 | 376 | const width = world.map_world_scale; 377 | const height = world.map_world_scale + 3; 378 | const half_width = width / 2; 379 | const half_height = height / 4; 380 | 381 | const x0 = screen_pos.x; 382 | const y0 = screen_pos.y - half_height; 383 | const x1 = screen_pos.x + half_width; 384 | const y1 = screen_pos.y; 385 | const x2 = screen_pos.x; 386 | const y2 = screen_pos.y + half_height; 387 | const x3 = screen_pos.x - half_width; 388 | const y3 = screen_pos.y; 389 | 390 | platform.line(x0, y0, x1, y1); 391 | platform.line(x1, y1, x2, y2); 392 | platform.line(x2, y2, x3, y3); 393 | platform.line(x3, y3, x0, y0); 394 | } 395 | } 396 | 397 | pub fn draw_hud(state: anytype) void { 398 | if (state.turn_state == .aim) { 399 | platform.set_draw_colours(0x04); 400 | 401 | if (state.action_targets.len == 0) { 402 | platform.text("NO TARGETS", 1, platform.screen_height - (8 + 1) * 2); 403 | } else { 404 | platform.text("AIM", 1, platform.screen_height - (8 + 1) * 2); 405 | 406 | const active_target = state.action_targets.get(state.action_target); 407 | 408 | const screen_pos = ScreenPosition.from_world_position( 409 | world.Position.from_map_location(active_target, 0), 410 | camera_world_pos, 411 | ); 412 | 413 | const width = world.map_world_scale; 414 | const height = world.map_world_scale + 3; 415 | const half_width = width / 2; 416 | const half_height = height / 2; 417 | 418 | const x0 = screen_pos.x - half_width; 419 | const y0 = screen_pos.y - half_height - 4; 420 | const x1 = screen_pos.x + half_width; 421 | const y1 = screen_pos.y + half_height - 4; 422 | 423 | platform.hline(x0, y0, width); 424 | platform.hline(x0, y1, width); 425 | platform.vline(x0, y0, height); 426 | platform.vline(x1, y0, height); 427 | } 428 | } 429 | 430 | { // draw health bar 431 | platform.set_draw_colours(0x40); 432 | 433 | const piece_width: u16 = 8; 434 | const piece_height: u16 = 8; 435 | if (state.player.entity.health > 0) { 436 | const width: u16 = @as(u16, @intCast(state.player.entity.health)) * piece_width; 437 | const y = @as(i32, @intCast(platform.screen_height)) - piece_height - 1; 438 | var x: i32 = @as(i32, @intCast(platform.screen_width)) - @as(i32, @intCast(width)) - 1; 439 | var i: usize = 0; 440 | while (i < state.player.entity.health) : (i += 1) { 441 | platform.blit( 442 | Texture.heart.bytes, 443 | x, 444 | y, 445 | piece_width, 446 | piece_height, 447 | 1, 448 | false, 449 | ); 450 | x += piece_width; 451 | } 452 | } 453 | } 454 | 455 | { // draw active item 456 | platform.set_draw_colours(0x04); 457 | const str = switch (state.player.active_item) { 458 | .fists => "FISTS", 459 | .sword => "SWORD", 460 | .small_axe => "THROWING AXE", 461 | }; 462 | platform.text(str, 1, platform.screen_height - 8 - 1); 463 | } 464 | } 465 | 466 | pub fn draw_screen_title(title_text: []const u8) void { 467 | platform.set_draw_colours(0x04); 468 | text_centered(title_text, @divTrunc(platform.screen_width, 4)); 469 | } 470 | 471 | pub fn draw_stats(stats: anytype) void { 472 | { 473 | const postfix = " turns taken"; 474 | const y = platform.screen_height / 3 * 2; 475 | const w = 8 * (count_digits_fast(stats.turns_taken) + 1 + postfix.len); 476 | var x = @as(i32, @intCast(platform.screen_width / 2 - w / 2)); 477 | x += draw_text_number(stats.turns_taken, x, y); 478 | x += 8; 479 | platform.text(postfix, x, y); 480 | } 481 | 482 | // { 483 | // const y = platform.screen_height / 2 + 1; 484 | // var x: i32 = 10; 485 | 486 | // if (stats.elapsed_m > 99) { 487 | // platform.text("> 99 minutes elapsed !?", x, y); 488 | // } else { 489 | // x += draw_text_number(@intCast(i32, stats.elapsed_m), x, y); 490 | // platform.text(":", x, y); 491 | // x += 8; 492 | // x += draw_text_number(@intCast(i32, stats.elapsed_s), x, y); 493 | // platform.text(" elapsed", x, y); 494 | // } 495 | // } 496 | } 497 | 498 | pub fn draw_menu_bg() void { 499 | const bg = Texture.title_screen_bg; 500 | platform.set_draw_colours(0x2321); 501 | platform.blit(bg.bytes, 0, 0, bg.width, bg.height, 2, false); 502 | } 503 | 504 | pub fn draw_title_menu() void { 505 | draw_menu_bg(); 506 | 507 | platform.set_draw_colours(0x04); 508 | text_centered("Escape Guldur", @divTrunc(platform.screen_height, 2) - 8); 509 | platform.text("\x80 START", 16, platform.screen_height - (8 + 4) * 2); 510 | platform.text("\x81 CONTROLS", 16, platform.screen_height - (8 + 4)); 511 | } 512 | 513 | pub fn draw_controls() void { 514 | draw_menu_bg(); 515 | draw_transparent_overlay(); 516 | 517 | platform.set_draw_colours(0x04); 518 | text_centered("CONTROLS", 12); 519 | platform.text("\x84\x85\x86\x87 move /", 10, 50 + (8 + 1) * 0); 520 | platform.text(" change target", 10, 50 + (8 + 1) * 1); 521 | platform.text("\x80 aim item /", 10, 50 + (8 + 1) * 4); 522 | platform.text(" use item", 10, 50 + (8 + 1) * 5); 523 | platform.text("\x81 cycle item /", 10, 50 + (8 + 1) * 8); 524 | platform.text(" cancel aim", 10, 50 + (8 + 1) * 9); 525 | } 526 | 527 | pub fn draw_reload_screen(state: anytype, menu_option: *u8) void { 528 | // TODO(hazeycode): rewrite 529 | platform.set_draw_colours(0x04); 530 | var y: i32 = 12; 531 | text_centered("RELOAD", y); 532 | y += 8 * 3; 533 | if (state.level >= 5) { 534 | text_centered( 535 | if (menu_option.* == 5) "> Level 6 <" else "Level 6", 536 | y, 537 | ); 538 | y += 8 + 4; 539 | } 540 | if (state.level >= 4) { 541 | text_centered( 542 | if (menu_option.* == 4) "> Level 5 <" else "Level 5", 543 | y, 544 | ); 545 | y += 8 + 4; 546 | } 547 | if (state.level >= 3) { 548 | text_centered( 549 | if (menu_option.* == 3) "> Level 4 <" else "Level 4", 550 | y, 551 | ); 552 | y += 8 + 4; 553 | } 554 | if (state.level >= 2) { 555 | text_centered( 556 | if (menu_option.* == 2) "> Level 3 <" else "Level 3", 557 | y, 558 | ); 559 | y += 8 + 4; 560 | } 561 | if (state.level >= 1) { 562 | text_centered( 563 | if (menu_option.* == 1) "> Level 2 <" else "Level 2", 564 | y, 565 | ); 566 | y += 8 + 4; 567 | } 568 | if (state.level >= 0) { 569 | text_centered( 570 | if (menu_option.* == 0) "> Level 1 <" else "Level 1", 571 | y, 572 | ); 573 | y += 8 + 4; 574 | } 575 | } 576 | 577 | pub fn draw_text_number(number: anytype, x: i32, y: i32) u16 { 578 | var dx: u16 = 0; 579 | 580 | if (number < 0) { 581 | platform.text("-", x + dx, y); 582 | } 583 | 584 | const number_abs: u32 = @intCast( 585 | if (number < 0) -number else number, 586 | ); 587 | 588 | var digit_iter = NumberDigitIterator(u32).init(number_abs); 589 | while (digit_iter.next()) |digit| { 590 | dx += 8; 591 | platform.text(&[_]u8{'0' + digit}, x + dx, y); 592 | } 593 | 594 | return dx; 595 | } 596 | 597 | pub fn text_centered(str: []const u8, y: i32) void { 598 | const glyph_width = 8; 599 | const text_width = str.len * glyph_width; 600 | platform.text( 601 | str, 602 | @as(i32, @intCast(platform.screen_width / 2 - text_width / 2)), 603 | y, 604 | ); 605 | } 606 | 607 | pub fn draw_transparent_overlay() void { 608 | platform.set_draw_colours(0x01); 609 | var i: i32 = 0; 610 | while (i < platform.screen_width) : (i += 2) { 611 | platform.line(i, 0, 0, i); 612 | } 613 | i = 1; 614 | while (i < platform.screen_height) : (i += 2) { 615 | platform.line(platform.screen_width - 1, i, i, platform.screen_height - 1); 616 | } 617 | } 618 | 619 | pub const Texture = struct { 620 | bytes: []const u8, 621 | width: u16, 622 | height: u16, 623 | bpp: u8, 624 | 625 | pub const alert_marker = @This(){ 626 | .bytes = &[6]u8{ 627 | 0b00000100, 628 | 0b00000100, 629 | 0b00000100, 630 | 0b00000000, 631 | 0b00000100, 632 | 0b00000000, 633 | }, 634 | .width = 8, 635 | .height = 6, 636 | .bpp = 1, 637 | }; 638 | 639 | pub const door = @This(){ 640 | .bytes = &[10]u8{ 641 | 0b00000000, 642 | 0b00000000, 643 | 0b00111100, 644 | 0b01111110, 645 | 0b11111111, 646 | 0b11111111, 647 | 0b11111111, 648 | 0b11111111, 649 | 0b11111111, 650 | 0b11111111, 651 | }, 652 | .width = 8, 653 | .height = 10, 654 | .bpp = 1, 655 | }; 656 | 657 | pub const player = @This(){ 658 | .bytes = &[10]u8{ 659 | 0b00011000, 660 | 0b00011000, 661 | 0b00111000, 662 | 0b01111100, 663 | 0b10111100, 664 | 0b10111010, 665 | 0b00111000, 666 | 0b00101100, 667 | 0b00100100, 668 | 0b00100100, 669 | }, 670 | .width = 8, 671 | .height = 10, 672 | .bpp = 1, 673 | }; 674 | 675 | pub const monster = @This(){ 676 | .bytes = &[10]u8{ 677 | 0b00000000, 678 | 0b00000001, 679 | 0b00000001, 680 | 0b00011001, 681 | 0b00011001, 682 | 0b00111101, 683 | 0b01011011, 684 | 0b01011001, 685 | 0b00100100, 686 | 0b00100100, 687 | }, 688 | .width = 8, 689 | .height = 10, 690 | .bpp = 1, 691 | }; 692 | 693 | pub const fire_monster = @This(){ 694 | .bytes = &[8]u8{ 695 | 0b00100100, 696 | 0b01011010, 697 | 0b01111110, 698 | 0b11111011, 699 | 0b10111111, 700 | 0b10111010, 701 | 0b00101000, 702 | 0b00101000, 703 | }, 704 | .width = 8, 705 | .height = 8, 706 | .bpp = 1, 707 | }; 708 | 709 | pub const charge_monster = @This(){ 710 | .bytes = &[8]u8{ 711 | 0b00000000, 712 | 0b00011100, 713 | 0b00111110, 714 | 0b01111111, 715 | 0b11111111, 716 | 0b11111110, 717 | 0b00100010, 718 | 0b00010001, 719 | }, 720 | .width = 8, 721 | .height = 8, 722 | .bpp = 1, 723 | }; 724 | 725 | pub const fire_small = @This(){ 726 | .bytes = &[8]u8{ 727 | 0b00000000, 728 | 0b00000100, 729 | 0b01000000, 730 | 0b00001000, 731 | 0b00011000, 732 | 0b00110100, 733 | 0b01111000, 734 | 0b00000000, 735 | }, 736 | .width = 8, 737 | .height = 8, 738 | .bpp = 1, 739 | }; 740 | 741 | pub const fire_big = @This(){ 742 | .bytes = &[8]u8{ 743 | 0b00100000, 744 | 0b00001000, 745 | 0b00011000, 746 | 0b00110100, 747 | 0b01100110, 748 | 0b11111110, 749 | 0b01111100, 750 | 0b00000000, 751 | }, 752 | .width = 8, 753 | .height = 8, 754 | .bpp = 1, 755 | }; 756 | 757 | pub const heart = @This(){ 758 | .bytes = &[8]u8{ 759 | 0b00000000, 760 | 0b00110110, 761 | 0b01111111, 762 | 0b01111111, 763 | 0b01111111, 764 | 0b00111110, 765 | 0b00011100, 766 | 0b00001000, 767 | }, 768 | .width = 8, 769 | .height = 8, 770 | .bpp = 1, 771 | }; 772 | 773 | pub const sword = @This(){ 774 | .bytes = &[8]u8{ 775 | 0b00000000, 776 | 0b00000010, 777 | 0b00000110, 778 | 0b00001100, 779 | 0b01011000, 780 | 0b00110000, 781 | 0b01010000, 782 | 0b10000000, 783 | }, 784 | .width = 8, 785 | .height = 8, 786 | .bpp = 1, 787 | }; 788 | 789 | pub const small_axe = @This(){ 790 | .bytes = &[8]u8{ 791 | 0b00000000, 792 | 0b00001100, 793 | 0b00011111, 794 | 0b00001110, 795 | 0b00010100, 796 | 0b00100000, 797 | 0b01000000, 798 | 0b00000000, 799 | }, 800 | .width = 8, 801 | .height = 8, 802 | .bpp = 1, 803 | }; 804 | 805 | pub const title_screen_bg = @This(){ 806 | .width = 160, 807 | .height = 160, 808 | .bpp = 2, 809 | .bytes = &[6400]u8{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x04, 0x00, 0x05, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x00, 0x00, 0x50, 0x00, 0x00, 0x15, 0x55, 0x55, 0x40, 0x00, 0x00, 0x00, 0x01, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x40, 0x05, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x04, 0x00, 0x55, 0x00, 0x00, 0x41, 0x55, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x40, 0x15, 0x45, 0x56, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x54, 0x15, 0x55, 0x54, 0x01, 0x45, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x50, 0x00, 0x01, 0x50, 0x05, 0x15, 0x56, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x50, 0x55, 0x55, 0x55, 0x55, 0x55, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x55, 0x56, 0xa9, 0x66, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x01, 0x55, 0x05, 0x50, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x45, 0x55, 0x56, 0xaa, 0xaa, 0xa9, 0x55, 0x95, 0x65, 0x55, 0x55, 0x15, 0x51, 0x15, 0x55, 0x05, 0x50, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x15, 0x01, 0x55, 0x5a, 0xae, 0xaa, 0xaa, 0xaa, 0x96, 0x55, 0x56, 0x54, 0x54, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x54, 0x00, 0x05, 0x55, 0x55, 0x6a, 0xaf, 0xeb, 0xab, 0xfa, 0x59, 0x65, 0x6a, 0xe5, 0x55, 0x50, 0x01, 0x54, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x01, 0x15, 0x55, 0xaf, 0xff, 0xef, 0xaf, 0xfe, 0xaa, 0xa9, 0xaf, 0xff, 0x95, 0x50, 0x51, 0x10, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x11, 0x50, 0x00, 0x05, 0x55, 0x55, 0xab, 0xfa, 0xff, 0xff, 0xff, 0xfe, 0xba, 0xaf, 0xff, 0x95, 0x54, 0x14, 0x10, 0x55, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x15, 0x55, 0x00, 0x55, 0x55, 0x56, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xa9, 0x55, 0x44, 0x15, 0x15, 0x50, 0x01, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x15, 0x55, 0x41, 0x05, 0x15, 0x56, 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x95, 0x55, 0x15, 0x55, 0x54, 0x51, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x15, 0x55, 0x55, 0x51, 0x05, 0x5a, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x95, 0x55, 0x55, 0x01, 0x55, 0x50, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x15, 0x55, 0x55, 0x55, 0x55, 0x6a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x95, 0x54, 0x55, 0x41, 0x55, 0x40, 0x54, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x10, 0x01, 0x40, 0x55, 0x55, 0x55, 0x56, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x95, 0x55, 0x51, 0x50, 0x55, 0x50, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x01, 0x41, 0x55, 0x55, 0x55, 0x5a, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xea, 0xbf, 0xff, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x50, 0x11, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x55, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x15, 0x01, 0x55, 0x55, 0x55, 0x6a, 0xab, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xff, 0xea, 0xff, 0xff, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0x54, 0x10, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x04, 0x00, 0x00, 0x50, 0x04, 0x00, 0x01, 0x00, 0x15, 0x51, 0x55, 0x6a, 0x95, 0x6a, 0xab, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xbf, 0xfa, 0xff, 0xff, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0x55, 0x00, 0x00, 0x05, 0x40, 0x00, 0x00, 0x00, 0x00, 0x50, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x55, 0x55, 0x96, 0xa5, 0xaa, 0xab, 0xff, 0xff, 0xff, 0xff, 0xeb, 0xff, 0xfe, 0xbf, 0xff, 0xfa, 0xaa, 0x95, 0x55, 0x55, 0x55, 0x55, 0x54, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x41, 0x55, 0x55, 0x6a, 0xaa, 0xaa, 0xaa, 0xaf, 0xbb, 0xff, 0xff, 0x6b, 0xff, 0xea, 0xbf, 0xff, 0xfa, 0xaa, 0x95, 0x55, 0x55, 0x55, 0x55, 0x54, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x55, 0x55, 0x6a, 0xaa, 0xaa, 0xaa, 0xab, 0xea, 0xff, 0xfe, 0x27, 0xff, 0xea, 0xbf, 0xff, 0xfa, 0xaa, 0x95, 0x55, 0x55, 0x55, 0x55, 0x55, 0x50, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x40, 0x00, 0x55, 0x55, 0x6a, 0xaa, 0xaa, 0xaa, 0xab, 0xfb, 0xff, 0xff, 0x17, 0xff, 0xda, 0xbf, 0xff, 0xfa, 0xaa, 0x95, 0x55, 0x55, 0x55, 0x55, 0x51, 0x00, 0x15, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x04, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x55, 0x5a, 0x6a, 0x5a, 0xaa, 0xaa, 0xab, 0xff, 0xff, 0xfe, 0x16, 0xff, 0xd5, 0x7f, 0xff, 0xfa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x40, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x5a, 0xaa, 0x6a, 0xaa, 0xaa, 0xaf, 0xaf, 0xff, 0xfe, 0x06, 0xff, 0xd5, 0x7f, 0xff, 0xee, 0xaa, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x45, 0x40, 0x00, 0x00, 0x01, 0x00, 0x15, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x01, 0x55, 0x5a, 0x5a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaf, 0xff, 0xfe, 0x11, 0xff, 0xd5, 0x7f, 0xff, 0xfe, 0xaa, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x14, 0x00, 0x00, 0x00, 0x05, 0x40, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x45, 0x55, 0xa9, 0x56, 0x5a, 0xba, 0xaa, 0xaf, 0xab, 0xff, 0xfe, 0x05, 0xff, 0xd5, 0x2f, 0xff, 0xfe, 0xaa, 0x55, 0x55, 0x55, 0x69, 0x55, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x04, 0x50, 0x55, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x55, 0xa5, 0x56, 0x5a, 0xba, 0xaa, 0xab, 0xfb, 0xff, 0xfe, 0x11, 0xff, 0xd5, 0x2f, 0xff, 0xfe, 0xaa, 0x55, 0x55, 0x55, 0x59, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x55, 0x55, 0xa5, 0x69, 0x5a, 0xaa, 0xaa, 0xaa, 0xab, 0xff, 0xfd, 0x15, 0xff, 0xd5, 0x2f, 0xff, 0xff, 0xaa, 0xa6, 0x96, 0x56, 0x55, 0x55, 0x55, 0x40, 0x00, 0x00, 0x00, 0x05, 0x50, 0x01, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x5a, 0x69, 0xa9, 0x56, 0xaa, 0xaa, 0xab, 0xfa, 0xaa, 0xf9, 0x01, 0xff, 0xd5, 0x2f, 0xff, 0xff, 0xfa, 0xa9, 0x96, 0x56, 0x55, 0x55, 0x54, 0x00, 0x00, 0x44, 0x00, 0x05, 0x40, 0x01, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x6a, 0x59, 0xa9, 0x56, 0xaa, 0xaa, 0xab, 0xea, 0xbf, 0xfd, 0x00, 0xff, 0xd5, 0x1f, 0xff, 0xff, 0xfa, 0xa9, 0x59, 0x5a, 0x55, 0x55, 0x55, 0x04, 0x54, 0x51, 0x00, 0x01, 0x50, 0x15, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x15, 0xa5, 0x55, 0xa5, 0x5a, 0xaa, 0xaa, 0xaa, 0xaa, 0xfb, 0xfd, 0x00, 0xff, 0x95, 0x1f, 0xff, 0xff, 0xfa, 0xa9, 0x5a, 0xa9, 0x55, 0x55, 0x54, 0x55, 0x55, 0x54, 0x00, 0x05, 0x54, 0x15, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x56, 0xa5, 0x55, 0x55, 0x59, 0x6a, 0xaa, 0xaa, 0xab, 0xff, 0xf9, 0x00, 0xbf, 0x95, 0x1f, 0xff, 0xff, 0xfa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x00, 0x05, 0x50, 0x55, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x56, 0x95, 0x55, 0x55, 0x69, 0x6a, 0xaa, 0x9a, 0xab, 0xff, 0xfe, 0x01, 0xbf, 0xe5, 0x1f, 0xff, 0xff, 0xfa, 0xa9, 0xa9, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x45, 0x05, 0x50, 0x5a, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x55, 0x55, 0x95, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaf, 0xfe, 0xba, 0xfd, 0x01, 0xbf, 0xe5, 0x4f, 0xff, 0xff, 0xfa, 0xa9, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x15, 0x55, 0x5a, 0xaf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x55, 0x55, 0x59, 0x55, 0x55, 0x56, 0xaa, 0xaa, 0xaf, 0xff, 0xbf, 0xfd, 0x01, 0x7f, 0xe5, 0x4b, 0xff, 0xff, 0xff, 0xea, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xa9, 0x59, 0x55, 0x55, 0x65, 0x6a, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xa5, 0x55, 0x55, 0x55, 0x59, 0x56, 0xaa, 0xaa, 0xaa, 0xaf, 0xaa, 0xfd, 0x05, 0xff, 0xe4, 0x0b, 0xff, 0xff, 0xff, 0xfa, 0x65, 0xa5, 0x55, 0x55, 0x55, 0x5a, 0xba, 0xaa, 0xaa, 0x96, 0xaa, 0xab, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xa5, 0x55, 0x55, 0x55, 0x56, 0xa6, 0x5a, 0xaa, 0xaa, 0xbe, 0xaa, 0xa9, 0x05, 0xbf, 0xe4, 0x4f, 0xff, 0xff, 0xfa, 0xfe, 0xa9, 0xa5, 0x55, 0x55, 0x55, 0xab, 0xff, 0xff, 0xaa, 0x9a, 0xaa, 0xaf, 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x41, 0x06, 0xa5, 0x55, 0x55, 0x55, 0x56, 0xaa, 0x5a, 0xaa, 0xaa, 0xba, 0xaa, 0xbd, 0x00, 0x2f, 0xe9, 0x4b, 0xff, 0xff, 0xfa, 0xee, 0xaa, 0x95, 0x55, 0xa5, 0x5a, 0xaf, 0xff, 0xff, 0xfe, 0xaa, 0xaa, 0xbf, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x56, 0xa9, 0x69, 0x55, 0x55, 0x56, 0x95, 0x55, 0xaa, 0x9a, 0xaa, 0xaa, 0xad, 0x00, 0x1b, 0xe8, 0x4b, 0xff, 0xff, 0xea, 0xaa, 0xaa, 0x96, 0x56, 0xaa, 0xaa, 0xbf, 0xff, 0xff, 0xff, 0xab, 0xbf, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x5a, 0x55, 0x55, 0x55, 0x55, 0x5a, 0x95, 0x56, 0xa6, 0x6a, 0xaa, 0xaa, 0xa5, 0x00, 0x0b, 0xe8, 0x4f, 0xff, 0xff, 0xfa, 0xea, 0xaa, 0xaa, 0x9a, 0xaa, 0xaa, 0xbf, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x55, 0x55, 0x56, 0x55, 0x55, 0x6a, 0xa5, 0x5a, 0x5a, 0x5a, 0xaa, 0xaa, 0xa4, 0x00, 0x07, 0xe4, 0x4b, 0xff, 0xff, 0xfa, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x50, 0x15, 0x95, 0x55, 0x55, 0x55, 0x55, 0x5a, 0xa5, 0xaa, 0x5a, 0x6a, 0xaa, 0xaa, 0xa9, 0x10, 0x06, 0x94, 0x0b, 0xff, 0xff, 0xbe, 0xaf, 0xab, 0xaa, 0xaa, 0xab, 0xeb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0x01, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x56, 0x96, 0xaa, 0xaa, 0x5a, 0xaa, 0xaa, 0x95, 0x00, 0x02, 0x94, 0x0b, 0xae, 0xbe, 0xaa, 0xaa, 0xab, 0xfa, 0xaa, 0xab, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x55, 0x55, 0x65, 0x55, 0x55, 0x55, 0x55, 0x55, 0x56, 0x9a, 0xaa, 0xaa, 0x5a, 0xaa, 0xaa, 0x89, 0x00, 0x02, 0xa4, 0x16, 0xaa, 0xae, 0xbe, 0xea, 0xaf, 0xff, 0xaa, 0xaf, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x55, 0x55, 0x55, 0x65, 0x55, 0x55, 0x55, 0x55, 0x55, 0x56, 0x9a, 0xaa, 0xaa, 0x5a, 0xae, 0xaa, 0x85, 0x00, 0x02, 0x90, 0x01, 0x6a, 0xaf, 0xbf, 0xfa, 0xbf, 0xff, 0xef, 0xfa, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x69, 0x55, 0x6a, 0x95, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5a, 0x56, 0xaa, 0xaa, 0x4a, 0xfe, 0xfa, 0x85, 0x00, 0x02, 0x94, 0x02, 0x6a, 0xaa, 0xbf, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x5a, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0xaa, 0x69, 0x6a, 0x66, 0xaa, 0xaa, 0x4a, 0xfb, 0xaa, 0x45, 0x00, 0x02, 0xd4, 0x06, 0x69, 0xae, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x1a, 0xaa, 0x55, 0x55, 0x55, 0x55, 0x56, 0xaa, 0xaa, 0xaa, 0x56, 0xaa, 0xaa, 0x4b, 0xbf, 0xaa, 0x80, 0x00, 0x01, 0xd4, 0x02, 0x9a, 0xff, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x1a, 0xaa, 0xa9, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xa6, 0x55, 0x6a, 0xaa, 0x4b, 0xff, 0xa9, 0x40, 0x00, 0x01, 0xd4, 0x01, 0x55, 0x6e, 0x2b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x05, 0x45, 0x6a, 0x9a, 0xa5, 0x55, 0x55, 0x15, 0x55, 0xaa, 0xaa, 0xaa, 0x5a, 0xaa, 0xbe, 0x4b, 0xfb, 0x90, 0x40, 0x00, 0x06, 0x94, 0x02, 0x99, 0xae, 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa5, 0x15, 0x55, 0x55, 0x5a, 0x55, 0x55, 0x55, 0x51, 0x56, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xae, 0x4b, 0xaa, 0x54, 0x40, 0x00, 0x06, 0x94, 0x02, 0x99, 0xae, 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaa, 0x15, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x51, 0x5a, 0xaa, 0xbf, 0xaa, 0xaa, 0xaa, 0xae, 0x4b, 0xaa, 0x54, 0x00, 0x00, 0x01, 0x98, 0x02, 0x90, 0xaa, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaa, 0x45, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5a, 0xaa, 0xbf, 0xfa, 0xab, 0xaa, 0xaa, 0x5b, 0xaa, 0x44, 0x00, 0x00, 0x01, 0x98, 0x02, 0xa1, 0xae, 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaa, 0x15, 0x55, 0x55, 0x55, 0x55, 0xa9, 0x55, 0x95, 0x5a, 0xfa, 0xbf, 0xfa, 0xab, 0xea, 0xae, 0x4b, 0xaa, 0x40, 0x00, 0x10, 0x00, 0x54, 0x02, 0xa0, 0xfa, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xea, 0x95, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xfa, 0xaf, 0xfe, 0xff, 0xfe, 0xaf, 0xfe, 0xae, 0x0b, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x50, 0x01, 0x94, 0xbf, 0x1f, 0xff, 0xff, 0xff, 0xfe, 0xbb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xea, 0xfa, 0x69, 0x65, 0x55, 0x56, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xee, 0x1b, 0xaa, 0x40, 0x00, 0x00, 0x01, 0x94, 0x01, 0xa5, 0xae, 0x0b, 0xff, 0xff, 0xff, 0xfa, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xbf, 0xff, 0xb9, 0x55, 0x6a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xeb, 0xee, 0x16, 0xba, 0x40, 0x00, 0x00, 0x00, 0x54, 0x40, 0x91, 0x6e, 0x07, 0xff, 0xff, 0xbe, 0xff, 0xba, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xfe, 0xaa, 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xaa, 0x06, 0xaa, 0x80, 0x00, 0x00, 0x00, 0x90, 0x00, 0x90, 0x19, 0x07, 0xff, 0xfe, 0xaa, 0xaf, 0xaa, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xef, 0xeb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xaa, 0x06, 0xaa, 0x40, 0x00, 0x00, 0x00, 0x90, 0x00, 0x90, 0x04, 0x07, 0xff, 0xfe, 0xbb, 0xff, 0xae, 0xeb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xad, 0x06, 0xae, 0x40, 0x04, 0x00, 0x00, 0x40, 0x01, 0x50, 0x05, 0x03, 0xff, 0xff, 0xeb, 0xff, 0xfe, 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xbd, 0x06, 0xaa, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x05, 0x02, 0xff, 0xea, 0xef, 0xff, 0xff, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x99, 0x06, 0xa5, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x07, 0xeb, 0xef, 0xef, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x99, 0x06, 0x91, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x02, 0xeb, 0xef, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x69, 0x06, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x05, 0x02, 0xeb, 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x55, 0x01, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x05, 0x02, 0xff, 0xeb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x54, 0x01, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xeb, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x55, 0x00, 0x40, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xaa, 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x45, 0x01, 0x40, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xaa, 0x9b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x40, 0x01, 0x01, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x06, 0xaa, 0x5a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x01, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xa5, 0x55, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xea, 0xbb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x00, 0x10, 0x11, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x05, 0xbf, 0xff, 0xff, 0xff, 0xbf, 0xea, 0xaa, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa1, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xfe, 0xaa, 0xaa, 0xab, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x44, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa1, 0x56, 0x6f, 0xff, 0xff, 0xff, 0xba, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xa0, 0x05, 0x3f, 0xff, 0xff, 0xff, 0xaa, 0x56, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xa0, 0x14, 0x7f, 0xff, 0xff, 0xff, 0xaa, 0x5a, 0xaa, 0xaa, 0xaf, 0xff, 0xff, 0xff, 0xaa, 0xae, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xa0, 0x14, 0x7f, 0xff, 0xff, 0xff, 0xaa, 0xaa, 0xaa, 0xaa, 0xaf, 0xff, 0xff, 0xff, 0xa9, 0xbb, 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x54, 0x50, 0x7f, 0xff, 0xff, 0xfe, 0xaa, 0x9a, 0xaa, 0xab, 0xab, 0xff, 0xff, 0xff, 0xaa, 0xba, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x2f, 0xff, 0xff, 0xfa, 0xa6, 0xa9, 0x6a, 0xab, 0xef, 0xff, 0xff, 0xff, 0x5a, 0x6a, 0xae, 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x3f, 0xff, 0xff, 0xaa, 0x9a, 0xaa, 0x9a, 0xab, 0xbf, 0xff, 0xff, 0xff, 0x55, 0x06, 0xae, 0xaa, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x45, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x40, 0x2f, 0xff, 0xff, 0xaa, 0x9a, 0xaa, 0x6a, 0xaa, 0xbf, 0xff, 0xff, 0xff, 0x00, 0x02, 0xab, 0xaa, 0xaa, 0xbf, 0xff, 0xfa, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x54, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x2f, 0xef, 0xff, 0xaa, 0x6a, 0xa9, 0xaa, 0xaa, 0xbf, 0xff, 0xff, 0xff, 0x00, 0x00, 0x5b, 0xaa, 0xaa, 0xbf, 0xff, 0xea, 0xaf, 0xff, 0xff, 0xff, 0xff, 0xf4, 0x00, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, 0xff, 0xfe, 0xaa, 0xaa, 0x95, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0x00, 0x00, 0x06, 0xaa, 0xaa, 0xaf, 0xea, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xf4, 0x40, 0x10, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x50, 0x6f, 0xff, 0xff, 0xa5, 0x5a, 0xa6, 0xaa, 0x9a, 0xab, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0x9a, 0xaa, 0xab, 0xaa, 0xaa, 0xaa, 0xbf, 0xff, 0xff, 0xff, 0xf4, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x40, 0x00, 0x00, 0x01, 0x54, 0x6f, 0xff, 0xea, 0xa5, 0x6a, 0xa6, 0xaa, 0x5a, 0xaf, 0xaf, 0xff, 0xff, 0x00, 0x00, 0x00, 0xaa, 0xa6, 0x6b, 0xe9, 0xaa, 0xaa, 0xaa, 0xbf, 0xff, 0xff, 0xf4, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x01, 0x54, 0x6f, 0xff, 0xfa, 0xa6, 0x9a, 0x9a, 0x65, 0x5a, 0xae, 0xaf, 0xff, 0xea, 0x00, 0x00, 0x00, 0x6a, 0xa5, 0xaa, 0xba, 0x55, 0x6a, 0xaa, 0xab, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x6b, 0xff, 0xea, 0xaa, 0x5a, 0x99, 0x55, 0x5a, 0xaa, 0xaa, 0xfa, 0x55, 0x00, 0x00, 0x00, 0x1a, 0xa9, 0x6a, 0xab, 0x95, 0x5a, 0x9a, 0xaa, 0xff, 0xff, 0xf0, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0xfe, 0xa9, 0x6a, 0x6a, 0x59, 0x55, 0x55, 0x5a, 0xaa, 0x90, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x69, 0x6a, 0xab, 0xf5, 0x5a, 0x55, 0x9a, 0xbf, 0xff, 0xf0, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6a, 0xba, 0xa5, 0x55, 0xaa, 0x65, 0x65, 0x55, 0x45, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x59, 0x5a, 0xaa, 0xfe, 0x5a, 0x55, 0x5a, 0xaa, 0xbf, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x6a, 0xaa, 0x95, 0x6a, 0xa9, 0x65, 0x94, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x5a, 0xaa, 0xbe, 0x96, 0x55, 0xa5, 0xaa, 0xa7, 0xe0, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x40, 0x6a, 0xa9, 0x55, 0x6a, 0x55, 0xa9, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x59, 0xaa, 0xaa, 0x91, 0x96, 0xa9, 0x56, 0xaa, 0xf0, 0x00, 0x05, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6a, 0xa6, 0x95, 0x95, 0x56, 0xae, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x9a, 0xaa, 0xae, 0x91, 0x56, 0xa9, 0x65, 0xaa, 0xf0, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6a, 0xa5, 0x6a, 0xaa, 0xaa, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x9a, 0xaa, 0xaa, 0x91, 0x56, 0x69, 0xa5, 0x55, 0xf0, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6a, 0x95, 0x5a, 0x9a, 0xa9, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xaa, 0xaa, 0xab, 0x91, 0x55, 0x6a, 0x55, 0x55, 0x50, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x2a, 0x95, 0x55, 0x95, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xaa, 0xaa, 0xaf, 0x91, 0x15, 0x6a, 0xa5, 0x55, 0x50, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x1a, 0x65, 0xa6, 0x55, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xaa, 0xaa, 0xab, 0xc0, 0x15, 0x55, 0x95, 0x55, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x15, 0x66, 0xa9, 0x55, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0xaa, 0xaa, 0x80, 0x15, 0x55, 0x59, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x1a, 0x5a, 0x65, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x66, 0xaa, 0x00, 0x15, 0x56, 0xaa, 0x95, 0x40, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x6a, 0x55, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x11, 0xaa, 0x80, 0x19, 0x6a, 0xaa, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x69, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xaa, 0x40, 0x19, 0xaa, 0x99, 0x54, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xaa, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x40, 0x15, 0xaa, 0x55, 0x94, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x40, 0x15, 0xaa, 0x54, 0x40, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x40, 0x05, 0x9a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x05, 0x55, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x55, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x65, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x55, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x01, 0x56, 0x55, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x14, 0x41, 0x55, 0xa5, 0x55, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, 0x00, 0x50, 0x05, 0x55, 0x55, 0x55, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x04, 0x00, 0x50, 0x15, 0x45, 0x54, 0x14, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x10, 0x55, 0x51, 0x55, 0x55, 0x55, 0x50, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0x55, 0x50, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55, 0xa6, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x55, 0x55, 0x55, 0x95, 0xa9, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x54, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x55, 0x55, 0x56, 0x51, 0x54, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x41, 0x54, 0x00, 0x00, 0x01, 0x55, 0x55, 0x55, 0x55, 0x90, 0x44, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x15, 0x00, 0x40, 0x15, 0xa5, 0x55, 0x55, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x01, 0x55, 0xaa, 0x55, 0x6a, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x05, 0x55, 0x55, 0x51, 0x96, 0x5a, 0x6a, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x40, 0x55, 0x55, 0x50, 0x55, 0x55, 0x19, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x54, 0x01, 0x00, 0x00, 0x45, 0x15, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x50, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x14, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x55, 0x69, 0x51, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x15, 0x5a, 0xa5, 0x55, 0x51, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x15, 0x6a, 0xb9, 0x55, 0x14, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x51, 0x00, 0x00, 0x01, 0x00, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x50, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x05, 0x5a, 0x69, 0x56, 0x69, 0x59, 0x45, 0xa6, 0xaa, 0xa6, 0x95, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x15, 0x55, 0x55, 0x95, 0x59, 0x59, 0x55, 0x5b, 0xcf, 0xee, 0xba, 0x95, 0xa5, 0x55, 0x95, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x15, 0x55, 0x95, 0x55, 0x56, 0x65, 0x95, 0xaa, 0x4a, 0xaa, 0xba, 0xaa, 0xaa, 0xa5, 0x55, 0x54, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x41, 0x55, 0x6a, 0xaa, 0xa6, 0xa9, 0xab, 0xae, 0xfa, 0x96, 0xaa, 0xaa, 0xaa, 0xaa, 0x55, 0x55, 0x55, 0x55, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x55, 0x01, 0x55, 0x55, 0x5b, 0xfe, 0xaa, 0xaa, 0xaa, 0xa6, 0x96, 0xaa, 0x56, 0xa5, 0x55, 0x55, 0x51, 0x55, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x56, 0xaa, 0xaa, 0xaa, 0xaf, 0xff, 0xea, 0xa5, 0x55, 0x6a, 0xaa, 0xaa, 0x56, 0x55, 0x65, 0x6a, 0x50, 0x45, 0x55, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x55, 0xbe, 0xaf, 0xe5, 0x59, 0x5a, 0xba, 0xff, 0xea, 0xff, 0xaf, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x40, 0x55, 0x55, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x55, 0x55, 0x55, 0x50, 0x00, 0x01, 0xaa, 0xef, 0xea, 0xfa, 0xaf, 0xeb, 0xfe, 0xfa, 0x95, 0x59, 0x40, 0x15, 0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x55, 0x54, 0x00, 0x00, 0x00, 0x5a, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xfa, 0xaa, 0xa9, 0x55, 0x00, 0x15, 0x41, 0x55, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x06, 0xbe, 0xaa, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xea, 0x95, 0x00, 0x15, 0x50, 0x41, 0x69, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x6a, 0xbe, 0xff, 0x6f, 0xff, 0xbf, 0xff, 0xe9, 0x40, 0x16, 0x5a, 0xab, 0xe4, 0x40, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0xaa, 0xaa, 0xff, 0x8f, 0xff, 0xaf, 0xff, 0xeb, 0x40, 0x2a, 0xaf, 0xfa, 0xa9, 0xaa, 0x55, 0x69, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xbf, 0xff, 0xbf, 0xff, 0x4b, 0xff, 0xaa, 0xff, 0xff, 0x40, 0x1e, 0xfe, 0xa5, 0x54, 0xba, 0xaa, 0x9a, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xfb, 0xff, 0xff, 0x4b, 0xff, 0xff, 0xfa, 0xfe, 0x00, 0x0a, 0x95, 0x15, 0x51, 0x95, 0x46, 0xf9, 0xb9, 0x50, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xd6, 0xaa, 0xaa, 0x07, 0xbf, 0xff, 0xaa, 0xaa, 0x40, 0x00, 0x09, 0x54, 0x55, 0x15, 0x00, 0x55, 0x90, 0x15, 0x40, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x45, 0x7f, 0x55, 0x02, 0xbf, 0xff, 0xff, 0xfa, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x50, 0x45, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x55, 0x02, 0xff, 0xaa, 0xaa, 0x95, 0x00, 0x00, 0x00, 0x40, 0x00, 0x01, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x55, 0x55, 0x55, 0x51, 0x55, 0x55, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x45, 0x1d, 0x55, 0x54, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x04, 0x01, 0x40, 0x40, 0x00 }, 810 | }; 811 | }; 812 | 813 | test { 814 | std.testing.refAllDecls(@This()); 815 | } 816 | --------------------------------------------------------------------------------