use super::Animation;
use crate::render::Canvas;
use rand::RngExt;
pub struct Pong {
ball_x: f64,
ball_y: f64,
ball_vx: f64,
ball_vy: f64,
left_y: f64,
right_y: f64,
paddle_h: f64,
left_score: u32,
right_score: u32,
serve_timer: f64,
speed_mult: f64,
rng: rand::rngs::ThreadRng,
}
impl Pong {
pub fn new(width: usize, height: usize, _scale: f64) -> Self {
let mut rng = rand::rng();
let w = width as f64;
let h = height as f64;
let dir: f64 = if rng.random_range(0u8..2) == 0 {
1.0
} else {
-1.0
};
Pong {
ball_x: w * 0.5,
ball_y: h * 0.5,
ball_vx: dir * 30.0,
ball_vy: rng.random_range(-15.0..15.0),
left_y: h * 0.5,
right_y: h * 0.5,
paddle_h: (h * 0.2).max(4.0),
left_score: 0,
right_score: 0,
serve_timer: 0.0,
speed_mult: 1.0,
rng: rand::rng(),
}
}
fn serve(&mut self, w: f64, h: f64) {
self.ball_x = w * 0.5;
self.ball_y = h * 0.5;
let dir: f64 = if self.rng.random_range(0u8..2) == 0 {
1.0
} else {
-1.0
};
self.ball_vx = dir * 30.0;
self.ball_vy = self.rng.random_range(-15.0..15.0);
self.serve_timer = 0.5;
}
}
impl Animation for Pong {
fn name(&self) -> &str {
"pong"
}
fn set_params(&mut self, params: &crate::external::ExternalParams) {
if let Some(speed) = params.speed {
self.speed_mult = speed.clamp(0.2, 3.0);
}
}
fn supported_params(&self) -> &'static [(&'static str, f64, f64)] {
&[("speed", 0.2, 3.0)]
}
fn update(&mut self, canvas: &mut Canvas, dt: f64, _time: f64) {
let w = canvas.width as f64;
let h = canvas.height as f64;
let paddle_w = 2.0;
let left_x = 3.0;
let right_x = w - 3.0 - paddle_w;
self.paddle_h = (h * 0.2).max(4.0);
let half_paddle = self.paddle_h * 0.5;
if self.serve_timer > 0.0 {
self.serve_timer -= dt;
} else {
self.ball_x += self.ball_vx * self.speed_mult * dt;
self.ball_y += self.ball_vy * self.speed_mult * dt;
if self.ball_y <= 0.0 {
self.ball_y = 0.0;
self.ball_vy = self.ball_vy.abs();
}
if self.ball_y >= h - 1.0 {
self.ball_y = h - 1.0;
self.ball_vy = -self.ball_vy.abs();
}
if self.ball_vx < 0.0
&& self.ball_x <= left_x + paddle_w
&& self.ball_x >= left_x
&& (self.ball_y - self.left_y).abs() < half_paddle
{
self.ball_vx = self.ball_vx.abs() * 1.05;
let offset = (self.ball_y - self.left_y) / half_paddle;
self.ball_vy += offset * 15.0;
self.ball_x = left_x + paddle_w + 0.1;
}
if self.ball_vx > 0.0
&& self.ball_x >= right_x
&& self.ball_x <= right_x + paddle_w
&& (self.ball_y - self.right_y).abs() < half_paddle
{
self.ball_vx = -self.ball_vx.abs() * 1.05;
let offset = (self.ball_y - self.right_y) / half_paddle;
self.ball_vy += offset * 15.0;
self.ball_x = right_x - 0.1;
}
self.ball_vx = self.ball_vx.clamp(-80.0, 80.0);
self.ball_vy = self.ball_vy.clamp(-40.0, 40.0);
if self.ball_x < 0.0 {
self.right_score += 1;
if self.right_score >= 11 {
self.left_score = 0;
self.right_score = 0;
}
self.serve(w, h);
}
if self.ball_x >= w {
self.left_score += 1;
if self.left_score >= 11 {
self.left_score = 0;
self.right_score = 0;
}
self.serve(w, h);
}
}
let ai_speed = 35.0;
let left_diff = self.ball_y - self.left_y;
self.left_y += left_diff.clamp(-ai_speed * dt, ai_speed * dt);
self.left_y = self.left_y.clamp(half_paddle, h - half_paddle);
let right_diff = self.ball_y - self.right_y;
self.right_y += right_diff.clamp(-ai_speed * dt, ai_speed * dt);
self.right_y = self.right_y.clamp(half_paddle, h - half_paddle);
canvas.clear();
let cx = (w * 0.5) as usize;
for y in 0..canvas.height {
if y % 3 != 0 && cx < canvas.width {
canvas.set_colored(cx, y, 0.2, 100, 100, 100);
}
}
let lx = left_x as usize;
let l_top = (self.left_y - half_paddle).max(0.0) as usize;
let l_bot = (self.left_y + half_paddle).min(h) as usize;
for y in l_top..l_bot {
for dx in 0..(paddle_w as usize) {
let px = lx + dx;
if px < canvas.width && y < canvas.height {
canvas.set_colored(px, y, 0.9, 100, 200, 255);
}
}
}
let rx = right_x as usize;
let r_top = (self.right_y - half_paddle).max(0.0) as usize;
let r_bot = (self.right_y + half_paddle).min(h) as usize;
for y in r_top..r_bot {
for dx in 0..(paddle_w as usize) {
let px = rx + dx;
if px < canvas.width && y < canvas.height {
canvas.set_colored(px, y, 0.9, 255, 100, 100);
}
}
}
let bx = self.ball_x as usize;
let by = self.ball_y as usize;
if bx < canvas.width && by < canvas.height {
canvas.set_colored(bx, by, 1.0, 255, 255, 255);
}
for &(ox, oy) in &[(1i32, 0i32), (-1, 0), (0, 1), (0, -1)] {
let gx = (self.ball_x + ox as f64) as usize;
let gy = (self.ball_y + oy as f64) as usize;
if gx < canvas.width && gy < canvas.height {
canvas.set_colored(gx, gy, 0.4, 200, 200, 200);
}
}
let score_y = 2_usize;
let left_score_x = (w * 0.3) as usize;
let right_score_x = (w * 0.7) as usize;
for i in 0..self.left_score.min(10) as usize {
let sx = left_score_x + i * 2;
if sx < canvas.width && score_y < canvas.height {
canvas.set_colored(sx, score_y, 0.8, 100, 200, 255);
}
}
for i in 0..self.right_score.min(10) as usize {
let sx = right_score_x + i * 2;
if sx < canvas.width && score_y < canvas.height {
canvas.set_colored(sx, score_y, 0.8, 255, 100, 100);
}
}
}
}