diff options
author | alaric <alaric@netmythos.org> | 2024-04-07 06:07:29 -0700 |
---|---|---|
committer | alaric <alaric@netmythos.org> | 2024-04-07 06:07:29 -0700 |
commit | 4aef5c2443bd6eb57c6be3ff9bfa422c3ce3d2cf (patch) | |
tree | e942a8df99871e2095d020fb351e290f015bc5c0 | |
parent | b614db3cf2a89f565b06471dc47e1bd345c0b070 (diff) | |
download | snake-4aef5c2443bd6eb57c6be3ff9bfa422c3ce3d2cf.tar.gz snake-4aef5c2443bd6eb57c6be3ff9bfa422c3ce3d2cf.zip |
Complete Snake for now
-rw-r--r-- | src/grid_vertex.glsl | 14 | ||||
-rw-r--r-- | src/matrix.zig | 4 | ||||
-rw-r--r-- | src/shell.html | 5 | ||||
-rw-r--r-- | src/wasm_snake.zig | 233 | ||||
-rw-r--r-- | src/webgl.js | 5 | ||||
-rw-r--r-- | src/webgl.zig | 18 | ||||
-rw-r--r-- | src/webgl_bindings.zig | 1 |
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; |