use ggez::{conf, event, graphics, nalgebra::Point2, timer, Context, ContextBuilder, GameResult};
use rand::Rng;
use ron::de::from_reader;
use serde::Deserialize;
use std::{fs::File, path::PathBuf};
use evolution::fish::{Fish, FishConfig};
use evolution::food::{Food, FoodConfig};
use evolution::inverse_map_range;
#[derive(Debug, Deserialize)]
struct Config {
fullscreen: bool,
window_size: (f32, f32),
show_fps: bool,
boundary_padding: f32,
fish: FishConfig,
food: FoodConfig,
poison: FoodConfig,
}
struct State {
config: Config,
rng: rand::rngs::ThreadRng,
fish: Vec<Fish>,
food: Vec<Food>,
poison: Vec<Food>,
fish_image: graphics::Image,
}
impl State {
fn new(ctx: &mut Context, config: Config) -> GameResult<State> {
let mut rng = rand::thread_rng();
let mut fish = Vec::new();
for _ in 1..config.fish.quantity {
let scale = rng.gen_range(config.fish.scale_range.0, config.fish.scale_range.1);
let max_speed =
inverse_map_range(scale, config.fish.scale_range, config.fish.max_speed_range);
let max_steering_force = inverse_map_range(
scale,
config.fish.scale_range,
config.fish.max_steering_force_range,
);
let angle = rng.gen_range(0.0, 2.0 * std::f32::consts::PI);
let pos = Point2::new(
rng.gen_range(0.0, config.window_size.0),
rng.gen_range(0.0, config.window_size.1),
);
let dna = [rng.gen_range(-2.0, 2.0), rng.gen_range(-2.0, 2.0)];
fish.push(Fish::new(
scale,
max_speed,
max_steering_force,
pos,
angle,
dna,
));
}
let mut food = Vec::new();
for _ in 1..config.food.quantity {
food.push(Food::new(Point2::new(
rng.gen_range(0.0, config.window_size.0),
rng.gen_range(0.0, config.window_size.1)
)));
}
let mut poison = Vec::new();
for _ in 1..config.poison.quantity {
poison.push(Food {
radius: rng.gen_range(config.poison.radius_range.0, config.poison.radius_range.1),
pos: Point2::new(
rng.gen_range(0.0, config.window_size.0),
rng.gen_range(0.0, config.window_size.1),
),
color: [1.0, 0.0, 0.0, 0.8],
});
}
let mut fish_image = graphics::Image::new(ctx, "/frames.png").unwrap();
fish_image.set_filter(graphics::FilterMode::Nearest);
Ok(State {
config,
rng,
fish,
food,
poison,
fish_image,
})
}
}
impl event::EventHandler for State {
fn update(&mut self, _ctx: &mut Context) -> GameResult {
if self.rng.gen_ratio(1, 20) {
self.food.push(Food::new(Point2::new(
self.rng.gen_range(0.0, self.config.window_size.0),
self.rng.gen_range(0.0, self.config.window_size.1)
)))
}
self.fish.retain(|fish| fish.is_alive());
for fish in self.fish.iter_mut() {
if fish.is_alive() {
fish.behaviors(&mut self.food, &mut self.poison);
fish.bound(&self.config.window_size, self.config.boundary_padding);
fish.update();
}
}
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult {
graphics::clear(ctx, [0.1, 0.2, 0.3, 1.0].into());
for poison in self.poison.iter() {
if let Err(error) = poison.draw(ctx) {
return Err(error);
}
}
for food in self.food.iter() {
if let Err(error) = food.draw(ctx) {
return Err(error);
}
}
for fish in self.fish.iter_mut() {
if let Err(error) = fish.draw(
ctx,
&self.fish_image,
self.config.fish.frames_per_animation_frame,
) {
return Err(error);
}
}
if self.config.show_fps {
let fps = timer::fps(ctx);
let fps_text = graphics::Text::new(format!("FPS: {:.*}", 1, fps));
graphics::draw(ctx, &fps_text, (Point2::new(5.0, 5.0), graphics::WHITE))?;
}
graphics::present(ctx)?;
Ok(())
}
}
pub fn main() -> GameResult {
let input_path = format!("{}/config.ron", env!("CARGO_MANIFEST_DIR"));
let f = File::open(&input_path)?;
let config: Config = match from_reader(f) {
Ok(x) => x,
Err(e) => {
println!("Failed to load `config.ron`: {}", e);
std::process::exit(1);
}
};
let assets_dir = PathBuf::from(format!("{}/assets", env!("CARGO_MANIFEST_DIR")));
let window_settings = if config.fullscreen {
conf::WindowMode::default().fullscreen_type(conf::FullscreenType::True)
} else {
conf::WindowMode::default().dimensions(config.window_size.0, config.window_size.1)
};
let (ctx, event_loop) = &mut ContextBuilder::new("evolution", "Austin Baugh")
.window_setup(conf::WindowSetup::default().title("Evolution!"))
.window_mode(window_settings)
.add_resource_path(assets_dir)
.build()?;
let state = &mut State::new(ctx, config)?;
event::run(ctx, event_loop, state)
}