use crate::grid::PopulationConfig; use crate::{buffer::Buf, util::wrap}; use fastapprox::faster::{cos, sin}; use rand::prelude::IndexedRandom; use rand::Rng; use std::f32::consts::TAU; use std::fmt::{Display, Formatter}; /// A single Physarum agent. The x and y positions are continuous, hence we use floating point numbers instead of integers. #[derive(Debug, Clone, PartialEq)] pub struct Agent { pub x: f32, pub y: f32, heading: f32, } impl Display for Agent { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } } impl Agent { /// Construct a new agent with random parameters. pub fn new(width: usize, height: usize, rng: &mut R) -> Self { let (x, y, angle) = rng.random::<(f32, f32, f32)>(); Agent { x: x * width as f32, y: y * height as f32, heading: angle * TAU, } } /// Tick an agent pub fn tick(&mut self, buf: &Buf, pop_config: PopulationConfig, width: usize, height: usize) { let xc = self.x + cos(self.heading) * pop_config.sensor_distance; let yc = self.y + sin(self.heading) * pop_config.sensor_distance; let agent_add_sens = self.heading + pop_config.sensor_angle; let agent_sub_sens = self.heading - pop_config.sensor_angle; let xl = self.x + cos(agent_sub_sens) * pop_config.sensor_distance; let yl = self.y + sin(agent_sub_sens) * pop_config.sensor_distance; let xr = self.x + cos(agent_add_sens) * pop_config.sensor_distance; let yr = self.y + sin(agent_add_sens) * pop_config.sensor_distance; // We sense from the buffer because this is where we previously combined data from all the grid. let center = buf.get_buf(xc, yc); let left = buf.get_buf(xl, yl); let right = buf.get_buf(xr, yr); // Rotate and move logic let direction = if (center > left) && (center > right) { 0.0 } else if (center < left) && (center < right) { *[-1.0, 1.0] .choose(&mut rand::rng()) .expect("unable to choose random direction") } else if left < right { 1.0 } else if right < left { -1.0 } else { 0.0 }; let delta_angle = pop_config.rotation_angle * direction; self.heading = wrap(self.heading + delta_angle, TAU); self.x = wrap( self.x + pop_config.step_distance * cos(self.heading), width as f32, ); self.y = wrap( self.y + pop_config.step_distance * sin(self.heading), height as f32, ); } }