use crate::{buffer::Buf, util::wrap}; use fastapprox::faster::{cos, sin}; use rand::{seq::SliceRandom, 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, pub angle: f32, pub population_id: usize, pub i: usize, } 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, id: usize, rng: &mut R, i: usize, ) -> Self { let (x, y, angle) = rng.gen::<(f32, f32, f32)>(); Agent { x: x * width as f32, y: y * height as f32, angle: angle * TAU, population_id: id, i, } } /// Tick an agent pub fn tick( &mut self, buf: &Buf, sensor_distance: f32, sensor_angle: f32, rotation_angle: f32, step_distance: f32, width: usize, height: usize, ) { let xc = self.x + cos(self.angle) * sensor_distance; let yc = self.y + sin(self.angle) * sensor_distance; let agent_add_sens = self.angle + sensor_angle; let agent_sub_sens = self.angle - sensor_angle; let xl = self.x + cos(agent_sub_sens) * sensor_distance; let yl = self.y + sin(agent_sub_sens) * sensor_distance; let xr = self.x + cos(agent_add_sens) * sensor_distance; let yr = self.y + sin(agent_add_sens) * 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 mut rng = rand::thread_rng(); let mut direction: f32 = 0.0; if (center > left) && (center > right) { direction = 0.0; } else if (center < left) && (center < right) { direction = *[-1.0, 1.0] .choose(&mut rng) .expect("unable to choose random direction"); } else if left < right { direction = 1.0; } else if right < left { direction = -1.0; } let delta_angle = rotation_angle * direction; self.angle = wrap(self.angle + delta_angle, TAU); self.x = wrap(self.x + step_distance * cos(self.angle), width as f32); self.y = wrap(self.y + step_distance * sin(self.angle), height as f32); } }