use schellings_model::simulation::Simulation;
use schellings_model::{Agent, Field};
use sdl2::event::Event;
use sdl2::event::WindowEvent;
use sdl2::keyboard::Keycode;
use sdl2::pixels::Color;
use sdl2::rect::Point;
use sdl2::rect::Rect;
use sdl2::render::TextureQuery;
use sdl2::rwops::RWops;
use sdl2::url::open_url;
use std::sync::{Arc, Mutex};
use std::thread;
use std::{thread::sleep, time::Duration};
fn main() {
let dark_gray: Color = Color::RGB(35, 35, 35);
let white: Color = Color::RGB(250, 250, 250);
let green: Color = Color::RGB(0, 153, 31);
let yellow: Color = Color::RGB(255, 204, 0);
let red: Color = Color::RGB(226, 61, 61);
let blue: Color = Color::RGB(49, 76, 146);
let black: Color = Color::RGB(0, 0, 0);
let mut background_colour = dark_gray;
let mut ui_colour = white;
let mut group1_colour = red;
let mut group2_colour = blue;
let scale: f32 = 8.0;
let sdl_context = sdl2::init().unwrap();
let ttf_context = sdl2::ttf::init().unwrap();
let video_subsystem = sdl_context.video().unwrap();
let window = video_subsystem
.window("Schelling's model", 1000, 600)
.position_centered()
.resizable()
.build()
.unwrap();
let mut canvas = window.into_canvas().build().unwrap();
canvas.set_scale(scale, scale).unwrap();
let mut event_pump = sdl_context.event_pump().unwrap();
let texture_creator = canvas.texture_creator();
let rwops = include_bytes!("NimbusSanL-Regu.ttf");
let font = ttf_context
.load_font_from_rwops(RWops::from_bytes(rwops).unwrap(), 13)
.unwrap();
let opened_toolbar_size = 24;
let mut toolbar_size = opened_toolbar_size;
let mut width = canvas.window().size().0;
let mut height = canvas.window().size().1;
let mut field = Field::new(
(width / scale as u32) as usize - toolbar_size,
height as usize / scale as usize,
);
field.fill(4);
let simulation = Mutex::new(Simulation {
field,
speed: 500,
wanted_happiness: 0.50,
running: true,
});
let simulation_ref = Arc::new(simulation);
let simulation_ref1 = simulation_ref.clone();
let simulation_thread = thread::spawn(move || loop {
let mut simulation = simulation_ref1.lock().unwrap();
if !simulation.running {
break;
}
let wanted_happiness = simulation.wanted_happiness;
simulation.field.move_agent(wanted_happiness);
let speed = simulation.speed;
drop(simulation);
sleep(Duration::from_millis(speed));
});
let mut draw_to_screen: bool = true;
let mut dark_theme: bool = true;
let mut ui_changed: &str = "Press Q for help.";
let mut toolbar_visible: bool = true;
'running: loop {
for event in event_pump.poll_iter() {
match event {
Event::Quit { .. }
| Event::KeyDown {
keycode: Some(Keycode::Escape),
..
} => {
simulation_ref.lock().unwrap().running = false;
simulation_thread.join().unwrap();
break 'running;
}
Event::Window {
win_event: WindowEvent::Resized(x, y),
..
} => {
width = x as u32;
height = y as u32;
let mut field = Field::new(
(width / scale as u32) as usize - toolbar_size,
height as usize / scale as usize,
);
field.fill(4);
let field_ref = &mut simulation_ref.lock().unwrap().field;
*field_ref = field;
ui_changed = "Window resized.";
}
Event::KeyDown {
keycode: Some(Keycode::A),
..
} => {
let speed = &mut simulation_ref.lock().unwrap().speed;
if *speed < 1000 {
*speed += 100;
ui_changed = "Slowed down the simulation.";
}
}
Event::KeyDown {
keycode: Some(Keycode::S),
..
} => {
let speed = &mut simulation_ref.lock().unwrap().speed;
if *speed > 0 {
*speed -= 100;
ui_changed = "Speeded up the simulation.";
}
}
Event::KeyDown {
keycode: Some(Keycode::D),
..
} => {
draw_to_screen = !draw_to_screen;
if draw_to_screen {
ui_changed = "Rendering enabled.";
}
}
Event::KeyDown {
keycode: Some(Keycode::Z),
..
} => {
let wanted_happiness = &mut simulation_ref.lock().unwrap().wanted_happiness;
if *wanted_happiness > 0.10 {
*wanted_happiness -= 0.10;
ui_changed = "Require less alike neighbours.";
}
}
Event::KeyDown {
keycode: Some(Keycode::X),
..
} => {
let wanted_happiness = &mut simulation_ref.lock().unwrap().wanted_happiness;
if *wanted_happiness < 1.0 {
*wanted_happiness += 0.10;
ui_changed = "Require more alike neighbours.";
}
}
Event::KeyDown {
keycode: Some(Keycode::W),
..
} => {
dark_theme = !dark_theme;
ui_changed = "Theme changed.";
if dark_theme {
background_colour = dark_gray;
ui_colour = white;
group1_colour = red;
group2_colour = blue;
} else {
background_colour = white;
ui_colour = black;
group1_colour = green;
group2_colour = yellow;
}
}
Event::KeyDown {
keycode: Some(Keycode::T),
..
} => {
toolbar_visible = !toolbar_visible;
if toolbar_visible {
toolbar_size = opened_toolbar_size;
ui_changed = "Toolbar opened.";
} else {
toolbar_size = 0;
}
let mut field = Field::new(
(width / scale as u32) as usize - toolbar_size,
height as usize / scale as usize,
);
field.fill(4);
let field_ref = &mut simulation_ref.lock().unwrap().field;
*field_ref = field;
}
Event::KeyDown {
keycode: Some(Keycode::Q),
..
} => {
let _ = open_url("https://github.com/GreatC0der/schellings_model/blob/master/readme.md#key-bindings");
}
Event::KeyDown { .. } => ui_changed = "Press Q for help.",
_ => {}
}
}
if !draw_to_screen {
continue;
}
let Ok(simulation) = simulation_ref.try_lock() else { continue };
canvas.clear();
canvas.set_draw_color(background_colour);
canvas.set_draw_color(ui_colour);
let toolbar_beggining = simulation.field.field.len() + 1;
for i in 0..(1100 - simulation.speed) / 100 {
let _ = canvas.draw_point(Point::new(i as i32 + toolbar_beggining as i32, 1));
}
for i in 0..(simulation.wanted_happiness * 10.0 + 1.0) as i32 {
let _ = canvas.draw_point(Point::new(i + toolbar_beggining as i32, 4));
}
let _ = canvas.set_scale(1.0, 1.0);
let surface = font.render(ui_changed).blended(ui_colour).unwrap();
let texture = texture_creator
.create_texture_from_surface(&surface)
.unwrap();
let TextureQuery { width, height, .. } = texture.query();
let text_rect = Rect::new(
(toolbar_beggining * scale as usize) as i32,
6 * scale as i32,
width,
height,
);
let _ = canvas.copy(&texture, None, text_rect);
let _ = canvas.set_scale(scale, scale);
let mut points1: Vec<Point> = Vec::new();
let mut points2: Vec<Point> = Vec::new();
let mut empty_points: Vec<Point> = Vec::new();
for x in 0..simulation.field.field.len() {
for y in 0..simulation.field.field[0].len() {
let point = Point::new(x as i32, y as i32);
match simulation.field.field[x][y] {
None => {
empty_points.push(point);
}
Some(Agent::One) => {
points1.push(point);
}
Some(Agent::Two) => {
points2.push(point);
}
}
}
}
canvas.set_draw_color(group1_colour);
let _ = canvas.draw_points(points1.as_slice());
canvas.set_draw_color(group2_colour);
let _ = canvas.draw_points(points2.as_slice());
canvas.set_draw_color(background_colour);
let _ = canvas.draw_points(empty_points.as_slice());
canvas.present();
}
}