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

Show available moves for pawn

This commit is contained in:
Daniel Barber 2018-03-11 23:21:17 -04:00
parent c64d40f99a
commit 375be4711c
Signed by: danbarber
GPG Key ID: 931D8112E0103DD8
14 changed files with 123 additions and 54 deletions

View File

@ -117,6 +117,11 @@ $board-size: 90vmin;
background-color: mix($black-square-color, $selected-square-color, 60%); background-color: mix($black-square-color, $selected-square-color, 60%);
box-shadow: 0 0 0 ($board-size / 400) $selected-outline-color; box-shadow: 0 0 0 ($board-size / 400) $selected-outline-color;
} }
&.available {
background-color: mix($black-square-color, $available-square-color, 80%);
box-shadow: 0 0 0 ($board-size / 400) $available-outline-color;
}
} }
@mixin white-square { @mixin white-square {
@ -126,6 +131,11 @@ $board-size: 90vmin;
background-color: mix($white-square-color, $selected-square-color, 60%); background-color: mix($white-square-color, $selected-square-color, 60%);
box-shadow: 0 0 0 ($board-size / 400) $selected-outline-color; box-shadow: 0 0 0 ($board-size / 400) $selected-outline-color;
} }
&.available {
background-color: mix($white-square-color, $available-square-color, 80%);
box-shadow: 0 0 0 ($board-size / 400) $available-outline-color;
}
} }
.board-rank:nth-child(odd) { .board-rank:nth-child(odd) {

View File

@ -15,10 +15,12 @@ $base-font-size: 16px;
$black-square-color: #777; $black-square-color: #777;
$white-square-color: #bbb; $white-square-color: #bbb;
$selected-square-color: #6f0; $selected-square-color: #0cf;
$available-square-color: #6f0;
$square-outline-color: darken($black-square-color, 20%); $square-outline-color: darken($black-square-color, 20%);
$selected-outline-color: lighten($selected-square-color, 20%); $selected-outline-color: lighten($selected-square-color, 20%);
$available-outline-color: rgba(lighten($available-square-color, 20%), 0.7);
$colours: "black" "white"; $colours: "black" "white";
$pieces: king queen bishop knight rook pawn; $pieces: king queen bishop knight rook pawn;

View File

@ -24,10 +24,6 @@ class App extends React.Component {
this.channel = new Channel(store, gameId); this.channel = new Channel(store, gameId);
} }
sendMove(gameId, move) {
this.channel.sendMove(move);
}
render() { render() {
const { store, gameId } = this.props; const { store, gameId } = this.props;
@ -35,7 +31,7 @@ class App extends React.Component {
<ChessBoard <ChessBoard
gameId={gameId} gameId={gameId}
store={store} store={store}
sendMove={this.sendMove.bind(this)} channel={this.channel}
/> />
); );
} }

View File

