├── .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 |
11 |
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 |
--------------------------------------------------------------------------------