Skip to content

Commit

Permalink
Add clear and random button
Browse files Browse the repository at this point in the history
  • Loading branch information
jotaviobiondo committed Oct 5, 2024
1 parent c96b67e commit 1425d35
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 30 deletions.
29 changes: 18 additions & 11 deletions lib/game_of_life/grid.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ defmodule GameOfLife.Grid do
"""

alias GameOfLife.Cell
alias GameOfLife.Grid

@type position :: {row :: integer, columns :: integer}
@type cells :: %{required(position) => Cell.t()}
Expand All @@ -30,7 +29,7 @@ defmodule GameOfLife.Grid do

cells = matrix_to_cells(cell_matrix, rows, cols)

%Grid{cells: cells, rows: rows, cols: cols, generation: 1}
%__MODULE__{cells: cells, rows: rows, cols: cols, generation: 1}
else
{:error, reason} -> raise(ArgumentError, reason)
end
Expand Down Expand Up @@ -70,8 +69,15 @@ defmodule GameOfLife.Grid do
|> new!()
end

@spec clear(t()) :: t
def clear(%__MODULE__{} = grid) do
new_cells = Map.new(grid.cells, fn {position, _value} -> {position, :dead} end)

%__MODULE__{grid | cells: new_cells, generation: 1}
end

@spec neighbors(t(), position()) :: MapSet.t(position)
def neighbors(grid, {cell_x, cell_y}) do
def neighbors(%__MODULE__{} = grid, {cell_x, cell_y}) do
for offset_x <- [-1, 0, 1],
offset_y <- [-1, 0, 1],
neighbor = {offset_x + cell_x, offset_y + cell_y},
Expand All @@ -82,21 +88,22 @@ defmodule GameOfLife.Grid do
end

@spec inside_grid?(t(), position()) :: boolean()
defp inside_grid?(grid, {x, y}), do: x in 0..(grid.rows - 1) and y in 0..(grid.cols - 1)
defp inside_grid?(%__MODULE__{} = grid, {x, y}),
do: x in 0..(grid.rows - 1) and y in 0..(grid.cols - 1)

@spec count_neighbors_alive(t(), position()) :: non_neg_integer
def count_neighbors_alive(grid, cell_position) do
def count_neighbors_alive(%__MODULE__{} = grid, cell_position) do
grid
|> neighbors(cell_position)
|> Enum.map(fn neighbor -> get_cell(grid, neighbor) end)
|> Enum.count(&(&1 == :alive))
end

@spec get_cell(t(), position()) :: Cell.t()
def get_cell(grid, cell_position), do: Map.get(grid.cells, cell_position, :dead)
def get_cell(%__MODULE__{} = grid, cell_position), do: Map.get(grid.cells, cell_position, :dead)

@spec toggle_cell(t(), position()) :: t()
def toggle_cell(grid, cell_position) do
def toggle_cell(%__MODULE__{} = grid, cell_position) do
if not inside_grid?(grid, cell_position) do
raise ArgumentError, "position given is outside the grid. Got '#{inspect(cell_position)}'"
end
Expand All @@ -111,15 +118,15 @@ defmodule GameOfLife.Grid do
%__MODULE__{grid | cells: updated_cells}
end

defimpl String.Chars, for: Grid do
@spec to_string(Grid.t()) :: String.t()
def to_string(%Grid{} = grid) do
defimpl String.Chars do
@spec to_string(GameOfLife.Grid.t()) :: String.t()
def to_string(%GameOfLife.Grid{} = grid) do
grid_str =
grid.cells
|> Map.keys()
|> Enum.sort()
|> Enum.map_join("|", fn position ->
cell_str = grid |> Grid.get_cell(position) |> Cell.to_string()
cell_str = grid |> GameOfLife.Grid.get_cell(position) |> Cell.to_string()

last_row_index = grid.rows - 1
last_col_index = grid.cols - 1
Expand Down
3 changes: 2 additions & 1 deletion lib/game_of_life_web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ defmodule GameOfLifeWeb.CoreComponents do
<.button>Send!</.button>
<.button phx-click="go" class="ml-2">Send!</.button>
"""
attr :type, :string, default: nil
attr :type, :string, default: "button"
attr :class, :string, default: nil
attr :rest, :global, include: ~w(disabled form name value)

