mirror of
https://github.com/danbee/chess
synced 2025-03-04 08:39:06 +00:00
Now we're correctly handling moves
This commit is contained in:
parent
dfc3fd669e
commit
5363271b95
@ -2,17 +2,19 @@ defmodule Chess.Board do
|
|||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
def transform(board) do
|
def transform(board) do
|
||||||
Enum.map(0..7, fn (rank) ->
|
Enum.map(0..7, fn rank ->
|
||||||
Enum.map(0..7, fn (file) ->
|
{rank,
|
||||||
|
Enum.map(0..7, fn file ->
|
||||||
|
{file,
|
||||||
board
|
board
|
||||||
|> piece({file, rank})
|
|> piece({file, rank})}
|
||||||
end)
|
end)}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def search(board, %{"type" => type, "colour" => colour}) do
|
def search(board, %{"type" => type, "colour" => colour}) do
|
||||||
board
|
board
|
||||||
|> Enum.filter(fn({_index, piece}) ->
|
|> Enum.filter(fn {_index, piece} ->
|
||||||
match?(%{"type" => ^type, "colour" => ^colour}, piece)
|
match?(%{"type" => ^type, "colour" => ^colour}, piece)
|
||||||
end)
|
end)
|
||||||
|> indexes_to_tuples
|
|> indexes_to_tuples
|
||||||
@ -20,7 +22,7 @@ defmodule Chess.Board do
|
|||||||
|
|
||||||
def search(board, %{"colour" => colour}) do
|
def search(board, %{"colour" => colour}) do
|
||||||
board
|
board
|
||||||
|> Enum.filter(fn({_index, piece}) ->
|
|> Enum.filter(fn {_index, piece} ->
|
||||||
match?(%{"colour" => ^colour}, piece)
|
match?(%{"colour" => ^colour}, piece)
|
||||||
end)
|
end)
|
||||||
|> indexes_to_tuples
|
|> indexes_to_tuples
|
||||||
@ -31,8 +33,8 @@ defmodule Chess.Board do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def move_piece(board, %{
|
def move_piece(board, %{
|
||||||
"from" => [from_file, from_rank],
|
from: {from_file, from_rank},
|
||||||
"to" => [to_file, to_rank]
|
to: {to_file, to_rank}
|
||||||
}) do
|
}) do
|
||||||
{piece, board} = Map.pop(board, to_index({from_file, from_rank}))
|
{piece, board} = Map.pop(board, to_index({from_file, from_rank}))
|
||||||
{piece_captured, board} = Map.pop(board, to_index({to_file, to_rank}))
|
{piece_captured, board} = Map.pop(board, to_index({to_file, to_rank}))
|
||||||
@ -42,8 +44,8 @@ defmodule Chess.Board do
|
|||||||
if castling_move?(piece, from_file, to_file) do
|
if castling_move?(piece, from_file, to_file) do
|
||||||
board
|
board
|
||||||
|> castling_move(%{
|
|> castling_move(%{
|
||||||
"from" => [from_file, from_rank],
|
from: {from_file, from_rank},
|
||||||
"to" => [to_file, to_rank]
|
to: {to_file, to_rank}
|
||||||
})
|
})
|
||||||
|> Map.get(:board)
|
|> Map.get(:board)
|
||||||
else
|
else
|
||||||
@ -55,25 +57,27 @@ defmodule Chess.Board do
|
|||||||
to: %{"file" => to_file, "rank" => to_rank},
|
to: %{"file" => to_file, "rank" => to_rank},
|
||||||
board: board,
|
board: board,
|
||||||
piece: piece,
|
piece: piece,
|
||||||
piece_captured: piece_captured,
|
piece_captured: piece_captured
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def castling_move?(%{"type" => "king"}, 4, to_file) do
|
def castling_move?(%{"type" => "king"}, 4, to_file) do
|
||||||
to_file == 2 || to_file == 6
|
to_file == 2 || to_file == 6
|
||||||
end
|
end
|
||||||
|
|
||||||
def castling_move?(_, _, _), do: false
|
def castling_move?(_, _, _), do: false
|
||||||
|
|
||||||
def castling_move(board, %{"from" => [4, rank], "to" => [2, _rank]}) do
|
def castling_move(board, %{from: {4, rank}, to: {2, _rank}}) do
|
||||||
move_piece(board, %{
|
move_piece(board, %{
|
||||||
"from" => [0, rank],
|
from: {0, rank},
|
||||||
"to" => [3, rank],
|
to: {3, rank}
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
def castling_move(board, %{"from" => [4, rank], "to" => [6, _rank]}) do
|
|
||||||
|
def castling_move(board, %{from: {4, rank}, to: {6, _rank}}) do
|
||||||
move_piece(board, %{
|
move_piece(board, %{
|
||||||
"from" => [7, rank],
|
from: {7, rank},
|
||||||
"to" => [5, rank],
|
to: {5, rank}
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -87,7 +91,6 @@ defmodule Chess.Board do
|
|||||||
"5,7" => %{"type" => "bishop", "colour" => "black"},
|
"5,7" => %{"type" => "bishop", "colour" => "black"},
|
||||||
"6,7" => %{"type" => "knight", "colour" => "black"},
|
"6,7" => %{"type" => "knight", "colour" => "black"},
|
||||||
"7,7" => %{"type" => "rook", "colour" => "black"},
|
"7,7" => %{"type" => "rook", "colour" => "black"},
|
||||||
|
|
||||||
"0,6" => %{"type" => "pawn", "colour" => "black"},
|
"0,6" => %{"type" => "pawn", "colour" => "black"},
|
||||||
"1,6" => %{"type" => "pawn", "colour" => "black"},
|
"1,6" => %{"type" => "pawn", "colour" => "black"},
|
||||||
"2,6" => %{"type" => "pawn", "colour" => "black"},
|
"2,6" => %{"type" => "pawn", "colour" => "black"},
|
||||||
@ -96,7 +99,6 @@ defmodule Chess.Board do
|
|||||||
"5,6" => %{"type" => "pawn", "colour" => "black"},
|
"5,6" => %{"type" => "pawn", "colour" => "black"},
|
||||||
"6,6" => %{"type" => "pawn", "colour" => "black"},
|
"6,6" => %{"type" => "pawn", "colour" => "black"},
|
||||||
"7,6" => %{"type" => "pawn", "colour" => "black"},
|
"7,6" => %{"type" => "pawn", "colour" => "black"},
|
||||||
|
|
||||||
"0,1" => %{"type" => "pawn", "colour" => "white"},
|
"0,1" => %{"type" => "pawn", "colour" => "white"},
|
||||||
"1,1" => %{"type" => "pawn", "colour" => "white"},
|
"1,1" => %{"type" => "pawn", "colour" => "white"},
|
||||||
"2,1" => %{"type" => "pawn", "colour" => "white"},
|
"2,1" => %{"type" => "pawn", "colour" => "white"},
|
||||||
@ -105,7 +107,6 @@ defmodule Chess.Board do
|
|||||||
"5,1" => %{"type" => "pawn", "colour" => "white"},
|
"5,1" => %{"type" => "pawn", "colour" => "white"},
|
||||||
"6,1" => %{"type" => "pawn", "colour" => "white"},
|
"6,1" => %{"type" => "pawn", "colour" => "white"},
|
||||||
"7,1" => %{"type" => "pawn", "colour" => "white"},
|
"7,1" => %{"type" => "pawn", "colour" => "white"},
|
||||||
|
|
||||||
"0,0" => %{"type" => "rook", "colour" => "white"},
|
"0,0" => %{"type" => "rook", "colour" => "white"},
|
||||||
"1,0" => %{"type" => "knight", "colour" => "white"},
|
"1,0" => %{"type" => "knight", "colour" => "white"},
|
||||||
"2,0" => %{"type" => "bishop", "colour" => "white"},
|
"2,0" => %{"type" => "bishop", "colour" => "white"},
|
||||||
@ -123,13 +124,13 @@ defmodule Chess.Board do
|
|||||||
|
|
||||||
defp indexes_to_tuples(list) do
|
defp indexes_to_tuples(list) do
|
||||||
list
|
list
|
||||||
|> Enum.map(fn({index, _piece}) -> index_to_tuple(index) end)
|
|> Enum.map(fn {index, _piece} -> index_to_tuple(index) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp index_to_tuple(index) do
|
defp index_to_tuple(index) do
|
||||||
index
|
index
|
||||||
|> String.split(",")
|
|> String.split(",")
|
||||||
|> Enum.map(&(String.to_integer(&1)))
|
|> Enum.map(&String.to_integer(&1))
|
||||||
|> List.to_tuple
|
|> List.to_tuple()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -14,11 +14,15 @@ defmodule Chess.GameState do
|
|||||||
cond do
|
cond do
|
||||||
player_checkmated?(board, colour) ->
|
player_checkmated?(board, colour) ->
|
||||||
"checkmate"
|
"checkmate"
|
||||||
|
|
||||||
player_stalemated?(board, colour) ->
|
player_stalemated?(board, colour) ->
|
||||||
"stalemate"
|
"stalemate"
|
||||||
|
|
||||||
king_in_check?(board, colour) ->
|
king_in_check?(board, colour) ->
|
||||||
"check"
|
"check"
|
||||||
true -> nil
|
|
||||||
|
true ->
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -36,7 +40,7 @@ defmodule Chess.GameState do
|
|||||||
king =
|
king =
|
||||||
board
|
board
|
||||||
|> Board.search(%{"type" => "king", "colour" => colour})
|
|> Board.search(%{"type" => "king", "colour" => colour})
|
||||||
|> List.first
|
|> List.first()
|
||||||
|
|
||||||
if is_nil(king) do
|
if is_nil(king) do
|
||||||
raise "There is no #{colour} king!"
|
raise "There is no #{colour} king!"
|
||||||
@ -49,7 +53,7 @@ defmodule Chess.GameState do
|
|||||||
def player_cannot_move?(board, colour) do
|
def player_cannot_move?(board, colour) do
|
||||||
board
|
board
|
||||||
|> Board.search(%{"colour" => colour})
|
|> Board.search(%{"colour" => colour})
|
||||||
|> Enum.all?(fn({file, rank}) ->
|
|> Enum.all?(fn {file, rank} ->
|
||||||
board
|
board
|
||||||
|> piece_cannot_move?({file, rank})
|
|> piece_cannot_move?({file, rank})
|
||||||
end)
|
end)
|
||||||
@ -62,9 +66,9 @@ defmodule Chess.GameState do
|
|||||||
|
|
||||||
board
|
board
|
||||||
|> Moves.available({file, rank})
|
|> Moves.available({file, rank})
|
||||||
|> Enum.all?(fn({to_file, to_rank}) ->
|
|> Enum.all?(fn {to_file, to_rank} ->
|
||||||
board
|
board
|
||||||
|> Board.move_piece(%{"from" => [file, rank], "to" => [to_file, to_rank]})
|
|> Board.move_piece(%{from: {file, rank}, to: {to_file, to_rank}})
|
||||||
|> Map.get(:board)
|
|> Map.get(:board)
|
||||||
|> king_in_check?(piece["colour"])
|
|> king_in_check?(piece["colour"])
|
||||||
end)
|
end)
|
||||||
|
|||||||
@ -19,10 +19,10 @@ defmodule Chess.Moves do
|
|||||||
game.board
|
game.board
|
||||||
|> Board.move_piece(move_params)
|
|> Board.move_piece(move_params)
|
||||||
|
|
||||||
Multi.new
|
Multi.new()
|
||||||
|> Multi.update(:game, Game.move_changeset(game, params))
|
|> Multi.update(:game, Game.move_changeset(game, params))
|
||||||
|> Multi.insert(:move, Ecto.build_assoc(game, :moves, params))
|
|> Multi.insert(:move, Ecto.build_assoc(game, :moves, params))
|
||||||
|> Repo.transaction
|
|> Repo.transaction()
|
||||||
end
|
end
|
||||||
|
|
||||||
def available(board, {file, rank}, move_list \\ []) do
|
def available(board, {file, rank}, move_list \\ []) do
|
||||||
@ -33,14 +33,19 @@ defmodule Chess.Moves do
|
|||||||
case piece do
|
case piece do
|
||||||
%{"type" => "pawn"} ->
|
%{"type" => "pawn"} ->
|
||||||
Pawn.moves(board, {file, rank})
|
Pawn.moves(board, {file, rank})
|
||||||
|
|
||||||
%{"type" => "rook"} ->
|
%{"type" => "rook"} ->
|
||||||
Rook.moves(board, {file, rank})
|
Rook.moves(board, {file, rank})
|
||||||
|
|
||||||
%{"type" => "bishop"} ->
|
%{"type" => "bishop"} ->
|
||||||
Bishop.moves(board, {file, rank})
|
Bishop.moves(board, {file, rank})
|
||||||
|
|
||||||
%{"type" => "knight"} ->
|
%{"type" => "knight"} ->
|
||||||
Knight.moves(board, {file, rank})
|
Knight.moves(board, {file, rank})
|
||||||
|
|
||||||
%{"type" => "king"} ->
|
%{"type" => "king"} ->
|
||||||
King.moves(board, {file, rank}, move_list)
|
King.moves(board, {file, rank}, move_list)
|
||||||
|
|
||||||
%{"type" => "queen"} ->
|
%{"type" => "queen"} ->
|
||||||
Queen.moves(board, {file, rank})
|
Queen.moves(board, {file, rank})
|
||||||
end
|
end
|
||||||
|
|||||||
@ -18,12 +18,13 @@ defmodule Chess.Moves.Pieces.King.Castling do
|
|||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def moves(_board, _piece, _move_list), do: []
|
def moves(_board, _piece, _move_list), do: []
|
||||||
|
|
||||||
def _moves(board, _rank, colour, move_list) do
|
def _moves(board, _rank, colour, move_list) do
|
||||||
board
|
board
|
||||||
|> Board.search(%{"type" => "rook", "colour" => colour})
|
|> Board.search(%{"type" => "rook", "colour" => colour})
|
||||||
|> Enum.map(fn ({file, rank}) ->
|
|> Enum.map(fn {file, rank} ->
|
||||||
case file do
|
case file do
|
||||||
0 -> queen_side_move(board, rank, colour, move_list)
|
0 -> queen_side_move(board, rank, colour, move_list)
|
||||||
7 -> king_side_move(board, rank, colour, move_list)
|
7 -> king_side_move(board, rank, colour, move_list)
|
||||||
@ -35,10 +36,13 @@ defmodule Chess.Moves.Pieces.King.Castling do
|
|||||||
|
|
||||||
defp king_has_moved?(move_list, colour) do
|
defp king_has_moved?(move_list, colour) do
|
||||||
move_list
|
move_list
|
||||||
|> Enum.any?(fn (move) ->
|
|> Enum.any?(fn move ->
|
||||||
match?(%Move{
|
match?(
|
||||||
|
%Move{
|
||||||
piece: %{"type" => "king", "colour" => ^colour}
|
piece: %{"type" => "king", "colour" => ^colour}
|
||||||
}, move)
|
},
|
||||||
|
move
|
||||||
|
)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -60,19 +64,22 @@ defmodule Chess.Moves.Pieces.King.Castling do
|
|||||||
|
|
||||||
defp rook_has_moved?(file, move_list, colour) do
|
defp rook_has_moved?(file, move_list, colour) do
|
||||||
move_list
|
move_list
|
||||||
|> Enum.any?(fn (move) ->
|
|> Enum.any?(fn move ->
|
||||||
match?(%Move{
|
match?(
|
||||||
|
%Move{
|
||||||
piece: %{"type" => "rook", "colour" => ^colour},
|
piece: %{"type" => "rook", "colour" => ^colour},
|
||||||
from: %{"file" => ^file},
|
from: %{"file" => ^file}
|
||||||
}, move)
|
},
|
||||||
|
move
|
||||||
|
)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp queen_side_in_check?(board, rank, colour) do
|
defp queen_side_in_check?(board, rank, colour) do
|
||||||
[{2, rank}, {3, rank}]
|
[{2, rank}, {3, rank}]
|
||||||
|> Enum.any?(fn ({to_file, to_rank}) ->
|
|> Enum.any?(fn {to_file, to_rank} ->
|
||||||
board
|
board
|
||||||
|> Board.move_piece(%{"from" => [4, rank], "to" => [to_file, to_rank]})
|
|> Board.move_piece(%{from: {4, rank}, to: {to_file, to_rank}})
|
||||||
|> Map.get(:board)
|
|> Map.get(:board)
|
||||||
|> GameState.king_in_check?(colour)
|
|> GameState.king_in_check?(colour)
|
||||||
end)
|
end)
|
||||||
@ -80,9 +87,9 @@ defmodule Chess.Moves.Pieces.King.Castling do
|
|||||||
|
|
||||||
defp king_side_in_check?(board, rank, colour) do
|
defp king_side_in_check?(board, rank, colour) do
|
||||||
[{5, rank}, {6, rank}]
|
[{5, rank}, {6, rank}]
|
||||||
|> Enum.any?(fn ({to_file, to_rank}) ->
|
|> Enum.any?(fn {to_file, to_rank} ->
|
||||||
board
|
board
|
||||||
|> Board.move_piece(%{"from" => [4, rank], "to" => [to_file, to_rank]})
|
|> Board.move_piece(%{from: {4, rank}, to: {to_file, to_rank}})
|
||||||
|> Map.get(:board)
|
|> Map.get(:board)
|
||||||
|> GameState.king_in_check?(colour)
|
|> GameState.king_in_check?(colour)
|
||||||
end)
|
end)
|
||||||
|
|||||||
@ -14,14 +14,14 @@ defmodule Chess.Store.Game do
|
|||||||
alias Chess.Store.User
|
alias Chess.Store.User
|
||||||
|
|
||||||
schema "games" do
|
schema "games" do
|
||||||
field :board, :map, default: Board.default()
|
field(:board, :map, default: Board.default())
|
||||||
field :turn, :string, default: "white"
|
field(:turn, :string, default: "white")
|
||||||
field :state, :string
|
field(:state, :string)
|
||||||
|
|
||||||
belongs_to :user, User
|
belongs_to(:user, User)
|
||||||
belongs_to :opponent, User, references: :id
|
belongs_to(:opponent, User, references: :id)
|
||||||
|
|
||||||
has_many :moves, Move
|
has_many(:moves, Move)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
@ -55,15 +55,17 @@ defmodule Chess.Store.Game do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def for_user_id(user_id) do
|
def for_user_id(user_id) do
|
||||||
from game in Game,
|
from(game in Game,
|
||||||
where: game.user_id == ^user_id,
|
where: game.user_id == ^user_id,
|
||||||
or_where: game.opponent_id == ^user_id
|
or_where: game.opponent_id == ^user_id
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_game_state(changeset) do
|
def check_game_state(changeset) do
|
||||||
changeset
|
changeset
|
||||||
|> put_change(
|
|> put_change(
|
||||||
:state, GameState.state(changeset.changes.board, changeset.changes.turn)
|
:state,
|
||||||
|
GameState.state(changeset.changes.board, changeset.changes.turn)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -78,6 +80,7 @@ defmodule Chess.Store.Game do
|
|||||||
changeset
|
changeset
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_king_in_check(changeset, _, _), do: changeset
|
def validate_king_in_check(changeset, _, _), do: changeset
|
||||||
|
|
||||||
def ordered(query) do
|
def ordered(query) do
|
||||||
|
|||||||
@ -32,7 +32,7 @@ defmodule ChessWeb.GameChannel do
|
|||||||
board: Board.transform(game.board),
|
board: Board.transform(game.board),
|
||||||
turn: game.turn,
|
turn: game.turn,
|
||||||
state: game.state,
|
state: game.state,
|
||||||
moves: MoveList.transform(game.moves),
|
moves: MoveList.transform(game.moves)
|
||||||
}
|
}
|
||||||
|
|
||||||
socket
|
socket
|
||||||
@ -57,6 +57,7 @@ defmodule ChessWeb.GameChannel do
|
|||||||
update_opponent(socket, game)
|
update_opponent(socket, game)
|
||||||
|
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
|
|
||||||
{:error, :game, changeset, _} ->
|
{:error, :game, changeset, _} ->
|
||||||
{message, _} = changeset.errors[:board]
|
{message, _} = changeset.errors[:board]
|
||||||
|
|
||||||
@ -73,13 +74,18 @@ defmodule ChessWeb.GameChannel do
|
|||||||
socket.assigns.user_id
|
socket.assigns.user_id
|
||||||
|> Queries.game_with_moves(socket.assigns.game_id)
|
|> Queries.game_with_moves(socket.assigns.game_id)
|
||||||
|
|
||||||
moves = Moves.available(game.board, {
|
moves =
|
||||||
|
Moves.available(
|
||||||
|
game.board,
|
||||||
|
{
|
||||||
String.to_integer(file),
|
String.to_integer(file),
|
||||||
String.to_integer(rank)
|
String.to_integer(rank)
|
||||||
}, game.moves)
|
},
|
||||||
|
game.moves
|
||||||
|
)
|
||||||
|
|
||||||
reply = %{
|
reply = %{
|
||||||
moves: Enum.map(moves, &(Tuple.to_list(&1)))
|
moves: Enum.map(moves, &Tuple.to_list(&1))
|
||||||
}
|
}
|
||||||
|
|
||||||
{:reply, {:ok, reply}, socket}
|
{:reply, {:ok, reply}, socket}
|
||||||
@ -88,24 +94,26 @@ defmodule ChessWeb.GameChannel do
|
|||||||
def update_opponent(socket, game) do
|
def update_opponent(socket, game) do
|
||||||
opponent_id =
|
opponent_id =
|
||||||
opponent(game, socket.assigns.user_id).id
|
opponent(game, socket.assigns.user_id).id
|
||||||
|> Integer.to_string
|
|> Integer.to_string()
|
||||||
|
|
||||||
send_update(socket)
|
send_update(socket)
|
||||||
|
|
||||||
"game:#{game.id}"
|
"game:#{game.id}"
|
||||||
|> Presence.list
|
|> Presence.list()
|
||||||
|> case do
|
|> case do
|
||||||
%{^opponent_id => _} ->
|
%{^opponent_id => _} ->
|
||||||
nil
|
nil
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
socket
|
socket
|
||||||
|> Emails.opponent_moved_email(game)
|
|> Emails.opponent_moved_email(game)
|
||||||
|> Mailer.deliver_later
|
|> Mailer.deliver_later()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def track_presence(socket) do
|
def track_presence(socket) do
|
||||||
{:ok, _} = Presence.track(socket, socket.assigns.user_id, %{
|
{:ok, _} =
|
||||||
|
Presence.track(socket, socket.assigns.user_id, %{
|
||||||
user_id: socket.assigns.user_id,
|
user_id: socket.assigns.user_id,
|
||||||
online_at: inspect(System.system_time(:second))
|
online_at: inspect(System.system_time(:second))
|
||||||
})
|
})
|
||||||
@ -116,8 +124,8 @@ defmodule ChessWeb.GameChannel do
|
|||||||
|
|
||||||
def convert_params(%{"from" => from, "to" => to}) do
|
def convert_params(%{"from" => from, "to" => to}) do
|
||||||
%{
|
%{
|
||||||
"from" => Enum.map(from, &(String.to_integer(&1))),
|
"from" => Enum.map(from, &String.to_integer(&1)),
|
||||||
"to" => Enum.map(to, &(String.to_integer(&1))),
|
"to" => Enum.map(to, &String.to_integer(&1))
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -130,7 +138,7 @@ defmodule ChessWeb.GameChannel do
|
|||||||
board: Board.transform(game.board),
|
board: Board.transform(game.board),
|
||||||
turn: game.turn,
|
turn: game.turn,
|
||||||
state: game.state,
|
state: game.state,
|
||||||
moves: MoveList.transform(game.moves),
|
moves: MoveList.transform(game.moves)
|
||||||
}
|
}
|
||||||
|
|
||||||
ChessWeb.Endpoint.broadcast("game:#{game.id}", "game:update", payload)
|
ChessWeb.Endpoint.broadcast("game:#{game.id}", "game:update", payload)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<div class="board board--black-to-move board--player-is-<%= player_colour(@user, @game) %>">
|
<div class="board board--<%= @game.turn %>-to-move board--player-is-<%= player_colour(@user, @game) %>">
|
||||||
<div class="board__rank-labels">
|
<div class="board__rank-labels">
|
||||||
<div class="board__label">1</div>
|
<div class="board__label">1</div>
|
||||||
<div class="board__label">2</div>
|
<div class="board__label">2</div>
|
||||||
@ -22,14 +22,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="board__body">
|
<div class="board__body">
|
||||||
<%= for rank <- 0..7 do %>
|
<%= for {rank, row} <- @board do %>
|
||||||
<div class="board__row">
|
<div class="board__row">
|
||||||
<%= for file <- 0..7 do %>
|
<%= for {file, piece} <- row do %>
|
||||||
<%= render ChessWeb.SquareView,
|
<%= render ChessWeb.SquareView,
|
||||||
"square.html",
|
"square.html",
|
||||||
rank: rank,
|
rank: rank,
|
||||||
file: file,
|
file: file,
|
||||||
piece: @game.board["#{file},#{rank}"],
|
piece: piece,
|
||||||
selected: {file, rank} == @selected,
|
selected: {file, rank} == @selected,
|
||||||
available: {file, rank} in @available %>
|
available: {file, rank} in @available %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@ -5,6 +5,7 @@ defmodule ChessWeb.BoardLive do
|
|||||||
alias Chess.Store.Game
|
alias Chess.Store.Game
|
||||||
alias Chess.Repo
|
alias Chess.Repo
|
||||||
alias Chess.Board
|
alias Chess.Board
|
||||||
|
alias Chess.Moves
|
||||||
|
|
||||||
import Chess.Auth, only: [get_user!: 1]
|
import Chess.Auth, only: [get_user!: 1]
|
||||||
|
|
||||||
@ -19,11 +20,24 @@ defmodule ChessWeb.BoardLive do
|
|||||||
Game.for_user(user)
|
Game.for_user(user)
|
||||||
|> Repo.get!(game_id)
|
|> Repo.get!(game_id)
|
||||||
|
|
||||||
{:ok, assign(socket, game: game, user: user, selected: nil, available: [])}
|
{:ok, assign(socket, default_assigns(game, user))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("click", %{"rank" => rank, "file" => file}, socket) do
|
def handle_event("click", %{"rank" => rank, "file" => file}, socket) do
|
||||||
{:noreply, socket |> handle_click(file, rank)}
|
{
|
||||||
|
:noreply,
|
||||||
|
socket |> handle_click(String.to_integer(file), String.to_integer(rank))
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp default_assigns(game, user) do
|
||||||
|
%{
|
||||||
|
board: Board.transform(game.board),
|
||||||
|
game: game,
|
||||||
|
user: user,
|
||||||
|
selected: nil,
|
||||||
|
available: []
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp handle_click(socket, file, rank) do
|
defp handle_click(socket, file, rank) do
|
||||||
@ -34,27 +48,56 @@ defmodule ChessWeb.BoardLive do
|
|||||||
colour = ChessWeb.GameView.player_colour(user, game)
|
colour = ChessWeb.GameView.player_colour(user, game)
|
||||||
|
|
||||||
assigns =
|
assigns =
|
||||||
|
if colour == game.turn do
|
||||||
case socket.assigns do
|
case socket.assigns do
|
||||||
%{:selected => nil} ->
|
%{selected: nil} ->
|
||||||
case Board.piece(board, {file, rank}) do
|
handle_selection(board, colour, file, rank)
|
||||||
%{"colour" => ^colour} ->
|
|
||||||
[{:selected, selected(file, rank)}]
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
[]
|
handle_move(socket.assigns, file, rank)
|
||||||
end
|
end
|
||||||
|
|
||||||
_ ->
|
|
||||||
[{:selected, nil}]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
assign(socket, assigns)
|
assign(socket, assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp selected(file, rank) do
|
defp handle_selection(board, colour, file, rank) do
|
||||||
{
|
case Board.piece(board, {file, rank}) do
|
||||||
String.to_integer(file),
|
%{"colour" => ^colour} ->
|
||||||
String.to_integer(rank)
|
[
|
||||||
}
|
{:selected, {file, rank}},
|
||||||
|
{:available, Moves.available(board, {file, rank})}
|
||||||
|
]
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_move(
|
||||||
|
%{game: game, available: available, selected: selected},
|
||||||
|
file,
|
||||||
|
rank
|
||||||
|
) do
|
||||||
|
if {file, rank} in available do
|
||||||
|
new_game =
|
||||||
|
game
|
||||||
|
|> Moves.make_move(%{from: selected, to: {file, rank}})
|
||||||
|
|> case do
|
||||||
|
{:ok, %{game: new_game}} ->
|
||||||
|
new_game
|
||||||
|
end
|
||||||
|
|
||||||
|
new_board = Board.transform(new_game.board)
|
||||||
|
|
||||||
|
[
|
||||||
|
{:selected, nil},
|
||||||
|
{:available, []},
|
||||||
|
{:board, new_board},
|
||||||
|
{:game, new_game}
|
||||||
|
]
|
||||||
|
else
|
||||||
|
[{:selected, nil}, {:available, []}]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -6,35 +6,62 @@ defmodule Chess.BoardTest do
|
|||||||
alias Chess.Board
|
alias Chess.Board
|
||||||
|
|
||||||
test "returns a piece from the board" do
|
test "returns a piece from the board" do
|
||||||
board = Board.default
|
board = Board.default()
|
||||||
|
|
||||||
expected_piece = %{"type" => "pawn", "colour" => "white"}
|
expected_piece = %{"type" => "pawn", "colour" => "white"}
|
||||||
assert Board.piece(board, {4, 1}) == expected_piece
|
assert Board.piece(board, {4, 1}) == expected_piece
|
||||||
end
|
end
|
||||||
|
|
||||||
test "finds pieces on the board" do
|
test "finds pieces on the board" do
|
||||||
board = Board.default
|
board = Board.default()
|
||||||
|
|
||||||
piece = %{"type" => "pawn", "colour" => "white"}
|
piece = %{"type" => "pawn", "colour" => "white"}
|
||||||
|
|
||||||
expected_result = [
|
expected_result = [
|
||||||
{0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
|
{0, 1},
|
||||||
|
{1, 1},
|
||||||
|
{2, 1},
|
||||||
|
{3, 1},
|
||||||
|
{4, 1},
|
||||||
|
{5, 1},
|
||||||
|
{6, 1},
|
||||||
|
{7, 1}
|
||||||
]
|
]
|
||||||
|
|
||||||
assert Board.search(board, piece) == expected_result
|
assert Board.search(board, piece) == expected_result
|
||||||
end
|
end
|
||||||
|
|
||||||
test "finds pieces on the board with a partial search" do
|
test "finds pieces on the board with a partial search" do
|
||||||
board = Board.default
|
board = Board.default()
|
||||||
|
|
||||||
piece = %{"colour" => "white"}
|
piece = %{"colour" => "white"}
|
||||||
expected_result = [
|
|
||||||
{0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
|
expected_result =
|
||||||
{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0},
|
[
|
||||||
] |> Enum.sort
|
{0, 1},
|
||||||
|
{1, 1},
|
||||||
|
{2, 1},
|
||||||
|
{3, 1},
|
||||||
|
{4, 1},
|
||||||
|
{5, 1},
|
||||||
|
{6, 1},
|
||||||
|
{7, 1},
|
||||||
|
{0, 0},
|
||||||
|
{1, 0},
|
||||||
|
{2, 0},
|
||||||
|
{3, 0},
|
||||||
|
{4, 0},
|
||||||
|
{5, 0},
|
||||||
|
{6, 0},
|
||||||
|
{7, 0}
|
||||||
|
]
|
||||||
|
|> Enum.sort()
|
||||||
|
|
||||||
assert Board.search(board, piece) == expected_result
|
assert Board.search(board, piece) == expected_result
|
||||||
end
|
end
|
||||||
|
|
||||||
test "finds a single piece on the board" do
|
test "finds a single piece on the board" do
|
||||||
board = Board.default
|
board = Board.default()
|
||||||
|
|
||||||
piece = %{"type" => "king", "colour" => "black"}
|
piece = %{"type" => "king", "colour" => "black"}
|
||||||
assert Board.search(board, piece) == [{4, 7}]
|
assert Board.search(board, piece) == [{4, 7}]
|
||||||
@ -42,44 +69,41 @@ defmodule Chess.BoardTest do
|
|||||||
|
|
||||||
test "moves a piece" do
|
test "moves a piece" do
|
||||||
board = %{
|
board = %{
|
||||||
"3,0" => %{"type" => "queen", "colour" => "white"},
|
"3,0" => %{"type" => "queen", "colour" => "white"}
|
||||||
}
|
}
|
||||||
|
|
||||||
%{board: new_board} =
|
%{board: new_board} = Board.move_piece(board, %{from: {3, 0}, to: {5, 2}})
|
||||||
Board.move_piece(board, %{"from" => [3, 0], "to" => [5, 2]})
|
|
||||||
|
|
||||||
assert new_board == %{
|
assert new_board == %{
|
||||||
"5,2" => %{"type" => "queen", "colour" => "white"},
|
"5,2" => %{"type" => "queen", "colour" => "white"}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "can perform a castling move on the kings side" do
|
test "can perform a castling move on the kings side" do
|
||||||
board = %{
|
board = %{
|
||||||
"4,0" => %{"type" => "king", "colour" => "white"},
|
"4,0" => %{"type" => "king", "colour" => "white"},
|
||||||
"7,0" => %{"type" => "rook", "colour" => "white"},
|
"7,0" => %{"type" => "rook", "colour" => "white"}
|
||||||
}
|
}
|
||||||
|
|
||||||
%{board: new_board} =
|
%{board: new_board} = Board.move_piece(board, %{from: {4, 0}, to: {6, 0}})
|
||||||
Board.move_piece(board, %{"from" => [4, 0], "to" => [6, 0]})
|
|
||||||
|
|
||||||
assert new_board == %{
|
assert new_board == %{
|
||||||
"6,0" => %{"type" => "king", "colour" => "white"},
|
"6,0" => %{"type" => "king", "colour" => "white"},
|
||||||
"5,0" => %{"type" => "rook", "colour" => "white"},
|
"5,0" => %{"type" => "rook", "colour" => "white"}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "can perform a castling move on the queens side" do
|
test "can perform a castling move on the queens side" do
|
||||||
board = %{
|
board = %{
|
||||||
"4,0" => %{"type" => "king", "colour" => "white"},
|
"4,0" => %{"type" => "king", "colour" => "white"},
|
||||||
"0,0" => %{"type" => "rook", "colour" => "white"},
|
"0,0" => %{"type" => "rook", "colour" => "white"}
|
||||||
}
|
}
|
||||||
|
|
||||||
%{board: new_board} =
|
%{board: new_board} = Board.move_piece(board, %{from: {4, 0}, to: {2, 0}})
|
||||||
Board.move_piece(board, %{"from" => [4, 0], "to" => [2, 0]})
|
|
||||||
|
|
||||||
assert new_board == %{
|
assert new_board == %{
|
||||||
"2,0" => %{"type" => "king", "colour" => "white"},
|
"2,0" => %{"type" => "king", "colour" => "white"},
|
||||||
"3,0" => %{"type" => "rook", "colour" => "white"},
|
"3,0" => %{"type" => "rook", "colour" => "white"}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -20,6 +20,7 @@ defmodule Chess.Store.GameTest do
|
|||||||
opponent_id: opponent.id,
|
opponent_id: opponent.id,
|
||||||
turn: "white"
|
turn: "white"
|
||||||
}
|
}
|
||||||
|
|
||||||
changeset = Game.changeset(%Game{}, attrs)
|
changeset = Game.changeset(%Game{}, attrs)
|
||||||
|
|
||||||
assert changeset.valid?
|
assert changeset.valid?
|
||||||
@ -33,6 +34,7 @@ defmodule Chess.Store.GameTest do
|
|||||||
opponent_id: 2,
|
opponent_id: 2,
|
||||||
turn: "white"
|
turn: "white"
|
||||||
}
|
}
|
||||||
|
|
||||||
changeset = Game.changeset(%Game{}, attrs)
|
changeset = Game.changeset(%Game{}, attrs)
|
||||||
|
|
||||||
assert changeset.valid?
|
assert changeset.valid?
|
||||||
@ -81,15 +83,17 @@ defmodule Chess.Store.GameTest do
|
|||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
opponent = insert(:opponent)
|
opponent = insert(:opponent)
|
||||||
|
|
||||||
game = insert(:game, %{
|
game =
|
||||||
board: Board.default,
|
insert(:game, %{
|
||||||
|
board: Board.default(),
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
opponent_id: opponent.id,
|
opponent_id: opponent.id
|
||||||
})
|
})
|
||||||
|
|
||||||
move_params = %{"from" => [4, 1], "to" => [4, 3]}
|
move_params = %{from: {4, 1}, to: {4, 3}}
|
||||||
|
|
||||||
changeset = Game.move_changeset(
|
changeset =
|
||||||
|
Game.move_changeset(
|
||||||
game,
|
game,
|
||||||
Board.move_piece(game.board, move_params)
|
Board.move_piece(game.board, move_params)
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user