diff --git a/assets/css/_board.scss b/assets/css/_board.scss
index 2aef1aa..a9f05dc 100644
--- a/assets/css/_board.scss
+++ b/assets/css/_board.scss
@@ -49,6 +49,35 @@
&.player-is-black.white-to-move::before {
top: 2%;
}
+
+ .board-rank-labels,
+ .board-file-labels {
+ display: none;
+ }
+
+ &.player-is-white {
+ .board-rank-labels {
+ display: flex;
+ flex-direction: column-reverse;
+ }
+
+ .board-file-labels {
+ display: flex;
+ flex-direction: row;
+ }
+ }
+
+ &.player-is-black {
+ .board-rank-labels {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .board-file-labels {
+ display: flex;
+ flex-direction: row-reverse;
+ }
+ }
}
.board-rank-labels {
@@ -59,26 +88,6 @@
width: 5%;
}
-.board.player-is-white {
- .board-rank-labels {
- flex-direction: column-reverse;
- }
-
- .board-file-labels {
- flex-direction: row;
- }
-}
-
-.board.player-is-black {
- .board-rank-labels {
- flex-direction: column;
- }
-
- .board-file-labels {
- flex-direction: row-reverse;
- }
-}
-
.board-file-labels {
display: flex;
height: 5%;
@@ -87,6 +96,14 @@
width: 90%;
}
+.board-game-state {
+ bottom: 0;
+ color: white;
+ height: 5%;
+ position: absolute;
+ width: 90%;
+}
+
.board-label {
align-items: center;
display: flex;
diff --git a/assets/js/components/chess-board.js b/assets/js/components/chess-board.js
index ac417e9..e8af715 100644
--- a/assets/js/components/chess-board.js
+++ b/assets/js/components/chess-board.js
@@ -4,6 +4,8 @@ import { connect } from "react-redux";
import classNames from "classnames";
import ChessBoardSquare from "./chess-board-square";
+import RankLabels from "./rank-labels";
+import FileLabels from "./file-labels";
class ChessBoard extends React.Component {
componentWillMount() {
@@ -72,6 +74,12 @@ class ChessBoard extends React.Component {
});
}
+ get gameState() {
+ const { store } = this.props;
+ console.log(store.getState().state);
+ return store.getState().state;
+ }
+
get boardClass() {
const turn = this.getTurn();
const player = this.getPlayer();
@@ -79,43 +87,19 @@ class ChessBoard extends React.Component {
return classNames("board", turn + "-to-move", "player-is-" + player);
}
- get rankLabels() {
- return [1, 2, 3, 4, 5, 6, 7, 8];
- }
-
- get fileLabels() {
- return ["a", "b", "c", "d", "e", "f", "g", "h"];
- }
-
- renderRankLabels() {
- return _.map(this.rankLabels, (rankLabel) => {
- return (
-
{rankLabel}
- );
- });
- }
-
- renderFileLabels() {
- return _.map(this.fileLabels, (fileLabel) => {
- return (
- {fileLabel}
- );
- });
- }
-
render() {
return (
-
- {this.renderRankLabels()}
-
-
- {this.renderFileLabels()}
-
+
+
{this.renderSquares()}
+
+
+ {this.gameState}
+
);
}
diff --git a/assets/js/components/file-labels.js b/assets/js/components/file-labels.js
new file mode 100644
index 0000000..81cc3da
--- /dev/null
+++ b/assets/js/components/file-labels.js
@@ -0,0 +1,31 @@
+import React from "react";
+import _ from "lodash";
+import classNames from "classnames";
+
+class FileLabels extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ get fileLabels() {
+ return ["a", "b", "c", "d", "e", "f", "g", "h"];
+ }
+
+ renderFileLabels() {
+ return _.map(this.fileLabels, (fileLabel) => {
+ return (
+ {fileLabel}
+ );
+ });
+ }
+
+ render() {
+ return (
+
+ {this.renderFileLabels()}
+
+ );
+ }
+}
+
+export default FileLabels;
diff --git a/assets/js/components/rank-labels.js b/assets/js/components/rank-labels.js
new file mode 100644
index 0000000..00781f7
--- /dev/null
+++ b/assets/js/components/rank-labels.js
@@ -0,0 +1,31 @@
+import React from "react";
+import _ from "lodash";
+import classNames from "classnames";
+
+class RankLabels extends React.Component {
+ constructor(props) {
+ super(props);
+ }
+
+ get rankLabels() {
+ return [1, 2, 3, 4, 5, 6, 7, 8];
+ }
+
+ renderRankLabels() {
+ return _.map(this.rankLabels, (rankLabel) => {
+ return (
+ {rankLabel}
+ );
+ });
+ }
+
+ render() {
+ return (
+
+ {this.renderRankLabels()}
+
+ );
+ }
+}
+
+export default RankLabels;
diff --git a/assets/js/reducers/chess-board.js b/assets/js/reducers/chess-board.js
index d838d7c..3a9d3b6 100644
--- a/assets/js/reducers/chess-board.js
+++ b/assets/js/reducers/chess-board.js
@@ -13,6 +13,7 @@ const chessBoardReducer = (state = defaultState, action) => {
return Immutable.fromJS(state)
.set("board", action.board)
.set("turn", action.turn)
+ .set("state", action.state)
.set("selectedSquare", null)
.set("moves", [])
.toJS();
diff --git a/assets/js/store/actions.js b/assets/js/store/actions.js
index f0dd769..96ea577 100644
--- a/assets/js/store/actions.js
+++ b/assets/js/store/actions.js
@@ -16,6 +16,7 @@ export const setGame = (data) => {
type: SET_GAME,
board: data.board,
turn: data.turn,
+ state: data.state,
};
};
diff --git a/assets/js/store/default-state.js b/assets/js/store/default-state.js
index 2613f47..1a3102e 100644
--- a/assets/js/store/default-state.js
+++ b/assets/js/store/default-state.js
@@ -3,6 +3,7 @@ const defaultState = {
player: null,
turn: null,
+ state: null,
moves: [],
diff --git a/lib/chess/game_state.ex b/lib/chess/game_state.ex
index 7b22fd7..79b78ce 100644
--- a/lib/chess/game_state.ex
+++ b/lib/chess/game_state.ex
@@ -5,16 +5,48 @@ defmodule Chess.GameState do
alias Chess.Moves
alias Chess.Moves.Piece
+ def state(board, colour) do
+ cond do
+ player_checkmated?(board, colour) ->
+ "checkmate"
+ player_stalemated?(board, colour) ->
+ "stalemate"
+ king_in_check?(board, colour) ->
+ "check"
+ true -> nil
+ end
+ end
+
def player_checkmated?(board, colour) do
+ king_in_check?(board, colour) &&
+ player_cannot_move?(board, colour)
+ end
+
+ def player_stalemated?(board, colour) do
+ !king_in_check?(board, colour) &&
+ player_cannot_move?(board, colour)
+ end
+
+ def king_in_check?(board, colour) do
+ king =
+ board
+ |> Board.search(%{"type" => "king", "colour" => colour})
+ |> List.first
+
+ board
+ |> Piece.attacked?(king)
+ end
+
+ def player_cannot_move?(board, colour) do
board
|> Board.search(%{"colour" => colour})
|> Enum.all?(fn({file, rank}) ->
board
- |> cannot_escape_check?({file, rank})
+ |> piece_cannot_move?({file, rank})
end)
end
- def cannot_escape_check?(board, {file, rank}) do
+ def piece_cannot_move?(board, {file, rank}) do
piece =
board
|> Board.piece({file, rank})
@@ -27,14 +59,4 @@ defmodule Chess.GameState do
|> king_in_check?(piece["colour"])
end)
end
-
- def king_in_check?(board, colour) do
- king =
- board
- |> Board.search(%{"type" => "king", "colour" => colour})
- |> List.first
-
- board
- |> Piece.attacked?(king)
- end
end
diff --git a/lib/chess/store/game.ex b/lib/chess/store/game.ex
index 48c4dc6..cfa04ce 100644
--- a/lib/chess/store/game.ex
+++ b/lib/chess/store/game.ex
@@ -15,6 +15,7 @@ defmodule Chess.Store.Game do
schema "games" do
field :board, :map, default: Board.default()
field :turn, :string, default: "white"
+ field :state, :string
belongs_to :user, Chess.Store.User
belongs_to :opponent, Chess.Store.User, references: :id
@@ -34,6 +35,7 @@ defmodule Chess.Store.Game do
struct
|> cast(params, required_attrs())
|> validate_king_in_check(struct, params)
+ |> check_game_state(struct, params)
end
def change_turn("black"), do: "white"
@@ -49,6 +51,11 @@ defmodule Chess.Store.Game do
or_where: game.opponent_id == ^user_id
end
+ def check_game_state(changeset, _struct, params) do
+ changeset
+ |> put_change(:state, GameState.state(params.board, params.turn))
+ end
+
def validate_king_in_check(changeset, %Game{turn: turn}, %{board: board}) do
if GameState.king_in_check?(board, turn) do
changeset
diff --git a/lib/chess_web/channels/game_channel.ex b/lib/chess_web/channels/game_channel.ex
index 4fe6d4b..0297259 100644
--- a/lib/chess_web/channels/game_channel.ex
+++ b/lib/chess_web/channels/game_channel.ex
@@ -22,7 +22,8 @@ defmodule ChessWeb.GameChannel do
payload = %{
player: player(socket, game),
board: Board.transform(game.board),
- turn: game.turn
+ turn: game.turn,
+ state: game.state
}
socket
@@ -81,7 +82,8 @@ defmodule ChessWeb.GameChannel do
def send_update(game) do
payload = %{
board: Board.transform(game.board),
- turn: game.turn
+ turn: game.turn,
+ state: game.state
}
ChessWeb.Endpoint.broadcast("game:#{game.id}", "game:update", payload)
end
diff --git a/priv/repo/migrations/20180407190333_add_state_to_game.exs b/priv/repo/migrations/20180407190333_add_state_to_game.exs
new file mode 100644
index 0000000..e6d7e2d
--- /dev/null
+++ b/priv/repo/migrations/20180407190333_add_state_to_game.exs
@@ -0,0 +1,9 @@
+defmodule Chess.Repo.Migrations.AddStateToGame do
+ use Ecto.Migration
+
+ def change do
+ alter table("games") do
+ add :state, :string
+ end
+ end
+end
diff --git a/test/chess/game_state_test.exs b/test/chess/game_state_test.exs
index d04b251..8453293 100644
--- a/test/chess/game_state_test.exs
+++ b/test/chess/game_state_test.exs
@@ -91,4 +91,14 @@ defmodule Chess.GameStateTest do
refute GameState.player_checkmated?(board, "white")
end
+
+ test "game can be stalemated" do
+ board = %{
+ "0,0" => %{"type" => "king", "colour" => "white"},
+ "1,2" => %{"type" => "rook", "colour" => "black"},
+ "2,1" => %{"type" => "rook", "colour" => "black"},
+ }
+
+ assert GameState.player_stalemated?(board, "white")
+ end
end