From b0f31f40c17a1312f6b93aeab2dd9715020c13ec Mon Sep 17 00:00:00 2001 From: Squibid Date: Wed, 31 Dec 2025 23:02:09 -0500 Subject: [PATCH] inital work on pong --- src/subcmds/arcade/arcade.zig | 21 +++-- src/subcmds/arcade/pong.zig | 146 ++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 src/subcmds/arcade/pong.zig diff --git a/src/subcmds/arcade/arcade.zig b/src/subcmds/arcade/arcade.zig index 99e11a7..86bb136 100644 --- a/src/subcmds/arcade/arcade.zig +++ b/src/subcmds/arcade/arcade.zig @@ -2,11 +2,21 @@ const std = @import("std"); const gpa = std.heap.page_allocator; const Conway = @import("conway.zig"); +const Pong = @import("pong.zig"); const games = enum(u16) { @"Conways Game of Life", + // Pong, }; +fn run_game(game: i64, width: u32, height: u32, writer: *std.Io.Writer) !void { + switch (game) { + @intFromEnum(games.@"Conways Game of Life") => try Conway.play(width, height, writer), + // @intFromEnum(games.Pong) => try Pong.play(width, height, writer), + else => @panic("Game does not exist."), + } +} + fn ui(width: u32, height: u32, writer: *std.Io.Writer) !void { var fd = [_]std.posix.pollfd{.{ .fd = std.posix.STDIN_FILENO, @@ -42,10 +52,10 @@ fn ui(width: u32, height: u32, writer: *std.Io.Writer) !void { cursor_y = @intCast(std.math.clamp(cursor_y, 0, @typeInfo(games).@"enum".fields.len - 1)); inline for (@typeInfo(games).@"enum".fields, 0..) |f, i| { try writer.print("\x1B[{};{}H", .{ // y, x - ((height + i) / 2) + 3, + ((height) / 2) + 3 + i, (width - f.name.len) / 2, }); - try writer.print("{s}{s}\x1B[0m\n", .{ + try writer.print("{s} {s} \x1B[0m\n", .{ if (i == cursor_y) "\x1B[47;30;1m" else "\x1B[40;37m", f.name, }); @@ -65,7 +75,7 @@ fn ui(width: u32, height: u32, writer: *std.Io.Writer) !void { 'k' => cursor_y -= 1, 'g' => cursor_y = 1, 'G' => cursor_y = @intFromEnum(games.@"Conways Game of Life"), - ' ' => { + ' ', '\n' => { // reset the terminal state for the game try std.posix.tcsetattr(std.posix.STDIN_FILENO, std.posix.TCSA.NOW, oldios); _ = try writer.write("\x1B[?25h"); // show cursor @@ -73,10 +83,7 @@ fn ui(width: u32, height: u32, writer: *std.Io.Writer) !void { // not much we can do }; - switch (cursor_y) { - @intFromEnum(games.@"Conways Game of Life") => try Conway.play(width, height, writer), - else => @panic("Game does not exist."), - } + try run_game(cursor_y, width, height, writer); _ = try writer.write("\x1B[?25l"); // hide cursor try writer.flush(); diff --git a/src/subcmds/arcade/pong.zig b/src/subcmds/arcade/pong.zig new file mode 100644 index 0000000..34aef37 --- /dev/null +++ b/src/subcmds/arcade/pong.zig @@ -0,0 +1,146 @@ +const Pong = @This(); + +const std = @import("std"); +const gpa = std.heap.page_allocator; + +width: u64, +height: u64, +writer: *std.io.Writer, +players: []Player, +ball: Ball, +mode: Mode, +cps: f32, + +const Player = struct { + score: u8 = 0, + /// top y position + paddle_y: u64, + paddle_x: u64, + + const paddle_height = 4; +}; +const Ball = struct { x: f32, y: f32, vx: f16, vy: f16 }; +const Mode = enum{ easy, hard }; + +pub fn init(mode: Mode, width: u64, height: u64, writer: *std.Io.Writer) !Pong { + std.debug.assert(width > 0); + std.debug.assert(height > 0); + + const self: Pong = .{ + .width = width, + .height = height, + .writer = writer, + + .players = try gpa.alloc(Player, 2), + .mode = mode, + .ball = .{ + .x = @as(f32, @floatFromInt(width)) / 2.0, + .y = @as(f32, @floatFromInt(height)) / 2.0, + .vx = 0.5, + .vy = 0.2, + }, + .cps = switch (mode) { + Mode.easy => @as(f32, @floatFromInt(width)) / 1.3, + Mode.hard => @as(f32, @floatFromInt(width)) / 0.65, + }, + }; + + self.players[0] = Player{ + .paddle_y = height / 2, + .paddle_x = 0, + .score = 0, + }; + self.players[1] = .{ + .paddle_y = height / 2, + .paddle_x = width - 1, + .score = 0, + }; + + return self; +} + +/// must be called self.cps times per second +pub fn step(self: *Pong) !void { + const next_x = self.ball.x + 1 / self.ball.vx; + const next_y = self.ball.y + 1 / self.ball.vy; + + if (next_y < 0 or next_y > @as(f32, @floatFromInt(self.height - 1))) self.ball.vy = -self.ball.vy; + for (self.players) |p| { + if ((@floor(next_x) == @as(f32, @floatFromInt(p.paddle_x))) and + (@floor(next_y) >= @as(f32, @floatFromInt(p.paddle_y)) and + @floor(next_y) <= @as(f32, @floatFromInt(p.paddle_y + Player.paddle_height)))) { + self.ball.vx = -self.ball.vx; + + // The paddle is 4 high but divided into 8 sections. + // This is done so that we may change the vy of the ball after its + // hit the paddle which allows the ball to traverse an arbitrary + // path. + // + // I've encoded it in the struct below which allows us to generate + // the if branching at comptime. + const strong = 0.8; + const medium = 0.4; + const weak = 0.2; + const zero = 0; + const result_vy = .{ + .{ 0.5, strong }, + .{ 1, medium }, + .{ 1.5, weak }, + .{ 2, zero }, + .{ 2.5, zero }, + .{ 3, weak }, + .{ 3.5, medium }, + .{ 4, strong }, + }; + self.ball.vy = vy: { + inline for (result_vy) |f| { + if (next_y <= @as(f32, @floatFromInt(p.paddle_y)) + f[0]) { + break: vy f[1]; + } + } + unreachable; // it should be impossible to be outside of the paddle + }; + } else if (@floor(next_x) == @as(f32, @floatFromInt(p.paddle_x))) { + // scored + var mult: f16 = 1; + if (std.meta.eql(p, self.players[0])) { + self.players[1].score += 1; + mult = -mult; + } else { + self.players[0].score += 1; + mult = mult; + } + + self.ball = .{ + .x = @as(f32, @floatFromInt(self.width)) / 2.0, + .y = @as(f32, @floatFromInt(self.height)) / 2.0, + .vx = 0.5 * mult, + .vy = self.ball.vy, + }; + } + } + + self.ball.x += self.ball.vx; + self.ball.y += self.ball.vy; +} + +pub fn play(width: u32, height: u32, writer: *std.Io.Writer) !void { + var pong = try Pong.init(.hard, width, height, writer); + + // var fd = [_]std.posix.pollfd{.{ + // .fd = std.posix.STDIN_FILENO, + // .events = std.posix.POLL.IN, + // .revents = 0 + // }}; + + + + while (true) { + try pong.writer.print("\x1B[{}A", .{height}); + + try pong.writer.print("\x1B[{};{}H", .{pong.ball.y, pong.ball.x}); + try pong.writer.writeAll("o"); + try pong.writer.flush(); + try pong.step(); + } +}