281 lines
12 KiB
Zig
281 lines
12 KiB
Zig
//
|
|
// WASM-4: https://wasm4.org/docs
|
|
|
|
const std = @import("std");
|
|
|
|
fn assert_equal(a: anytype, b: anytype, comptime error_msg: []const u8) void {
|
|
if (a != b) @compileError(std.fmt.comptimePrint("{} != {} {s}", .{a, b, error_msg}));
|
|
}
|
|
|
|
comptime {
|
|
const builtin = @import("builtin");
|
|
const native_endian = builtin.target.cpu.arch.endian();
|
|
assert_equal(native_endian, .Little, "Bit flags need little endian");
|
|
}
|
|
|
|
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
// │ │
|
|
// │ Platform Constants │
|
|
// │ │
|
|
// └───────────────────────────────────────────────────────────────────────────┘
|
|
|
|
pub const SCREEN_SIZE: u32 = 160;
|
|
|
|
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
// │ │
|
|
// │ Memory Addresses │
|
|
// │ │
|
|
// └───────────────────────────────────────────────────────────────────────────┘
|
|
|
|
pub const Memory = packed struct {
|
|
_padding: [4]u8,
|
|
palette: [4]Color,
|
|
colors: DrawColors,
|
|
gamepads: [4]GamePad,
|
|
mouse: Mouse,
|
|
system: SystemFlags,
|
|
_reserved: [128]u8,
|
|
framebuffer: [6400]u8,
|
|
userdata: [58976]u8,
|
|
};
|
|
|
|
comptime {
|
|
assert_equal(@bitSizeOf(Memory), 64 * 1024 * 8, "Memory layout wrong");
|
|
}
|
|
|
|
const dummym: Memory = undefined;
|
|
|
|
pub const m = struct {
|
|
pub const palette = @intToPtr(*@TypeOf(dummym.palette ), 0 + @offsetOf(Memory, "palette" ));
|
|
pub const colors = @intToPtr(*@TypeOf(dummym.colors ), 0 + @offsetOf(Memory, "colors" ));
|
|
pub const gamepads = @intToPtr(*@TypeOf(dummym.gamepads ), 0 + @offsetOf(Memory, "gamepads" ));
|
|
pub const mouse = @intToPtr(*@TypeOf(dummym.mouse ), 0 + @offsetOf(Memory, "mouse" ));
|
|
pub const system = @intToPtr(*@TypeOf(dummym.system ), 0 + @offsetOf(Memory, "system" ));
|
|
pub const framebuffer = @intToPtr(*@TypeOf(dummym.framebuffer), 0 + @offsetOf(Memory, "framebuffer"));
|
|
pub const userdata = @intToPtr(*@TypeOf(dummym.userdata ), 0 + @offsetOf(Memory, "userdata" ));
|
|
};
|
|
|
|
pub const Color = packed struct {
|
|
blue : u8,
|
|
green : u8,
|
|
red : u8,
|
|
_reserved: u8 = 0,
|
|
};
|
|
|
|
pub const DrawColors = packed struct {
|
|
_0: ColorIndex,
|
|
_1: ColorIndex,
|
|
_2: ColorIndex,
|
|
_3: ColorIndex,
|
|
};
|
|
|
|
pub const ColorIndex = enum(u4) {
|
|
transparent = 0,
|
|
/// Use palette[0]
|
|
p0 = 1,
|
|
/// Use palette[1]
|
|
p1 = 2,
|
|
/// Use palette[2]
|
|
p2 = 3,
|
|
/// Use palette[3]
|
|
p3 = 4,
|
|
};
|
|
|
|
pub const GamePad = packed struct {
|
|
a: bool,
|
|
b: bool,
|
|
_reserved: u2,
|
|
left: bool,
|
|
right: bool,
|
|
up: bool,
|
|
down: bool,
|
|
};
|
|
|
|
pub const Mouse = packed struct {
|
|
x: i16,
|
|
y: i16,
|
|
/// primary button
|
|
b0: bool,
|
|
/// secondary button
|
|
b1: bool,
|
|
/// third button
|
|
b2: bool,
|
|
_reserved: u5,
|
|
};
|
|
|
|
pub const SystemFlags = packed struct {
|
|
preserve_framebuffer: bool,
|
|
hide_gamepad_overlay: bool,
|
|
_reserved: u6,
|
|
};
|
|
|
|
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
// │ │
|
|
// │ Raw Functions │
|
|
// │ │
|
|
// └───────────────────────────────────────────────────────────────────────────┘
|
|
|
|
const raw_api = struct {
|
|
extern fn blit(sprite: [*]const u8, x: u32, y: u32, width: u32, height: u32, flags: u32) void;
|
|
extern fn blitSub(sprite: [*]const u8, x: u32, y: u32, width: u32, height: u32, src_x: u32, src_y: u32, stride: u32, flags: u32) void;
|
|
extern fn tone(frequency: u32, duration: u32, volume: u32, flags: u32) void;
|
|
};
|
|
|
|
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
// │ │
|
|
// │ Drawing Functions │
|
|
// │ │
|
|
// └───────────────────────────────────────────────────────────────────────────┘
|
|
|
|
pub const BlitFlags = packed struct {
|
|
/// one or two bits per pixel
|
|
two_bits: bool = false,
|
|
flip_x: bool = false,
|
|
flip_y: bool = false,
|
|
rotate: bool = false,
|
|
_reserved: u28 = 0,
|
|
};
|
|
|
|
/// Copies pixels to the framebuffer.
|
|
pub fn blit(sprite: []const u8, x: u32, y: u32, width: u32, height: u32, flags: BlitFlags) void {
|
|
raw_api.blit(sprite.ptr, x, y, width, height, @bitCast(u32, flags));
|
|
}
|
|
|
|
/// Copies a subregion within a larger sprite atlas to the framebuffer.
|
|
/// srcX: Source X position of the sprite region.
|
|
/// srcY: Source Y position of the sprite region.
|
|
/// stride: Total width of the overall sprite atlas. This is typically larger than width.
|
|
pub fn blitSub(sprite: []const u8, x: u32, y: u32, width: u32, height: u32, src_x: u32, src_y: u32, stride: u32, flags: BlitFlags) void {
|
|
raw_api.blitSub(sprite.ptr, x, y, width, height, src_x, src_y, stride, @bitCast(u32, flags));
|
|
}
|
|
|
|
/// Draws a line between two points.
|
|
pub extern fn line(x1: u32, y1: u32, x2: u32, y2: u32) void;
|
|
|
|
/// Draws an oval (or circle).
|
|
pub extern fn oval(x: u32, y: u32, width: u32, height: u32) void;
|
|
|
|
/// Draws a rectangle.
|
|
pub extern fn rect(x: u32, y: u32, width: u32, height: u32) void;
|
|
|
|
/// Draws text using the built-in system font.
|
|
pub fn text(str: []const u8, x: u32, y: u32) void {
|
|
textUtf8(str.ptr, str.len, x, y);
|
|
}
|
|
extern fn textUtf8(strPtr: [*]const u8, strLen: usize, x: u32, y: u32) void;
|
|
|
|
/// Draws a vertical line
|
|
pub extern fn vline(x: u32, y: u32, len: u32) void;
|
|
|
|
/// Draws a horizontal line
|
|
pub extern fn hline(x: u32, y: u32, len: u32) void;
|
|
|
|
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
// │ │
|
|
// │ Sound Functions │
|
|
// │ │
|
|
// └───────────────────────────────────────────────────────────────────────────┘
|
|
|
|
/// Plays a sound tone.
|
|
/// frequency: Wave frequency in hertz.
|
|
/// duration: Duration of the tone in frames (1/60th of a second), up to 255 frames.
|
|
/// volume: Volume of the sustain and attack durations, between 0 and 100.
|
|
pub fn tone(frequency: Tone.Frequency, duration: Tone.Duration, volume: Tone.Volume, flags: Tone.Flags) void {
|
|
std.debug.assert(volume.is_valid());
|
|
raw_api.tone(@bitCast(u32, frequency), @bitCast(u32, duration), @bitCast(u32, volume), @bitCast(u32, flags));
|
|
}
|
|
|
|
pub const Tone = struct {
|
|
/// Wave frequency in hertz.
|
|
pub const Frequency = packed struct {
|
|
start: u16,
|
|
end: u16 = 0,
|
|
};
|
|
|
|
/// Duration of ADSR of note (unit: frames)
|
|
///
|
|
/// ^
|
|
/// / \ decay
|
|
/// attack / \
|
|
/// / \--------
|
|
/// / sustain \ release
|
|
/// / \
|
|
pub const Duration = packed struct {
|
|
sustain : u8 = 0,
|
|
release : u8 = 0,
|
|
decay : u8 = 0,
|
|
attack : u8 = 0,
|
|
};
|
|
|
|
/// Volume of note (0 to 100)
|
|
///
|
|
/// ^ <-- attack volume
|
|
/// / \
|
|
/// / \
|
|
/// / \-------- <-- sustain valume
|
|
/// / \
|
|
/// / \
|
|
pub const Volume = packed struct {
|
|
sustain : u8,
|
|
attack : u8 = 100,
|
|
_reserved: u16 = 0,
|
|
|
|
pub fn is_valid(volume: @This()) bool {
|
|
return (0 <= volume.sustain) and (volume.sustain <= 100)
|
|
and (0 <= volume.attack ) and (volume.attack <= 100);
|
|
}
|
|
};
|
|
|
|
pub const Flags = packed struct {
|
|
channel: Channel,
|
|
pulse_duty: DutyCycle = 2,
|
|
pan: Pan = .both,
|
|
_reserved: u26 = 0,
|
|
};
|
|
|
|
pub const Channel = enum(u2) {
|
|
pulse0 = 0,
|
|
pulse1 = 1,
|
|
triangle = 2,
|
|
noise = 3,
|
|
};
|
|
|
|
pub const DutyCycle = enum(u2) {
|
|
@"1/8" = 0,
|
|
@"1/4" = 1,
|
|
@"1/2" = 2,
|
|
};
|
|
|
|
/// Which stereo channel(s) to play audio in
|
|
pub const Pan = enum(u2) {
|
|
/// play audio in both stereo channels
|
|
both = 0,
|
|
left = 1,
|
|
right = 2,
|
|
};
|
|
};
|
|
|
|
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
// │ │
|
|
// │ Storage Functions │
|
|
// │ │
|
|
// └───────────────────────────────────────────────────────────────────────────┘
|
|
|
|
/// Reads up to `size` bytes from persistent storage into the pointer `dest`.
|
|
pub extern fn diskr(dest: [*]u8, size: u32) u32;
|
|
|
|
/// Writes up to `size` bytes from the pointer `src` into persistent storage.
|
|
pub extern fn diskw(src: [*]const u8, size: u32) u32;
|
|
|
|
// ┌───────────────────────────────────────────────────────────────────────────┐
|
|
// │ │
|
|
// │ Other Functions │
|
|
// │ │
|
|
// └───────────────────────────────────────────────────────────────────────────┘
|
|
|
|
/// Prints a message to the debug console.
|
|
pub fn trace(x: []const u8) void {
|
|
traceUtf8(x.ptr, x.len);
|
|
}
|
|
extern fn traceUtf8(strPtr: [*]const u8, strLen: usize) void;
|