diff --git a/assets/css/app.css b/assets/css/app.css
index 2c2cd8b..20e0f7a 100644
--- a/assets/css/app.css
+++ b/assets/css/app.css
@@ -150,6 +150,10 @@
background-color: #d18b47;
}
+.square.selected {
+ border: 3px solid #0e0e0d;
+}
+
.figure {
position: relative;
z-index: 0;
@@ -193,3 +197,21 @@
.figure.rook {
background-position-x: -182.5px;
}
+
+.final_game {
+ font-size: 24px;
+ padding: 73px;
+ border-radius: 5px;
+ position: absolute;
+ top: 0px;
+ margin-top: 27%;
+ margin-left: 27%;
+}
+.final_game.white{
+ background-color: #f6cd9e;
+ border: 1px solid #d18b47;
+}
+.final_game.black{
+ background-color: #d18b47;
+ border: 1px solid #f6cd9e;
+}
\ No newline at end of file
diff --git a/lib/chess/movements.ex b/lib/chess/movements.ex
new file mode 100644
index 0000000..9442640
--- /dev/null
+++ b/lib/chess/movements.ex
@@ -0,0 +1,185 @@
+defmodule Chess.Movements do
+ @board_length 8
+
+ defguard is_inside_board(x, y) when x < @board_length and y < @board_length
+
+ defguard is_diagonal_movement(actual_x, actual_y, x, y)
+ when abs(actual_x - x) == abs(actual_y - y)
+
+ def movement(cells, piece, actual_position, {x, y} = new_position) when is_inside_board(x, y),
+ do: piece_movement(cells, piece, actual_position, new_position)
+
+ def movement(_, _, _, _), do: :error
+
+ # One position movement in any direction
+ defp piece_movement(cells, %{type: :king, color: color}, {actual_x, y}, {x, y})
+ when abs(actual_x - x) == 1 and actual_x < x,
+ do:
+ check_next_position(
+ cells,
+ color,
+ {actual_x + 1, y},
+ {1, 0},
+ get_movements_number(actual_x, x)
+ )
+
+ defp piece_movement(cells, %{type: :king, color: color}, {actual_x, y}, {x, y})
+ when abs(actual_x - x) == 1 and actual_x > x,
+ do:
+ check_next_position(
+ cells,
+ color,
+ {actual_x - 1, y},
+ {-1, 0},
+ get_movements_number(actual_x, x)
+ )
+
+ defp piece_movement(cells, %{type: :king, color: color}, {x, actual_y}, {x, y})
+ when abs(actual_y - y) == 1 and actual_y < y,
+ do:
+ check_next_position(
+ cells,
+ color,
+ {x, actual_y + 1},
+ {0, 1},
+ get_movements_number(actual_y, y)
+ )
+
+ defp piece_movement(cells, %{type: :king, color: color}, {x, actual_y}, {x, y})
+ when abs(actual_y - y) == 1 and actual_y > y,
+ do:
+ check_next_position(
+ cells,
+ color,
+ {x, actual_y - 1},
+ {0, -1},
+ get_movements_number(actual_y, y)
+ )
+
+ defp piece_movement(_, %{type: :king}, _, _), do: :error
+
+ # Lineal movement for queen
+ defp piece_movement(cells, %{type: :queen, color: color}, {actual_x, y}, {x, y})
+ when actual_x < x,
+ do:
+ check_next_position(
+ cells,
+ color,
+ {actual_x + 1, y},
+ {1, 0},
+ get_movements_number(actual_x, x)
+ )
+
+ defp piece_movement(cells, %{type: :queen, color: color}, {actual_x, y}, {x, y})
+ when actual_x > x,
+ do:
+ check_next_position(
+ cells,
+ color,
+ {actual_x - 1, y},
+ {-1, 0},
+ get_movements_number(actual_x, x)
+ )
+
+ defp piece_movement(cells, %{type: :queen, color: color}, {x, actual_y}, {x, y})
+ when actual_y < y,
+ do:
+ check_next_position(
+ cells,
+ color,
+ {x, actual_y + 1},
+ {0, 1},
+ get_movements_number(actual_y, y)
+ )
+
+ defp piece_movement(cells, %{type: :queen, color: color}, {x, actual_y}, {x, y})
+ when actual_y > y,
+ do:
+ check_next_position(
+ cells,
+ color,
+ {x, actual_y - 1},
+ {0, -1},
+ get_movements_number(actual_y, y)
+ )
+
+ # Diagonal movement for queen
+ defp piece_movement(cells, %{type: :queen, color: color}, {actual_x, actual_y}, {x, y})
+ when is_diagonal_movement(actual_x, actual_y, x, y) and actual_x < x and actual_y > y,
+ do:
+ check_next_position(
+ cells,
+ color,
+ {actual_x + 1, actual_y - 1},
+ {1, -1},
+ get_movements_number(actual_x, x)
+ )
+
+ defp piece_movement(cells, %{type: :queen, color: color}, {actual_x, actual_y}, {x, y})
+ when is_diagonal_movement(actual_x, actual_y, x, y) and actual_x > x and actual_y < y,
+ do:
+ check_next_position(
+ cells,
+ color,
+ {actual_x - 1, actual_y + 1},
+ {-1, 1},
+ get_movements_number(actual_x, x)
+ )
+
+ defp piece_movement(cells, %{type: :queen, color: color}, {actual_x, actual_y}, {x, y})
+ when is_diagonal_movement(actual_x, actual_y, x, y) and actual_x < x and actual_y < y,
+ do:
+ check_next_position(
+ cells,
+ color,
+ {actual_x - 1, actual_y - 1},
+ {-1, -1},
+ get_movements_number(actual_x, x)
+ )
+
+ defp piece_movement(cells, %{type: :queen, color: color}, {actual_x, actual_y}, {x, y})
+ when is_diagonal_movement(actual_x, actual_y, x, y) and actual_x > x and actual_y > y,
+ do:
+ check_next_position(
+ cells,
+ color,
+ {actual_x + 1, actual_y + 1},
+ {1, 1},
+ get_movements_number(actual_x, x)
+ )
+
+ defp piece_movement(_, %{type: :queen}, _, _), do: :error
+
+ defp piece_movement(_, _, _, _), do: {:ok, nil}
+
+ defp check_next_position(cells, color, {x, y} = position, {increment_x, increment_y}, n)
+ when n > 0 do
+ case position_availability(color, Map.get(cells, position)) do
+ {:ok, nil} ->
+ check_next_position(
+ cells,
+ color,
+ {x + increment_x, y + increment_y},
+ {increment_x, increment_y},
+ n - 1
+ )
+
+ _ ->
+ :error
+ end
+ end
+
+ defp check_next_position(cells, color, position, _, 0),
+ do: position_availability(color, Map.get(cells, position))
+
+ defp position_availability(_, nil), do: {:ok, nil}
+
+ defp position_availability(color, %{color: occupied_color}) when occupied_color == color,
+ do: :error
+
+ defp position_availability(color, %{color: occupied_color} = piece)
+ when occupied_color != color,
+ do: {:ok, piece}
+
+ defp get_movements_number(initial, final), do: abs(initial - final) - 1
+end
diff --git a/lib/chess/structs.ex b/lib/chess/structs.ex
new file mode 100644
index 0000000..34fd362
--- /dev/null
+++ b/lib/chess/structs.ex
@@ -0,0 +1,54 @@
+defmodule Chess.Cell do
+ defstruct [:x, :y]
+end
+
+defmodule Chess.Piece do
+ defstruct [:type, :color]
+end
+
+defmodule Chess.Board do
+ defstruct cells: %{
+ {0, 0} => %Chess.Piece{type: :rook, color: :black},
+ {0, 1} => %Chess.Piece{type: :knight, color: :black},
+ {0, 2} => %Chess.Piece{type: :bishop, color: :black},
+ {0, 3} => %Chess.Piece{type: :queen, color: :black},
+ {0, 4} => %Chess.Piece{type: :king, color: :black},
+ {0, 5} => %Chess.Piece{type: :bishop, color: :black},
+ {0, 6} => %Chess.Piece{type: :knight, color: :black},
+ {0, 7} => %Chess.Piece{type: :rook, color: :black},
+ {1, 0} => %Chess.Piece{type: :pawn, color: :black},
+ {1, 1} => %Chess.Piece{type: :pawn, color: :black},
+ {1, 2} => %Chess.Piece{type: :pawn, color: :black},
+ {1, 3} => %Chess.Piece{type: :pawn, color: :black},
+ {1, 4} => %Chess.Piece{type: :pawn, color: :black},
+ {1, 5} => %Chess.Piece{type: :pawn, color: :black},
+ {1, 6} => %Chess.Piece{type: :pawn, color: :black},
+ {1, 7} => %Chess.Piece{type: :pawn, color: :black},
+ {6, 0} => %Chess.Piece{type: :pawn, color: :white},
+ {6, 1} => %Chess.Piece{type: :pawn, color: :white},
+ {6, 2} => %Chess.Piece{type: :pawn, color: :white},
+ {6, 3} => %Chess.Piece{type: :pawn, color: :white},
+ {6, 4} => %Chess.Piece{type: :pawn, color: :white},
+ {6, 5} => %Chess.Piece{type: :pawn, color: :white},
+ {6, 6} => %Chess.Piece{type: :pawn, color: :white},
+ {6, 7} => %Chess.Piece{type: :pawn, color: :white},
+ {7, 0} => %Chess.Piece{type: :rook, color: :white},
+ {7, 1} => %Chess.Piece{type: :knight, color: :white},
+ {7, 2} => %Chess.Piece{type: :bishop, color: :white},
+ {7, 3} => %Chess.Piece{type: :queen, color: :white},
+ {7, 4} => %Chess.Piece{type: :king, color: :white},
+ {7, 5} => %Chess.Piece{type: :bishop, color: :white},
+ {7, 6} => %Chess.Piece{type: :knight, color: :white},
+ {7, 7} => %Chess.Piece{type: :rook, color: :white}
+ }
+end
+
+defmodule Chess.Game do
+ defstruct name: nil,
+ initial_position: nil,
+ final_position: nil,
+ next_player: :white,
+ board: %Chess.Board{},
+ white: %{removed_pieces: []},
+ black: %{removed_pieces: []}
+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..aa7f367
--- /dev/null
+++ b/lib/chess_web/live/chess_live.ex
@@ -0,0 +1,136 @@
+defmodule ChessWeb.ChessLive do
+ use Phoenix.LiveView, layout: {ChessWeb.LayoutView, "live.html"}
+
+ alias Chess.Game
+ alias Chess.Movements
+
+ @impl Phoenix.LiveView
+ def render(assigns) do
+ ChessWeb.PageView.render("index.html", assigns)
+ end
+
+ @impl Phoenix.LiveView
+ def mount(%{"id" => id}, _session, socket) do
+ socket =
+ socket
+ |> assign(:game, %Game{name: String.to_atom(id)})
+
+ {:ok, socket}
+ end
+
+ @impl Phoenix.LiveView
+ def handle_event(
+ "select_cell",
+ %{"x" => x, "y" => y},
+ %{assigns: %{game: %{board: %{cells: cells}, initial_position: nil}}} = socket
+ ) do
+ {x, _} = Integer.parse(x)
+ {y, _} = Integer.parse(y)
+
+ socket =
+ socket
+ |> Phoenix.LiveView.clear_flash()
+ |> set_initial_position({x, y}, Map.get(cells, {x, y}))
+
+ {:noreply, socket}
+ end
+
+ def handle_event(
+ "select_cell",
+ %{"x" => x, "y" => y},
+ %{assigns: %{game: %{board: %{cells: cells}, initial_position: initial_position}}} =
+ socket
+ ) do
+ {x, _} = Integer.parse(x)
+ {y, _} = Integer.parse(y)
+ socket = Phoenix.LiveView.clear_flash(socket, :error)
+ piece = Map.get(cells, initial_position)
+
+ socket =
+ cells
+ |> Movements.movement(piece, initial_position, {x, y})
+ |> then(&attack_and_move(socket, &1, piece, initial_position, {x, y}))
+
+ {:noreply, socket}
+ end
+
+ # Means the user click again over the same piece so we unselect it
+ defp attack_and_move(socket, _, _piece, position, position),
+ do: update_game_status(socket, [Access.key(:initial_position)], nil)
+
+ defp attack_and_move(socket, :error, _piece, _, _), do: error_message(socket, "Wrong movement")
+
+ defp attack_and_move(socket, {:ok, nil}, piece, initial_position, final_position),
+ do: move_piece(socket, piece, initial_position, final_position)
+
+ defp attack_and_move(
+ %{assigns: %{game: game}} = socket,
+ {:ok, removed_piece},
+ piece,
+ initial_position,
+ final_position
+ ),
+ do:
+ game
+ |> get_in([Access.key(removed_piece.color), :removed_pieces])
+ |> Enum.concat([removed_piece])
+ |> then(
+ &update_game_status(socket, [Access.key(removed_piece.color), :removed_pieces], &1)
+ )
+ |> move_piece(piece, initial_position, final_position)
+
+ defp change_player(%{assigns: %{game: %{next_player: :white}}}), do: :black
+
+ defp change_player(%{assigns: %{game: %{next_player: :black}}}), do: :white
+
+ defp error_message(socket, msg), do: Phoenix.LiveView.put_flash(socket, :error, msg)
+
+ defp move_piece(
+ %{assigns: %{game: %{board: %{cells: cells} = board}}} = socket,
+ piece,
+ initial_position,
+ final_position
+ ),
+ do:
+ cells
+ |> Map.put(final_position, piece)
+ |> Map.put(initial_position, nil)
+ |> then(&Map.put(board, :cells, &1))
+ |> then(&update_game_status(socket, &1, nil, nil, change_player(socket)))
+
+ defp set_initial_position(socket, _position, nil),
+ do: error_message(socket, "Select a cheese piece to move")
+
+ defp set_initial_position(%{assigns: %{game: %{next_player: color}}} = socket, position, %{
+ color: color
+ }),
+ do: update_game_status(socket, [Access.key(:initial_position)], position)
+
+ defp set_initial_position(
+ %{assigns: %{game: %{next_player: color}}} = socket,
+ _position,
+ _piece
+ ),
+ do: error_message(socket, "It is #{color} player turn")
+
+ defp update_game_status(%{assigns: %{game: game}} = socket, key, value),
+ do:
+ game
+ |> put_in(key, value)
+ |> then(&assign(socket, :game, &1))
+
+ defp update_game_status(
+ %{assigns: %{game: game}} = socket,
+ board,
+ initial_position,
+ final_position,
+ next_player
+ ),
+ do:
+ game
+ |> Map.put(:board, board)
+ |> Map.put(:initial_position, initial_position)
+ |> Map.put(:final_position, final_position)
+ |> Map.put(:next_player, next_player)
+ |> then(&assign(socket, :game, &1))
+end
diff --git a/lib/chess_web/router.ex b/lib/chess_web/router.ex
index 6545232..4f68f76 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", ChessLive
end
# Other scopes may use custom stacks.
diff --git a/lib/chess_web/templates/page/index.html.heex b/lib/chess_web/templates/page/index.html.heex
index 97fcea1..2640ae7 100644
--- a/lib/chess_web/templates/page/index.html.heex
+++ b/lib/chess_web/templates/page/index.html.heex
@@ -1,139 +1,18 @@