summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralaric <alaric@netmythos.org>2024-04-07 06:07:29 -0700
committeralaric <alaric@netmythos.org>2024-04-07 06:07:29 -0700
commit4aef5c2443bd6eb57c6be3ff9bfa422c3ce3d2cf (patch)
treee942a8df99871e2095d020fb351e290f015bc5c0
parentb614db3cf2a89f565b06471dc47e1bd345c0b070 (diff)
downloadsnake-4aef5c2443bd6eb57c6be3ff9bfa422c3ce3d2cf.tar.gz
snake-4aef5c2443bd6eb57c6be3ff9bfa422c3ce3d2cf.zip
Complete Snake for now
-rw-r--r--src/grid_vertex.glsl14
-rw-r--r--src/matrix.zig4
-rw-r--r--src/shell.html5
-rw-r--r--src/wasm_snake.zig233
-rw-r--r--src/webgl.js5
-rw-r--r--src/webgl.zig18
-rw-r--r--src/webgl_bindings.zig1
7 files changed, 232 insertions, 48 deletions
diff --git a/src/grid_vertex.glsl b/src/grid_vertex.glsl
index 2cfb4f0..c163c38 100644
--- a/src/grid_vertex.glsl
+++ b/src/grid_vertex.glsl
@@ -7,10 +7,23 @@ uniform float gap;
uniform int columns;
uniform vec2 root;
uniform vec3 color;
+uniform vec3 anim_color;
+uniform ivec2 anim_coord;
+uniform float anim_t;
out vec4 a_col;
void main() {
+ int coord_x = gl_InstanceID % columns;
+ int coord_y = gl_InstanceID / columns;
+ ivec2 dist = ivec2(coord_x, coord_y) - anim_coord;
+ int dist_sq = dist.x * dist.x + dist.y * dist.y;
+ if (dist_sq >= int(anim_t * anim_t) && dist_sq < int((anim_t + 1.0) * (anim_t + 1.0))) {
+ a_col = vec4(anim_color, 1);
+ } else {
+ a_col = vec4(color, 1);
+ }
+
float row = float(gl_InstanceID / columns);
float col = float(gl_InstanceID % columns);
vec2 offset = vec2(col * scale + gap * col, row * scale + gap * row) + root;
@@ -18,5 +31,4 @@ void main() {
vec2(0, 1), vec2(1, 0), vec2(1, 1) );
vec2 p = (verts[gl_VertexID] * scale) + offset;
gl_Position = vec4((matrix * vec3(p, 1)).xy, 0, 1);
- a_col = vec4(color, 1);
}
diff --git a/src/matrix.zig b/src/matrix.zig
index 485f2f1..76b8a4d 100644
--- a/src/matrix.zig
+++ b/src/matrix.zig
@@ -36,6 +36,10 @@ pub fn Matrix(comptime T: type, row_count: usize, col_count: usize) type {
return result;
}
+ pub fn translate(self: Self, t: [rows - 1]T) Self {
+ return self.multiply(Self.translation(t));
+ }
+
pub fn translation(t: [rows - 1]T) Self {
var base = Self.identity;
for (0..rows - 1) |ri| {
diff --git a/src/shell.html b/src/shell.html
index 3d84bf2..6fc484c 100644
--- a/src/shell.html
+++ b/src/shell.html
@@ -14,6 +14,7 @@
margin: 0;
}
#app-canvas {
+ box-sizing: border-box;
display: flex;
flex-grow: 1;
width: 1280px;
@@ -27,8 +28,8 @@
<canvas id="app-canvas" tabindex="0"></canvas>
<script src="webgl.js"></script>
<script>
- canvas.width = canvas.clientWidth;
- canvas.height = canvas.clientHeight;
+ canvas.width = parseFloat(window.getComputedStyle(canvas).getPropertyValue("width"));
+ canvas.height = parseFloat(window.getComputedStyle(canvas).getPropertyValue("height"));
let memory = null;
let last_frame_time = Date.now();
diff --git a/src/wasm_snake.zig b/src/wasm_snake.zig
index 22e7258..98ba1b4 100644
--- a/src/wasm_snake.zig
+++ b/src/wasm_snake.zig
@@ -13,6 +13,9 @@ const GridProg = webgl.Program(
.{ .identifier = "gap", .shader_name = "gap", .kind = .f32 },
.{ .identifier = "root", .shader_name = "root", .kind = .vec2 },
.{ .identifier = "color", .shader_name = "color", .kind = .vec3 },
+ .{ .identifier = "anim_color", .shader_name = "anim_color", .kind = .vec3 },
+ .{ .identifier = "anim_coord", .shader_name = "anim_coord", .kind = .ivec2 },
+ .{ .identifier = "anim_t", .shader_name = "anim_t", .kind = .f32 },
},
&.{},
);
@@ -34,33 +37,21 @@ var grid_prog: GridProg = undefined;
var vao: webgl.VertexArrayObject = undefined;
var pos_buffer: webgl.Buffer = undefined;
-const grid = .{
- .origin = .{ 100, 100 },
+var grid: struct {
+ origin: [2]f32,
+ gap: f32,
+ width: i32,
+ height: i32,
+ scale: f32,
+} = .{
+ .origin = .{ 0, 0 },
.gap = 4,
- .width = 16,
- .height = 8,
- .scale = 64,
+ .width = 32,
+ .height = 16,
+ .scale = 32,
};
-const RingBuffer = struct {
- data: [][2]i32,
- len: usize = 0,
- write_idx: usize = 0,
-
- fn write(self: *RingBuffer, val: [2]i32) void {
- if (self.len == 0) return;
-
- self.data[self.write_idx] = val;
- self.write_idx = (self.write_idx + 1) % self.len;
- }
-
- fn oldest(self: RingBuffer) [2]i32 {
- const i = (self.data.len + self.write_idx - self.len) % self.data.len;
- return self.data[i];
- }
-};
-
-const max_len = 40;
+const max_len = 200;
var segment_mem: [max_len][2]i32 = [1][2]i32{.{ 0, 0 }} ** max_len;
var snake: struct { x: i32, y: i32, dir: [2]i32, len: usize } = .{
@@ -88,6 +79,96 @@ var p_right: bool = false;
const plane_verts = geometry.planeVertices(1, 1);
+const DifficultySettings = struct {
+ width: i32,
+ height: i32,
+ ticks_per_move: u8,
+ next_level_at: u32,
+};
+
+const min_grid_width = 7;
+const max_grid_width = 65;
+
+const min_grid_height = 5;
+const max_grid_height = 33;
+
+const max_ticks_per_move = 12;
+const min_ticks_per_move = 2;
+const difficulties = generateDifficulties(9);
+fn generateDifficulties(comptime levels: u32) [levels]DifficultySettings {
+ var result: [levels]DifficultySettings = undefined;
+
+ for (&result, 0..) |*settings, i| {
+ const fi: f32 = @floatFromInt(i);
+
+ //Ticks
+ const ticks: u8 = @intFromFloat(interpolateRange(
+ easeInSine,
+ max_ticks_per_move,
+ min_ticks_per_move,
+ fi / (levels - 1),
+ ));
+ settings.ticks_per_move = ticks + ticks % 2;
+
+ settings.next_level_at = @intFromFloat(interpolateRange(easeInSine, 3, max_len * 0.75, fi / levels));
+
+ const width: i32 = @intFromFloat(interpolateRange(
+ easeInSine,
+ min_grid_width,
+ max_grid_width,
+ fi / (levels - 1),
+ ));
+ settings.width = width + (width + 1) % 2;
+
+ const height: i32 = @intFromFloat(interpolateRange(
+ easeInSine,
+ min_grid_height,
+ max_grid_height,
+ fi / (levels - 1),
+ ));
+ settings.height = height + (height + 1) % 2;
+ }
+
+ return result;
+}
+
+var current_level: u8 = 0;
+
+fn setDifficulty(level: u8) void {
+ assert(level < difficulties.len);
+ const settings = difficulties[level];
+ grid.height = settings.height;
+ grid.width = settings.width;
+ ticks_per_move = settings.ticks_per_move;
+ snake.x = @divFloor(grid.width, 2);
+ snake.y = @divFloor(grid.height, 2);
+ snake.dir = .{ 0, 0 };
+
+ food.x = rng.randInt(i32, 0, grid.width - 1);
+ food.y = rng.randInt(i32, 0, grid.height - 1);
+
+ @memset(&segment_mem, .{ snake.x, snake.y });
+
+ current_level = level;
+}
+
+fn calcGridScale() void {
+ const dims = webgl.getScreenSize();
+
+ const minimum_margin_x: f32 = 50;
+ const cols: f32 = @floatFromInt(grid.width);
+ const x_gap_size = grid.gap * (cols - 1);
+ const available_w = dims[0] - x_gap_size - minimum_margin_x * 2;
+ const x_scale = available_w / cols;
+
+ const minimum_margin_y = 50;
+ const rows: f32 = @floatFromInt(grid.height);
+ const y_gap_size = grid.gap * (rows - 1);
+ const available_y = dims[1] - y_gap_size - minimum_margin_y * 2;
+ const y_scale = available_y / rows;
+ grid.scale = @min(x_scale, y_scale);
+}
+
export fn init() void {
wasm.print("Hello Snake {s}!", .{"sssss"});
wasm.registerKey(.up, &up);
@@ -98,8 +179,6 @@ export fn init() void {
const f_seed: f32 = wasm.rand() * (pow(f32, 2, 16) - 1);
const seed: u64 = @intFromFloat(f_seed);
rng = LinearCongruentialGenerator.ZX81(seed);
- food.x = rng.randInt(i32, 0, grid.width - 1);
- food.y = rng.randInt(i32, 0, grid.height - 1);
general_prog = GeneralProg.init(
@embedFile("vertex.glsl"),
@@ -110,34 +189,53 @@ export fn init() void {
@embedFile("grid_vertex.glsl"),
@embedFile("fragment.glsl"),
) catch return;
+ grid_prog.use();
+ grid_prog.setUniform(.anim_color, .{ 1, 0, 1 });
+ grid_prog.setUniform(.anim_coord, .{ 2, 2 });
pos_buffer = webgl.Buffer.create();
pos_buffer.bindAndFill(.array_buffer, .vec3, toVec3Array(&plane_verts), .static_draw);
vao = webgl.VertexArrayObject.init();
general_prog.setVertexAttribPointer(.pos, vao, pos_buffer);
+
+ setDifficulty(0);
}
const tick_rate = 1.0 / 60.0;
var timer: f32 = 0;
+var anim_acc: f32 = 1000;
-const ticks_per_move = 12;
+var ticks_per_move: u8 = 6;
var counter: u8 = 0;
+var last_moved: [2]i32 = .{ 0, 0 };
+
export fn update(delta_time: f32) void {
- if (up and !p_up and snake.dir[1] != -1) {
+ calcGridScale();
+ const dims: @Vector(2, f32) = webgl.getScreenSize();
+ const half_dims = dims / @Vector(2, f32){ 2, 2 };
+ const grid_px_width = (grid.scale + grid.gap) * @as(f32, @floatFromInt(grid.width)) - grid.gap;
+ const grid_px_height = (grid.scale + grid.gap) * @as(f32, @floatFromInt(grid.height)) - grid.gap;
+ const grid_px_size: @Vector(2, f32) = .{ grid_px_width, grid_px_height };
+ const half_grid_px_size = grid_px_size / @Vector(2, f32){ 2, 2 };
+ grid.origin = half_dims - half_grid_px_size;
+ //grid.origin = dims - grid_px_size;
+
+ anim_acc += delta_time;
+ if (up and !p_up and last_moved[1] != -1) {
snake.dir = .{ 0, 1 };
}
- if (down and !p_down and snake.dir[1] != 1) {
+ if (down and !p_down and last_moved[1] != 1) {
snake.dir = .{ 0, -1 };
}
- if (right and !p_right and snake.dir[0] != -1) {
+ if (right and !p_right and last_moved[0] != -1) {
snake.dir = .{ 1, 0 };
}
- if (left and !p_left and snake.dir[0] != 1) {
+ if (left and !p_left and last_moved[0] != 1) {
snake.dir = .{ -1, 0 };
}
@@ -146,8 +244,9 @@ export fn update(delta_time: f32) void {
counter += 1;
timer -= tick_rate;
- if (counter >= ticks_per_move) {
- counter -= ticks_per_move;
+ if ((snake.dir[0] != 0 or snake.dir[1] != 0) and counter >= ticks_per_move) {
+ last_moved = snake.dir;
+ counter = 0;
snake.x += snake.dir[0];
snake.x = @mod(snake.x, grid.width);
@@ -155,11 +254,18 @@ export fn update(delta_time: f32) void {
snake.y = @mod(snake.y, grid.height);
if (snake.x == food.x and snake.y == food.y) {
+ grid_prog.use();
+ grid_prog.setUniform(.anim_coord, .{ food.x, food.y });
+ anim_acc = 0;
food.x = rng.randInt(i32, 0, grid.width - 1);
food.y = rng.randInt(i32, 0, grid.height - 1);
if (snake.len < max_len) {
snake.len += 1;
+ if (current_level + 1 < difficulties.len and snake.len >= difficulties[current_level].next_level_at) {
+ setDifficulty(current_level + 1);
+ break;
+ }
}
}
@@ -168,11 +274,12 @@ export fn update(delta_time: f32) void {
segment_mem[i] = segment_mem[i - 1];
if (segment_mem[i][0] == snake.x and segment_mem[i][1] == snake.y) {
snake.len = 1;
+ setDifficulty(0);
break;
}
}
segment_mem[0] = .{ snake.x, snake.y };
- }
+ } else if (counter > ticks_per_move) counter = ticks_per_move;
}
webgl.viewportToScreen();
@@ -189,6 +296,7 @@ export fn update(delta_time: f32) void {
fn drawGrid() void {
grid_prog.use();
+ grid_prog.setUniform(.anim_t, anim_acc * 8);
grid_prog.setUniform(.matrix, comptime projectionMatrix2D(1280, 720));
grid_prog.setUniform(.scale, grid.scale);
grid_prog.setUniform(.columns, grid.width);
@@ -204,8 +312,11 @@ fn drawEntities() void {
general_prog.setUniform(.projection, proj);
// Snake
+ const head_col = webgl.Color.red;
+ const tail_col = webgl.Color.blue;
for (0..snake.len) |i| {
- const coord = segment_mem[i];
+ const inverse_i = snake.len - 1 - i;
+ const coord = segment_mem[inverse_i];
const fx: f32 = @floatFromInt(coord[0]);
const fy: f32 = @floatFromInt(coord[1]);
@@ -217,7 +328,10 @@ fn drawEntities() void {
const scale = Mat3.scale(.{ grid.scale, grid.scale });
const tform = tlate.multiply(scale);
general_prog.setUniform(.model, tform);
- general_prog.setUniform(.color, .{ 0.7, 0.3, 0.3 });
+
+ const pct: f32 = @as(f32, @floatFromInt(inverse_i)) / @as(f32, @floatFromInt(snake.len));
+ const col = webgl.Color.interpolate(easeInOutSine, head_col, tail_col, pct);
+ general_prog.setUniform(.color, .{ col.r, col.g, col.b });
webgl.drawArrays(.triangles, 0, 6);
}
@@ -229,18 +343,25 @@ fn drawEntities() void {
x * grid.gap + x * grid.scale,
y * grid.gap + y * grid.scale,
};
- const tlate = Mat3.translation(pos);
- const scale = Mat3.scale(.{ grid.scale, grid.scale });
- const tform = tlate.multiply(scale);
- general_prog.setUniform(.model, tform);
- general_prog.setUniform(.color, .{ 0.0, 0.7, 0.3 });
- webgl.drawArrays(.triangles, 0, 6);
+ for (0..3) |c| {
+ const extra_x: f32 = @as(f32, @floatFromInt(c)) * grid.scale / 3;
+ for (0..3) |r| {
+ if ((c == 1 and r == 1) or (c != 1 and r != 1)) {
+ continue;
+ }
+ const extra_y: f32 = @as(f32, @floatFromInt(r)) * grid.scale / 3;
+ const tlate = Mat3.translation(pos).translate(.{ extra_x, extra_y });
+ const scale = Mat3.scale(.{ grid.scale / 3, grid.scale / 3 });
+ const tform = tlate.multiply(scale);
+ general_prog.setUniform(.model, tform);
+ general_prog.setUniform(.color, .{ 0.0, 0.7, 0.3 });
+ webgl.drawArrays(.triangles, 0, 6);
+ }
+ }
}
}
-comptime {}
-
fn projectionMatrix2D(width: f32, height: f32) Mat3 {
const mat_vals = .{
.{ 2.0 / width, 0, 0 },
@@ -271,6 +392,30 @@ pub fn pow(comptime T: type, base: T, exp: T) T {
unreachable;
}
+const pi = 3.1415926535;
+
+fn easeInSine(val: f32) f32 {
+ return 1.0 - @cos((val * pi) / 2.0);
+}
+
+fn easeOutSine(val: f32) f32 {
+ return @sin((val * pi) / 2.0);
+}
+
+fn easeInOutSine(val: f32) f32 {
+ return -(@cos(pi * val) - 1) / 2;
+}
+
+fn interpolateRange(
+ comptime EasingFn: *const fn (f32) f32,
+ range_start: f32,
+ range_end: f32,
+ val: f32,
+) f32 {
+ const pct = EasingFn(val);
+ return range_start + (range_end - range_start) * pct;
+}
+
pub const LinearCongruentialGenerator = struct {
mod: u64,
mul: u64,
diff --git a/src/webgl.js b/src/webgl.js
index 7b14d22..49b864c 100644
--- a/src/webgl.js
+++ b/src/webgl.js
@@ -151,6 +151,10 @@ const uniform1f = (location, v0) => {
gfx_ctx.uniform1f(glUniformLocations.get(location), v0);
};
+const uniform2i = (location, v0, v1) => {
+ gfx_ctx.uniform2i(glUniformLocations.get(location), v0, v1);
+};
+
const uniform2f = (location, v0, v1) => {
gfx_ctx.uniform2f(glUniformLocations.get(location), v0, v1);
};
@@ -264,6 +268,7 @@ const webgl = {
uniform1i,
uniform1f,
+ uniform2i,
uniform2f,
uniform3f,
uniform4f,
diff --git a/src/webgl.zig b/src/webgl.zig
index e252286..40fc46c 100644
--- a/src/webgl.zig
+++ b/src/webgl.zig
@@ -12,6 +12,8 @@ pub const Color = struct {
a: f32 = 1,
pub const red: Color = Color.fromVec(.{ 1, 0, 0, 1 });
+ pub const green: Color = Color.fromVec(.{ 0, 1, 0, 1 });
+ pub const blue: Color = Color.fromVec(.{ 0, 0, 1, 1 });
pub fn fromVec(val: @Vector(4, f32)) Color {
return .{
@@ -21,6 +23,18 @@ pub const Color = struct {
.a = val[3],
};
}
+
+ pub fn toVec(self: Color) @Vector(4, f32) {
+ return .{ self.r, self.g, self.b, self.a };
+ }
+
+ pub fn interpolate(comptime EaseFn: *const fn (f32) f32, lhs: Color, rhs: Color, pct: f32) Color {
+ const l = lhs.toVec();
+ const r = rhs.toVec();
+ const e: @Vector(4, f32) = @splat(EaseFn(pct));
+ const out = l + (r - l) * e;
+ return Color.fromVec(out);
+ }
};
//pub const Program = packed struct(u32) { handle: u32 };
@@ -30,12 +44,13 @@ const ShaderType = enum { vec3 };
const UniformInfo = struct {
identifier: [:0]const u8,
shader_name: []const u8,
- kind: enum { i32, f32, vec2, vec3, mat3, mat4 },
+ kind: enum { i32, f32, ivec2, vec2, vec3, mat3, mat4 },
fn SetType(self: UniformInfo) type {
return switch (self.kind) {
.i32 => i32,
.f32 => f32,
+ .ivec2 => [2]i32,
.vec2 => [2]f32,
.vec3 => [3]f32,
.mat3 => Mat3,
@@ -187,6 +202,7 @@ pub fn Program(
switch (info.kind) {
.i32 => bindings.uniform1i(handle, val),
.f32 => bindings.uniform1f(handle, val),
+ .ivec2 => bindings.uniform2i(handle, val[0], val[1]),
.vec2 => bindings.uniform2f(handle, val[0], val[1]),
.vec3 => bindings.uniform3f(handle, val[0], val[1], val[2]),
.mat3 => {
diff --git a/src/webgl_bindings.zig b/src/webgl_bindings.zig
index cb1bf29..7012a33 100644
--- a/src/webgl_bindings.zig
+++ b/src/webgl_bindings.zig
@@ -68,6 +68,7 @@ pub extern fn getUniformLocation(program: u32, buf: [*c]const u8, len: usize) i3
pub extern fn uniform1i(location: i32, v0: i32) void;
pub extern fn uniform1f(location: i32, v0: f32) void;
+pub extern fn uniform2i(location: i32, v0: i32, v1: i32) void;
pub extern fn uniform2f(location: i32, v0: f32, v1: f32) void;
pub extern fn uniform3f(location: i32, v0: f32, v1: f32, v2: f32) void;
pub extern fn uniform4f(location: i32, v0: f32, v1: f32, v2: f32, v3: f32) void;