Expand All @@ -231,6 +231,7 @@ defmodule GameOfLifeWeb.CoreComponents do
class={[
"phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3",
"text-sm font-semibold leading-6 text-white active:text-white/80",
"disabled:opacity-50 disabled:pointer-events-none",
@class
]}
{@rest}
Expand Down
38 changes: 24 additions & 14 deletions lib/game_of_life_web/live/home_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,20 @@ defmodule GameOfLifeWeb.HomeLive do
<section>
<div>
<div>
<.button
type="button"
phx-click="start"
disabled={@state == :running}
class="disabled:opacity-50 disabled:pointer-events-none"
>
Start
<.button :if={@state == :paused} phx-click="start">
<.icon name="hero-play" class="size-5" /> Start
</.button>
<.button
type="button"
phx-click="stop"
disabled={@state == :paused}
class="disabled:opacity-50 disabled:pointer-events-none"
>
Pause
<.button :if={@state == :running} phx-click="stop">
<.icon name="hero-pause" class="size-5" /> Pause
</.button>
<.button phx-click="random_grid" disabled={@state == :running}>
Random
</.button>
<.button phx-click="clear_grid" disabled={@state == :running}>
Clear
</.button>
</div>
</div>
Expand Down Expand Up @@ -87,6 +85,18 @@ defmodule GameOfLifeWeb.HomeLive do
{:noreply, socket}
end

@impl true
def handle_event("random_grid", _params, %Socket{} = socket) do
socket = assign(socket, grid: Grid.random(@grid_size, @grid_size))
{:noreply, socket}
end

@impl true
def handle_event("clear_grid", _params, %Socket{} = socket) do
socket = update(socket, :grid, &Grid.clear/1)
{:noreply, socket}
end

@impl true
def handle_event("cell_clicked", %{"row" => row, "col" => col} = _params, %Socket{} = socket) do
socket = update(socket, :grid, fn grid -> Grid.toggle_cell(grid, {row, col}) end)
Expand Down
43 changes: 39 additions & 4 deletions test/game_of_life/grid_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,29 @@ defmodule GameOfLife.GridTest do
|> Enum.all?(&(&1 in [:alive, :dead]))
end

describe "clear/1" do
test "should set all cells to :dead" do
matrix = [
[1, 1],
[1, 1]
]

grid = matrix |> Grid.new!() |> struct!(generation: 7)

assert %Grid{
rows: 2,
cols: 2,
generation: 1,
cells: %{
{0, 0} => :dead,
{0, 1} => :dead,
{1, 0} => :dead,
{1, 1} => :dead
}
} = Grid.clear(grid)
end
end

test "neighbors/2" do
grid = Grid.random(3, 3)

Expand Down Expand Up @@ -180,10 +203,22 @@ defmodule GameOfLife.GridTest do
end

test "should raise when position is outside the grid", %{grid: grid} do
assert_raise(ArgumentError, "position given is outside the grid. Got '{-1, 0}'", fn -> Grid.toggle_cell(grid, {-1, 0}) end)
assert_raise(ArgumentError, "position given is outside the grid. Got '{0, -1}'", fn -> Grid.toggle_cell(grid, {0, -1}) end)
assert_raise(ArgumentError, "position given is outside the grid. Got '{2, 0}'", fn -> Grid.toggle_cell(grid, {2, 0}) end)
assert_raise(ArgumentError, "position given is outside the grid. Got '{0, 2}'", fn -> Grid.toggle_cell(grid, {0, 2}) end)
assert_raise(ArgumentError, "position given is outside the grid. Got '{-1, 0}'", fn ->
Grid.toggle_cell(grid, {-1, 0})
end)

assert_raise(ArgumentError, "position given is outside the grid. Got '{0, -1}'", fn ->
Grid.toggle_cell(grid, {0, -1})
end)

assert_raise(ArgumentError, "position given is outside the grid. Got '{2, 0}'", fn ->
Grid.toggle_cell(grid, {2, 0})
end)

assert_raise(ArgumentError, "position given is outside the grid. Got '{0, 2}'", fn ->
Grid.toggle_cell(grid, {0, 2})
end)

assert %Grid{} = Grid.toggle_cell(grid, {0, 0})
assert %Grid{} = Grid.toggle_cell(grid, {0, 1})
assert %Grid{} = Grid.toggle_cell(grid, {1, 0})
Expand Down

0 comments on commit 1425d35

Please sign in to comment.