diff --git a/lib/chess/application.ex b/lib/chess/application.ex index f5921a7..41f323c 100644 --- a/lib/chess/application.ex +++ b/lib/chess/application.ex @@ -13,6 +13,7 @@ defmodule Chess.Application do # Start the PubSub system {Phoenix.PubSub, name: Chess.PubSub}, # Start the Endpoint (http/https) + Chess.State, ChessWeb.Endpoint # Start a worker by calling: Chess.Worker.start_link(arg) # {Chess.Worker, arg} diff --git a/lib/chess/board.ex b/lib/chess/board.ex new file mode 100644 index 0000000..d40e6f9 --- /dev/null +++ b/lib/chess/board.ex @@ -0,0 +1,98 @@ +defmodule Chess.Board do + defstruct a1: "figure white rook", + b1: "figure white knight", + c1: "figure white bishop", + d1: "figure white queen", + e1: "figure white king", + f1: "figure white bishop", + g1: "figure white knight", + h1: "figure white rook", + a2: "figure white pawn", + b2: "figure white pawn", + c2: "figure white pawn", + d2: "figure white pawn", + e2: "figure white pawn", + f2: "figure white pawn", + g2: "figure white pawn", + h2: "figure white pawn", + a3: nil, + b3: nil, + c3: nil, + d3: nil, + e3: nil, + f3: nil, + g3: nil, + h3: nil, + a4: nil, + b4: nil, + c4: nil, + d4: nil, + e4: nil, + f4: nil, + g4: nil, + h4: nil, + a5: nil, + b5: nil, + c5: nil, + d5: nil, + e5: nil, + f5: nil, + g5: nil, + h5: nil, + a6: nil, + b6: nil, + c6: nil, + d6: nil, + e6: nil, + f6: nil, + g6: nil, + h6: nil, + a7: "figure black pawn", + b7: "figure black pawn", + c7: "figure black pawn", + d7: "figure black pawn", + e7: "figure black pawn", + f7: "figure black pawn", + g7: "figure black pawn", + h7: "figure black pawn", + a8: "figure black rook", + b8: "figure black knight", + c8: "figure black bishop", + d8: "figure black queen", + e8: "figure black king", + f8: "figure black bishop", + g8: "figure black knight", + h8: "figure black rook" + + @num_conversion %{ + "a" => 1, + "b" => 2, + "c" => 3, + "d" => 4, + "e" => 5, + "f" => 6, + "g" => 7, + "h" => 8 + } + + @letter_conversion %{ + 1 => "a", + 2 => "b", + 3 => "c", + 4 => "d", + 5 => "e", + 6 => "f", + 7 => "g", + 8 => "h" + } + + def from_square_to_position(square) do + positions = String.graphemes(square) + + {@num_conversion[List.first(positions)], positions |> List.last() |> String.to_integer()} + end + + def from_position_to_square(x, y) do + Enum.join([@letter_conversion[x], y], "") + end +end diff --git a/lib/chess/move.ex b/lib/chess/move.ex new file mode 100644 index 0000000..d84ee1d --- /dev/null +++ b/lib/chess/move.ex @@ -0,0 +1,256 @@ +defmodule Chess.Move do + alias Chess.Board + alias Chess.State + + @kings ["figure white king", "figure black king"] + + def validate_move("figure white bishop", from_square, to_square, board, turn, id) do + {from_x, from_y} = Board.from_square_to_position(from_square) + + {to_x, to_y} = Board.from_square_to_position(to_square) + + bishop_posibilities(from_x, from_y, to_x, to_y, board, turn, id) + end + + def validate_move("figure black bishop", from_square, to_square, board, turn, id) do + {from_x, from_y} = Board.from_square_to_position(from_square) + + {to_x, to_y} = Board.from_square_to_position(to_square) + bishop_posibilities(from_x, from_y, to_x, to_y, board, turn, id) + end + + def validate_move("figure white rook", from_square, to_square, board, turn, id) do + {from_x, from_y} = Board.from_square_to_position(from_square) + + {to_x, to_y} = Board.from_square_to_position(to_square) + + rook_posibilities(from_x, from_y, to_x, to_y, board, turn, id) + end + + def validate_move("figure black rook", from_square, to_square, board, turn, id) do + {from_x, from_y} = Board.from_square_to_position(from_square) + + {to_x, to_y} = Board.from_square_to_position(to_square) + rook_posibilities(from_x, from_y, to_x, to_y, board, turn, id) + end + + def validate_move("figure white queen", from_square, to_square, board, turn, id) do + {from_x, from_y} = Board.from_square_to_position(from_square) + + {to_x, to_y} = Board.from_square_to_position(to_square) + + queen_posibilities(from_x, from_y, to_x, to_y, board, turn, id) + end + + def validate_move("figure black queen", from_square, to_square, board, turn, id) do + {from_x, from_y} = Board.from_square_to_position(from_square) + + {to_x, to_y} = Board.from_square_to_position(to_square) + queen_posibilities(from_x, from_y, to_x, to_y, board, turn, id) + end + + def validate_move("figure black pawn", from_square, to_square, board, turn, id) do + {from_x, from_y} = Board.from_square_to_position(from_square) + + {to_x, to_y} = Board.from_square_to_position(to_square) + + forward_pawn_posibility(from_x, from_y, to_x, to_y, 0, -1, board, turn, id) ++ + forward_pawn_posibility(from_x, from_y, to_x, to_y, 0, -2, board, turn, id) + end + + def validate_move("figure white pawn", from_square, to_square, board, turn, id) do + {from_x, from_y} = Board.from_square_to_position(from_square) + + {to_x, to_y} = Board.from_square_to_position(to_square) + + forward_pawn_posibility(from_x, from_y, to_x, to_y, 0, 1, board, turn, id) ++ + forward_pawn_posibility(from_x, from_y, to_x, to_y, 0, 2, board, turn, id) + end + + def validate_move("figure white knight", from_square, to_square, board, turn, id) do + {from_x, from_y} = Board.from_square_to_position(from_square) + + {to_x, to_y} = Board.from_square_to_position(to_square) + + forward_knight_posibility(from_x, from_y, to_x, to_y, 2, 1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, -2, 1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, -2, -1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, 2, -1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, -1, 2, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, 1, 2, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, -1, -2, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, 1, -2, board, turn, id) + end + + def validate_move("figure black knight", from_square, to_square, board, turn, id) do + {from_x, from_y} = Board.from_square_to_position(from_square) + + {to_x, to_y} = Board.from_square_to_position(to_square) + + forward_knight_posibility(from_x, from_y, to_x, to_y, 2, 1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, -2, 1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, -2, -1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, 2, -1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, -1, 2, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, 1, 2, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, -1, -2, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, 1, -2, board, turn, id) + end + + def validate_move("figure black king", from_square, to_square, board, turn, id) do + {from_x, from_y} = Board.from_square_to_position(from_square) + + {to_x, to_y} = Board.from_square_to_position(to_square) + + forward_knight_posibility(from_x, from_y, to_x, to_y, 1, 1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, -1, 1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, -1, -1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, 1, -1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, 1, 0, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, -1, 0, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, 0, -1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, 0, 1, board, turn, id) + end + + def validate_move("figure white king", from_square, to_square, board, turn, id) do + {from_x, from_y} = Board.from_square_to_position(from_square) + + {to_x, to_y} = Board.from_square_to_position(to_square) + + forward_knight_posibility(from_x, from_y, to_x, to_y, 1, 1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, -1, 1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, -1, -1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, 1, -1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, 1, 0, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, -1, 0, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, 0, -1, board, turn, id) ++ + forward_knight_posibility(from_x, from_y, to_x, to_y, 0, 1, board, turn, id) + end + + def bishop_posibilities(from_x, from_y, to_x, to_y, board, turn, id) do + posibilities(from_x, from_y, to_x, to_y, 1, -1, [], board, turn, id) ++ + posibilities(from_x, from_y, to_x, to_y, 1, 1, [], board, turn, id) ++ + posibilities(from_x, from_y, to_x, to_y, -1, -1, [], board, turn, id) ++ + posibilities(from_x, from_y, to_x, to_y, -1, 1, [], board, turn, id) + end + + def queen_posibilities(from_x, from_y, to_x, to_y, board, turn, id) do + posibilities(from_x, from_y, to_x, to_y, 1, -1, [], board, turn, id) ++ + posibilities(from_x, from_y, to_x, to_y, 1, 1, [], board, turn, id) ++ + posibilities(from_x, from_y, to_x, to_y, -1, -1, [], board, turn, id) ++ + posibilities(from_x, from_y, to_x, to_y, -1, 1, [], board, turn, id) ++ + posibilities(from_x, from_y, to_x, to_y, 1, 0, [], board, turn, id) ++ + posibilities(from_x, from_y, to_x, to_y, 0, 1, [], board, turn, id) ++ + posibilities(from_x, from_y, to_x, to_y, -1, 0, [], board, turn, id) ++ + posibilities(from_x, from_y, to_x, to_y, 0, -1, [], board, turn, id) + end + + def rook_posibilities(from_x, from_y, to_x, to_y, board, turn, id) do + posibilities(from_x, from_y, to_x, to_y, 1, 0, [], board, turn, id) ++ + posibilities(from_x, from_y, to_x, to_y, 0, 1, [], board, turn, id) ++ + posibilities(from_x, from_y, to_x, to_y, -1, 0, [], board, turn, id) ++ + posibilities(from_x, from_y, to_x, to_y, 0, -1, [], board, turn, id) + end + + def posibilities(from_x, from_y, to_x, to_y, x, y, acc, board, turn, id) do + next_x = from_x + x + next_y = from_y + y + + if end_of_road?(next_x, next_y, to_x, to_y, board, turn, id) do + acc + else + acc = acc ++ [{next_x, next_y}] + posibilities(next_x, next_y, to_x, to_y, x, y, acc, board, turn, id) + end + end + + def forward_pawn_posibility(from_x, 2, to_x, to_y, x, y, board, turn, id) + when abs(y) == 2 do + next_x = from_x + x + next_y = 2 + y + + if end_of_road?(next_x, next_y, to_x, to_y, board, turn, id) do + [] + else + [{next_x, next_y}] + end + end + + def forward_pawn_posibility(from_x, 7, to_x, to_y, x, y, board, turn, id) + when abs(y) == 2 do + next_x = from_x + x + next_y = 7 + y + + if end_of_road?(next_x, next_y, to_x, to_y, board, turn, id) do + [] + else + [{next_x, next_y}] + end + end + + def forward_pawn_posibility(from_x, from_y, to_x, to_y, x, 1, board, turn, id) do + next_x = from_x + x + next_y = from_y + 1 + + if end_of_road?(next_x, next_y, to_x, to_y, board, turn, id) do + [] + else + [{next_x, next_y}] + end + end + + def forward_pawn_posibility(from_x, from_y, to_x, to_y, x, -1, board, turn, id) do + next_x = from_x + x + next_y = from_y - 1 + + if end_of_road?(next_x, next_y, to_x, to_y, board, turn, id) do + [] + else + [{next_x, next_y}] + end + end + + def forward_pawn_posibility(_, _, _, _, _, _, _, _, _), do: [] + + def forward_knight_posibility(from_x, from_y, to_x, to_y, x, y, board, turn, id) do + next_x = from_x + x + next_y = from_y + y + + if end_of_road?(next_x, next_y, to_x, to_y, board, turn, id) do + [] + else + [{next_x, next_y}] + end + end + + def end_of_road?(-1, _, _, _, _, _, _), do: true + def end_of_road?(_, -1, _, _, _, _, _), do: true + def end_of_road?(0, _, _, _, _, _, _), do: true + def end_of_road?(_, 0, _, _, _, _, _), do: true + def end_of_road?(9, _, _, _, _, _, _), do: true + def end_of_road?(_, 9, _, _, _, _, _), do: true + def end_of_road?(10, _, _, _, _, _, _), do: true + def end_of_road?(_, 10, _, _, _, _, _), do: true + + def end_of_road?(next_x, next_y, _, _, board, turn, id) do + position = next_x |> Board.from_position_to_square(next_y) |> String.to_existing_atom() + + player = + Map.get(board, position) + |> then(&if not is_nil(&1), do: String.split(&1, " ") |> Enum.at(1), else: nil) + + eaten = eat_or_stop(Map.get(board, position), turn, player) + + if not eaten and Enum.member?(@kings, Map.get(board, position)) do + player = Map.get(board, position) |> String.split(" ") |> Enum.at(1) + State.won(%{won: player}, id) + eaten + else + eaten + end + end + + defp eat_or_stop(figure, _, _) when is_nil(figure), do: false + defp eat_or_stop(_figure, turn, player) when player != turn, do: false + defp eat_or_stop(_figure, _turn, _player), do: true +end diff --git a/lib/chess/state.ex b/lib/chess/state.ex new file mode 100644 index 0000000..c78ae0b --- /dev/null +++ b/lib/chess/state.ex @@ -0,0 +1,30 @@ +defmodule Chess.State do + use Agent + + alias Phoenix.PubSub + + def start_link(_) do + Agent.start_link(fn -> %{} end, name: __MODULE__) + end + + def value do + Agent.get(__MODULE__, & &1) + end + + def won(%{won: won}, topic) do + state = Map.get(value(), topic) + state = Map.put(state, :won, won) + Agent.update(__MODULE__, &Map.put(&1, topic, state)) + make_change(state, topic) + end + + def set(state, topic) do + Agent.update(__MODULE__, &Map.put(&1, topic, state)) + make_change(state, topic) + end + + defp make_change(state, topic) do + PubSub.broadcast(Chess.PubSub, topic, {:state, state}) + {:reply, state} + end +end diff --git a/lib/chess_web/live/chess_live.ex b/lib/chess_web/live/chess_live.ex new file mode 100644 index 0000000..656c750 --- /dev/null +++ b/lib/chess_web/live/chess_live.ex @@ -0,0 +1,134 @@ +defmodule ChessWeb.ChessLive do + use ChessWeb, :live_view + alias ChessWeb.Endpoint + alias Chess.Board + alias Chess.State + alias Chess.Move + + @player_order %{"white" => "black", "black" => "white"} + def mount(params, _session, socket) do + if connected?(socket) do + Endpoint.subscribe(params["id"]) + end + + socket = + socket + |> assign(board: %Board{}) + |> assign(selected_square: nil) + |> assign(selected_figure: nil) + |> assign(turn: "white") + |> assign(id: params["id"]) + |> assign(won: nil) + |> assign_new(:white, fn -> socket.id end) + |> assign_new(:black, fn -> nil end) + + {:ok, socket} + end + + def handle_params(params, _uri, socket) do + assigns = Map.get(State.value(), params["id"]) + + socket = + if is_nil(assigns) do + socket + else + socket + |> assign(board: assigns.board) + |> assign(selected_square: assigns.selected_square) + |> assign(selected_figure: assigns.selected_figure) + |> assign(turn: assigns.turn) + |> assign(white: assigns.white) + |> then( + &if &1.assigns.white != &1.id and not is_nil(&1.assigns.white), + do: assign(&1, black: &1.id) + ) + end + + State.set(socket.assigns, params["id"]) + + {:noreply, socket} + end + + def handle_info({:state, state}, socket) do + {:noreply, + socket + |> assign(board: state.board) + |> assign(white: state.white) + |> assign(black: state.black) + |> assign(turn: state.turn) + |> assign(won: state.won)} + end + + def handle_event( + "move_select", + %{ + "square" => square, + "selected_figure" => selected_figure, + "selected_square" => selected_square + }, + socket + ) do + square_atom = String.to_existing_atom(square) + selected_square_atom = String.to_existing_atom(selected_square) + player = String.split(selected_figure, " ") |> Enum.at(1) + posible_places = get_posible_places(selected_figure, selected_square, square, socket) + + socket = + if Enum.member?(posible_places, square_atom) do + socket + |> update(:board, &Map.put(&1, square_atom, selected_figure)) + |> update(:board, &Map.put(&1, selected_square_atom, nil)) + |> assign(turn: @player_order[player]) + |> clear_flash() + else + socket + |> put_flash(:error, "Invalid movement") + end + + socket = + socket + |> assign(selected_square: nil) + |> assign(selected_figure: nil) + + State.set(socket.assigns, socket.assigns.id) + + {:noreply, socket} + end + + def handle_event( + "move_select", + %{"square" => square, "figure" => figure}, + socket + ) do + player = String.split(figure, " ") |> Enum.at(1) + user = Map.get(socket.assigns, String.to_atom(player)) + + socket = + if player != socket.assigns.turn or socket.id != user do + put_flash(socket, :error, "Not your turn") + else + socket + |> assign(selected_square: square) + |> assign(selected_figure: figure) + |> clear_flash() + end + + {:noreply, socket} + end + + def handle_event("move_select", %{"square" => _}, socket) do + {:noreply, socket} + end + + defp get_posible_places(selected_figure, selected_square, square, socket) do + selected_figure + |> Move.validate_move( + selected_square, + square, + socket.assigns.board, + socket.assigns.turn, + socket.assigns.id + ) + |> Enum.map(fn {x, y} -> Board.from_position_to_square(x, y) |> String.to_existing_atom() end) + end +end diff --git a/lib/chess_web/live/chess_live.html.heex b/lib/chess_web/live/chess_live.html.heex new file mode 100644 index 0000000..8cf047d --- /dev/null +++ b/lib/chess_web/live/chess_live.html.heex @@ -0,0 +1,198 @@ +
+
White player <%= @white %>
Black player <%= @black %>
+
<%= if not is_nil(@won), do: "Gana #{@won}" %>
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 7
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 8 a
+
+
+ b
+
+
+ c
+
+
+ d
+
+
+ e
+
+
+ f
+
+
+ g
+
+
+ h
+
+
+
diff --git a/lib/chess_web/presence.ex b/lib/chess_web/presence.ex new file mode 100644 index 0000000..ce16c4f --- /dev/null +++ b/lib/chess_web/presence.ex @@ -0,0 +1,11 @@ +defmodule ChessWeb.Presence do + use Phoenix.Presence, + otp_app: :chess, + pubsub_server: Chess.PubSub + + alias ChessWeb.Presence + + def trak_game(pid, topic, state) do + Presence.track(pid, topic, "", state) + end +end diff --git a/lib/chess_web/router.ex b/lib/chess_web/router.ex index 6545232..2733839 100644 --- a/lib/chess_web/router.ex +++ b/lib/chess_web/router.ex @@ -16,7 +16,7 @@ defmodule ChessWeb.Router do scope "/", ChessWeb do pipe_through :browser - + live "/game/:id", ChessLive get "/", PageController, :index end 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"}, +}