├── .gitignore ├── pres.gif ├── futhark.pkg ├── Makefile ├── README.md ├── LICENSE └── gameoflife.fut /.gitignore: -------------------------------------------------------------------------------- 1 | *.h 2 | *.c 3 | *.o 4 | gameoflife 5 | lib/ 6 | 7 | -------------------------------------------------------------------------------- /pres.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nevrome/gameoflife/main/pres.gif -------------------------------------------------------------------------------- /futhark.pkg: -------------------------------------------------------------------------------- 1 | require { 2 | github.com/diku-dk/lys 0.1.12 #34e5ff985fefac9a9627d49e26a19ef5352e7019 3 | } 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | --LYS_BACKEND=opencl 2 | LYS_BACKEND=c 3 | PROGNAME=gameoflife 4 | include lib/github.com/diku-dk/lys/common.mk 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gameoflife 2 | 3 | ![Screenshot](pres.gif) 4 | 5 | A simple implementation of [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) written in [Futhark](http://futhark-lang.org) using the [lys](https://github.com/diku-dk/lys) library for graphics and interaction. 6 | 7 | Requires Futhark and SDL2 and SDL2-ttf libraries with associated header files. 8 | 9 | ## Building and running 10 | 11 | First run `futhark pkg sync` once. 12 | 13 | Then run `make run` to build and run in a window. 14 | 15 | ## Controls 16 | 17 | - Click to create a 2*2 block of living cells. 18 | - Change the game speed with `+` and `-`. 19 | - Zoom with the scroll wheel. 20 | - Pan with the arrow keys. 21 | - Pause with the space bar. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Clemens Schmid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /gameoflife.fut: -------------------------------------------------------------------------------- 1 | import "lib/github.com/athas/matte/colour" 2 | import "lib/github.com/diku-dk/lys/lys" 3 | 4 | -- #### general helper functions #### 5 | 6 | let replicate 't (n: i64) (x: t): [n]t = 7 | map (\_ -> x) (0.. map (\a2 -> bool2u8 a2) a1) as 13 | 14 | -- #### world array operations #### 15 | 16 | let starting_world_generator (h: i64) (w: i64): [][]bool = 17 | replicate h (replicate w false) with [9,10] = true with [8,10] = true with [7,10] = true with [9,9] = true with [8,8] = true 18 | 19 | let mouse_world_generator (h: i64) (w: i64) (x: i64) (y: i64): [][]bool = 20 | replicate h (replicate w false) with [y,x] = true with [y+1,x+1] = true with [y+1,x] = true with [y,x+1] = true 21 | 22 | let shift_array_left 't (x: i64) (y: i64) (as: [][]t) (d: i64): [][]t = 23 | concat_to x as[d:x,0:y] as[0:d,0:y] 24 | 25 | let shift_array_right 't (x: i64) (y: i64) (as: [][]t) (d: i64): [][]t = 26 | concat_to x as[(x-d):x,0:y] as[0:(x-d),0:y] 27 | 28 | let shift_array_top 't (x: i64) (y: i64) (as: [][]t) (d: i64): [][]t = 29 | shift_array_left y x (transpose as) d |> transpose 30 | 31 | let shift_array_bottom 't (x: i64) (y: i64) (as: [][]t) (d: i64): [][]t = 32 | shift_array_right y x (transpose as) d |> transpose 33 | 34 | let shift_array_top_left 't (x: i64) (y: i64) (as: [][]t) (d: i64): [][]t = 35 | shift_array_left x y (shift_array_top x y as d) d 36 | 37 | let shift_array_top_right 't (x: i64) (y: i64) (as: [][]t) (d: i64): [][]t = 38 | shift_array_right x y (shift_array_top x y as d) d 39 | 40 | let shift_array_bottom_left 't (x: i64) (y: i64) (as: [][]t) (d: i64): [][]t = 41 | shift_array_left x y (shift_array_bottom x y as d) d 42 | 43 | let shift_array_bottom_right 't (x: i64) (y: i64) (as: [][]t) (d: i64): [][]t = 44 | shift_array_right x y (shift_array_bottom x y as d) d 45 | 46 | let sum_array_4 (as: [][]u8) (bs: [][]u8) (cs: [][]u8) (ds: [][]u8): [][]u8 = 47 | map4 (\a1 b1 c1 d1 -> map4 (\a2 b2 c2 d2 -> (a2 + b2 + c2 + d2)) a1 b1 c1 d1) as bs cs ds 48 | 49 | -- #### game logic #### 50 | 51 | let conways_rules (a: u8) (b: bool): bool = 52 | if a < 2 53 | then false 54 | else if b && ((a == 2) || (a == 3)) 55 | then true 56 | else if !b && a == 3 57 | then true 58 | else if a > 3 59 | then false 60 | else false 61 | 62 | let change_world (width: i64) (height: i64) (mouse_activated: bool) (mouse: (i64, i64)) (world: [][]bool): [][]bool = 63 | -- apply mouse input 64 | let mouse_world = mouse_world_generator width height mouse.0 mouse.1 65 | let edited_world = 66 | if mouse_activated 67 | then map2 (\a1 b1 -> map2 (\a2 b2 -> a2 || b2) a1 b1) mouse_world[0:width,0:height] world[0:width,0:height] 68 | else world 69 | -- create u8 world for summing 70 | let world_u8 = bool2u8_array edited_world 71 | -- 4 degrees of freedom shifts 72 | let shift_left = shift_array_left width height world_u8 1 73 | let shift_right = shift_array_right width height world_u8 1 74 | let shift_top = shift_array_top width height world_u8 1 75 | let shift_bottom = shift_array_bottom width height world_u8 1 76 | -- 8 degrees of freedom shifts 77 | let shift_top_left = shift_array_top_left width height world_u8 1 78 | let shift_top_right = shift_array_top_right width height world_u8 1 79 | let shift_bottom_left = shift_array_bottom_left width height world_u8 1 80 | let shift_bottom_right = shift_array_bottom_right width height world_u8 1 81 | -- calculate sums per cell 82 | let number_of_true_normal = sum_array_4 shift_left shift_right shift_top shift_bottom 83 | let number_of_true_diagonal = sum_array_4 shift_top_left shift_top_right shift_bottom_left shift_bottom_right 84 | let number_true_total = map2 (\a1 b1 -> map2 (\a2 b2 -> (a2 + b2)) a1 b1) number_of_true_normal number_of_true_diagonal 85 | -- finally apply conway rules per cell 86 | let new_world = map2 (\a1 b1 -> map2 conways_rules a1 b1) number_true_total[0:width,0:height] edited_world[0:width,0:height] 87 | in new_world 88 | 89 | -- #### user interface with lys #### 90 | -- most of this code is adapted from https://github.com/diku-dk/lys and https://github.com/athas/abelian-sandpile 91 | 92 | let screen_point_to_world_point ((centre_x, centre_y): (f32,f32)) (s: f32) 93 | ((sw,sh): (i64,i64)) ((ww,wh): (i64,i64)) 94 | ((x,y): (i32,i32)) = 95 | let x' = i32.f32 ((centre_x + s * (f32.i32 (x-(i32.i64 ww)/2) / f32.i64 sw)) * f32.i64 ww) 96 | let y' = i32.f32 ((centre_y + s * (f32.i32 (y-(i32.i64 wh)/2) / f32.i64 sh)) * f32.i64 wh) 97 | in (x', y') 98 | 99 | let screen_point_to_world_point_64 ((centre_x, centre_y): (f32,f32)) (s: f32) 100 | ((sw,sh): (i64,i64)) ((ww,wh): (i64,i64)) 101 | ((x,y): (i64,i64)) = 102 | let x' = i64.f32 ((centre_x + s * (f32.i64 (x-ww/2) / f32.i64 sw)) * f32.i64 ww) 103 | let y' = i64.f32 ((centre_y + s * (f32.i64 (y-wh/2) / f32.i64 sh)) * f32.i64 wh) 104 | in (x', y') 105 | 106 | module zoom_wrapper (M: lys) : lys with text_content = M.text_content = { 107 | type~ state = { inner: M.state 108 | , centre: (f32, f32) 109 | , scale: f32 110 | , width: i64 111 | , height: i64 112 | } 113 | 114 | type text_content = M.text_content 115 | 116 | let init seed h w : state = { inner = M.init seed h w 117 | , centre = (0.5, 0.5) 118 | , scale = 1 119 | , width = w 120 | , height = h } 121 | 122 | let zoom dy (s : state) = 123 | s with scale = f32.min 1 (s.scale * (0.99**(f32.i32 dy * 10))) 124 | 125 | let event (e: event) (s: state) = 126 | match e 127 | case #mouse {buttons, x, y} -> 128 | let (x, y) = screen_point_to_world_point s.centre s.scale 129 | (s.width, s.height) (s.width, s.height) (x,y) 130 | in s with inner = M.event (#mouse {buttons, x, y}) s.inner 131 | case #wheel {dx=_, dy} -> 132 | zoom dy s with inner = M.event e s.inner 133 | case e -> 134 | s with inner = M.event e s.inner 135 | 136 | let resize h w (s: state) = s with inner = M.resize h w s.inner 137 | with width = w 138 | with height = h 139 | 140 | let render (s: state) = 141 | let screen = M.render s.inner 142 | let pixel y x = 143 | let (x',y') = screen_point_to_world_point_64 s.centre s.scale 144 | (s.width, s.height) (s.width, s.height) (x,y) 145 | in screen[y', x'] 146 | in tabulate_2d s.height s.width pixel 147 | 148 | let grab_mouse = M.grab_mouse 149 | let text_format = M.text_format 150 | let text_content fps (s: state) = M.text_content fps s.inner 151 | let text_colour (s: state) = M.text_colour s.inner 152 | } 153 | 154 | let plot (width: i64) (height: i64) (world:[][]bool): [height][width]argb.colour = 155 | let f j i = 156 | let is_alive = world[i,j] 157 | in if is_alive then argb.black else argb.white 158 | in tabulate_2d height width f 159 | 160 | type text_content = (i32, i32, i32) 161 | module lys: lys with text_content = text_content = zoom_wrapper { 162 | 163 | type~ state = { 164 | t:i32, 165 | h:i64, 166 | w:i64, 167 | paused:bool, 168 | mouse_activated:bool, 169 | mouse: (i64, i64), 170 | world:[][]bool, 171 | numer_of_steps:i64, 172 | speed:i32 173 | } 174 | 175 | let init _ h w: state = { 176 | t=0, 177 | h, 178 | w, 179 | paused = false, 180 | mouse_activated = false, 181 | mouse = (0,0), 182 | world = starting_world_generator w h, 183 | numer_of_steps=0, 184 | speed=5 185 | } 186 | 187 | let step (s: state) = 188 | s with t = s.t + 1 189 | with numer_of_steps = s.numer_of_steps + 1 190 | with world = change_world s.w s.h s.mouse_activated s.mouse s.world 191 | 192 | let keydown (key: i32) (s: state) = 193 | if key == SDLK_SPACE then s with paused = !s.paused 194 | else if key == SDLK_LEFT then s with world = shift_array_right s.w s.h s.world 10 195 | else if key == SDLK_RIGHT then s with world = shift_array_left s.w s.h s.world 10 196 | else if key == SDLK_UP then s with world = shift_array_bottom s.w s.h s.world 10 197 | else if key == SDLK_DOWN then s with world = shift_array_top s.w s.h s.world 10 198 | else if key == SDLK_PLUS then if s.speed == 10 199 | then s with speed = 1 200 | else s with speed = s.speed + 1 201 | else if key == SDLK_MINUS then if s.speed == 1 202 | then s with speed = 10 203 | else s with speed = s.speed - 1 204 | else s 205 | 206 | let event (e: event) (s: state) = 207 | match e 208 | case #step -> -- case #step td -> 209 | if s.paused 210 | then s 211 | else if s.t %% (11 - s.speed) == 0 212 | then step s 213 | else s with t = s.t + 1 214 | case #keydown {key} -> keydown key s 215 | case #mouse {buttons, x, y} -> 216 | if (buttons != 0) && (i64.i32 y < s.h) && (i64.i32 x < s.w) 217 | then s with mouse_activated = true 218 | with mouse =(i64.i32 y, i64.i32 x) 219 | else s with mouse_activated = false 220 | case _ -> s 221 | 222 | let resize h w (s: state) = s with h = h with w = w with world = starting_world_generator w h 223 | 224 | let render (s: state) = plot s.w s.h s.world 225 | 226 | type text_content = text_content 227 | let text_format () = "FPS: %d\nt: %d\nspeed: %d" 228 | let text_colour = const argb.black 229 | let text_content (fps: f32) (s: state): text_content = ( 230 | t32 fps, 231 | i32.i64 s.numer_of_steps, 232 | s.speed 233 | ) 234 | let grab_mouse = false 235 | 236 | } 237 | 238 | --------------------------------------------------------------------------------