inital work on pong

This commit is contained in:
Squibid 2025-12-31 23:02:09 -05:00
parent 1482e6e679
commit b0f31f40c1
2 changed files with 160 additions and 7 deletions

View file

@ -2,11 +2,21 @@ const std = @import("std");
const gpa = std.heap.page_allocator; const gpa = std.heap.page_allocator;
const Conway = @import("conway.zig"); const Conway = @import("conway.zig");
const Pong = @import("pong.zig");
const games = enum(u16) { const games = enum(u16) {
@"Conways Game of Life", @"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 { fn ui(width: u32, height: u32, writer: *std.Io.Writer) !void {
var fd = [_]std.posix.pollfd{.{ var fd = [_]std.posix.pollfd{.{
.fd = std.posix.STDIN_FILENO, .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)); cursor_y = @intCast(std.math.clamp(cursor_y, 0, @typeInfo(games).@"enum".fields.len - 1));
inline for (@typeInfo(games).@"enum".fields, 0..) |f, i| { inline for (@typeInfo(games).@"enum".fields, 0..) |f, i| {
try writer.print("\x1B[{};{}H", .{ // y, x try writer.print("\x1B[{};{}H", .{ // y, x
((height + i) / 2) + 3, ((height) / 2) + 3 + i,
(width - f.name.len) / 2, (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", if (i == cursor_y) "\x1B[47;30;1m" else "\x1B[40;37m",
f.name, f.name,
}); });
@ -65,7 +75,7 @@ fn ui(width: u32, height: u32, writer: *std.Io.Writer) !void {
'k' => cursor_y -= 1, 'k' => cursor_y -= 1,
'g' => cursor_y = 1, 'g' => cursor_y = 1,
'G' => cursor_y = @intFromEnum(games.@"Conways Game of Life"), 'G' => cursor_y = @intFromEnum(games.@"Conways Game of Life"),
' ' => { ' ', '\n' => {
// reset the terminal state for the game // reset the terminal state for the game
try std.posix.tcsetattr(std.posix.STDIN_FILENO, std.posix.TCSA.NOW, oldios); try std.posix.tcsetattr(std.posix.STDIN_FILENO, std.posix.TCSA.NOW, oldios);
_ = try writer.write("\x1B[?25h"); // show cursor _ = 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 // not much we can do
}; };
switch (cursor_y) { try run_game(cursor_y, width, height, writer);
@intFromEnum(games.@"Conways Game of Life") => try Conway.play(width, height, writer),
else => @panic("Game does not exist."),
}
_ = try writer.write("\x1B[?25l"); // hide cursor _ = try writer.write("\x1B[?25l"); // hide cursor
try writer.flush(); try writer.flush();

146
src/subcmds/arcade/pong.zig Normal file
View file

@ -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();
}
}