pixelfoo-apps/src/bin/predprey/main.rs
Juergen Stuber 7fe895f041 Replace the local implementation of 2d geometry by lowdim
This also forces an update of the rand dependency,
so while we are at it we update all the dependencies.
2023-04-10 01:00:41 +02:00

147 lines
4.0 KiB
Rust

use std::env::args;
use std::io::stdout;
use std::io::Write;
use std::thread::sleep;
use std::time::Duration;
use rand::thread_rng;
use rand::Rng;
use lowdim::bb2d;
use lowdim::p2d;
use lowdim::Array2d;
use lowdim::BBox2d;
use lowdim::Point2d;
use pixelfoo::color::Color;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Square {
Empty,
Grass,
Rabbit,
Fox,
}
#[derive(Clone, Debug)]
struct Board(Array2d<i64, Square>);
impl Board {
pub fn new(bbox: BBox2d, mut f: impl FnMut() -> Square) -> Board {
Board(Array2d::with(bbox, |_p| f()))
}
fn bbox(&self) -> BBox2d {
self.0.bbox()
}
fn get(&self, pos: Point2d) -> Square {
self.0[pos]
}
fn set(&mut self, pos: Point2d, sq: Square) {
self.0[pos] = sq;
}
}
fn send<T: Write>(w: &mut T, board: &Board) -> std::io::Result<()> {
for y in board.bbox().y_range() {
for x in board.bbox().x_range() {
let square = board.0[p2d(x, y)];
let c = match square {
Square::Empty => Color::blue(),
Square::Grass => Color::green(),
Square::Rabbit => Color::yellow(),
Square::Fox => Color::red(),
};
w.write_all(&c.rgb())?;
}
}
w.flush()
}
fn grow(board: &Board, pos: Point2d, grow: Square, die: Square) -> Square {
for pos1 in pos.neighbors_l1() {
if board.bbox().contains(&pos1) {
let neigh_sq = board.get(pos1);
if neigh_sq == grow {
return grow;
}
}
}
die
}
const DEFAULT_ARG: usize = 10;
fn main() -> std::io::Result<()> {
let args = args().collect::<Vec<_>>();
eprintln!("executing {}", args[0]);
let x_size = args[1].parse::<i64>().unwrap();
let y_size = args[2].parse::<i64>().unwrap();
let arg = args[3].parse::<usize>().unwrap_or(DEFAULT_ARG);
eprintln!("screen size {}x{}, arg {}", x_size, y_size, arg);
let mut rng = thread_rng();
let bbox = bb2d(0..x_size, 0..y_size);
let p_empty = 0.25;
let p_grass = 0.25;
let p_rabbit = 0.25;
let mut board = Board::new(bbox, || {
let p = rng.gen::<f64>();
if p < p_empty {
Square::Empty
} else if p < p_empty + p_grass {
Square::Grass
} else if p < p_empty + p_grass + p_rabbit {
Square::Rabbit
} else {
Square::Fox
}
});
let t_frame = 0.040; // s
let delay = Duration::new(0, (1_000_000_000.0 * t_frame) as u32);
// mid point of the board
let x_mid = (bbox.x_min() + bbox.x_max()) as f64 / 2.0;
let y_mid = (bbox.y_min() + bbox.y_max()) as f64 / 2.0;
// radius of the death zone in the middle
let r = (x_size.min(y_size) - 1) as f64 / 2.5;
loop {
for _ in 0..arg {
let pos = bbox.random_point(&mut rng);
let sq = board.get(pos);
let dx = (pos.x() as f64 - x_mid as f64) / r as f64;
let dy = (pos.y() as f64 - y_mid as f64) / r as f64;
let p0 = (dx * dx + dy * dy).min(1.0);
let p_survive = match sq {
Square::Empty => 1.0,
Square::Grass => p0,
Square::Rabbit => p0,
Square::Fox => 0.8 * p0,
};
let new_sq = if rng.gen::<f64>() < p_survive {
sq
} else {
Square::Empty
};
board.set(
pos,
match sq {
Square::Empty => grow(&board, pos, Square::Grass, new_sq),
Square::Grass => grow(&board, pos, Square::Rabbit, new_sq),
Square::Rabbit => grow(&board, pos, Square::Fox, new_sq),
Square::Fox => new_sq,
},
);
}
let mut buf = Vec::with_capacity(bbox.area() * 3);
send(&mut buf, &board)?;
stdout().write_all(&buf)?;
stdout().flush()?;
sleep(delay);
}
}