feat: initial commit?
This commit is contained in:
commit
a2241e5123
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
3
.rustfmt.toml
Normal file
3
.rustfmt.toml
Normal file
@ -0,0 +1,3 @@
|
||||
hard_tabs = true
|
||||
tab_spaces = 2
|
||||
max_width = 80
|
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "rust-game-of-life"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
raylib = "3.7.0"
|
85
src/game_of_life.rs
Normal file
85
src/game_of_life.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub type Point = (i32, i32);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GameOfLife {
|
||||
pub alive_tiles: HashSet<(i32, i32)>,
|
||||
}
|
||||
|
||||
impl GameOfLife {
|
||||
pub fn new() -> GameOfLife {
|
||||
GameOfLife {
|
||||
alive_tiles: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tile(&self, point: Point) -> bool {
|
||||
self.alive_tiles.contains(&point)
|
||||
}
|
||||
|
||||
pub fn toggle_tile(&mut self, point: Point) {
|
||||
if self.alive_tiles.contains(&point) {
|
||||
self.alive_tiles.remove(&point);
|
||||
} else {
|
||||
self.alive_tiles.insert(point);
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_neighbours(&self, (x, y): Point) -> Vec<Point> {
|
||||
(x-1..=x+1)
|
||||
.flat_map(move |x| (y-1..=y+1).map(move |y| (x, y)))
|
||||
.filter(|point| !(point.0 == x && point.1 == y))
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn count_neighbours(&self, point: Point) -> u8 {
|
||||
self.iter_neighbours(point).iter()
|
||||
.filter(|point| self.alive_tiles.contains(point))
|
||||
.count() as u8
|
||||
}
|
||||
|
||||
fn get_dead_tiles(&self) -> HashSet<Point> {
|
||||
self.alive_tiles.iter()
|
||||
.flat_map(|point| self.iter_neighbours(*point))
|
||||
.filter(|point| !self.alive_tiles.contains(point))
|
||||
.collect::<HashSet<_>>()
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
let mut new_alive_tiles = HashSet::new();
|
||||
|
||||
for alive_tile in &self.alive_tiles {
|
||||
let neighours = self.count_neighbours(*alive_tile);
|
||||
if neighours == 2 || neighours == 3 {
|
||||
new_alive_tiles.insert(*alive_tile);
|
||||
}
|
||||
}
|
||||
|
||||
for dead_tile in self.get_dead_tiles() {
|
||||
let neighours = self.count_neighbours(dead_tile);
|
||||
if neighours == 3 {
|
||||
new_alive_tiles.insert(dead_tile);
|
||||
}
|
||||
}
|
||||
|
||||
self.alive_tiles = new_alive_tiles;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut game = GameOfLife::new();
|
||||
|
||||
game.toggle_tile((3, 3));
|
||||
game.toggle_tile((4, 3));
|
||||
game.toggle_tile((4, 4));
|
||||
game.toggle_tile((3, 4));
|
||||
|
||||
assert_eq!(game.count_neighbours((3, 3)), 3);
|
||||
}
|
||||
}
|
141
src/main.rs
Normal file
141
src/main.rs
Normal file
@ -0,0 +1,141 @@
|
||||
mod game_of_life;
|
||||
|
||||
use std::ffi::CString;
|
||||
|
||||
use game_of_life::*;
|
||||
use raylib::prelude::*;
|
||||
|
||||
fn draw_label(d: &mut RaylibDrawHandle, x: f32, y: f32, text: &str) {
|
||||
d.gui_label(rrect(x, y, 50., 0.), Some(&CString::new(text).unwrap()));
|
||||
}
|
||||
|
||||
fn to_tile_space(x: i32, y: i32, offset_x: i32, offset_y: i32, tile_size: i32) -> (i32, i32) {
|
||||
(
|
||||
((x - offset_x) as f32 / tile_size as f32).floor() as i32,
|
||||
((y - offset_y) as f32 / tile_size as f32).floor() as i32,
|
||||
)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (mut rl, thread) = raylib::init()
|
||||
.size(640, 480)
|
||||
.resizable()
|
||||
.title("Game Of Life")
|
||||
.build();
|
||||
|
||||
rl.set_target_fps(60);
|
||||
|
||||
let mut game = GameOfLife::new();
|
||||
let tile_size = 16;
|
||||
let edge_thickness = 1.0;
|
||||
let mut dead_color = Color::BLACK;
|
||||
let mut alive_color = Color::WHITE;
|
||||
let mut edge_color = Color::GRAY;
|
||||
let overlay_color = Color { r: 0, g: 0, b: 0, a: 180 };
|
||||
|
||||
let mut running = false;
|
||||
let mut update_rate = 10; // in times per second
|
||||
let mut timer = 0;
|
||||
|
||||
let mut camera_x = 0;
|
||||
let mut camera_y = 0;
|
||||
let mut last_mouse = (0, 0);
|
||||
|
||||
let mut show_options = true;
|
||||
|
||||
while !rl.window_should_close() {
|
||||
let screen_width = rl.get_screen_width();
|
||||
let screen_height = rl.get_screen_height();
|
||||
let offset_x = camera_x + screen_width/2;
|
||||
let offset_y = camera_y + screen_height/2;
|
||||
|
||||
if rl.is_key_pressed(KeyboardKey::KEY_TAB) {
|
||||
show_options = !show_options
|
||||
}
|
||||
if !show_options {
|
||||
if rl.is_key_pressed(KeyboardKey::KEY_ENTER) {
|
||||
running = !running;
|
||||
}
|
||||
if running {
|
||||
if timer == 0 {
|
||||
game.tick();
|
||||
timer = 60 / update_rate;
|
||||
}
|
||||
timer -= 1;
|
||||
}
|
||||
if rl.is_key_pressed(KeyboardKey::KEY_SPACE) && !running {
|
||||
game.tick();
|
||||
}
|
||||
|
||||
let mx = rl.get_mouse_x();
|
||||
let my = rl.get_mouse_y();
|
||||
let (tile_x, tile_y) = to_tile_space(mx, my, offset_x, offset_y, tile_size);
|
||||
if rl.is_mouse_button_down(MouseButton::MOUSE_LEFT_BUTTON) {
|
||||
let (last_tile_x, last_tile_y) = to_tile_space(last_mouse.0, last_mouse.1, offset_x, offset_y, tile_size);
|
||||
if !(tile_x == last_tile_x && tile_y == last_tile_y) {
|
||||
game.toggle_tile((tile_x, tile_y));
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
if rl.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON) {
|
||||
game.toggle_tile((tile_x, tile_y));
|
||||
running = false;
|
||||
}
|
||||
if rl.is_mouse_button_down(MouseButton::MOUSE_MIDDLE_BUTTON) {
|
||||
let dx = rl.get_mouse_x() - last_mouse.0;
|
||||
let dy = rl.get_mouse_y() - last_mouse.1;
|
||||
camera_x += dx;
|
||||
camera_y += dy;
|
||||
}
|
||||
last_mouse = (mx, my);
|
||||
}
|
||||
|
||||
let mut d = rl.begin_drawing(&thread);
|
||||
d.clear_background(dead_color);
|
||||
for tile_dy in 0..screen_height/tile_size {
|
||||
for tile_dx in 0..screen_width/tile_size {
|
||||
let tile_x = tile_dx - offset_x / tile_size;
|
||||
let tile_y = tile_dy - offset_y / tile_size;
|
||||
if game.get_tile((tile_x, tile_y)) {
|
||||
let x = tile_dx * tile_size + (offset_x % tile_size);
|
||||
let y = tile_dy * tile_size + (offset_y % tile_size);
|
||||
d.draw_rectangle(x, y, tile_size, tile_size, alive_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for y in (0..screen_height+tile_size).step_by(tile_size as usize) {
|
||||
let y = y + offset_y % tile_size;
|
||||
d.draw_line_ex(rvec2(0.0, y), rvec2(screen_width, y), edge_thickness, edge_color);
|
||||
}
|
||||
for x in (0..screen_width+tile_size).step_by(tile_size as usize) {
|
||||
let x = x + offset_x % tile_size;
|
||||
d.draw_line_ex(rvec2(x, 0.0), rvec2(x, screen_height), edge_thickness, edge_color);
|
||||
}
|
||||
|
||||
if show_options {
|
||||
d.draw_rectangle(0, 0, screen_width, screen_height, overlay_color);
|
||||
if d.gui_window_box(rrect(10., 10., 300., 220.), Some(&CString::new("Options").unwrap())) {
|
||||
show_options = false;
|
||||
}
|
||||
|
||||
draw_label(&mut d, 15., 45., "Tab - Toggle options");
|
||||
draw_label(&mut d, 15., 60., "Enter - Toggle simulation");
|
||||
draw_label(&mut d, 15., 75., "Space - Step simulation");
|
||||
draw_label(&mut d, 15., 90., "Left mouse click - Toggle cell");
|
||||
draw_label(&mut d, 15., 105., "Middle mouse drag - Move camera");
|
||||
|
||||
draw_label(&mut d, 15., 132.5, "Simulation speed");
|
||||
update_rate = d.gui_slider(rrect(150., 125., 80., 15.), Some(&CString::new("1 FPS").unwrap()), Some(&CString::new("30 FPS").unwrap()), update_rate as f32, 1., 30.) as i32;
|
||||
|
||||
draw_label(&mut d, 15., 160., "\"Dead\" color");
|
||||
dead_color = d.gui_color_picker(rrect(20., 170., 40., 40.), dead_color);
|
||||
|
||||
draw_label(&mut d, 110., 160., "\"Alive\" color");
|
||||
alive_color = d.gui_color_picker(rrect(115., 170., 40., 40.), alive_color);
|
||||
|
||||
draw_label(&mut d, 205., 160., "Edge color");
|
||||
edge_color = d.gui_color_picker(rrect(210., 170., 40., 40.), edge_color);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user