@ -4,7 +4,7 @@ import classNames from "classnames";
import API from "../services/api"; import API from "../services/api";
import { setGame, selectPiece } from "../store/actions"; import { setGame, setMoves, selectPiece } from "../store/actions";
class ChessBoardSquare extends React.Component { class ChessBoardSquare extends React.Component {
constructor(props) { constructor(props) {
@ -16,23 +16,26 @@ class ChessBoardSquare extends React.Component {
} }
selectSquare() { selectSquare() {
const { piece, store, sendMove } = this.props; const { piece, store, channel } = this.props;
const { gameId, selectedSquare, player } = store.getState(); const { gameId, selectedSquare, player } = store.getState();
if (selectedSquare != null && this.moveIsValid()) { if (selectedSquare != null && this.moveIsValid()) {
sendMove(gameId, { store.dispatch(setMoves([]));
channel.sendMove({
from: selectedSquare, from: selectedSquare,
to: this.squareCoords, to: this.squareCoords,
}); });
} else if (selectedSquare != null) { } else if (selectedSquare != null) {
store.dispatch(setMoves([]));
store.dispatch(selectPiece(null)); store.dispatch(selectPiece(null));
} else if (this.playerCanSelectPiece(player, piece)) { } else if (this.playerCanSelectPiece(player, piece)) {
channel.getAvailableMoves(this.squareCoords);
store.dispatch(selectPiece(this.squareCoords)); store.dispatch(selectPiece(this.squareCoords));
} }
} }
moveIsValid() { moveIsValid() {
return !this.isSelectedSquare(); return !this.isSelectedSquare;
} }
playerCanSelectPiece(player, piece) { playerCanSelectPiece(player, piece) {
@ -44,7 +47,7 @@ class ChessBoardSquare extends React.Component {
player == turn; player == turn;
} }
isSelectedSquare() { get isSelectedSquare() {
const { store } = this.props; const { store } = this.props;
if (store.getState().selectedSquare == null) { if (store.getState().selectedSquare == null) {
@ -54,19 +57,32 @@ class ChessBoardSquare extends React.Component {
} }
} }
get isAvailableSquare() {
const { store } = this.props;
const moves = store.getState().moves;
return _.find(moves, function(square) {
return square.join() == this.squareCoords.join();
}.bind(this));
}
squareId() { squareId() {
return `f${this.props.file}-r${this.props.rank}`; return `f${this.props.file}-r${this.props.rank}`;
} }
get squareClass() { get squareClass() {
if (this.props.piece == undefined) { if (this.props.piece == undefined) {
return "board-square"; return classNames(
"board-square",
{ "available": this.isAvailableSquare }
);
} else { } else {
return classNames( return classNames(
"board-square", "board-square",
this.props.piece.type, this.props.piece.type,
this.props.piece.colour, this.props.piece.colour,
{ "selected": this.isSelectedSquare() } { "selected": this.isSelectedSquare },
{ "available": this.isAvailableSquare }
); );
} }
} }

View File

@ -26,7 +26,7 @@ class ChessBoard extends React.Component {
} }
renderFiles(rankId) { renderFiles(rankId) {
const { store, sendMove } = this.props; const { store, channel } = this.props;
const rank = this.getBoard()[rankId]; const rank = this.getBoard()[rankId];
return _.map(this.files(rank), fileId => { return _.map(this.files(rank), fileId => {
@ -37,7 +37,7 @@ class ChessBoard extends React.Component {
rank={rankId} rank={rankId}
piece={rank[fileId]} piece={rank[fileId]}
store={store} store={store}
sendMove={sendMove} channel={channel}
/> />
); );
}); });

View File

@ -16,6 +16,11 @@ const chessBoardReducer = (state = defaultState, action) => {
.set("selectedSquare", null) .set("selectedSquare", null)
.toJS(); .toJS();
case "SET_MOVES":
return Immutable.fromJS(state)
.set("moves", action.moves)
.toJS();
case "SET_GAME_ID": case "SET_GAME_ID":
return Immutable.fromJS(state) return Immutable.fromJS(state)
.set("gameId", action.gameId) .set("gameId", action.gameId)

View File

@ -1,5 +1,5 @@
import socket from "../socket"; import socket from "../socket";
import { setPlayer, setGame } from "../store/actions"; import { setPlayer, setGame, setMoves } from "../store/actions";
class Channel { class Channel {
constructor(store, gameId) { constructor(store, gameId) {
@ -26,6 +26,13 @@ class Channel {
}); });
} }
getAvailableMoves(square) {
this.channel.push("game:get_available_moves", { square })
.receive("ok", (data) => {
this.store.dispatch(setMoves(data.moves));
});
}
sendMove(move) { sendMove(move) {
this.channel.push("game:move", move); this.channel.push("game:move", move);
} }

View File

@ -1,5 +1,6 @@
const SET_PLAYER = "SET_PLAYER"; const SET_PLAYER = "SET_PLAYER";
const SET_GAME = "SET_GAME"; const SET_GAME = "SET_GAME";
const SET_MOVES = "SET_MOVES";
const SET_GAME_ID = "SET_GAME_ID"; const SET_GAME_ID = "SET_GAME_ID";
const SELECT_PIECE = "SELECT_PIECE"; const SELECT_PIECE = "SELECT_PIECE";
@ -18,6 +19,13 @@ export const setGame = (data) => {
}; };
}; };
export const setMoves = (moves) => {
return {
type: SET_MOVES,
moves,
};
};
export const setGameId = (gameId) => { export const setGameId = (gameId) => {
return { return {
type: SET_GAME_ID, type: SET_GAME_ID,

View File

@ -4,6 +4,8 @@ const defaultState = {
player: null, player: null,
turn: null, turn: null,
moves: [],
board: { board: {
8: { a: null, b: null, c: null, d: null, e: null, f: null, g: null, h: null }, 8: { a: null, b: null, c: null, d: null, e: null, f: null, g: null, h: null },
7: { a: null, b: null, c: null, d: null, e: null, f: null, g: null, h: null }, 7: { a: null, b: null, c: null, d: null, e: null, f: null, g: null, h: null },

View File

@ -20,41 +20,41 @@ defmodule Chess.Board do
def default do def default do
%{ %{
"0,7" => %{type: :rook, colour: :black}, "0,7" => %{"type" => "rook", "colour" => "black"},
"1,7" => %{type: :knight, colour: :black}, "1,7" => %{"type" => "knight", "colour" => "black"},
"2,7" => %{type: :bishop, colour: :black}, "2,7" => %{"type" => "bishop", "colour" => "black"},
"3,7" => %{type: :queen, colour: :black}, "3,7" => %{"type" => "queen", "colour" => "black"},
"4,7" => %{type: :king, colour: :black}, "4,7" => %{"type" => "king", "colour" => "black"},
"5,7" => %{type: :bishop, colour: :black}, "5,7" => %{"type" => "bishop", "colour" => "black"},
"6,7" => %{type: :knight, 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}, "0,6" => %{"type" => "pawn", "colour" => "black"},
"1,6" => %{type: :pawn, colour: :black}, "1,6" => %{"type" => "pawn", "colour" => "black"},
"2,6" => %{type: :pawn, colour: :black}, "2,6" => %{"type" => "pawn", "colour" => "black"},
"3,6" => %{type: :pawn, colour: :black}, "3,6" => %{"type" => "pawn", "colour" => "black"},
"4,6" => %{type: :pawn, colour: :black}, "4,6" => %{"type" => "pawn", "colour" => "black"},
"5,6" => %{type: :pawn, colour: :black}, "5,6" => %{"type" => "pawn", "colour" => "black"},
"6,6" => %{type: :pawn, colour: :black}, "6,6" => %{"type" => "pawn", "colour" => "black"},
"7,6" => %{type: :pawn, colour: :black}, "7,6" => %{"type" => "pawn", "colour" => "black"},
"0,1" => %{type: :pawn, colour: :white}, "0,1" => %{"type" => "pawn", "colour" => "white"},
"1,1" => %{type: :pawn, colour: :white}, "1,1" => %{"type" => "pawn", "colour" => "white"},
"2,1" => %{type: :pawn, colour: :white}, "2,1" => %{"type" => "pawn", "colour" => "white"},
"3,1" => %{type: :pawn, colour: :white}, "3,1" => %{"type" => "pawn", "colour" => "white"},
"4,1" => %{type: :pawn, colour: :white}, "4,1" => %{"type" => "pawn", "colour" => "white"},
"5,1" => %{type: :pawn, colour: :white}, "5,1" => %{"type" => "pawn", "colour" => "white"},
"6,1" => %{type: :pawn, colour: :white}, "6,1" => %{"type" => "pawn", "colour" => "white"},
"7,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}, "1,0" => %{"type" => "knight", "colour" => "white"},
"2,0" => %{type: :bishop, colour: :white}, "2,0" => %{"type" => "bishop", "colour" => "white"},
"3,0" => %{type: :queen, colour: :white}, "3,0" => %{"type" => "queen", "colour" => "white"},
"4,0" => %{type: :king, colour: :white}, "4,0" => %{"type" => "king", "colour" => "white"},
"5,0" => %{type: :bishop, colour: :white}, "5,0" => %{"type" => "bishop", "colour" => "white"},
"6,0" => %{type: :knight, colour: :white}, "6,0" => %{"type" => "knight", "colour" => "white"},
"7,0" => %{type: :rook, colour: :white} "7,0" => %{"type" => "rook", "colour" => "white"}
} }
end end
end end

View File

@ -7,7 +7,7 @@ defmodule Chess.Moves do
piece = board["#{file},#{rank}"] piece = board["#{file},#{rank}"]
case piece do case piece do
%{type: :pawn} -> %{"type" => "pawn"} ->
Pawn.moves(board, {file, rank}) Pawn.moves(board, {file, rank})
end end
end end

View File

@ -5,12 +5,12 @@ defmodule Chess.Moves.Pawn do
piece = board["#{file},#{rank}"] piece = board["#{file},#{rank}"]
case piece do case piece do
%{colour: :white} -> %{"colour" => "white"} ->
case rank do case rank do
1 -> [{file, rank + 1}, {file, rank + 2}] 1 -> [{file, rank + 1}, {file, rank + 2}]
_ -> [{file, rank + 1}] _ -> [{file, rank + 1}]
end end
%{colour: :black} -> %{"colour" => "black"} ->
case rank do case rank do
6 -> [{file, rank - 1}, {file, rank - 2}] 6 -> [{file, rank - 1}, {file, rank - 2}]
_ -> [{file, rank - 1}] _ -> [{file, rank - 1}]

View File

@ -5,6 +5,7 @@ defmodule ChessWeb.GameChannel do
alias Chess.Store.Game alias Chess.Store.Game
alias Chess.Board alias Chess.Board
alias Chess.Moves
import Chess.Auth, only: [current_user: 1] import Chess.Auth, only: [current_user: 1]
@ -47,10 +48,32 @@ defmodule ChessWeb.GameChannel do
{:ok, game} -> {:ok, game} ->
send_update(game) send_update(game)
{:noreply, socket} {:noreply, socket}
end end
end end
def handle_in(
"game:get_available_moves",
%{"square" => [file, rank]},
socket
) do
game =
socket.assigns.current_user_id
|> Game.for_user_id()
|> Repo.get!(socket.assigns.game_id)
moves = Moves.available(game.board, {
String.to_integer(file),
String.to_integer(rank)
})
reply = %{
moves: Enum.map(moves, &(Tuple.to_list(&1)))
}
{:reply, {:ok, reply}, socket}
end
def send_update(game) do def send_update(game) do
payload = %{ payload = %{
board: Board.transform(game.board), board: Board.transform(game.board),

View File

@ -18,7 +18,7 @@ defmodule Chess.Moves.PawnTest do
end end
test "white pawn not on starting square can move forward one space" do test "white pawn not on starting square can move forward one space" do
board = %{"4,2" => %{type: :pawn, colour: :white}} board = %{"4,2" => %{"type" => "pawn", "colour" => "white"}}
moves = Pawn.moves(board, {4, 2}) moves = Pawn.moves(board, {4, 2})
expected_moves = [{4, 3}] expected_moves = [{4, 3}]
@ -26,7 +26,7 @@ defmodule Chess.Moves.PawnTest do
end end
test "black pawn not on starting square can move forward one space" do test "black pawn not on starting square can move forward one space" do
board = %{"4,5" => %{type: :pawn, colour: :black}} board = %{"4,5" => %{"type" => "pawn", "colour" => "black"}}
moves = Pawn.moves(board, {4, 5}) moves = Pawn.moves(board, {4, 5})
expected_moves = [{4, 4}] expected_moves = [{4, 4}]