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:
parent
6b31491ad4
commit
5556de00b0
@ -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;
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
31
assets/js/components/file-labels.js
Normal file
31
assets/js/components/file-labels.js
Normal 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;
|
||||
31
assets/js/components/rank-labels.js
Normal file
31
assets/js/components/rank-labels.js
Normal 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;
|
||||
@ -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();
|
||||
|
||||
@ -16,6 +16,7 @@ export const setGame = (data) => {
|
||||
type: SET_GAME,
|
||||
board: data.board,
|
||||
turn: data.turn,
|
||||
state: data.state,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ const defaultState = {
|
||||
|
||||
player: null,
|
||||
turn: null,
|
||||
state: null,
|
||||
|
||||
moves: [],
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user