1
0
mirror of https://github.com/danbee/chess synced 2025-03-04 08:39:06 +00:00

Compare commits

...

9 Commits

Author SHA1 Message Date
b1451535ca Add assets step 2023-02-04 23:35:05 -06:00
e4fc3a1b6f Install ChromeDriver 2023-02-04 23:18:22 -06:00
6cecc3005c Force install of Chrome 2023-02-04 23:02:35 -06:00
c19f586d13 Update Apt 2023-02-04 23:01:22 -06:00
f5641150a0 Rename pg-db to db 2023-02-04 22:59:58 -06:00
f0ed129028 Add files for Codeship Pro 2023-02-04 22:58:09 -06:00
1a9358d5e0 Install Chromedriver for Codeship 2023-02-04 21:40:21 -06:00
8f683dd73f Mix format 2023-02-04 21:35:56 -06:00
69f05a1c5d Fix tests 2023-02-04 21:33:48 -06:00
68 changed files with 1025 additions and 565 deletions

3
.formatter.exs Normal file
View File

@ -0,0 +1,3 @@
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

35
Dockerfile Normal file
View File

@ -0,0 +1,35 @@
FROM --platform=linux/amd64 elixir:1.14.1
# Create a directory for your application code and set it as the WORKDIR. All following commands will be run in this directory.
RUN mkdir /app
WORKDIR /app
# Update package repo
RUN apt-get update
# Install Chrome
RUN wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \
apt-get -y install ./google-chrome-stable_current_amd64.deb
# Install ChromeDriver
RUN CHROMEDRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` && \
mkdir -p /opt/chromedriver-$CHROMEDRIVER_VERSION && \
curl -sS -o /tmp/chromedriver_linux64.zip http://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip && \
unzip -qq /tmp/chromedriver_linux64.zip -d /opt/chromedriver-$CHROMEDRIVER_VERSION && \
rm /tmp/chromedriver_linux64.zip && \
chmod +x /opt/chromedriver-$CHROMEDRIVER_VERSION/chromedriver && \
ln -fs /opt/chromedriver-$CHROMEDRIVER_VERSION/chromedriver /usr/local/bin/chromedriver
# Install Rebar and Hex
RUN mix local.rebar --force && mix local.hex --force
# Install the Phoenix Mix archive
RUN mix archive.install --sha512 2a2f5167f5ea30f314500da449dbd8cfb4b6986cc27197c82fa4cc328798814f89a4dbe0183a5f213faed3587e8133ce99c1fab74cf1597978a270bdcc7bf789 --force https://github.com/phoenixframework/archives/raw/master/phx_new.ez
# COPY mix.exs and mix.lock and install dependencies before adding the full code so the cache only
# gets invalidated when dependencies are changed
COPY mix.exs mix.lock ./
RUN mix deps.get
# Copy the app source code into the image
COPY ./ /app

View File

@ -6,8 +6,8 @@ source /dev/stdin <<< "$(curl -sSL https://raw.githubusercontent.com/codeship/sc
echo "Installing Elixir"
source /dev/stdin <<< "$(curl -sSL https://raw.githubusercontent.com/codeship/scripts/master/languages/elixir.sh)"
echo "Installing PhantomJS"
curl -sSL https://raw.githubusercontent.com/codeship/scripts/master/packages/phantomjs.sh | bash -s
echo "Installing ChromeDriver"
curl -sSL https://raw.githubusercontent.com/codeship/scripts/master/packages/chromedriver.sh | bash -s
echo "Installing NodeJS"
nvm install $NODE_VERSION

18
codeship-services.yml Normal file
View File

@ -0,0 +1,18 @@
# Docker Compose-like syntax, see here for details:
# https://documentation.codeship.com/pro/builds-and-configuration/services/
app:
build:
image: danbee/chess
dockerfile: Dockerfile
environment:
MIX_ENV: test
PGHOST: db
PGUSER: postgres
PGPASSWORD: password
depends_on:
- db
db:
image: healthcheck/postgres:alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password

9
codeship-steps.yml Normal file
View File

@ -0,0 +1,9 @@
# This is where we define the test steps that would be run in a Codeship Pro build.
# The build passes as long as every test step returns with a non-zero exit code.
# See here for more: https://documentation.codeship.com/pro/builds-and-configuration/steps/
- name: build_assets
command: bin/codeship_assets
service: app
- name: run_mix_test
command: /bin/bash -c 'mix ecto.create && mix test'
service: app

View File

@ -30,6 +30,7 @@ config :chess, Chess.Auth.Guardian,
secret_key: "vd2vXkrYTTFKSKmNMoS2/Hk4Fxn8BkyzsVArRkxJazdQ3mr6bI4YgAC6f8ODiWlM"
config :formulator,
validate: false,
translate_error_module: ChessWeb.ErrorHelpers
# Configure esbuild (the version is required)

View File

@ -18,7 +18,7 @@ config :chess, Chess.Mailer, adapter: Bamboo.TestAdapter
config :chess, Chess.Repo,
adapter: Ecto.Adapters.Postgres,
database: "chess_test",
hostname: "localhost",
hostname: System.get_env("PGHOST") || "localhost",
port: System.get_env("POSTGRES_PORT") || "5432",
pool: Ecto.Adapters.SQL.Sandbox

19
docker-compose.yml Normal file
View File

@ -0,0 +1,19 @@
version: '3'
services:
app:
build: .
command: /bin/bash -c 'mix ecto.create && mix phx.server'
ports:
- "4000:4000"
environment:
MIX_ENV: dev
PGHOST: db
PGUSER: postgres
PGPASSWORD: password
links:
- db
db:
image: healthcheck/postgres:alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password

View File

@ -11,7 +11,7 @@ defmodule Chess do
{Phoenix.PubSub, [name: Chess.PubSub, adapter: Phoenix.PubSub.PG2]},
{Chess.Repo, []},
{ChessWeb.Endpoint, []},
{ChessWeb.Presence, []},
{ChessWeb.Presence, []}
]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html

View File

@ -12,6 +12,7 @@ defmodule Chess.Auth.ErrorHandler do
|> put_flash(:info, "You must be logged in")
|> redirect(to: "/")
|> halt()
"json" ->
conn
|> put_status(403)

View File

@ -10,8 +10,9 @@ defmodule Chess.Auth.Guardian do
end
def resource_from_claims(claims) do
user = claims["sub"]
|> Auth.get_user!
user =
claims["sub"]
|> Auth.get_user!()
{:ok, user}
end

View File

@ -7,11 +7,11 @@ defmodule Chess.Auth.Pipeline do
module: Chess.Auth.Guardian
# If there is a session token, validate it
plug Guardian.Plug.VerifySession, claims: %{"typ" => "access"}
plug(Guardian.Plug.VerifySession, claims: %{"typ" => "access"})
# If there is an authorization header, validate it
plug Guardian.Plug.VerifyHeader, claims: %{"typ" => "access"}
plug(Guardian.Plug.VerifyHeader, claims: %{"typ" => "access"})
# Load the user if either of the verifications worked
plug Guardian.Plug.LoadResource, allow_blank: true
plug(Guardian.Plug.LoadResource, allow_blank: true)
end

View File

@ -2,8 +2,8 @@ defmodule Chess.Board do
@moduledoc false
def transform(board) do
Enum.map(0..7, fn (rank) ->
Enum.map(0..7, fn (file) ->
Enum.map(0..7, fn rank ->
Enum.map(0..7, fn file ->
board
|> piece({file, rank})
end)
@ -12,7 +12,7 @@ defmodule Chess.Board do
def search(board, %{"type" => type, "colour" => colour}) do
board
|> Enum.filter(fn({_index, piece}) ->
|> Enum.filter(fn {_index, piece} ->
match?(%{"type" => ^type, "colour" => ^colour}, piece)
end)
|> indexes_to_tuples
@ -20,7 +20,7 @@ defmodule Chess.Board do
def search(board, %{"colour" => colour}) do
board
|> Enum.filter(fn({_index, piece}) ->
|> Enum.filter(fn {_index, piece} ->
match?(%{"colour" => ^colour}, piece)
end)
|> indexes_to_tuples
@ -31,9 +31,9 @@ defmodule Chess.Board do
end
def move_piece(board, %{
"from" => [from_file, from_rank],
"to" => [to_file, to_rank]
}) do
"from" => [from_file, from_rank],
"to" => [to_file, to_rank]
}) do
{piece, board} = Map.pop(board, to_index({from_file, from_rank}))
{piece_captured, board} = Map.pop(board, to_index({to_file, to_rank}))
board = Map.put(board, to_index({to_file, to_rank}), piece)
@ -55,39 +55,40 @@ defmodule Chess.Board do
to: %{"file" => to_file, "rank" => to_rank},
board: board,
piece: piece,
piece_captured: piece_captured,
piece_captured: piece_captured
}
end
def castling_move?(%{"type" => "king"}, 4, to_file) do
to_file == 2 || to_file == 6
end
def castling_move?(_, _, _), do: false
def castling_move(board, %{"from" => [4, rank], "to" => [2, _rank]}) do
move_piece(board, %{
"from" => [0, rank],
"to" => [3, rank],
"to" => [3, rank]
})
end
def castling_move(board, %{"from" => [4, rank], "to" => [6, _rank]}) do
move_piece(board, %{
"from" => [7, rank],
"to" => [5, rank],
"to" => [5, rank]
})
end
def default do
%{
"0,7" => %{"type" => "rook", "colour" => "black"},
"0,7" => %{"type" => "rook", "colour" => "black"},
"1,7" => %{"type" => "knight", "colour" => "black"},
"2,7" => %{"type" => "bishop", "colour" => "black"},
"3,7" => %{"type" => "queen", "colour" => "black"},
"4,7" => %{"type" => "king", "colour" => "black"},
"3,7" => %{"type" => "queen", "colour" => "black"},
"4,7" => %{"type" => "king", "colour" => "black"},
"5,7" => %{"type" => "bishop", "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"},
"1,6" => %{"type" => "pawn", "colour" => "black"},
"2,6" => %{"type" => "pawn", "colour" => "black"},
@ -96,7 +97,6 @@ defmodule Chess.Board do
"5,6" => %{"type" => "pawn", "colour" => "black"},
"6,6" => %{"type" => "pawn", "colour" => "black"},
"7,6" => %{"type" => "pawn", "colour" => "black"},
"0,1" => %{"type" => "pawn", "colour" => "white"},
"1,1" => %{"type" => "pawn", "colour" => "white"},
"2,1" => %{"type" => "pawn", "colour" => "white"},
@ -105,15 +105,14 @@ defmodule Chess.Board do
"5,1" => %{"type" => "pawn", "colour" => "white"},
"6,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"},
"2,0" => %{"type" => "bishop", "colour" => "white"},
"3,0" => %{"type" => "queen", "colour" => "white"},
"4,0" => %{"type" => "king", "colour" => "white"},
"3,0" => %{"type" => "queen", "colour" => "white"},
"4,0" => %{"type" => "king", "colour" => "white"},
"5,0" => %{"type" => "bishop", "colour" => "white"},
"6,0" => %{"type" => "knight", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"}
"7,0" => %{"type" => "rook", "colour" => "white"}
}
end
@ -123,13 +122,13 @@ defmodule Chess.Board do
defp indexes_to_tuples(list) do
list
|> Enum.map(fn({index, _piece}) -> index_to_tuple(index) end)
|> Enum.map(fn {index, _piece} -> index_to_tuple(index) end)
end
defp index_to_tuple(index) do
index
|> String.split(",")
|> Enum.map(&(String.to_integer(&1)))
|> List.to_tuple
|> Enum.map(&String.to_integer(&1))
|> List.to_tuple()
end
end

View File

@ -12,9 +12,7 @@ defmodule Chess.Emails do
new_email()
|> to(game.opponent)
|> from({"64squares", "games@64squares.club"})
|> subject(
"[64squares] #{game.user.name} has invited you to play a game of chess."
)
|> subject("[64squares] #{game.user.name} has invited you to play a game of chess.")
|> text_body("""
Game link: #{Helpers.game_url(conn, :show, game)}
""")
@ -27,9 +25,7 @@ defmodule Chess.Emails do
new_email()
|> to(opponent)
|> from({"64squares", "games@64squares.club"})
|> subject(
"[64squares] #{user.name} has moved."
)
|> subject("[64squares] #{user.name} has moved.")
|> text_body("""
Game link: #{Helpers.game_url(socket, :show, game)}
""")

View File

@ -14,11 +14,15 @@ defmodule Chess.GameState do
cond do
player_checkmated?(board, colour) ->
"checkmate"
player_stalemated?(board, colour) ->
"stalemate"
king_in_check?(board, colour) ->
"check"
true -> nil
true ->
nil
end
end
@ -36,7 +40,7 @@ defmodule Chess.GameState do
king =
board
|> Board.search(%{"type" => "king", "colour" => colour})
|> List.first
|> List.first()
if is_nil(king) do
raise "There is no #{colour} king!"
@ -49,7 +53,7 @@ defmodule Chess.GameState do
def player_cannot_move?(board, colour) do
board
|> Board.search(%{"colour" => colour})
|> Enum.all?(fn({file, rank}) ->
|> Enum.all?(fn {file, rank} ->
board
|> piece_cannot_move?({file, rank})
end)
@ -62,7 +66,7 @@ defmodule Chess.GameState do
board
|> Moves.available({file, rank})
|> Enum.all?(fn({to_file, to_rank}) ->
|> Enum.all?(fn {to_file, to_rank} ->
board
|> Board.move_piece(%{"from" => [file, rank], "to" => [to_file, to_rank]})
|> Map.get(:board)

View File

@ -5,7 +5,7 @@ defmodule Chess.MoveList do
def transform(moves) do
moves
|> Enum.map(&(Move.transform(&1)))
|> Enum.map(&Move.transform(&1))
|> Enum.chunk_every(2)
end
end

View File

@ -19,10 +19,10 @@ defmodule Chess.Moves do
game.board
|> Board.move_piece(move_params)
Multi.new
Multi.new()
|> Multi.update(:game, Game.move_changeset(game, params))
|> Multi.insert(:move, Ecto.build_assoc(game, :moves, params))
|> Repo.transaction
|> Repo.transaction()
end
def available(board, {file, rank}, move_list \\ []) do
@ -33,14 +33,19 @@ defmodule Chess.Moves do
case piece do
%{"type" => "pawn"} ->
Pawn.moves(board, {file, rank})
%{"type" => "rook"} ->
Rook.moves(board, {file, rank})
%{"type" => "bishop"} ->
Bishop.moves(board, {file, rank})
%{"type" => "knight"} ->
Knight.moves(board, {file, rank})
%{"type" => "king"} ->
King.moves(board, {file, rank}, move_list)
%{"type" => "queen"} ->
Queen.moves(board, {file, rank})
end

View File

@ -22,13 +22,17 @@ defmodule Chess.Moves.Generator do
defp _moves(_colour, _board, {_file, 0}, {_, -1}), do: []
defp _moves(_colour, _board, {7, _rank}, {1, _}), do: []
defp _moves(_colour, _board, {_file, 7}, {_, 1}), do: []
defp _moves(colour, board, {file, rank}, {fv, rv}) do
next_square = {file + fv, rank + rv}
cond do
can_capture_piece?(colour, board, next_square) ->
[next_square]
obstruction?(colour, board, next_square) ->
[]
true ->
[next_square | _moves(colour, board, next_square, {fv, rv})]
end
@ -36,14 +40,18 @@ defmodule Chess.Moves.Generator do
# Move generation for pieces that follow a pattern
defp _moves(_colour, _board, {_file, _rank}, []), do: []
defp _moves(colour, board, {file, rank}, [{fv, rv} | moves]) do
move_square = {file + fv, rank + rv}
cond do
outside_board?(move_square) ||
obstruction?(colour, board, move_square) ->
obstruction?(colour, board, move_square) ->
_moves(colour, board, {file, rank}, moves)
can_capture_piece?(colour, board, move_square) ->
[move_square | _moves(colour, board, {file, rank}, moves)]
true ->
[move_square | _moves(colour, board, {file, rank}, moves)]
end
@ -66,6 +74,7 @@ defmodule Chess.Moves.Generator do
piece =
board
|> Board.piece({file, rank})
piece && piece["colour"] == colour
end
end

View File

@ -28,7 +28,7 @@ defmodule Chess.Moves.Piece do
end
defp attacked_by_knight?(board, {file, rank}) do
_attacked?(board, {file, rank}, Knight.pattern, "knight")
_attacked?(board, {file, rank}, Knight.pattern(), "knight")
end
defp attacked_by_pawn?(board, {file, rank}) do
@ -46,22 +46,24 @@ defmodule Chess.Moves.Piece do
board
|> Generator.moves({file, rank}, pattern)
Enum.any?(moves, &(match_piece(board, &1, "pawn")))
Enum.any?(moves, &match_piece(board, &1, "pawn"))
end
defp _attacked?(_board, {0, _rank}, {-1, _}, _), do: false
defp _attacked?(_board, {_file, 0}, {_, -1}, _), do: false
defp _attacked?(_board, {7, _rank}, {1, _}, _), do: false
defp _attacked?(_board, {_file, 7}, {_, 1}, _), do: false
defp _attacked?(board, {file, rank}, {fv, rv}, pieces) do
board
|> Generator.moves({file, rank}, {fv, rv})
|> List.last
|> List.last()
|> case do
{file, rank} ->
piece = board["#{file},#{rank}"]
Enum.any?(pieces, &(match?(%{"type" => ^&1}, piece)))
Enum.any?(pieces, &match?(%{"type" => ^&1}, piece))
nil ->
false
end
@ -72,7 +74,7 @@ defmodule Chess.Moves.Piece do
board
|> Generator.moves({file, rank}, pattern)
Enum.any?(moves, &(match_piece(board, &1, piece_type)))
Enum.any?(moves, &match_piece(board, &1, piece_type))
end
defp match_piece(board, {file, rank}, piece_type) do

View File

@ -18,12 +18,13 @@ defmodule Chess.Moves.Pieces.King.Castling do
[]
end
end
def moves(_board, _piece, _move_list), do: []
def _moves(board, _rank, colour, move_list) do
board
|> Board.search(%{"type" => "rook", "colour" => colour})
|> Enum.map(fn ({file, rank}) ->
|> Enum.map(fn {file, rank} ->
case file do
0 -> queen_side_move(board, rank, colour, move_list)
7 -> king_side_move(board, rank, colour, move_list)
@ -35,42 +36,48 @@ defmodule Chess.Moves.Pieces.King.Castling do
defp king_has_moved?(move_list, colour) do
move_list
|> Enum.any?(fn (move) ->
match?(%Move{
piece: %{"type" => "king", "colour" => ^colour}
}, move)
|> Enum.any?(fn move ->
match?(
%Move{
piece: %{"type" => "king", "colour" => ^colour}
},
move
)
end)
end
defp queen_side_move(board, rank, colour, move_list) do
if queen_side_squares_empty?(board, rank) &&
!queen_side_in_check?(board, rank, colour) &&
!rook_has_moved?(0, move_list, colour) do
!queen_side_in_check?(board, rank, colour) &&
!rook_has_moved?(0, move_list, colour) do
{2, rank}
end
end
defp king_side_move(board, rank, colour, move_list) do
if king_side_squares_empty?(board, rank) &&
!king_side_in_check?(board, rank, colour) &&
!rook_has_moved?(7, move_list, colour) do
!king_side_in_check?(board, rank, colour) &&
!rook_has_moved?(7, move_list, colour) do
{6, rank}
end
end
defp rook_has_moved?(file, move_list, colour) do
move_list
|> Enum.any?(fn (move) ->
match?(%Move{
piece: %{"type" => "rook", "colour" => ^colour},
from: %{"file" => ^file},
}, move)
|> Enum.any?(fn move ->
match?(
%Move{
piece: %{"type" => "rook", "colour" => ^colour},
from: %{"file" => ^file}
},
move
)
end)
end
defp queen_side_in_check?(board, rank, colour) do
[{2, rank}, {3, rank}]
|> Enum.any?(fn ({to_file, to_rank}) ->
|> Enum.any?(fn {to_file, to_rank} ->
board
|> Board.move_piece(%{"from" => [4, rank], "to" => [to_file, to_rank]})
|> Map.get(:board)
@ -80,7 +87,7 @@ defmodule Chess.Moves.Pieces.King.Castling do
defp king_side_in_check?(board, rank, colour) do
[{5, rank}, {6, rank}]
|> Enum.any?(fn ({to_file, to_rank}) ->
|> Enum.any?(fn {to_file, to_rank} ->
board
|> Board.move_piece(%{"from" => [4, rank], "to" => [to_file, to_rank]})
|> Map.get(:board)

View File

@ -37,8 +37,10 @@ defmodule Chess.Moves.Pieces.Pawn do
cond do
obstruction?(board, {file, rank + 1}) ->
[]
rank == 1 ->
[{file, rank + 1} | _moves("white", board, {file, rank + 1})]
true ->
[{file, rank + 1}]
end
@ -48,16 +50,20 @@ defmodule Chess.Moves.Pieces.Pawn do
cond do
obstruction?(board, {file, rank - 1}) ->
[]
rank == 6 ->
[{file, rank - 1} | _moves("black", board, {file, rank - 1})]
true ->
[{file, rank - 1}]
end
end
def _capture_moves(_colour, _board, {_file, _rank}, []), do: []
def _capture_moves(colour, board, {file, rank}, [{fv, rv} | moves]) do
move_square = {file + fv, rank + rv}
if can_capture_piece?(colour, board, move_square) do
[move_square | _capture_moves(colour, board, {file, rank}, moves)]
else

28
lib/chess/release.ex Normal file
View File

@ -0,0 +1,28 @@
defmodule Chess.Release do
@moduledoc """
Used for executing DB release tasks when run in production without Mix
installed.
"""
@app :chess
def migrate do
load_app()
for repo <- repos() do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
end
def rollback(repo, version) do
load_app()
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
end
defp repos do
Application.fetch_env!(@app, :ecto_repos)
end
defp load_app do
Application.load(@app)
end
end

View File

@ -23,7 +23,7 @@ defmodule Chess.Repo.Queries do
def opponents(user, query_string) do
user
|> User.opponents
|> User.opponents()
|> User.matches(query_string)
end
end

View File

@ -14,14 +14,14 @@ defmodule Chess.Store.Game do
alias Chess.Store.User
schema "games" do
field :board, :map, default: Board.default()
field :turn, :string, default: "white"
field :state, :string
field(:board, :map, default: Board.default())
field(:turn, :string, default: "white")
field(:state, :string)
belongs_to :user, User
belongs_to :opponent, User, references: :id
belongs_to(:user, User)
belongs_to(:opponent, User, references: :id)
has_many :moves, Move
has_many(:moves, Move)
timestamps()
end
@ -55,15 +55,17 @@ defmodule Chess.Store.Game do
end
def for_user_id(user_id) do
from game in Game,
from(game in Game,
where: game.user_id == ^user_id,
or_where: game.opponent_id == ^user_id
)
end
def check_game_state(changeset) do
changeset
|> put_change(
:state, GameState.state(changeset.changes.board, changeset.changes.turn)
:state,
GameState.state(changeset.changes.board, changeset.changes.turn)
)
end
@ -78,6 +80,7 @@ defmodule Chess.Store.Game do
changeset
end
end
def validate_king_in_check(changeset, _, _), do: changeset
def ordered(query) do

View File

@ -8,13 +8,13 @@ defmodule Chess.Store.Move do
alias Chess.Store.Game
schema "moves" do
field :from, :map
field :to, :map
field(:from, :map)
field(:to, :map)
field :piece, :map
field :piece_captured, :map
field(:piece, :map)
field(:piece_captured, :map)
belongs_to :game, Game
belongs_to(:game, Game)
timestamps()
end
@ -31,7 +31,7 @@ defmodule Chess.Store.Move do
piece: move.piece,
piece_captured: move.piece_captured,
from: <<97 + move.from["file"], 49 + move.from["rank"]>>,
to: <<97 + move.to["file"], 49 + move.to["rank"]>>,
to: <<97 + move.to["file"], 49 + move.to["rank"]>>
}
end

View File

@ -76,6 +76,7 @@ defmodule Chess.Store.User do
if password do
changeset
|> change(Argon2.add_hash(password))
|> change(password: nil)
else
changeset
end

View File

@ -31,8 +31,9 @@ defmodule ChessWeb do
def view do
quote do
use Phoenix.View, root: "lib/chess_web/templates",
namespace: ChessWeb
use Phoenix.View,
root: "lib/chess_web/templates",
namespace: ChessWeb
# Import convenience functions from controllers
import Phoenix.Controller,

View File

@ -32,7 +32,7 @@ defmodule ChessWeb.GameChannel do
board: Board.transform(game.board),
turn: game.turn,
state: game.state,
moves: MoveList.transform(game.moves),
moves: MoveList.transform(game.moves)
}
socket
@ -57,6 +57,7 @@ defmodule ChessWeb.GameChannel do
update_opponent(socket, game)
{:noreply, socket}
{:error, :game, changeset, _} ->
{message, _} = changeset.errors[:board]
@ -65,21 +66,26 @@ defmodule ChessWeb.GameChannel do
end
def handle_in(
"game:get_available_moves",
%{"square" => [file, rank]},
socket
) do
"game:get_available_moves",
%{"square" => [file, rank]},
socket
) do
game =
socket.assigns.user_id
|> Queries.game_with_moves(socket.assigns.game_id)
moves = Moves.available(game.board, {
String.to_integer(file),
String.to_integer(rank)
}, game.moves)
moves =
Moves.available(
game.board,
{
String.to_integer(file),
String.to_integer(rank)
},
game.moves
)
reply = %{
moves: Enum.map(moves, &(Tuple.to_list(&1)))
moves: Enum.map(moves, &Tuple.to_list(&1))
}
{:reply, {:ok, reply}, socket}
@ -88,27 +94,29 @@ defmodule ChessWeb.GameChannel do
def update_opponent(socket, game) do
opponent_id =
opponent(game, socket.assigns.user_id).id
|> Integer.to_string
|> Integer.to_string()
send_update(socket)
"game:#{game.id}"
|> Presence.list
|> Presence.list()
|> case do
%{^opponent_id => _} ->
nil
_ ->
socket
|> Emails.opponent_moved_email(game)
|> Mailer.deliver_later
|> Mailer.deliver_later()
end
end
def track_presence(socket) do
{:ok, _} = Presence.track(socket, socket.assigns.user_id, %{
user_id: socket.assigns.user_id,
online_at: inspect(System.system_time(:second))
})
{:ok, _} =
Presence.track(socket, socket.assigns.user_id, %{
user_id: socket.assigns.user_id,
online_at: inspect(System.system_time(:second))
})
socket
|> push("presence_state", Presence.list(socket))
@ -116,8 +124,8 @@ defmodule ChessWeb.GameChannel do
def convert_params(%{"from" => from, "to" => to}) do
%{
"from" => Enum.map(from, &(String.to_integer(&1))),
"to" => Enum.map(to, &(String.to_integer(&1))),
"from" => Enum.map(from, &String.to_integer(&1)),
"to" => Enum.map(to, &String.to_integer(&1))
}
end
@ -130,7 +138,7 @@ defmodule ChessWeb.GameChannel do
board: Board.transform(game.board),
turn: game.turn,
state: game.state,
moves: MoveList.transform(game.moves),
moves: MoveList.transform(game.moves)
}
ChessWeb.Endpoint.broadcast("game:#{game.id}", "game:update", payload)

View File

@ -68,6 +68,7 @@ defmodule ChessWeb.Presence do
information, while maintaining the required `:metas` field from the
original presence data.
"""
use Phoenix.Presence, otp_app: :chess,
pubsub_server: Chess.PubSub
use Phoenix.Presence,
otp_app: :chess,
pubsub_server: Chess.PubSub
end

View File

@ -4,7 +4,7 @@ defmodule ChessWeb.UserSocket do
alias Phoenix.Token
## Channels
channel "game:*", ChessWeb.GameChannel
channel("game:*", ChessWeb.GameChannel)
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
@ -21,10 +21,12 @@ defmodule ChessWeb.UserSocket do
case Token.verify(socket, "game socket", token, max_age: 1_209_600) do
{:ok, user_id} ->
{:ok, assign(socket, :user_id, user_id)}
{:error, _reason} ->
:error
end
end
def connect(%{}, _socket), do: :error
# Socket id's are topics that allow you to identify all sockets for a given user:

View File

@ -10,8 +10,8 @@ defmodule ChessWeb.Api.OpponentsController do
conn
|> current_user()
|> Queries.opponents(query_string)
|> Repo.all
|> Repo.all()
render conn, "index.json", %{opponents: opponents}
render(conn, "index.json", %{opponents: opponents})
end
end

View File

@ -15,9 +15,9 @@ defmodule ChessWeb.GameController do
conn
|> current_user()
|> Game.for_user()
|> Game.ordered
|> Game.ordered()
|> preload([:user, :opponent])
|> Repo.all
|> Repo.all()
conn
|> render("index.html", games: games, changeset: changeset)
@ -42,7 +42,7 @@ defmodule ChessWeb.GameController do
|> Repo.preload(:user)
|> Repo.preload(:opponent)
)
|> Mailer.deliver_later
|> Mailer.deliver_later()
conn
|> put_flash(:info, "Game created successfully.")
@ -53,7 +53,7 @@ defmodule ChessWeb.GameController do
conn
|> current_user()
|> User.opponents()
|> Repo.all
|> Repo.all()
conn
|> render("new.html", changeset: changeset, opponents: opponents)

View File

@ -22,6 +22,7 @@ defmodule ChessWeb.PasswordController do
conn
|> put_flash(:info, gettext("Password updated successfully."))
|> redirect(to: page_path(conn, :index))
{:error, changeset} ->
render(conn, "edit.html", changeset: changeset)
end

View File

@ -22,6 +22,7 @@ defmodule ChessWeb.ProfileController do
conn
|> put_flash(:info, gettext("Profile updated successfully."))
|> redirect(to: page_path(conn, :index))
{:error, changeset} ->
render(conn, "edit.html", changeset: changeset)
end

View File

@ -18,6 +18,7 @@ defmodule ChessWeb.RegistrationController do
|> Guardian.Plug.sign_in(user)
|> put_flash(:info, "Registered successfully.")
|> redirect(to: page_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end

View File

@ -11,17 +11,19 @@ defmodule ChessWeb.SessionController do
end
def create(
conn,
%{"user" => %{"email" => email, "password" => password}}
) do
conn,
%{"user" => %{"email" => email, "password" => password}}
) do
case Auth.authenticate_user(email, password) do
{:ok, user} ->
conn
|> Guardian.Plug.sign_in(user)
|> put_flash(:info, "You are logged in")
|> redirect(to: game_path(conn, :index))
{:error, _error} ->
changeset = User.changeset(%User{})
conn
|> put_flash(:error, "Bad email or password")
|> render("new.html", changeset: changeset)

View File

@ -4,57 +4,53 @@ defmodule ChessWeb.Router do
alias Phoenix.Token
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug(:accepts, ["html"])
plug(:fetch_session)
plug(:fetch_flash)
plug(:protect_from_forgery)
plug(:put_secure_browser_headers)
end
pipeline :auth do
plug Chess.Auth.Pipeline
plug(Chess.Auth.Pipeline)
end
pipeline :ensure_auth do
plug Guardian.Plug.EnsureAuthenticated
plug :put_user_token
plug(Guardian.Plug.EnsureAuthenticated)
plug(:put_user_token)
end
pipeline :api do
plug :fetch_session
plug :accepts, ["json"]
plug(:fetch_session)
plug(:accepts, ["json"])
end
scope "/", ChessWeb do
pipe_through [:browser, :auth] # Use the default browser stack
# Use the default browser stack
pipe_through([:browser, :auth])
get "/", PageController, :index
resources "/session", SessionController,
only: [:new, :create, :delete], singleton: true
resources "/registration", RegistrationController,
only: [:new, :create], singleton: true
get("/", PageController, :index)
resources("/session", SessionController, only: [:new, :create, :delete], singleton: true)
resources("/registration", RegistrationController, only: [:new, :create], singleton: true)
end
scope "/", ChessWeb do
pipe_through [:browser, :auth, :ensure_auth]
pipe_through([:browser, :auth, :ensure_auth])
resources "/games", GameController,
only: [:index, :new, :create, :show, :delete]
resources "/profile", ProfileController,
only: [:edit, :update], singleton: true
resources "/password", PasswordController,
only: [:edit, :update], singleton: true
resources("/games", GameController, only: [:index, :new, :create, :show, :delete])
resources("/profile", ProfileController, only: [:edit, :update], singleton: true)
resources("/password", PasswordController, only: [:edit, :update], singleton: true)
end
# Other scopes may use custom stacks.
scope "/api", as: :api do
pipe_through [:api, :auth, :ensure_auth]
pipe_through([:api, :auth, :ensure_auth])
resources "/opponents", ChessWeb.Api.OpponentsController, only: [:index]
resources("/opponents", ChessWeb.Api.OpponentsController, only: [:index])
end
if Mix.env == :dev do
forward "/sent_emails", Bamboo.SentEmailViewerPlug
if Mix.env() == :dev do
forward("/sent_emails", Bamboo.SentEmailViewerPlug)
end
defp put_user_token(conn, _) do

View File

@ -3,16 +3,17 @@ defmodule ChessWeb.Api.OpponentsView do
def render("index.json", %{opponents: opponents}) do
%{
opponents: Enum.map(opponents, fn opponent ->
opponent_attrs(opponent)
end)
opponents:
Enum.map(opponents, fn opponent ->
opponent_attrs(opponent)
end)
}
end
def opponent_attrs(opponent) do
%{
id: opponent.id,
name: opponent.name,
name: opponent.name
}
end
end

View File

@ -10,7 +10,7 @@ defmodule ChessWeb.ErrorHelpers do
"""
def error_tag(form, field) do
if error = form.errors[field] do
content_tag :span, translate_error(error), class: "help-block"
content_tag(:span, translate_error(error), class: "help-block")
end
end

View File

@ -2,16 +2,16 @@ defmodule ChessWeb.ErrorView do
use ChessWeb, :view
def render("404.html", _assigns) do
gettext "Page not found"
gettext("Page not found")
end
def render("500.html", _assigns) do
gettext "Internal server error"
gettext("Internal server error")
end
# In case no render clause matches or no
# template is found, let's render it as 500
def template_not_found(_template, assigns) do
render "500.html", assigns
render("500.html", assigns)
end
end

View File

@ -7,8 +7,8 @@ defmodule ChessWeb.GameView do
def won_lost(conn, game) do
if game_over?(game) && game.state == "checkmate" do
your_turn?(conn, game) &&
gettext("You lost") ||
(your_turn?(conn, game) &&
gettext("You lost")) ||
gettext("You won")
end
end
@ -21,9 +21,12 @@ defmodule ChessWeb.GameView do
cond do
GameState.game_over?(game) ->
states()[game.state]
your_turn?(conn, game) ->
gettext("Your turn")
true -> nil
true ->
nil
end
end
@ -38,7 +41,7 @@ defmodule ChessWeb.GameView do
end
def player_colour(conn, game) do
current_user(conn).id == game.user_id && "white" || "black"
(current_user(conn).id == game.user_id && "white") || "black"
end
def player(game, user_id) do
@ -61,7 +64,7 @@ defmodule ChessWeb.GameView do
%{
"checkmate" => gettext("Checkmate!"),
"stalemate" => gettext("Stalemate"),
"check" => gettext("Check"),
"check" => gettext("Check")
}
end
end

View File

@ -76,15 +76,13 @@ defmodule Chess.AuthTest do
test "authenticate_user/1 returns false on incorrect password " do
user_fixture(email: "link@hyrule.com", password: "eyeofsheikah")
assert {:error, message} =
Auth.authenticate_user("link@hyrule.com", "shadowtemple")
assert {:error, message} = Auth.authenticate_user("link@hyrule.com", "shadowtemple")
assert message == "invalid password"
end
test "authenticate_user/1 returns true on correct password " do
user = user_fixture(email: "link@hyrule.com", password: "eyeofsheikah")
assert {:ok, ^user} =
Auth.authenticate_user("link@hyrule.com", "eyeofsheikah")
assert {:ok, ^user} = Auth.authenticate_user("link@hyrule.com", "eyeofsheikah")
end
end
end

View File

@ -6,35 +6,62 @@ defmodule Chess.BoardTest do
alias Chess.Board
test "returns a piece from the board" do
board = Board.default
board = Board.default()
expected_piece = %{"type" => "pawn", "colour" => "white"}
assert Board.piece(board, {4, 1}) == expected_piece
end
test "finds pieces on the board" do
board = Board.default
board = Board.default()
piece = %{"type" => "pawn", "colour" => "white"}
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
end
test "finds pieces on the board with a partial search" do
board = Board.default
board = Board.default()
piece = %{"colour" => "white"}
expected_result = [
{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
expected_result =
[
{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
end
test "finds a single piece on the board" do
board = Board.default
board = Board.default()
piece = %{"type" => "king", "colour" => "black"}
assert Board.search(board, piece) == [{4, 7}]
@ -42,44 +69,41 @@ defmodule Chess.BoardTest do
test "moves a piece" do
board = %{
"3,0" => %{"type" => "queen", "colour" => "white"},
"3,0" => %{"type" => "queen", "colour" => "white"}
}
%{board: new_board} =
Board.move_piece(board, %{"from" => [3, 0], "to" => [5, 2]})
%{board: new_board} = Board.move_piece(board, %{"from" => [3, 0], "to" => [5, 2]})
assert new_board == %{
"5,2" => %{"type" => "queen", "colour" => "white"},
}
"5,2" => %{"type" => "queen", "colour" => "white"}
}
end
test "can perform a castling move on the kings side" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"}
}
%{board: new_board} =
Board.move_piece(board, %{"from" => [4, 0], "to" => [6, 0]})
%{board: new_board} = Board.move_piece(board, %{"from" => [4, 0], "to" => [6, 0]})
assert new_board == %{
"6,0" => %{"type" => "king", "colour" => "white"},
"5,0" => %{"type" => "rook", "colour" => "white"},
}
"6,0" => %{"type" => "king", "colour" => "white"},
"5,0" => %{"type" => "rook", "colour" => "white"}
}
end
test "can perform a castling move on the queens side" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"}
}
%{board: new_board} =
Board.move_piece(board, %{"from" => [4, 0], "to" => [2, 0]})
%{board: new_board} = Board.move_piece(board, %{"from" => [4, 0], "to" => [2, 0]})
assert new_board == %{
"2,0" => %{"type" => "king", "colour" => "white"},
"3,0" => %{"type" => "rook", "colour" => "white"},
}
"2,0" => %{"type" => "king", "colour" => "white"},
"3,0" => %{"type" => "rook", "colour" => "white"}
}
end
end

View File

@ -8,7 +8,7 @@ defmodule Chess.GameStateTest do
test "king is in check" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"4,7" => %{"type" => "queen", "colour" => "black"},
"4,7" => %{"type" => "queen", "colour" => "black"}
}
assert GameState.king_in_check?(board, "white")
@ -17,7 +17,7 @@ defmodule Chess.GameStateTest do
test "king is not in check" do
board = %{
"5,0" => %{"type" => "king", "colour" => "white"},
"4,7" => %{"type" => "queen", "colour" => "black"},
"4,7" => %{"type" => "queen", "colour" => "black"}
}
refute GameState.king_in_check?(board, "white")
@ -26,7 +26,7 @@ defmodule Chess.GameStateTest do
test "king is in check by a knight" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"3,2" => %{"type" => "knight", "colour" => "black"},
"3,2" => %{"type" => "knight", "colour" => "black"}
}
assert GameState.king_in_check?(board, "white")
@ -35,7 +35,7 @@ defmodule Chess.GameStateTest do
test "king is in check by a pawn" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"3,1" => %{"type" => "pawn", "colour" => "black"},
"3,1" => %{"type" => "pawn", "colour" => "black"}
}
assert GameState.king_in_check?(board, "white")
@ -45,7 +45,7 @@ defmodule Chess.GameStateTest do
board = %{
"0,0" => %{"type" => "king", "colour" => "white"},
"0,4" => %{"type" => "queen", "colour" => "black"},
"1,4" => %{"type" => "rook", "colour" => "black"},
"1,4" => %{"type" => "rook", "colour" => "black"}
}
assert GameState.player_checkmated?(board, "white")
@ -54,7 +54,7 @@ defmodule Chess.GameStateTest do
test "king is not in checkmate by a queen" do
board = %{
"0,0" => %{"type" => "king", "colour" => "white"},
"0,4" => %{"type" => "queen", "colour" => "black"},
"0,4" => %{"type" => "queen", "colour" => "black"}
}
refute GameState.player_checkmated?(board, "white")
@ -64,7 +64,7 @@ defmodule Chess.GameStateTest do
board = %{
"0,0" => %{"type" => "king", "colour" => "white"},
"1,1" => %{"type" => "queen", "colour" => "black"},
"2,3" => %{"type" => "knight", "colour" => "black"},
"2,3" => %{"type" => "knight", "colour" => "black"}
}
assert GameState.player_checkmated?(board, "white")
@ -75,7 +75,7 @@ defmodule Chess.GameStateTest do
"0,0" => %{"type" => "king", "colour" => "white"},
"2,0" => %{"type" => "knight", "colour" => "white"},
"0,5" => %{"type" => "queen", "colour" => "black"},
"1,5" => %{"type" => "rook", "colour" => "black"},
"1,5" => %{"type" => "rook", "colour" => "black"}
}
refute GameState.player_checkmated?(board, "white")
@ -86,7 +86,7 @@ defmodule Chess.GameStateTest do
"0,0" => %{"type" => "king", "colour" => "white"},
"2,3" => %{"type" => "bishop", "colour" => "white"},
"0,5" => %{"type" => "queen", "colour" => "black"},
"1,5" => %{"type" => "rook", "colour" => "black"},
"1,5" => %{"type" => "rook", "colour" => "black"}
}
refute GameState.player_checkmated?(board, "white")
@ -96,7 +96,7 @@ defmodule Chess.GameStateTest do
board = %{
"0,0" => %{"type" => "king", "colour" => "white"},
"1,2" => %{"type" => "rook", "colour" => "black"},
"2,1" => %{"type" => "rook", "colour" => "black"},
"2,1" => %{"type" => "rook", "colour" => "black"}
}
assert GameState.player_stalemated?(board, "white")

View File

@ -12,18 +12,18 @@ defmodule Chess.MoveListTest do
%Move{
piece: %{"type" => "pawn", "colour" => "white"},
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3},
to: %{"file" => 4, "rank" => 3}
},
%Move{
piece: %{"type" => "pawn", "colour" => "black"},
from: %{"file" => 4, "rank" => 6},
to: %{"file" => 4, "rank" => 4},
to: %{"file" => 4, "rank" => 4}
},
%Move{
piece: %{"type" => "knight", "colour" => "white"},
from: %{"file" => 1, "rank" => 0},
to: %{"file" => 2, "rank" => 2},
},
to: %{"file" => 2, "rank" => 2}
}
]
expected_result = [
@ -51,7 +51,7 @@ defmodule Chess.MoveListTest do
from: "b1",
to: "c3"
}
],
]
]
assert MoveList.transform(moves) == expected_result

View File

@ -6,7 +6,7 @@ defmodule Chess.Moves.PieceTest do
test "piece is not being attacked" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"2,1" => %{"type" => "rook", "colour" => "black"},
"2,1" => %{"type" => "rook", "colour" => "black"}
}
refute Piece.attacked?(board, {4, 5})
@ -15,7 +15,7 @@ defmodule Chess.Moves.PieceTest do
test "piece on the edge of the board is not being attacked" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"2,7" => %{"type" => "rook", "colour" => "black"},
"2,7" => %{"type" => "rook", "colour" => "black"}
}
refute Piece.attacked?(board, {4, 0})
@ -25,7 +25,7 @@ defmodule Chess.Moves.PieceTest do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"4,1" => %{"type" => "pawn", "colour" => "white"},
"7,3" => %{"type" => "bishop", "colour" => "black"},
"7,3" => %{"type" => "bishop", "colour" => "black"}
}
assert Piece.attacked?(board, {4, 0})
@ -34,7 +34,7 @@ defmodule Chess.Moves.PieceTest do
test "piece is not being attacked by piece of its own colour" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"2,5" => %{"type" => "rook", "colour" => "white"},
"2,5" => %{"type" => "rook", "colour" => "white"}
}
refute Piece.attacked?(board, {4, 5})
@ -43,7 +43,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a rook" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"2,5" => %{"type" => "rook", "colour" => "black"},
"2,5" => %{"type" => "rook", "colour" => "black"}
}
assert Piece.attacked?(board, {4, 5})
@ -52,7 +52,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a bishop" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"6,7" => %{"type" => "bishop", "colour" => "black"},
"6,7" => %{"type" => "bishop", "colour" => "black"}
}
assert Piece.attacked?(board, {4, 5})
@ -61,7 +61,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a queen" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"6,7" => %{"type" => "queen", "colour" => "black"},
"6,7" => %{"type" => "queen", "colour" => "black"}
}
assert Piece.attacked?(board, {4, 5})
@ -70,7 +70,7 @@ defmodule Chess.Moves.PieceTest do
test "piece is not attacked by a knight" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"7,7" => %{"type" => "knight", "colour" => "black"},
"7,7" => %{"type" => "knight", "colour" => "black"}
}
refute Piece.attacked?(board, {4, 5})
@ -79,7 +79,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a knight" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"5,7" => %{"type" => "knight", "colour" => "black"},
"5,7" => %{"type" => "knight", "colour" => "black"}
}
assert Piece.attacked?(board, {4, 5})
@ -88,7 +88,7 @@ defmodule Chess.Moves.PieceTest do
test "piece can be attacked by a pawn" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"5,6" => %{"type" => "pawn", "colour" => "black"},
"5,6" => %{"type" => "pawn", "colour" => "black"}
}
assert Piece.attacked?(board, {4, 5})
@ -97,7 +97,7 @@ defmodule Chess.Moves.PieceTest do
test "piece is not attacked by a pawn directly in front" do
board = %{
"4,5" => %{"type" => "king", "colour" => "white"},
"4,6" => %{"type" => "pawn", "colour" => "black"},
"4,6" => %{"type" => "pawn", "colour" => "black"}
}
refute Piece.attacked?(board, {4, 5})

View File

@ -7,10 +7,21 @@ defmodule Chess.Moves.Pieces.BishopTest do
board = %{"4,5" => %{"type" => "bishop", "colour" => "white"}}
moves = Moves.available(board, {4, 5})
expected_moves = Enum.sort([
{0, 1}, {1, 2}, {2, 3}, {3, 4}, {5, 6}, {6, 7},
{2, 7}, {3, 6}, {5, 4}, {6, 3}, {7, 2},
])
expected_moves =
Enum.sort([
{0, 1},
{1, 2},
{2, 3},
{3, 4},
{5, 6},
{6, 7},
{2, 7},
{3, 6},
{5, 4},
{6, 3},
{7, 2}
])
assert Enum.sort(moves) == expected_moves
end
@ -18,39 +29,60 @@ defmodule Chess.Moves.Pieces.BishopTest do
board = %{"0,0" => %{"type" => "bishop", "colour" => "white"}}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7},
])
expected_moves =
Enum.sort([
{1, 1},
{2, 2},
{3, 3},
{4, 4},
{5, 5},
{6, 6},
{7, 7}
])
assert Enum.sort(moves) == expected_moves
end
test "bishops are blocked by pieces of their own colour" do
board = %{
"0,0" => %{"type" => "bishop", "colour" => "white"},
"5,5" => %{"type" => "king", "colour" => "white"},
"5,5" => %{"type" => "king", "colour" => "white"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{1, 1}, {2, 2}, {3, 3}, {4, 4},
])
expected_moves =
Enum.sort([
{1, 1},
{2, 2},
{3, 3},
{4, 4}
])
assert Enum.sort(moves) == expected_moves
end
test "bishops can take an opponents piece" do
board = %{
"0,0" => %{"type" => "bishop", "colour" => "white"},
"5,5" => %{"type" => "knight", "colour" => "black"},
"5,5" => %{"type" => "knight", "colour" => "black"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5},
])
expected_moves =
Enum.sort([
{1, 1},
{2, 2},
{3, 3},
{4, 4},
{5, 5}
])
assert Enum.sort(moves) == expected_moves
end
def board do
Chess.Board.default
Chess.Board.default()
end
end

View File

@ -7,28 +7,42 @@ defmodule Chess.Moves.Pieces.King.CastlingTest do
test "king can move two spaces to castle with the king side rook" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"}
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
{6, 0},
])
expected_moves =
Enum.sort([
{3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1},
{6, 0}
])
assert Enum.sort(moves) == expected_moves
end
test "king can move two spaces to castle with the queen side rook" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"}
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
{2, 0},
])
expected_moves =
Enum.sort([
{3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1},
{2, 0}
])
assert Enum.sort(moves) == expected_moves
end
@ -36,13 +50,19 @@ defmodule Chess.Moves.Pieces.King.CastlingTest do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"3,0" => %{"type" => "queen", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"}
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{5, 0}, {3, 1}, {4, 1}, {5, 1},
])
expected_moves =
Enum.sort([
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves
end
@ -50,13 +70,20 @@ defmodule Chess.Moves.Pieces.King.CastlingTest do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"2,7" => %{"type" => "queen", "colour" => "black"},
"2,7" => %{"type" => "queen", "colour" => "black"}
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
expected_moves =
Enum.sort([
{3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves
end
@ -64,88 +91,119 @@ defmodule Chess.Moves.Pieces.King.CastlingTest do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"3,7" => %{"type" => "queen", "colour" => "black"},
"3,7" => %{"type" => "queen", "colour" => "black"}
}
moves = Moves.available(board, {4, 0})
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
expected_moves =
Enum.sort([
{3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves
end
test "cannot castle if the king has moved" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"}
}
move_list = [
%Move{
from: %{"file" => 4, "rank" => 0},
to: %{"file" => 4, "rank" => 1},
piece: %{"type" => "king", "colour" => "white"}
piece: %{"type" => "king", "colour" => "white"}
},
%Move{
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 0},
piece: %{"type" => "king", "colour" => "white"}
},
piece: %{"type" => "king", "colour" => "white"}
}
]
moves = Moves.available(board, {4, 0}, move_list)
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
expected_moves =
Enum.sort([
{3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves
end
test "cannot castle if the queen side rook has moved" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,0" => %{"type" => "rook", "colour" => "white"}
}
move_list = [
%Move{
from: %{"file" => 0, "rank" => 0},
to: %{"file" => 0, "rank" => 1},
piece: %{"type" => "rook", "colour" => "white"}
piece: %{"type" => "rook", "colour" => "white"}
},
%Move{
from: %{"file" => 0, "rank" => 1},
to: %{"file" => 0, "rank" => 0},
piece: %{"type" => "rook", "colour" => "white"}
},
piece: %{"type" => "rook", "colour" => "white"}
}
]
moves = Moves.available(board, {4, 0}, move_list)
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
expected_moves =
Enum.sort([
{3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves
end
test "cannot castle if the king side rook has moved" do
board = %{
"4,0" => %{"type" => "king", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"},
"7,0" => %{"type" => "rook", "colour" => "white"}
}
move_list = [
%Move{
from: %{"file" => 7, "rank" => 0},
to: %{"file" => 7, "rank" => 1},
piece: %{"type" => "rook", "colour" => "white"}
piece: %{"type" => "rook", "colour" => "white"}
},
%Move{
from: %{"file" => 7, "rank" => 1},
to: %{"file" => 7, "rank" => 0},
piece: %{"type" => "rook", "colour" => "white"}
},
piece: %{"type" => "rook", "colour" => "white"}
}
]
moves = Moves.available(board, {4, 0}, move_list)
expected_moves = Enum.sort([
{3, 0}, {5, 0}, {3, 1}, {4, 1}, {5, 1},
])
expected_moves =
Enum.sort([
{3, 0},
{5, 0},
{3, 1},
{4, 1},
{5, 1}
])
assert Enum.sort(moves) == expected_moves
end
end

View File

@ -7,9 +7,18 @@ defmodule Chess.Moves.Pieces.KingTest do
board = %{"4,5" => %{"type" => "king", "colour" => "white"}}
moves = Moves.available(board, {4, 5})
expected_moves = Enum.sort([
{3, 4}, {4, 4}, {5, 4}, {5, 5}, {5, 6}, {4, 6}, {3, 6}, {3, 5},
])
expected_moves =
Enum.sort([
{3, 4},
{4, 4},
{5, 4},
{5, 5},
{5, 6},
{4, 6},
{3, 6},
{3, 5}
])
assert Enum.sort(moves) == expected_moves
end
@ -17,26 +26,34 @@ defmodule Chess.Moves.Pieces.KingTest do
board = %{"0,0" => %{"type" => "king", "colour" => "white"}}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{0, 1}, {1, 1}, {1, 0},
])
expected_moves =
Enum.sort([
{0, 1},
{1, 1},
{1, 0}
])
assert Enum.sort(moves) == expected_moves
end
test "kings are blocked by pieces of the same colour" do
board = %{
"0,0" => %{"type" => "king", "colour" => "white"},
"1,1" => %{"type" => "rook", "colour" => "white"},
"1,1" => %{"type" => "rook", "colour" => "white"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{0, 1}, {1, 0},
])
expected_moves =
Enum.sort([
{0, 1},
{1, 0}
])
assert Enum.sort(moves) == expected_moves
end
def board do
Chess.Board.default
Chess.Board.default()
end
end

View File

@ -7,9 +7,18 @@ defmodule Chess.Moves.Pieces.KnightTest do
board = %{"4,5" => %{"type" => "knight", "colour" => "white"}}
moves = Moves.available(board, {4, 5})
expected_moves = Enum.sort([
{3, 7}, {5, 7}, {6, 6}, {6, 4}, {5, 3}, {3, 3}, {2, 4}, {2, 6},
])
expected_moves =
Enum.sort([
{3, 7},
{5, 7},
{6, 6},
{6, 4},
{5, 3},
{3, 3},
{2, 4},
{2, 6}
])
assert Enum.sort(moves) == expected_moves
end
@ -17,26 +26,32 @@ defmodule Chess.Moves.Pieces.KnightTest do
board = %{"0,0" => %{"type" => "knight", "colour" => "white"}}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{1, 2}, {2, 1}
])
expected_moves =
Enum.sort([
{1, 2},
{2, 1}
])
assert Enum.sort(moves) == expected_moves
end
test "knights are blocked by other pieces of the same colour" do
board = %{
"0,0" => %{"type" => "knight", "colour" => "white"},
"1,2" => %{"type" => "king", "colour" => "white"},
"1,2" => %{"type" => "king", "colour" => "white"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{2, 1},
])
expected_moves =
Enum.sort([
{2, 1}
])
assert Enum.sort(moves) == expected_moves
end
def board do
Chess.Board.default
Chess.Board.default()
end
end

View File

@ -36,8 +36,9 @@ defmodule Chess.Moves.Pieces.PawnTest do
test "pawn is blocked from moving two squares by another piece" do
board = %{
"4,1" => %{"type" => "pawn", "colour" => "white"},
"4,3" => %{"type" => "pawn", "colour" => "black"},
"4,3" => %{"type" => "pawn", "colour" => "black"}
}
moves = Moves.available(board, {4, 1})
expected_moves = [{4, 2}]
@ -47,8 +48,9 @@ defmodule Chess.Moves.Pieces.PawnTest do
test "pawn is blocked from moving one or two squares by another piece" do
board = %{
"4,1" => %{"type" => "pawn", "colour" => "white"},
"4,2" => %{"type" => "pawn", "colour" => "black"},
"4,2" => %{"type" => "pawn", "colour" => "black"}
}
moves = Moves.available(board, {4, 1})
expected_moves = []
@ -58,8 +60,9 @@ defmodule Chess.Moves.Pieces.PawnTest do
test "pawn is blocked from moving one square by another piece" do
board = %{
"4,2" => %{"type" => "pawn", "colour" => "white"},
"4,3" => %{"type" => "pawn", "colour" => "black"},
"4,3" => %{"type" => "pawn", "colour" => "black"}
}
moves = Moves.available(board, {4, 2})
expected_moves = []
@ -69,8 +72,9 @@ defmodule Chess.Moves.Pieces.PawnTest do
test "white pawn can take an opponents piece" do
board = %{
"4,2" => %{"type" => "pawn", "colour" => "white"},
"5,3" => %{"type" => "pawn", "colour" => "black"},
"5,3" => %{"type" => "pawn", "colour" => "black"}
}
moves = Moves.available(board, {4, 2})
expected_moves = [{4, 3}, {5, 3}]
@ -80,8 +84,9 @@ defmodule Chess.Moves.Pieces.PawnTest do
test "black pawn can take an opponents piece" do
board = %{
"6,6" => %{"type" => "pawn", "colour" => "black"},
"5,5" => %{"type" => "pawn", "colour" => "white"},
"5,5" => %{"type" => "pawn", "colour" => "white"}
}
moves = Moves.available(board, {6, 6})
expected_moves = [{6, 5}, {6, 4}, {5, 5}]
@ -89,6 +94,6 @@ defmodule Chess.Moves.Pieces.PawnTest do
end
def default_board do
Chess.Board.default
Chess.Board.default()
end
end

View File

@ -7,12 +7,35 @@ defmodule Chess.Moves.Pieces.QueenTest do
board = %{"4,5" => %{"type" => "queen", "colour" => "white"}}
moves = Moves.available(board, {4, 5})
expected_moves = Enum.sort([
{4, 0}, {4, 1}, {4, 2}, {4, 3}, {4, 4}, {4, 6}, {4, 7},
{0, 5}, {1, 5}, {2, 5}, {3, 5}, {5, 5}, {6, 5}, {7, 5},
{0, 1}, {1, 2}, {2, 3}, {3, 4}, {5, 6}, {6, 7},
{2, 7}, {3, 6}, {5, 4}, {6, 3}, {7, 2},
])
expected_moves =
Enum.sort([
{4, 0},
{4, 1},
{4, 2},
{4, 3},
{4, 4},
{4, 6},
{4, 7},
{0, 5},
{1, 5},
{2, 5},
{3, 5},
{5, 5},
{6, 5},
{7, 5},
{0, 1},
{1, 2},
{2, 3},
{3, 4},
{5, 6},
{6, 7},
{2, 7},
{3, 6},
{5, 4},
{6, 3},
{7, 2}
])
assert Enum.sort(moves) == expected_moves
end
@ -20,15 +43,30 @@ defmodule Chess.Moves.Pieces.QueenTest do
board = %{
"0,0" => %{"type" => "queen", "colour" => "white"},
"0,5" => %{"type" => "king", "colour" => "white"},
"5,0" => %{"type" => "bishop", "colour" => "white"},
"5,0" => %{"type" => "bishop", "colour" => "white"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{0, 1}, {0, 2}, {0, 3}, {0, 4},
{1, 0}, {2, 0}, {3, 0}, {4, 0},
{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7},
])
expected_moves =
Enum.sort([
{0, 1},
{0, 2},
{0, 3},
{0, 4},
{1, 0},
{2, 0},
{3, 0},
{4, 0},
{1, 1},
{2, 2},
{3, 3},
{4, 4},
{5, 5},
{6, 6},
{7, 7}
])
assert Enum.sort(moves) == expected_moves
end
@ -36,19 +74,36 @@ defmodule Chess.Moves.Pieces.QueenTest do
board = %{
"0,0" => %{"type" => "queen", "colour" => "white"},
"0,5" => %{"type" => "knight", "colour" => "black"},
"5,0" => %{"type" => "rook", "colour" => "black"},
"5,0" => %{"type" => "rook", "colour" => "black"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5},
{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0},
{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7},
])
expected_moves =
Enum.sort([
{0, 1},
{0, 2},
{0, 3},
{0, 4},
{0, 5},
{1, 0},
{2, 0},
{3, 0},
{4, 0},
{5, 0},
{1, 1},
{2, 2},
{3, 3},
{4, 4},
{5, 5},
{6, 6},
{7, 7}
])
assert Enum.sort(moves) == expected_moves
end
def board do
Chess.Board.default
Chess.Board.default()
end
end

View File

@ -7,10 +7,24 @@ defmodule Chess.Moves.Pieces.RookTest do
board = %{"4,5" => %{"type" => "rook", "colour" => "white"}}
moves = Moves.available(board, {4, 5})
expected_moves = Enum.sort([
{4, 0}, {4, 1}, {4, 2}, {4, 3}, {4, 4}, {4, 6}, {4, 7},
{0, 5}, {1, 5}, {2, 5}, {3, 5}, {5, 5}, {6, 5}, {7, 5},
])
expected_moves =
Enum.sort([
{4, 0},
{4, 1},
{4, 2},
{4, 3},
{4, 4},
{4, 6},
{4, 7},
{0, 5},
{1, 5},
{2, 5},
{3, 5},
{5, 5},
{6, 5},
{7, 5}
])
assert Enum.sort(moves) == expected_moves
end
@ -18,42 +32,81 @@ defmodule Chess.Moves.Pieces.RookTest do
board = %{"0,0" => %{"type" => "rook", "colour" => "white"}}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7},
{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0},
])
expected_moves =
Enum.sort([
{0, 1},
{0, 2},
{0, 3},
{0, 4},
{0, 5},
{0, 6},
{0, 7},
{1, 0},
{2, 0},
{3, 0},
{4, 0},
{5, 0},
{6, 0},
{7, 0}
])
assert Enum.sort(moves) == expected_moves
end
test "rooks are blocked by other pieces of the same colour" do
board = %{
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,5" => %{"type" => "king", "colour" => "white"},
"0,5" => %{"type" => "king", "colour" => "white"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{0, 1}, {0, 2}, {0, 3}, {0, 4},
{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0},
])
expected_moves =
Enum.sort([
{0, 1},
{0, 2},
{0, 3},
{0, 4},
{1, 0},
{2, 0},
{3, 0},
{4, 0},
{5, 0},
{6, 0},
{7, 0}
])
assert Enum.sort(moves) == expected_moves
end
test "rooks can take an opponents piece" do
board = %{
"0,0" => %{"type" => "rook", "colour" => "white"},
"0,5" => %{"type" => "knight", "colour" => "black"},
"0,5" => %{"type" => "knight", "colour" => "black"}
}
moves = Moves.available(board, {0, 0})
expected_moves = Enum.sort([
{0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5},
{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0},
])
expected_moves =
Enum.sort([
{0, 1},
{0, 2},
{0, 3},
{0, 4},
{0, 5},
{1, 0},
{2, 0},
{3, 0},
{4, 0},
{5, 0},
{6, 0},
{7, 0}
])
assert Enum.sort(moves) == expected_moves
end
def board do
Chess.Board.default
Chess.Board.default()
end
end

View File

@ -7,51 +7,59 @@ defmodule Chess.Repo.QueriesTest do
describe "opponents" do
test "it finds a user on a partial name match" do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent = insert(:user, %{
name: "Princess Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent =
insert(:user, %{
name: "Princess Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
result =
user
|> Queries.opponents("zelda")
|> Repo.one
|> Repo.one()
assert result.id == opponent.id
end
test "it finds a user on a complete email match" do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent = insert(:user, %{
name: "Princess Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent =
insert(:user, %{
name: "Princess Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
result =
user
|> Queries.opponents("zelda@hyrule.com")
|> Repo.one
|> Repo.one()
assert result.id == opponent.id
end
test "it does not find a user on a partial email" do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
insert(:user, %{
name: "Princess Zelda",
email: "zelda@hyrule.com",
@ -61,7 +69,7 @@ defmodule Chess.Repo.QueriesTest do
result =
user
|> Queries.opponents("hyrule")
|> Repo.one
|> Repo.one()
assert result == nil
end

View File

@ -20,6 +20,7 @@ defmodule Chess.Store.GameTest do
opponent_id: opponent.id,
turn: "white"
}
changeset = Game.changeset(%Game{}, attrs)
assert changeset.valid?
@ -33,6 +34,7 @@ defmodule Chess.Store.GameTest do
opponent_id: 2,
turn: "white"
}
changeset = Game.changeset(%Game{}, attrs)
assert changeset.valid?
@ -81,18 +83,20 @@ defmodule Chess.Store.GameTest do
user = insert(:user)
opponent = insert(:opponent)
game = insert(:game, %{
board: Board.default,
user_id: user.id,
opponent_id: opponent.id,
})
game =
insert(:game, %{
board: Board.default(),
user_id: user.id,
opponent_id: opponent.id
})
move_params = %{"from" => [4, 1], "to" => [4, 3]}
changeset = Game.move_changeset(
game,
Board.move_piece(game.board, move_params)
)
changeset =
Game.move_changeset(
game,
Board.move_piece(game.board, move_params)
)
assert {:ok, new_game} = Repo.update(changeset)
assert new_game.turn == "black"

View File

@ -14,29 +14,32 @@ defmodule Chess.Store.MoveTest do
user = insert(:user)
opponent = insert(:opponent)
game = insert(:game, %{
board: Board.default,
user_id: user.id,
opponent_id: opponent.id,
})
game =
insert(:game, %{
board: Board.default(),
user_id: user.id,
opponent_id: opponent.id
})
changeset = Move.changeset(%Move{}, %{
game_id: game.id,
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3},
piece: %{"type" => "pawn", "colour" => "white"},
})
changeset =
Move.changeset(%Move{}, %{
game_id: game.id,
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3},
piece: %{"type" => "pawn", "colour" => "white"}
})
assert changeset.valid?
assert {:ok, _move} = Repo.insert(changeset)
end
test "move is invalid without a game" do
changeset = Move.changeset(%Move{}, %{
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3},
piece: %{"type" => "pawn", "colour" => "white"},
})
changeset =
Move.changeset(%Move{}, %{
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3},
piece: %{"type" => "pawn", "colour" => "white"}
})
refute changeset.valid?
end
@ -45,16 +48,18 @@ defmodule Chess.Store.MoveTest do
user = insert(:user)
opponent = insert(:opponent)
game = insert(:game, %{
board: Board.default,
user_id: user.id,
opponent_id: opponent.id,
})
game =
insert(:game, %{
board: Board.default(),
user_id: user.id,
opponent_id: opponent.id
})
changeset = Move.changeset(%Move{}, %{
game_id: game.id,
piece: %{"type" => "pawn", "colour" => "white"},
})
changeset =
Move.changeset(%Move{}, %{
game_id: game.id,
piece: %{"type" => "pawn", "colour" => "white"}
})
refute changeset.valid?
end
@ -63,17 +68,19 @@ defmodule Chess.Store.MoveTest do
user = insert(:user)
opponent = insert(:opponent)
game = insert(:game, %{
board: Board.default,
user_id: user.id,
opponent_id: opponent.id,
})
game =
insert(:game, %{
board: Board.default(),
user_id: user.id,
opponent_id: opponent.id
})
changeset = Move.changeset(%Move{}, %{
game_id: game.id,
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3},
})
changeset =
Move.changeset(%Move{}, %{
game_id: game.id,
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3}
})
refute changeset.valid?
end
@ -83,16 +90,16 @@ defmodule Chess.Store.MoveTest do
piece: %{"type" => "pawn", "colour" => "white"},
piece_captured: %{"type" => "pawn", "colour" => "black"},
from: %{"file" => 4, "rank" => 1},
to: %{"file" => 4, "rank" => 3},
to: %{"file" => 4, "rank" => 3}
}
assert Move.transform(move) == %{
id: nil,
piece: %{"type" => "pawn", "colour" => "white"},
piece_captured: %{"type" => "pawn", "colour" => "black"},
from: "e2",
to: "e4"
}
id: nil,
piece: %{"type" => "pawn", "colour" => "white"},
piece_captured: %{"type" => "pawn", "colour" => "black"},
from: "e2",
to: "e4"
}
end
end
end

View File

@ -8,10 +8,12 @@ defmodule ChessWeb.GameChannelTest do
test "assigns game_id to the socket after join" do
user = insert(:user)
game = insert(:game, %{
user_id: user.id,
opponent_id: insert(:opponent).id
})
game =
insert(:game, %{
user_id: user.id,
opponent_id: insert(:opponent).id
})
token = Phoenix.Token.sign(@endpoint, "game socket", user.id)
{:ok, socket} = connect(UserSocket, %{"token" => token})
@ -24,20 +26,22 @@ defmodule ChessWeb.GameChannelTest do
test "returns the game state after join" do
user = insert(:user)
opponent = insert(:opponent, %{name: "Daruk"})
game = insert(:game, %{
user_id: user.id,
opponent_id: opponent.id
})
game =
insert(:game, %{
user_id: user.id,
opponent_id: opponent.id
})
token = Phoenix.Token.sign(@endpoint, "game socket", user.id)
{:ok, socket} = connect(UserSocket, %{"token" => token})
{:ok, _, _} = subscribe_and_join(socket, "game:#{game.id}", %{})
assert_push "game:update", %{
assert_push("game:update", %{
player: "white",
opponent: "Daruk",
turn: "white",
}
turn: "white"
})
end
end

View File

@ -74,25 +74,25 @@ defmodule ChessWeb.GameControllerTest do
conn
|> login(other_user)
assert_error_sent 404, fn ->
get conn, game_path(conn, :show, game.id)
end
assert_error_sent(404, fn ->
get(conn, game_path(conn, :show, game.id))
end)
end
test "renders page not found when id is nonexistent", %{conn: conn} do
user = insert(:user)
conn = login(conn, user)
assert_error_sent 404, fn ->
get conn, game_path(conn, :show, -1)
end
assert_error_sent(404, fn ->
get(conn, game_path(conn, :show, -1))
end)
end
test "deletes game", %{conn: conn} do
game = Repo.insert! %Game{}
game = Repo.insert!(%Game{})
user = insert(:user)
conn = login(conn, user)
conn = delete conn, game_path(conn, :delete, game)
conn = delete(conn, game_path(conn, :delete, game))
assert redirected_to(conn) == game_path(conn, :index)
refute Repo.get(Game, game.id)
end

View File

@ -2,7 +2,7 @@ defmodule ChessWeb.SessionControllerTest do
use ChessWeb.ConnCase
test "shows log in form", %{conn: conn} do
conn = get conn, session_path(conn, :new)
conn = get(conn, session_path(conn, :new))
assert html_response(conn, 200) =~ "Log in"
end
end

View File

@ -40,22 +40,24 @@ defmodule Chess.Features.GamesTest do
|> click(button("Create game"))
session
|> assert_has(
css(".help-block", text: "can't be blank")
)
|> assert_has(css(".help-block", text: "can't be blank"))
end
test "can only see own games", %{session: session} do
opponent = insert(:user, %{
name: "Urbosa",
email: "urbosa@gerudo.town",
password: "gerudoqueen"
})
user = insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
opponent =
insert(:user, %{
name: "Urbosa",
email: "urbosa@gerudo.town",
password: "gerudoqueen"
})
user =
insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
insert(:game, %{user_id: user.id, opponent_id: opponent.id})
session
@ -72,16 +74,20 @@ defmodule Chess.Features.GamesTest do
end
test "can see games as an opponent", %{session: session} do
opponent = insert(:user, %{
name: "Urbosa",
email: "urbosa@gerudo.town",
password: "gerudoqueen"
})
user = insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
opponent =
insert(:user, %{
name: "Urbosa",
email: "urbosa@gerudo.town",
password: "gerudoqueen"
})
user =
insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganonsucks"
})
insert(:game, %{user_id: user.id, opponent_id: opponent.id})
session

View File

@ -22,27 +22,28 @@ defmodule Chess.Features.MovesTest do
|> click(link("New game"))
|> select_opponent("Zelda")
|> click(button("Create game"))
|> click(css("#f4-r1"))
|> assert_has(square_selected("f4-r1"))
|> assert_has(square_containing("f4-r1", %{type: "pawn", colour: "white"}))
|> click(css("#f4-r3"))
|> assert_has(square_containing("f4-r3", %{type: "pawn", colour: "white"}))
|> refute_has(square_containing("f4-r1", %{type: "pawn", colour: "white"}))
end
test "opponents recieves an email on move", %{session: session} do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent = insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganondorfsucks"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent =
insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganondorfsucks"
})
session
|> login("link@hyrule.com", "ilovezelda")
@ -88,6 +89,7 @@ defmodule Chess.Features.MovesTest do
email: "link@hyrule.com",
password: "ilovezelda"
})
insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
@ -101,7 +103,8 @@ defmodule Chess.Features.MovesTest do
|> select_opponent("Zelda")
|> click(button("Create game"))
{:ok, session2} = Wallaby.start_session
{:ok, session2} = Wallaby.start_session()
session2
|> login("zelda@hyrule.com", "ganondorfsucks")
|> click(link("Game with Link"))
@ -117,6 +120,7 @@ defmodule Chess.Features.MovesTest do
email: "link@hyrule.com",
password: "ilovezelda"
})
insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
@ -130,7 +134,8 @@ defmodule Chess.Features.MovesTest do
|> select_opponent("Zelda")
|> click(button("Create game"))
{:ok, session2} = Wallaby.start_session
{:ok, session2} = Wallaby.start_session()
session2
|> login("zelda@hyrule.com", "ganondorfsucks")
|> click(link("Game with Link"))
@ -147,25 +152,29 @@ defmodule Chess.Features.MovesTest do
test "cannot move the king into a position that would result in check",
%{session: session} do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent = insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganondorfsucks"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent =
insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganondorfsucks"
})
insert(:game, %{
board: %{
"4,0" => %{"type" => "king", "colour" => "white"},
"3,7" => %{"type" => "queen", "colour" => "black"},
"7,7" => %{"type" => "king", "colour" => "black"},
"4,0" => %{"type" => "king", "colour" => "white"},
"3,7" => %{"type" => "queen", "colour" => "black"},
"7,7" => %{"type" => "king", "colour" => "black"}
},
user_id: user.id,
opponent_id: opponent.id,
turn: "white",
turn: "white"
})
session
@ -182,26 +191,30 @@ defmodule Chess.Features.MovesTest do
test "cannot make a move that would place the king in check",
%{session: session} do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent = insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganondorfsucks"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent =
insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganondorfsucks"
})
insert(:game, %{
board: %{
"4,0" => %{"type" => "king", "colour" => "white"},
"4,1" => %{"type" => "rook", "colour" => "white"},
"4,7" => %{"type" => "queen", "colour" => "black"},
"7,7" => %{"type" => "king", "colour" => "black"},
"7,7" => %{"type" => "king", "colour" => "black"}
},
user_id: user.id,
opponent_id: opponent.id,
turn: "white",
turn: "white"
})
session
@ -217,25 +230,29 @@ defmodule Chess.Features.MovesTest do
end
test "user is informed when the game is in check", %{session: session} do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent = insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganondorfsucks"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
opponent =
insert(:user, %{
name: "Zelda",
email: "zelda@hyrule.com",
password: "ganondorfsucks"
})
insert(:game, %{
board: %{
"4,0" => %{"type" => "king", "colour" => "white"},
"3,7" => %{"type" => "queen", "colour" => "black"},
"7,7" => %{"type" => "king", "colour" => "black"},
"4,0" => %{"type" => "king", "colour" => "white"},
"3,7" => %{"type" => "queen", "colour" => "black"},
"7,7" => %{"type" => "king", "colour" => "black"}
},
user_id: user.id,
opponent_id: opponent.id,
turn: "black",
turn: "black"
})
session

View File

@ -7,11 +7,12 @@ defmodule Chess.Features.PasswordTest do
import Chess.AuthenticationHelpers
test "user can change their password", %{session: session} do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
session
|> login(user.email, "ilovezelda")
@ -26,11 +27,12 @@ defmodule Chess.Features.PasswordTest do
end
test "password cannot be blank", %{session: session} do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
session
|> login(user.email, "ilovezelda")
@ -41,8 +43,6 @@ defmodule Chess.Features.PasswordTest do
|> click(button("Update Password"))
session
|> assert_has(
css("[data-role='password-error']", text: "can't be blank")
)
|> assert_has(css("[data-role='password-error']", text: "can't be blank"))
end
end

View File

@ -7,11 +7,12 @@ defmodule Chess.Features.ProfileTest do
import Chess.AuthenticationHelpers
test "user can update their details", %{session: session} do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
session
|> login(user.email, "ilovezelda")
@ -25,11 +26,12 @@ defmodule Chess.Features.ProfileTest do
end
test "name cannot be blank", %{session: session} do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
session
|> login(user.email, "ilovezelda")
@ -40,17 +42,16 @@ defmodule Chess.Features.ProfileTest do
|> click(button("Update Profile"))
session
|> assert_has(
css("[data-role='name-error']", text: "can't be blank")
)
|> assert_has(css("[data-role='name-error']", text: "can't be blank"))
end
test "email cannot be blank", %{session: session} do
user = insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
user =
insert(:user, %{
name: "Link",
email: "link@hyrule.com",
password: "ilovezelda"
})
session
|> login(user.email, "ilovezelda")
@ -61,8 +62,6 @@ defmodule Chess.Features.ProfileTest do
|> click(button("Update Profile"))
session
|> assert_has(
css("[data-role='email-error']", text: "can't be blank")
)
|> assert_has(css("[data-role='email-error']", text: "can't be blank"))
end
end

View File

@ -25,9 +25,7 @@ defmodule Chess.Features.RegistrationTest do
|> click(button("Register"))
session
|> assert_has(
css("[data-role='name-error']", text: "can't be blank")
)
|> assert_has(css("[data-role='name-error']", text: "can't be blank"))
end
test "user cannot register without an email", %{session: session} do
@ -39,9 +37,7 @@ defmodule Chess.Features.RegistrationTest do
|> click(button("Register"))
session
|> assert_has(
css("[data-role='email-error']", text: "can't be blank")
)
|> assert_has(css("[data-role='email-error']", text: "can't be blank"))
end
test "user cannot register without a password", %{session: session} do
@ -53,8 +49,6 @@ defmodule Chess.Features.RegistrationTest do
|> click(button("Register"))
session
|> assert_has(
css("[data-role='password-error']", text: "can't be blank")
)
|> assert_has(css("[data-role='password-error']", text: "can't be blank"))
end
end

View File

@ -5,7 +5,7 @@ defmodule Chess.FormHelpers do
import Wallaby.Query
def select(session, name, [option: option]) do
def select(session, name, option: option) do
session
|> find(css("[name='#{name}']"))
|> click(option(option))

View File

@ -18,7 +18,7 @@ defmodule Chess.Factory do
%User{}
|> User.changeset(params)
|> Repo.insert!
|> Repo.insert!()
end
def insert(:opponent, new_params) do
@ -32,12 +32,12 @@ defmodule Chess.Factory do
%User{}
|> User.changeset(params)
|> Repo.insert!
|> Repo.insert!()
end
def insert(:game, params) do
%Game{}
|> Game.changeset(params)
|> Repo.insert!
|> Repo.insert!()
end
end

View File

@ -4,4 +4,4 @@ ExUnit.start()
Ecto.Adapters.SQL.Sandbox.mode(Chess.Repo, {:shared, self()})
Application.put_env(:wallaby, :base_url, ChessWeb.Endpoint.url)
Application.put_env(:wallaby, :base_url, ChessWeb.Endpoint.url())

View File

@ -8,16 +8,16 @@ defmodule Chess.ErrorViewTest do
test "renders 404.html" do
assert render_to_string(ErrorView, "404.html", []) ==
"Page not found"
"Page not found"
end
test "render 500.html" do
assert render_to_string(ErrorView, "500.html", []) ==
"Internal server error"
"Internal server error"
end
test "render any other" do
assert render_to_string(ErrorView, "505.html", []) ==
"Internal server error"
"Internal server error"
end
end