summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMalte Voos <git@mal.tc>2022-08-20 13:27:09 +0200
committerMalte Voos <git@mal.tc>2022-08-20 13:27:09 +0200
commit7d9836ebef1950c550081b2ea9cd3f7718280a02 (patch)
tree8bb48736644fe40fb1861f980844c37a5ed44eff /src
downloadlife-7d9836ebef1950c550081b2ea9cd3f7718280a02.tar.gz
life-7d9836ebef1950c550081b2ea9cd3f7718280a02.zip
init
Diffstat (limited to 'src')
-rw-r--r--src/main.rs233
1 files changed, 233 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..a9af30d
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,233 @@
+use pixels::wgpu::Color;
+use pixels::PixelsBuilder;
+use pixels::SurfaceTexture;
+use rand::distributions::Standard;
+use rand::prelude::Distribution;
+use winit::dpi::PhysicalSize;
+use winit::event::Event;
+use winit::event_loop::EventLoop;
+use winit::window::WindowBuilder;
+use winit_input_helper::WinitInputHelper;
+
+const RES_X: usize = 265;
+const RES_Y: usize = 265;
+
+const WIN_WIDTH: u32 = 4 * RES_X as u32;
+const WIN_HEIGHT: u32 = 4 * RES_Y as u32;
+
+fn main() -> anyhow::Result<()> {
+ let event_loop = EventLoop::new();
+ let window = WindowBuilder::new()
+ .with_inner_size(PhysicalSize {
+ width: WIN_WIDTH,
+ height: WIN_HEIGHT,
+ })
+ .with_resizable(false)
+ .build(&event_loop)?;
+ let mut input = WinitInputHelper::new();
+
+ let window_size = window.inner_size();
+ let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
+
+ let mut pixels = PixelsBuilder::new(RES_X as u32, RES_Y as u32, surface_texture)
+ .clear_color(Color {
+ r: 0.0,
+ g: 0.0,
+ b: 0.0,
+ a: 1.0,
+ })
+ .build()?;
+ let mut life = Life::new();
+
+ event_loop.run(move |event, _, control_flow| {
+ control_flow.set_poll();
+
+ if let Event::RedrawRequested(_) = event {
+ life.draw(pixels.get_frame());
+ pixels.render().expect("failed to render");
+ }
+
+ if input.update(&event) {
+ if input.quit() {
+ control_flow.set_exit();
+ return;
+ };
+
+ life.update();
+ window.request_redraw();
+ };
+ });
+}
+
+#[derive(Clone, Copy, Debug)]
+enum Cell {
+ Alive,
+ Dead { since: u8 },
+}
+
+#[derive(Debug)]
+struct Grid {
+ pub cells: [[Cell; RES_X]; RES_Y],
+}
+
+enum FrontGrid {
+ A,
+ B,
+}
+
+struct Life {
+ grid_a: Grid,
+ grid_b: Grid,
+ front_grid: FrontGrid,
+}
+
+impl Cell {
+ pub fn is_alive(&self) -> bool {
+ match self {
+ Cell::Alive => true,
+ Cell::Dead { .. } => false,
+ }
+ }
+}
+
+impl Grid {
+ pub fn blank() -> Self {
+ Self {
+ cells: [[Cell::Dead { since: 0xff }; RES_X]; RES_Y],
+ }
+ }
+
+ pub fn with_random_borders() -> Self {
+ let mut ret = Self::blank();
+ ret.randomize_border();
+ ret
+ }
+
+ pub fn randomize_border(&mut self) {
+ self.cells[RES_X - 1] = rand::random();
+ }
+
+ pub fn draw(&self, frame: &mut [u8]) {
+ for row in 0..RES_X {
+ for col in 0..RES_Y {
+ let pixel_idx = 4 * (RES_X * row + col);
+
+ match self.cells[row][col] {
+ Cell::Alive => {
+ frame[pixel_idx + 0] = 0xff; // R
+ frame[pixel_idx + 1] = 0xff; // G
+ frame[pixel_idx + 2] = 0xff; // B
+ frame[pixel_idx + 3] = 0xff; // A
+ }
+ Cell::Dead { since } => {
+ frame[pixel_idx + 0] = 0xff - since.saturating_mul(16); // R
+ frame[pixel_idx + 1] = if since < 0x8 {
+ 0
+ } else {
+ (since - 0x8).saturating_mul(0x8) / 2
+ };
+ // G
+ frame[pixel_idx + 2] = 0xff; // B
+ frame[pixel_idx + 3] = 0xff - since; // A
+ }
+ };
+ }
+ }
+ }
+
+ pub fn num_alive_neighbors(&self, row: usize, col: usize) -> u8 {
+ let mut ret = 0;
+
+ for i in row.saturating_sub(1)..=(row + 1).min(RES_X - 1) {
+ for j in col.saturating_sub(1)..=(col + 1).min(RES_Y - 1) {
+ if self.cells[i][j].is_alive() {
+ ret += 1;
+ }
+ }
+ }
+ if self.cells[row][col].is_alive() {
+ ret -= 1;
+ }
+
+ ret
+ }
+
+ pub fn new_state(&self, row: usize, col: usize) -> Cell {
+ match self.cells[row][col] {
+ Cell::Alive => {
+ if (2..=3).contains(&self.num_alive_neighbors(row, col)) {
+ Cell::Alive
+ } else {
+ Cell::Dead { since: 0 }
+ }
+ }
+ Cell::Dead { since } => {
+ if self.num_alive_neighbors(row, col) == 3 {
+ Cell::Alive
+ } else {
+ Cell::Dead {
+ since: since.saturating_add(1),
+ }
+ }
+ }
+ }
+ }
+}
+
+impl Life {
+ pub fn new() -> Self {
+ Life {
+ grid_a: Grid::with_random_borders(),
+ grid_b: Grid::blank(),
+ front_grid: FrontGrid::A,
+ }
+ }
+
+ pub fn draw(&self, frame: &mut [u8]) {
+ self.front_grid().draw(frame);
+ }
+
+ pub fn update(&mut self) {
+ let (front, back) = self.grids();
+
+ for row in 0..RES_X {
+ for col in 0..RES_Y {
+ back.cells[row][col] = front.new_state(row, col);
+ }
+ }
+ back.randomize_border();
+
+ self.swap_grids();
+ }
+
+ fn grids(&mut self) -> (&Grid, &mut Grid) {
+ match self.front_grid {
+ FrontGrid::A => (&self.grid_a, &mut self.grid_b),
+ FrontGrid::B => (&self.grid_b, &mut self.grid_a),
+ }
+ }
+
+ fn front_grid(&self) -> &Grid {
+ match self.front_grid {
+ FrontGrid::A => &self.grid_a,
+ FrontGrid::B => &self.grid_b,
+ }
+ }
+
+ fn swap_grids(&mut self) {
+ self.front_grid = match self.front_grid {
+ FrontGrid::A => FrontGrid::B,
+ FrontGrid::B => FrontGrid::A,
+ }
+ }
+}
+
+impl Distribution<Cell> for Standard {
+ fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Cell {
+ if rng.gen() {
+ Cell::Alive
+ } else {
+ Cell::Dead { since: 0xff }
+ }
+ }
+}