diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..1b972ea --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +elixir 1.12 diff --git a/assets/css/app.css b/assets/css/app.css index 2c2cd8b..dfc8678 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -193,3 +193,17 @@ .figure.rook { background-position-x: -182.5px; } + +.selected-cell { + background-color: #31708f !important; +} + +.available_move { + position: fixed; + opacity: 0.9; + background-color: red !important; + z-index: 1; + border-radius: 8px; + /* height: 10px; + width: 10px; */ +} \ No newline at end of file diff --git a/lib/chess/figures.ex b/lib/chess/figures.ex new file mode 100644 index 0000000..1a41f6c --- /dev/null +++ b/lib/chess/figures.ex @@ -0,0 +1,8 @@ +defmodule Chess.Figures do + defstruct [ + :type, + :player, + :id, + :position + ] +end diff --git a/lib/chess/game.ex b/lib/chess/game.ex new file mode 100644 index 0000000..2f7df43 --- /dev/null +++ b/lib/chess/game.ex @@ -0,0 +1,436 @@ +defmodule Chess.Game do + alias Chess.Figures + + @spec new :: map() + def new do + %{ + turn: :white, + pid_player_white: nil, + pid_player_black: nil, + spectators: [], + mode: :initial, + jaque_to: nil, + deleted_figures: [], + figures: [ + %Figures{position: {1, 1}, id: 1, player: :black, type: :rook}, + %Figures{position: {1, 2}, id: 2, player: :black, type: :knight}, + %Figures{position: {1, 3}, id: 3, player: :black, type: :bishop}, + %Figures{position: {1, 4}, id: 4, player: :black, type: :queen}, + %Figures{position: {1, 5}, id: 5, player: :black, type: :king}, + %Figures{position: {1, 6}, id: 6, player: :black, type: :bishop}, + %Figures{position: {1, 7}, id: 7, player: :black, type: :knight}, + %Figures{position: {1, 8}, id: 8, player: :black, type: :rook}, + %Figures{position: {2, 1}, id: 9, player: :black, type: :pawn}, + %Figures{position: {2, 2}, id: 10, player: :black, type: :pawn}, + %Figures{position: {2, 3}, id: 11, player: :black, type: :pawn}, + %Figures{position: {2, 4}, id: 12, player: :black, type: :pawn}, + %Figures{position: {2, 5}, id: 13, player: :black, type: :pawn}, + %Figures{position: {2, 6}, id: 14, player: :black, type: :pawn}, + %Figures{position: {2, 7}, id: 15, player: :black, type: :pawn}, + %Figures{position: {2, 8}, id: 16, player: :black, type: :pawn}, + %Figures{position: {7, 1}, id: 17, player: :white, type: :pawn}, + %Figures{position: {7, 2}, id: 18, player: :white, type: :pawn}, + %Figures{position: {7, 3}, id: 19, player: :white, type: :pawn}, + %Figures{position: {7, 4}, id: 20, player: :white, type: :pawn}, + %Figures{position: {7, 5}, id: 21, player: :white, type: :pawn}, + %Figures{position: {7, 6}, id: 22, player: :white, type: :pawn}, + %Figures{position: {7, 7}, id: 23, player: :white, type: :pawn}, + %Figures{position: {7, 8}, id: 24, player: :white, type: :pawn}, + %Figures{position: {8, 1}, id: 25, player: :white, type: :rook}, + %Figures{position: {8, 2}, id: 26, player: :white, type: :knight}, + %Figures{position: {8, 3}, id: 27, player: :white, type: :bishop}, + %Figures{position: {8, 4}, id: 28, player: :white, type: :queen}, + %Figures{position: {8, 5}, id: 29, player: :white, type: :king}, + %Figures{position: {8, 6}, id: 30, player: :white, type: :bishop}, + %Figures{position: {8, 7}, id: 31, player: :white, type: :knight}, + %Figures{position: {8, 8}, id: 32, player: :white, type: :rook} + ] + } + end + + def join(pid, %{pid_player_white: nil} = state) do + {:ok, Map.put(state, :pid_player_white, pid), :white} + end + + def join(pid, %{pid_player_black: nil} = state) do + new_state = + state + |> Map.put(:pid_player_black, pid) + |> Map.put(:mode, :playing) + + Process.send(state.pid_player_white, state, []) + + {:ok, new_state, :black} + end + + def join(pid, %{spectators: spectators} = state) do + {:spectator, Map.put(state, :spectators, spectators ++ [pid])} + end + + def update_position(state, selected_figure, new_position) do + figures = + Enum.reject(state.figures, fn figure -> + selected_figure == figure || figure.position == new_position + end) + + deleted_figure = Enum.filter(state.figures, fn figure -> figure.position == new_position end) + + new_figure = Map.put(selected_figure, :position, new_position) + Map.put(state, :figures, figures ++ [new_figure]) + |> Map.update(:deleted_figures, nil, fn deleted_figures -> deleted_figures ++ deleted_figure end) + end + + def update_turn(%{turn: :white} = state) do + state + |> Map.put(:turn, :black) + |> check_jaque_to_black_king() + end + + def update_turn(%{turn: :black} = state) do + state + |> Map.put(:turn, :white) + |> check_jaque_to_white_king() + end + + def check_finish(state) do + white_king = + Enum.find(state.figures, fn figure -> figure.player == :white && figure.type == :king end) + + black_king = + Enum.find(state.figures, fn figure -> figure.player == :black && figure.type == :king end) + + cond do + is_nil(white_king) -> Map.put(state, :mode, :black_wins) + is_nil(black_king) -> Map.put(state, :mode, :white_wins) + true -> state + end + end + + defp check_jaque_to_white_king(state) do + white_king = Enum.find(state.figures, fn figure -> figure.player == :white && figure.type == :king end) + if white_king do + jaque_positions = + state.figures + |> Enum.reject(fn figure -> figure.player == :white end) + |> Enum.map(fn figure -> get_availables_moves(state, figure) end) + |> List.flatten() + + if white_king.position in jaque_positions do + Map.put(state, :jaque_to, :white) + else + Map.put(state, :jaque_to, nil) + end + else + state + end + end + + defp check_jaque_to_black_king(state) do + black_king = Enum.find(state.figures, fn figure -> figure.player == :black && figure.type == :king end) + + if black_king do + jaque_positions = + state.figures + |> Enum.reject(fn figure -> figure.player == :black end) + |> Enum.map(fn figure -> get_availables_moves(state, figure) end) + |> List.flatten() + + if black_king.position in jaque_positions do + Map.put(state, :jaque_to, :black) + else + Map.put(state, :jaque_to, nil) + end + else + state + end + end + + def get_availables_moves(_state, nil) do + [] + end + + def get_availables_moves(state, %Figures{type: :pawn} = figure) do + get_pawn_moves(state.figures, figure.position, figure.player) + |> reject_out_board_moves() + end + + def get_availables_moves(state, %Figures{type: :king} = figure) do + figure.position + |> get_king_moves() + |> reject_my_figures_position(state.figures, figure.player) + |> reject_out_board_moves() + |> reject_get_in_jaque_moves_king(state, figure.player) + end + + def get_availables_moves(state, %Figures{type: :knight} = figure) do + figure.position + |> get_knight_moves() + |> reject_my_figures_position(state.figures, figure.player) + |> reject_out_board_moves() + end + + def get_availables_moves(state, %Figures{type: :bishop} = figure) do + (get_up_left_moves(state.figures, figure.position, figure.player, []) ++ + get_up_rigth_moves(state.figures, figure.position, figure.player, []) ++ + get_down_left_moves(state.figures, figure.position, figure.player, []) ++ + get_down_rigth_moves(state.figures, figure.position, figure.player, [])) + |> reject_my_figures_position(state.figures, figure.player) + |> reject_out_board_moves() + end + + def get_availables_moves(state, %Figures{type: :rook} = figure) do + (get_up_moves(state.figures, figure.position, figure.player, []) ++ + get_down_moves(state.figures, figure.position, figure.player, []) ++ + get_rigth_moves(state.figures, figure.position, figure.player, []) ++ + get_left_moves(state.figures, figure.position, figure.player, [])) + |> reject_my_figures_position(state.figures, figure.player) + |> reject_out_board_moves() + end + + def get_availables_moves(state, %Figures{type: :queen} = figure) do + (get_up_moves(state.figures, figure.position, figure.player, []) ++ + get_down_moves(state.figures, figure.position, figure.player, []) ++ + get_rigth_moves(state.figures, figure.position, figure.player, []) ++ + get_left_moves(state.figures, figure.position, figure.player, []) ++ + get_up_left_moves(state.figures, figure.position, figure.player, []) ++ + get_up_rigth_moves(state.figures, figure.position, figure.player, []) ++ + get_down_left_moves(state.figures, figure.position, figure.player, []) ++ + get_down_rigth_moves(state.figures, figure.position, figure.player, [])) + |> reject_my_figures_position(state.figures, figure.player) + |> reject_out_board_moves() + end + + def get_availables_moves(_state, _figure) do + [] + end + + defp get_up_moves(_figures, {0, _column}, _player, acc) do + acc + end + + defp get_up_moves(figures, {row, column}, player, acc) do + if Enum.any?(figures, fn figure -> figure.position == {row - 1, column} end) do + acc ++ [{row - 1, column}] + else + get_up_moves(figures, {row - 1, column}, player, acc ++ [{row - 1, column}]) + end + end + + defp get_down_moves(_figures, {9, _column}, _player, acc) do + acc + end + + defp get_down_moves(figures, {row, column}, player, acc) do + if Enum.any?(figures, fn figure -> figure.position == {row + 1, column} end) do + acc ++ [{row + 1, column}] + else + get_down_moves(figures, {row + 1, column}, player, acc ++ [{row + 1, column}]) + end + end + + defp get_rigth_moves(_figures, {_row, 9}, _player, acc) do + acc + end + + defp get_rigth_moves(figures, {row, column}, player, acc) do + if Enum.any?(figures, fn figure -> figure.position == {row, column + 1} end) do + acc ++ [{row, column + 1}] + else + get_rigth_moves(figures, {row, column + 1}, player, acc ++ [{row, column + 1}]) + end + end + + defp get_left_moves(_figures, {_row, 0}, _player, acc) do + acc + end + + defp get_left_moves(figures, {row, column}, player, acc) do + if Enum.any?(figures, fn figure -> figure.position == {row, column - 1} end) do + acc ++ [{row, column - 1}] + else + get_left_moves(figures, {row, column - 1}, player, acc ++ [{row, column - 1}]) + end + end + + defp get_up_rigth_moves(_figures, {_row, 9}, _player, acc) do + acc + end + + defp get_up_rigth_moves(_figures, {0, _column}, _player, acc) do + acc + end + + defp get_up_rigth_moves(figures, {row, column}, player, acc) do + if Enum.any?(figures, fn figure -> figure.position == {row - 1, column + 1} end) do + acc ++ [{row - 1, column + 1}] + else + get_up_rigth_moves(figures, {row - 1, column + 1}, player, acc ++ [{row - 1, column + 1}]) + end + end + + defp get_up_left_moves(_figures, {_row, 0}, _player, acc) do + acc + end + + defp get_up_left_moves(_figures, {0, _column}, _player, acc) do + acc + end + + defp get_up_left_moves(figures, {row, column}, player, acc) do + if Enum.any?(figures, fn figure -> figure.position == {row - 1, column - 1} end) do + acc ++ [{row - 1, column - 1}] + else + get_up_left_moves(figures, {row - 1, column - 1}, player, acc ++ [{row - 1, column - 1}]) + end + end + + defp get_down_left_moves(_figures, {_row, 0}, _player, acc) do + acc + end + + defp get_down_left_moves(_figures, {9, _column}, _player, acc) do + acc + end + + defp get_down_left_moves(figures, {row, column}, player, acc) do + if Enum.any?(figures, fn figure -> figure.position == {row + 1, column - 1} end) do + acc ++ [{row + 1, column - 1}] + else + get_down_left_moves(figures, {row + 1, column - 1}, player, acc ++ [{row + 1, column - 1}]) + end + end + + defp get_down_rigth_moves(_figures, {_row, 9}, _player, acc) do + acc + end + + defp get_down_rigth_moves(_figures, {9, _column}, _player, acc) do + acc + end + + defp get_down_rigth_moves(figures, {row, column}, player, acc) do + if Enum.any?(figures, fn figure -> figure.position == {row + 1, column + 1} end) do + acc ++ [{row + 1, column + 1}] + else + get_down_rigth_moves(figures, {row + 1, column + 1}, player, acc ++ [{row + 1, column + 1}]) + end + end + + defp get_knight_moves({row, column}) do + [ + {row + 1, column + 2}, + {row + 2, column + 1}, + {row - 2, column + 1}, + {row - 1, column + 2}, + {row + 1, column - 2}, + {row + 2, column - 1}, + {row - 2, column - 1}, + {row - 1, column - 2} + ] + end + + defp get_king_moves({row, column}) do + [ + {row + 1, column}, + {row - 1, column}, + {row + 1, column + 1}, + {row - 1, column - 1}, + {row + 1, column - 1}, + {row - 1, column + 1}, + {row, column + 1}, + {row, column - 1} + ] + end + + defp get_pawn_moves(figures, {row, column}, :black) do + down_move = + if Enum.any?(figures, fn figure -> figure.position == {row + 1, column} end) do + [] + else + [{row + 1, column}] + end + + down_left_move = + if Enum.any?(figures, fn figure -> + figure.position == {row + 1, column - 1} && figure.player == :white + end) do + [{row + 1, column - 1}] + else + [] + end + + down_rigth_move = + if Enum.any?(figures, fn figure -> + figure.position == {row + 1, column + 1} && figure.player == :white + end) do + [{row + 1, column + 1}] + else + [] + end + + double_start_move = + if row == 2 do + [{row + 2, column}] + else + [] + end + + down_left_move ++ down_rigth_move ++ down_move ++ double_start_move + end + + defp get_pawn_moves(figures, {row, column}, :white) do + up_move = + if Enum.any?(figures, fn figure -> figure.position == {row - 1, column} end) do + [] + else + [{row - 1, column}] + end + + up_left_move = + if Enum.any?(figures, fn figure -> + figure.position == {row - 1, column - 1} && figure.player == :black + end) do + [{row - 1, column - 1}] + else + [] + end + + up_rigth_move = + if Enum.any?(figures, fn figure -> + figure.position == {row - 1, column + 1} && figure.player == :black + end) do + [{row - 1, column + 1}] + else + [] + end + + double_start_move = + if row == 7 do + [{row - 2, column}] + else + [] + end + + up_left_move ++ up_rigth_move ++ up_move ++ double_start_move + end + + defp reject_my_figures_position(moves, figures, player) do + Enum.reject(moves, fn move -> + Enum.any?(figures, fn figure -> figure.position == move && figure.player == player end) + end) + end + + defp reject_out_board_moves(moves) do + Enum.reject(moves, fn {row, column} -> row < 1 or row > 8 || (column < 1 or column > 8) end) + end + + defp reject_get_in_jaque_moves_king(moves, state, player) do + forbbiden_moves = + state.figures + |> Enum.reject(fn figure -> figure.player == player || figure.type == :king end) + |> Enum.map(fn figure -> get_availables_moves(state, figure) end) + |> List.flatten() + + Enum.reject(moves, fn move -> move in forbbiden_moves end) + end +end diff --git a/lib/chess/game_server.ex b/lib/chess/game_server.ex new file mode 100644 index 0000000..9b0f6d5 --- /dev/null +++ b/lib/chess/game_server.ex @@ -0,0 +1,76 @@ +defmodule Chess.GameServer do + use GenServer + alias Chess.Game + + # CLIENT FUNCTIONS + def start_link(game_name) do + GenServer.start_link(__MODULE__, nil, name: game_name) + end + + def join(game_name) do + GenServer.call(game_name, :join) + end + + def move(game_name, figure, new_position) do + GenServer.call(game_name, {:move, figure, new_position}) + end + + def get_availables_moves(game_name, nil) do + [] + end + + def get_availables_moves(game_name, selected_figure) do + GenServer.call(game_name, {:get_availables_moves, selected_figure}) + end + + # SERVER FUNCTIONS + + def init(_) do + {:ok, Game.new()} + end + + def handle_call(:join, from, state) do + {pid, _} = from + + case Game.join(pid, state) do + {:ok, new_state, player} -> + {:reply, {:ok, new_state, player}, new_state} + + {:spectator, new_state} -> + send_state_to_other_player(new_state, :white) + send_state_to_other_player(new_state, :black) + send_state_to_spectators(new_state) + {:reply, {:spectator, new_state}, new_state} + end + end + + def handle_call({:move, figure, new_position}, _from, state) do + new_state = + Game.update_position(state, figure, new_position) + |> Game.update_turn() + |> Game.check_finish() + + send_state_to_other_player(new_state, figure.player) + send_state_to_spectators(new_state) + {:reply, {:ok, new_state}, new_state} + end + + def handle_call({:get_availables_moves, selected_figure}, _from, state) do + moves = Game.get_availables_moves(state, selected_figure) + {:reply, moves, state} + end + + # PRIVATE FUNCIONS + + def send_state_to_other_player(state, :white) do + Process.send(state.pid_player_black, state, []) + end + + def send_state_to_other_player(state, :black) do + Process.send(state.pid_player_white, state, []) + end + + def send_state_to_spectators(state) do + Enum.each(state.spectators, fn spectator_pid -> Process.send(spectator_pid, state, []) end) + end +end diff --git a/lib/chess_web/live/player_game_live.ex b/lib/chess_web/live/player_game_live.ex new file mode 100644 index 0000000..5264785 --- /dev/null +++ b/lib/chess_web/live/player_game_live.ex @@ -0,0 +1,137 @@ +defmodule ChessWeb.PlayerGameLive do + use ChessWeb, :live_view + + alias Chess.Game + alias Chess.GameServer + + def mount(%{"id" => id}, _session, socket) do + game_name = String.to_atom(id) + socket = assign(socket, :game_name, game_name) + GameServer.start_link(socket.assigns.game_name) + + socket = + socket + |> assign(game_state: Game.new()) + |> assign(selected_figure: nil) + |> assign(selected_cell: {0, 0}) + |> assign(available_moves: []) + |> assign(allow: false) + + if connected?(socket) do + case GameServer.join(socket.assigns.game_name) do + {:ok, game_state, player} -> + socket = + socket + |> assign(game_state: game_state) + |> assign(player: player) + |> assign(allow: true) + + {:ok, socket} + + {:spectator, game_state} -> + {:ok, assign(socket, player: :view_mode, allow: true, game_state: game_state)} + end + else + {:ok, socket} + end + end + + def handle_event( + "select", + _params, + %{assigns: %{player: :white, game_state: %{turn: :black}}} = socket + ) do + {:noreply, socket} + end + + def handle_event( + "select", + _params, + %{assigns: %{player: :black, game_state: %{turn: :white}}} = socket + ) do + {:noreply, socket} + end + + def handle_event("select", _params, %{assigns: %{player: :view_mode}} = socket) do + {:noreply, socket} + end + + def handle_event( + "select", + %{"column" => column, "row" => row}, + %{assigns: %{selected_cell: {0, 0}}} = socket + ) do + row = String.to_integer(row) + column = String.to_integer(column) + + selected_figure = get_figure(row, column, socket.assigns.game_state) |> List.first() + + available_moves = + GameServer.get_availables_moves(socket.assigns.game_name, selected_figure) + + cond do + is_nil(selected_figure) -> + {:noreply, socket} + + selected_figure.player == socket.assigns.player -> + {:noreply, + socket + |> assign(selected_cell: {row, column}) + |> assign(available_moves: available_moves) + |> assign(selected_figure: selected_figure)} + + true -> + {:noreply, socket} + end + end + + def handle_event("select", %{"column" => column, "row" => row}, socket) do + row = String.to_integer(row) + column = String.to_integer(column) + + if {row, column} in socket.assigns.available_moves do + {:ok, new_state} = + GameServer.move(socket.assigns.game_name, socket.assigns.selected_figure, {row, column}) + + {:noreply, + assign(socket, + selected_cell: {0, 0}, + selected_figure: nil, + game_state: new_state, + available_moves: [] + )} + else + {:noreply, + assign(socket, + selected_cell: {0, 0}, + selected_figure: nil, + available_moves: [] + )} + end + end + + def handle_info(new_state, socket) do + {:noreply, assign(socket, game_state: new_state)} + end + + def get_figure(row, column, game_state) do + figure = + Enum.find(game_state.figures, fn %Chess.Figures{position: {figure_row, figure_column}} -> + {figure_row, figure_column} == {row, column} + end) + + if figure do + [figure] + else + [] + end + end + + def square_color(row, column) do + if rem(row + column, 2) == 0 do + "white" + else + "black" + end + end +end diff --git a/lib/chess_web/live/player_game_live.html.heex b/lib/chess_web/live/player_game_live.html.heex new file mode 100644 index 0000000..88d1a2c --- /dev/null +++ b/lib/chess_web/live/player_game_live.html.heex @@ -0,0 +1,50 @@ +<%= if @allow do %> +
+
+ <%= for row <- 1..8 do %> + <%= for column <- 1..8 do %> +
+ <%= if {row, column} in @available_moves do %> +
X
+ <% end %> + <%= for figure <- get_figure(row, column, @game_state) do %> +
+ <% end %> +
+ <% end %> + <% end %> +
+
+ <%= if @player == :white do %> +

You move White

+ <% end %> + <%= if @player == :black do %> +

You move Black

+ <% end %> + <%= if @player == :view_mode do %> +

You are an spectator

+ <% end %> + <%= if @game_state.mode == :black_wins do %> +

Blacks Wins

+ <% end %> + <%= if @game_state.mode == :white_wins do %> +

White Wins

+ <% end %> +<% else %> +
The game is full
+<% end %> +

<%= Enum.count(@game_state.spectators) %> Live viewers, Turn: <%= @game_state.turn %> Moves + <%= if @game_state.jaque_to do %> + , <%= @game_state.jaque_to %> king is in Jaque + <% end %> +

+
+

Deleted Figures

+ <%= for figure <- @game_state.deleted_figures do %> +
+ <% end %> +
diff --git a/lib/chess_web/router.ex b/lib/chess_web/router.ex index 6545232..f46675b 100644 --- a/lib/chess_web/router.ex +++ b/lib/chess_web/router.ex @@ -18,6 +18,7 @@ defmodule ChessWeb.Router do pipe_through :browser get "/", PageController, :index + live "/:id", PlayerGameLive end # Other scopes may use custom stacks. diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..e827ca2 --- /dev/null +++ b/mix.lock @@ -0,0 +1,25 @@ +%{ + "castore": {:hex, :castore, "0.1.18", "deb5b9ab02400561b6f5708f3e7660fc35ca2d51bfc6a940d2f513f89c2975fc", [:mix], [], "hexpm", "61bbaf6452b782ef80b33cdb45701afbcf0a918a45ebe7e73f1130d661e66a06"}, + "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, + "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, + "esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "floki": {:hex, :floki, "0.33.1", "f20f1eb471e726342b45ccb68edb9486729e7df94da403936ea94a794f072781", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "461035fd125f13fdf30f243c85a0b1e50afbec876cbf1ceefe6fddd2e6d712c6"}, + "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, + "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, + "phoenix": {:hex, :phoenix, "1.6.14", "57678366dc1d5bad49832a0fc7f12c2830c10d3eacfad681bfe9602cd4445f04", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d48c0da00b3d4cd1aad6055387917491af9f6e1f1e96cedf6c6b7998df9dba26"}, + "phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.12", "74f4c0ad02d7deac2d04f50b52827a5efdc5c6e7fac5cede145f5f0e4183aedc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "af6dd5e0aac16ff43571f527a8e0616d62cb80b10eb87aac82170243e50d99c8"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, + "phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"}, + "plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, +}