Skip to content

Commit

Permalink
Fully functional example
Browse files Browse the repository at this point in the history
Probably not ideomatic for gleam, but it works
  • Loading branch information
afarinetti committed Nov 21, 2024
1 parent 97df2a8 commit 8aac0d5
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 69 deletions.
4 changes: 2 additions & 2 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# You typically do not need to edit this file

packages = [
{ name = "fmglee", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "fmglee", source = "hex", outer_checksum = "8EF1D688382982A134A3F4702F8D5ACF2F3C13F7F3E035893CD25EEABD29B49E" },
{ name = "gleam_stdlib", version = "0.40.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86606B75A600BBD05E539EB59FABC6E307EEEA7B1E5865AFB6D980A93BCB2181" },
{ name = "fmglee", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "fmglee", source = "hex", outer_checksum = "3AF5C4AD5EB04E64B54920F4EEFDB0111CDBD3ABFB8997DACF1E1083C030E296" },
{ name = "gleam_stdlib", version = "0.43.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "69EF22E78FDCA9097CBE7DF91C05B2A8B5436826D9F66680D879182C0860A747" },
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
]

Expand Down
73 changes: 42 additions & 31 deletions src/conway_sim.gleam
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import cell
import gleam/bool
import gleam/int
import gleam/iterator
import gleam/list
import gleam/option
import gleam/string
import gleam/string_tree
import grid
import operation

Expand Down Expand Up @@ -61,36 +66,6 @@ fn apply_rules(
let neighbor_count = neighbor_count(this, row, col)
let alive = grid.is_cell_alive(this.grid, row, col)

// case alive {
// // RULES FOR LIVE CELLS //////////////////////////////////////////
// True -> {
// case neighbor_count {
// // rule 1: any live cell with fewer than two live neighbors dies,
// // as if caused by under-population.
// c if c < 2 -> option.Some(operation.new(row, col, cell.Dead))
//
// // rule 2: any live cell with two or three live neighbors lives on
// // to the next generation.
// c if c <= 3 -> option.None
//
// // rule 3: any live cell with more than three neighbors dies, as if
// // caused by overcrowding.
// _ -> option.Some(operation.new(row, col, cell.Dead))
// }
// }
// // RULES FOR DEAD CELLS //////////////////////////////////////////
// False -> {
// case neighbor_count {
// // rule 4: any dead cell with exactly three live neighbors becomes
// // a live cell, as if by reproduction.
// c if c == 3 -> option.Some(operation.new(row, col, cell.Alive))
//
// // otherwise
// _ -> option.None
// }
// }
// }

case alive, neighbor_count {
// RULES FOR LIVE CELLS ////////////////////////////////////////////////////
// rule 1: any live cell with fewer than two live neighbors dies,
Expand All @@ -115,6 +90,42 @@ fn apply_rules(
}
}

fn apply_operation(this: ConwaySim, op: operation.Operation) -> ConwaySim {
ConwaySim(grid.set(this.grid, op.row, op.col, op.state), this.generation)
}

pub fn step(this: ConwaySim) -> ConwaySim {
todo
let sim_after_step: ConwaySim =
grid.to_iterator_coords(this.grid)
|> iterator.filter_map(fn(coords: #(Int, Int)) {
let #(row, col) = coords
let operation = apply_rules(this, row, col)
case operation {
option.Some(op) -> Ok(op)
option.None -> Error(Nil)
}
})
|> iterator.fold(this, fn(sim: ConwaySim, op: operation.Operation) {
apply_operation(sim, op)
})

ConwaySim(sim_after_step.grid, this.generation + 1)
}

pub fn to_string(this: ConwaySim) -> String {
let divider = string.repeat("-", { this.grid.num_cols * 2 } + 2) <> "\n"

string_tree.new()
|> string_tree.append(divider)
|> string_tree.append("Generation: ")
|> string_tree.append(int.to_string(this.generation))
|> string_tree.append("\n")
|> string_tree.append(divider)
|> string_tree.append(grid.to_string(this.grid))
|> string_tree.append(divider)
|> string_tree.append("Any cell alive?: ")
|> string_tree.append(bool.to_string(grid.is_any_cell_alive(this.grid)))
|> string_tree.append("\n")
|> string_tree.append(divider)
|> string_tree.to_string
}
42 changes: 32 additions & 10 deletions src/gameoflife.gleam
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
import cell
import conway_sim
import fmglee
import gleam/io
import gleam/iterator
import gleam/list
import grid

pub fn main() {
let grid1 =
grid.new(10, 10)
|> grid.set(0, 0, cell.Alive)
|> grid.set(5, 5, cell.Alive)
|> grid.display
|> grid.set(1, 1, cell.Alive)
|> grid.set(1, 2, cell.Alive)
|> grid.set(2, 1, cell.Alive)
|> grid.set(2, 2, cell.Alive)
|> grid.set(3, 3, cell.Alive)
|> grid.set(3, 4, cell.Alive)
|> grid.set(4, 3, cell.Alive)
|> grid.set(4, 4, cell.Alive)

let game = conway_sim.new_from_grid(grid1)
let count = conway_sim.neighbor_count(game, 1, 0)
fmglee.new("count = %d")
|> fmglee.d(count)
|> fmglee.build
|> io.println
// let grid1 =
// grid.new(10, 10)
// |> grid.set(1, 6, cell.Alive)
// |> grid.set(1, 7, cell.Alive)
// |> grid.set(1, 8, cell.Alive)

let sim1 = conway_sim.new_from_grid(grid1)
io.println(conway_sim.to_string(sim1))

iterator.range(1, 30)
|> iterator.fold_until(sim1, fn(sim, _) {
let any_cell_alive = grid.is_any_cell_alive(sim.grid)

case any_cell_alive {
True -> {
let sim_after_step = conway_sim.step(sim)
io.println(conway_sim.to_string(sim_after_step))
list.Continue(sim_after_step)
}
False -> list.Stop(sim)
}
})
}
64 changes: 38 additions & 26 deletions src/grid.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import cell
import gleam/io
import gleam/iterator
import gleam/set
import gleam/string
import gleam/string_tree

pub type Grid {
Grid(num_rows: Int, num_cols: Int, grid: set.Set(Int))
Grid(num_rows: Int, num_cols: Int, data: set.Set(Int))
}

pub fn new(num_rows: Int, num_cols: Int) -> Grid {
Expand All @@ -15,56 +17,66 @@ fn cell_to_index(this: Grid, row: Int, col: Int) -> Int {
row * this.num_cols + col
}

fn index_to_cell(this: Grid, index: Int) -> #(Int, Int) {
let row = index / this.num_cols
let col = index - { row * this.num_cols }
#(row, col)
}

pub fn get(this: Grid, row: Int, col: Int) -> cell.Cell {
let index = cell_to_index(this, row, col)
cell.from_bool(set.contains(this.grid, index))
cell.from_bool(set.contains(this.data, index))
}

pub fn set(this: Grid, row: Int, col: Int, state: cell.Cell) -> Grid {
let index = cell_to_index(this, row, col)
let grid = case state {
cell.Alive -> set.insert(this.grid, index)
cell.Dead -> set.delete(this.grid, index)
let data_edit = case state {
cell.Alive -> set.insert(this.data, index)
cell.Dead -> set.delete(this.data, index)
}
Grid(num_rows: this.num_rows, num_cols: this.num_cols, grid: grid)
Grid(num_rows: this.num_rows, num_cols: this.num_cols, data: data_edit)
}

pub fn is_cell_alive(this: Grid, row: Int, col: Int) -> Bool {
let index = cell_to_index(this, row, col)
set.contains(this.grid, index)
set.contains(this.data, index)
}

pub fn is_any_cell_alive(this: Grid) -> Bool {
set.size(this.grid) > 0
set.size(this.data) > 0
}

pub type GridCell {
GridCell(row: Int, col: Int, state: cell.Cell)
}

pub fn to_iterator(this: Grid) -> iterator.Iterator(#(Int, cell.Cell)) {
pub fn to_iterator_coords(this: Grid) -> iterator.Iterator(#(Int, Int)) {
iterator.range(0, { this.num_rows * this.num_cols } - 1)
|> iterator.map(fn(idx) {
#(idx, cell.from_bool(set.contains(this.grid, idx)))
})
|> iterator.map(fn(idx: Int) { index_to_cell(this, idx) })
}

// pub fn to_iterator2(this: Grid) -> iterator.Iterator(GridCell) {
// iterator.range(0, this.num_rows - 1)
// |> iterator.zip(iterator.range(0, this.num_cols - 1))
// |> iterator.to_list
// |> io.debug
// iterator.empty()
// }
pub fn to_iterator(this: Grid) -> iterator.Iterator(GridCell) {
iterator.range(0, { this.num_rows * this.num_cols } - 1)
|> iterator.map(fn(idx: Int) {
let #(row, col) = index_to_cell(this, idx)
let value = cell.from_bool(set.contains(this.data, idx))
GridCell(row, col, value)
})
}

pub fn display(this: Grid) -> Grid {
pub fn to_string(this: Grid) -> String {
to_iterator(this)
|> iterator.each(fn(tuple) {
io.print(cell.to_string(tuple.1))
case tuple.0 {
idx if { idx + 1 } % this.num_cols == 0 -> io.println("")
_ -> Nil
|> iterator.map(fn(grid_cell: GridCell) {
// append the cell state
let b = string_tree.from_string(cell.to_string(grid_cell.state))

// append a newline if needed
case grid_cell.col {
c if c == this.num_cols - 1 -> string_tree.append(b, "\n")
_ -> b
}
})
this
|> iterator.to_list
|> string_tree.join("")
|> string_tree.to_string
}

0 comments on commit 8aac0d5

Please sign in to comment.