Skip to content

Commit

Permalink
Add toggle cell functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
jotaviobiondo committed Oct 5, 2024
1 parent c0ae8f9 commit c96b67e
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 12 deletions.
32 changes: 23 additions & 9 deletions lib/game_of_life/grid.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule GameOfLife.Grid do
alias GameOfLife.Cell
alias GameOfLife.Grid

@type position :: {integer, integer}
@type position :: {row :: integer, columns :: integer}
@type cells :: %{required(position) => Cell.t()}
@type cell_matrix :: [[integer]]
@type size :: pos_integer
Expand Down Expand Up @@ -36,12 +36,10 @@ defmodule GameOfLife.Grid do
end
end

@spec validate_not_empty(cell_matrix) :: {:ok, cell_matrix} | {:error, String.t()}
defp validate_not_empty([]), do: {:error, "matrix can not be empty"}
defp validate_not_empty([[]]), do: {:error, "matrix can not be empty"}
defp validate_not_empty(matrix), do: {:ok, matrix}

@spec validate_matrix_dimension(cell_matrix) :: {:ok, cell_matrix} | {:error, String.t()}
defp validate_matrix_dimension(matrix) do
first_row_count = matrix |> List.first() |> Enum.count()
all_rows_has_same_size? = Enum.all?(matrix, fn row -> Enum.count(row) == first_row_count end)
Expand Down Expand Up @@ -72,7 +70,7 @@ defmodule GameOfLife.Grid do
|> new!()
end

@spec neighbors(t, position) :: MapSet.t(position)
@spec neighbors(t(), position()) :: MapSet.t(position)
def neighbors(grid, {cell_x, cell_y}) do
for offset_x <- [-1, 0, 1],
offset_y <- [-1, 0, 1],
Expand All @@ -83,29 +81,45 @@ defmodule GameOfLife.Grid do
end
end

@spec inside_grid?(t, position) :: boolean
@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)

@spec count_neighbors_alive(t, position) :: non_neg_integer
@spec count_neighbors_alive(t(), position()) :: non_neg_integer
def count_neighbors_alive(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()
@spec get_cell(t(), position()) :: Cell.t()
def get_cell(grid, cell_position), do: Map.get(grid.cells, cell_position, :dead)

@spec toggle_cell(t(), position()) :: t()
def toggle_cell(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

new_cell =
case get_cell(grid, cell_position) do
:alive -> :dead
:dead -> :alive
end

updated_cells = Map.put(grid.cells, cell_position, new_cell)
%__MODULE__{grid | cells: updated_cells}
end

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

last_row_index = grid.rows - 1
last_col_index = grid.cols - 1
Expand Down
14 changes: 11 additions & 3 deletions lib/game_of_life_web/live/home_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule GameOfLifeWeb.HomeLive do
alias GameOfLife.Grid
alias Phoenix.LiveView.Socket

@grid_size 50
@grid_size 30
@update_interval to_timeout(millisecond: 100)

@impl true
Expand Down Expand Up @@ -50,8 +50,9 @@ defmodule GameOfLifeWeb.HomeLive do
<tr :for={row <- 0..(@grid.rows - 1)}>
<td
:for={col <- 0..(@grid.cols - 1)}
class="border border-black text-center w-[15px] h-[15px] data-[alive]:bg-black"
class="border border-black text-center w-[20px] h-[20px] data-[alive]:bg-black hover:bg-gray-400"
data-alive={Grid.get_cell(@grid, {row, col}) == :alive}
phx-click={JS.push("cell_clicked", value: %{row: row, col: col})}
>
</td>
</tr>
Expand Down Expand Up @@ -79,13 +80,20 @@ defmodule GameOfLifeWeb.HomeLive do
end

@impl true
def handle_event("stop", _params, socket) do
def handle_event("stop", _params, %Socket{} = socket) do
Process.cancel_timer(socket.assigns.timer_ref)
socket = assign(socket, state: :paused, timer_ref: nil)

{: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)

{:noreply, socket}
end

@impl true
def handle_info(:next_generation, %Socket{} = socket) do
%{grid: grid} = socket.assigns
Expand Down
33 changes: 33 additions & 0 deletions test/game_of_life/grid_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,39 @@ defmodule GameOfLife.GridTest do
end
end

describe "toggle_cell/2" do
setup do
matrix = [
[0, 0],
[0, 0]
]

[grid: Grid.new!(matrix)]
end

test "should toggle the cell value between :alive and :dead", %{grid: grid} do
grid
|> tap(fn grid -> assert :dead == Grid.get_cell(grid, {0, 0}) end)
|> Grid.toggle_cell({0, 0})
|> tap(fn grid -> assert :alive == Grid.get_cell(grid, {0, 0}) end)
|> Grid.toggle_cell({0, 0})
|> tap(fn grid -> assert :dead == Grid.get_cell(grid, {0, 0}) end)
|> Grid.toggle_cell({0, 0})
|> tap(fn grid -> assert :alive == Grid.get_cell(grid, {0, 0}) end)
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 %Grid{} = Grid.toggle_cell(grid, {0, 0})
assert %Grid{} = Grid.toggle_cell(grid, {0, 1})
assert %Grid{} = Grid.toggle_cell(grid, {1, 0})
assert %Grid{} = Grid.toggle_cell(grid, {1, 1})
end
end

test "to_string/1" do
cell_matrix = [
[0, 0, 0, 1],
Expand Down

0 comments on commit c96b67e

Please sign in to comment.