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

Show game state on the board

This commit is contained in:
Daniel Barber 2018-04-07 17:17:35 -04:00
parent 6b31491ad4
commit 5556de00b0
Signed by: danbarber
GPG Key ID: 931D8112E0103DD8
12 changed files with 180 additions and 64 deletions

View File

@ -49,6 +49,35 @@
&.player-is-black.white-to-move::before { &.player-is-black.white-to-move::before {
top: 2%; 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 { .board-rank-labels {
@ -59,26 +88,6 @@
width: 5%; 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 { .board-file-labels {
display: flex; display: flex;
height: 5%; height: 5%;
@ -87,6 +96,14 @@
width: 90%; width: 90%;
} }
.board-game-state {
bottom: 0;
color: white;
height: 5%;
position: absolute;
width: 90%;
}
.board-label { .board-label {
align-items: center; align-items: center;
display: flex; display: flex;

View File

@ -4,6 +4,8 @@ import { connect } from "react-redux";
import classNames from "classnames"; import classNames from "classnames";
import ChessBoardSquare from "./chess-board-square"; import ChessBoardSquare from "./chess-board-square";
import RankLabels from "./rank-labels";
import FileLabels from "./file-labels";
class ChessBoard extends React.Component { class ChessBoard extends React.Component {
componentWillMount() { 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() { get boardClass() {
const turn = this.getTurn(); const turn = this.getTurn();
const player = this.getPlayer(); const player = this.getPlayer();
@ -79,43 +87,19 @@ class ChessBoard extends React.Component {
return classNames("board", turn + "-to-move", "player-is-" + player); 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 (
<div key={rankLabel} className="board-label">{rankLabel}</div>
);
});
}
renderFileLabels() {
return _.map(this.fileLabels, (fileLabel) => {
return (
<div key={fileLabel} className="board-label">{fileLabel}</div>
);
});
}
render() { render() {
return ( return (
<div className={this.boardClass}> <div className={this.boardClass}>
<div className="board-rank-labels"> <RankLabels />
{this.renderRankLabels()} <FileLabels />
</div>
<div className="board-file-labels">
{this.renderFileLabels()}
</div>
<div className="board-body"> <div className="board-body">
{this.renderSquares()} {this.renderSquares()}
</div> </div>
<div className="board-game-state">
{this.gameState}
</div>
</div> </div>
); );
} }

View File

@ -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 (
<div key={fileLabel} className="board-label">{fileLabel}</div>
);
});
}
render() {
return (
<div className="board-file-labels">
{this.renderFileLabels()}
</div>
);
}
}
export default FileLabels;

View File

@ -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 (
<div key={rankLabel} className="board-label">{rankLabel}</div>
);
});
}
render() {
return (
<div className="board-rank-labels">
{this.renderRankLabels()}
</div>
);
}
}
export default RankLabels;

View File

@ -13,6 +13,7 @@ const chessBoardReducer = (state = defaultState, action) => {
return Immutable.fromJS(state) return Immutable.fromJS(state)
.set("board", action.board) .set("board", action.board)
.set("turn", action.turn) .set("turn", action.turn)
.set("state", action.state)
.set("selectedSquare", null) .set("selectedSquare", null)
.set("moves", []) .set("moves", [])
.toJS(); .toJS();

View File

@ -16,6 +16,7 @@ export const setGame = (data) => {
type: SET_GAME, type: SET_GAME,
board: data.board, board: data.board,
turn: data.turn, turn: data.turn,
state: data.state,
}; };
}; };

View File

@ -3,6 +3,7 @@ const defaultState = {
player: null, player: null,
turn: null, turn: null,
state: null,
moves: [], moves: [],

View File

@ -5,16 +5,48 @@ defmodule Chess.GameState do
alias Chess.Moves alias Chess.Moves
alias Chess.Moves.Piece 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 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
|> Board.search(%{"colour" => colour}) |> Board.search(%{"colour" => colour})
|> Enum.all?(fn({file, rank}) -> |> Enum.all?(fn({file, rank}) ->
board board
|> cannot_escape_check?({file, rank}) |> piece_cannot_move?({file, rank})
end) end)
end end
def cannot_escape_check?(board, {file, rank}) do def piece_cannot_move?(board, {file, rank}) do
piece = piece =
board board
|> Board.piece({file, rank}) |> Board.piece({file, rank})
@ -27,14 +59,4 @@ defmodule Chess.GameState do
|> king_in_check?(piece["colour"]) |> king_in_check?(piece["colour"])
end) end)
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 end

View File

@ -15,6 +15,7 @@ defmodule Chess.Store.Game do
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
belongs_to :user, Chess.Store.User belongs_to :user, Chess.Store.User
belongs_to :opponent, Chess.Store.User, references: :id belongs_to :opponent, Chess.Store.User, references: :id
@ -34,6 +35,7 @@ defmodule Chess.Store.Game do
struct struct
|> cast(params, required_attrs()) |> cast(params, required_attrs())
|> validate_king_in_check(struct, params) |> validate_king_in_check(struct, params)
|> check_game_state(struct, params)
end end
def change_turn("black"), do: "white" def change_turn("black"), do: "white"
@ -49,6 +51,11 @@ defmodule Chess.Store.Game do
or_where: game.opponent_id == ^user_id or_where: game.opponent_id == ^user_id
end 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 def validate_king_in_check(changeset, %Game{turn: turn}, %{board: board}) do
if GameState.king_in_check?(board, turn) do if GameState.king_in_check?(board, turn) do
changeset changeset

View File

@ -22,7 +22,8 @@ defmodule ChessWeb.GameChannel do
payload = %{ payload = %{
player: player(socket, game), player: player(socket, game),
board: Board.transform(game.board), board: Board.transform(game.board),
turn: game.turn turn: game.turn,
state: game.state
} }
socket socket
@ -81,7 +82,8 @@ defmodule ChessWeb.GameChannel do
def send_update(game) do def send_update(game) do
payload = %{ payload = %{
board: Board.transform(game.board), board: Board.transform(game.board),
turn: game.turn turn: game.turn,
state: game.state
} }
ChessWeb.Endpoint.broadcast("game:#{game.id}", "game:update", payload) ChessWeb.Endpoint.broadcast("game:#{game.id}", "game:update", payload)
end end

View File

@ -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

View File

@ -91,4 +91,14 @@ defmodule Chess.GameStateTest do
refute GameState.player_checkmated?(board, "white") refute GameState.player_checkmated?(board, "white")
end 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 end