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 {
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;

View File

@ -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 (
<div key={rankLabel} className="board-label">{rankLabel}</div>
);
});
}
renderFileLabels() {
return _.map(this.fileLabels, (fileLabel) => {
return (
<div key={fileLabel} className="board-label">{fileLabel}</div>
);
});
}
render() {
return (
<div className={this.boardClass}>
<div className="board-rank-labels">
{this.renderRankLabels()}
</div>
<div className="board-file-labels">
{this.renderFileLabels()}
</div>
<RankLabels />
<FileLabels />
<div className="board-body">
{this.renderSquares()}
</div>
<div className="board-game-state">
{this.gameState}
</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)
.set("board", action.board)
.set("turn", action.turn)
.set("state", action.state)
.set("selectedSquare", null)
.set("moves", [])
.toJS();

View File

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

View File

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

View File

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

View File

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

View File

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

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")
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