From 375be4711ca9c57d6da13e6883bc92724765e4cb Mon Sep 17 00:00:00 2001 From: Dan Barber Date: Sun, 11 Mar 2018 23:21:17 -0400 Subject: [PATCH] Show available moves for pawn --- assets/css/_board.scss | 10 ++++ assets/css/_variables.scss | 4 +- assets/js/app.js | 6 +- assets/js/components/chess-board-square.js | 30 +++++++--- assets/js/components/chess-board.js | 4 +- assets/js/reducers/chess-board.js | 5 ++ assets/js/services/channel.js | 9 ++- assets/js/store/actions.js | 8 +++ assets/js/store/default-state.js | 2 + lib/chess/board.ex | 64 +++++++++++----------- lib/chess/moves.ex | 2 +- lib/chess/moves/pawn.ex | 4 +- lib/chess_web/channels/game_channel.ex | 25 ++++++++- test/chess/moves/pawn_test.exs | 4 +- 14 files changed, 123 insertions(+), 54 deletions(-) diff --git a/assets/css/_board.scss b/assets/css/_board.scss index 8cb04b2..4d0aa44 100644 --- a/assets/css/_board.scss +++ b/assets/css/_board.scss @@ -117,6 +117,11 @@ $board-size: 90vmin; background-color: mix($black-square-color, $selected-square-color, 60%); 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 { @@ -126,6 +131,11 @@ $board-size: 90vmin; background-color: mix($white-square-color, $selected-square-color, 60%); 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) { diff --git a/assets/css/_variables.scss b/assets/css/_variables.scss index d95a0ed..62f5f1d 100644 --- a/assets/css/_variables.scss +++ b/assets/css/_variables.scss @@ -15,10 +15,12 @@ $base-font-size: 16px; $black-square-color: #777; $white-square-color: #bbb; -$selected-square-color: #6f0; +$selected-square-color: #0cf; +$available-square-color: #6f0; $square-outline-color: darken($black-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"; $pieces: king queen bishop knight rook pawn; diff --git a/assets/js/app.js b/assets/js/app.js index a3bfe60..8a50d90 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -24,10 +24,6 @@ class App extends React.Component { this.channel = new Channel(store, gameId); } - sendMove(gameId, move) { - this.channel.sendMove(move); - } - render() { const { store, gameId } = this.props; @@ -35,7 +31,7 @@ class App extends React.Component { ); } diff --git a/assets/js/components/chess-board-square.js b/assets/js/components/chess-board-square.js index 0c6469e..7ec5fa0 100644 --- a/assets/js/components/chess-board-square.js +++ b/assets/js/components/chess-board-square.js @@ -4,7 +4,7 @@ import classNames from "classnames"; import API from "../services/api"; -import { setGame, selectPiece } from "../store/actions"; +import { setGame, setMoves, selectPiece } from "../store/actions"; class ChessBoardSquare extends React.Component { constructor(props) { @@ -16,23 +16,26 @@ class ChessBoardSquare extends React.Component { } selectSquare() { - const { piece, store, sendMove } = this.props; + const { piece, store, channel } = this.props; const { gameId, selectedSquare, player } = store.getState(); if (selectedSquare != null && this.moveIsValid()) { - sendMove(gameId, { + store.dispatch(setMoves([])); + channel.sendMove({ from: selectedSquare, to: this.squareCoords, }); } else if (selectedSquare != null) { + store.dispatch(setMoves([])); store.dispatch(selectPiece(null)); } else if (this.playerCanSelectPiece(player, piece)) { + channel.getAvailableMoves(this.squareCoords); store.dispatch(selectPiece(this.squareCoords)); } } moveIsValid() { - return !this.isSelectedSquare(); + return !this.isSelectedSquare; } playerCanSelectPiece(player, piece) { @@ -44,7 +47,7 @@ class ChessBoardSquare extends React.Component { player == turn; } - isSelectedSquare() { + get isSelectedSquare() { const { store } = this.props; 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() { return `f${this.props.file}-r${this.props.rank}`; } get squareClass() { if (this.props.piece == undefined) { - return "board-square"; + return classNames( + "board-square", + { "available": this.isAvailableSquare } + ); } else { return classNames( "board-square", this.props.piece.type, this.props.piece.colour, - { "selected": this.isSelectedSquare() } + { "selected": this.isSelectedSquare }, + { "available": this.isAvailableSquare } ); } } diff --git a/assets/js/components/chess-board.js b/assets/js/components/chess-board.js index b9ec0e9..dedc03b 100644 --- a/assets/js/components/chess-board.js +++ b/assets/js/components/chess-board.js @@ -26,7 +26,7 @@ class ChessBoard extends React.Component { } renderFiles(rankId) { - const { store, sendMove } = this.props; + const { store, channel } = this.props; const rank = this.getBoard()[rankId]; return _.map(this.files(rank), fileId => { @@ -37,7 +37,7 @@ class ChessBoard extends React.Component { rank={rankId} piece={rank[fileId]} store={store} - sendMove={sendMove} + channel={channel} /> ); }); diff --git a/assets/js/reducers/chess-board.js b/assets/js/reducers/chess-board.js index aff3243..ef36def 100644 --- a/assets/js/reducers/chess-board.js +++ b/assets/js/reducers/chess-board.js @@ -16,6 +16,11 @@ const chessBoardReducer = (state = defaultState, action) => { .set("selectedSquare", null) .toJS(); + case "SET_MOVES": + return Immutable.fromJS(state) + .set("moves", action.moves) + .toJS(); + case "SET_GAME_ID": return Immutable.fromJS(state) .set("gameId", action.gameId) diff --git a/assets/js/services/channel.js b/assets/js/services/channel.js index 6db049a..8b29412 100644 --- a/assets/js/services/channel.js +++ b/assets/js/services/channel.js @@ -1,5 +1,5 @@ import socket from "../socket"; -import { setPlayer, setGame } from "../store/actions"; +import { setPlayer, setGame, setMoves } from "../store/actions"; class Channel { 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) { this.channel.push("game:move", move); } diff --git a/assets/js/store/actions.js b/assets/js/store/actions.js index 3536b93..f0dd769 100644 --- a/assets/js/store/actions.js +++ b/assets/js/store/actions.js @@ -1,5 +1,6 @@ const SET_PLAYER = "SET_PLAYER"; const SET_GAME = "SET_GAME"; +const SET_MOVES = "SET_MOVES"; const SET_GAME_ID = "SET_GAME_ID"; 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) => { return { type: SET_GAME_ID, diff --git a/assets/js/store/default-state.js b/assets/js/store/default-state.js index e414de3..2613f47 100644 --- a/assets/js/store/default-state.js +++ b/assets/js/store/default-state.js @@ -4,6 +4,8 @@ const defaultState = { player: null, turn: null, + moves: [], + board: { 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 }, diff --git a/lib/chess/board.ex b/lib/chess/board.ex index 683ae94..1107e16 100644 --- a/lib/chess/board.ex +++ b/lib/chess/board.ex @@ -20,41 +20,41 @@ defmodule Chess.Board do def default do %{ - "0,7" => %{type: :rook, colour: :black}, - "1,7" => %{type: :knight, colour: :black}, - "2,7" => %{type: :bishop, colour: :black}, - "3,7" => %{type: :queen, colour: :black}, - "4,7" => %{type: :king, colour: :black}, - "5,7" => %{type: :bishop, colour: :black}, - "6,7" => %{type: :knight, colour: :black}, - "7,7" => %{type: :rook, colour: :black}, + "0,7" => %{"type" => "rook", "colour" => "black"}, + "1,7" => %{"type" => "knight", "colour" => "black"}, + "2,7" => %{"type" => "bishop", "colour" => "black"}, + "3,7" => %{"type" => "queen", "colour" => "black"}, + "4,7" => %{"type" => "king", "colour" => "black"}, + "5,7" => %{"type" => "bishop", "colour" => "black"}, + "6,7" => %{"type" => "knight", "colour" => "black"}, + "7,7" => %{"type" => "rook", "colour" => "black"}, - "0,6" => %{type: :pawn, colour: :black}, - "1,6" => %{type: :pawn, colour: :black}, - "2,6" => %{type: :pawn, colour: :black}, - "3,6" => %{type: :pawn, colour: :black}, - "4,6" => %{type: :pawn, colour: :black}, - "5,6" => %{type: :pawn, colour: :black}, - "6,6" => %{type: :pawn, colour: :black}, - "7,6" => %{type: :pawn, colour: :black}, + "0,6" => %{"type" => "pawn", "colour" => "black"}, + "1,6" => %{"type" => "pawn", "colour" => "black"}, + "2,6" => %{"type" => "pawn", "colour" => "black"}, + "3,6" => %{"type" => "pawn", "colour" => "black"}, + "4,6" => %{"type" => "pawn", "colour" => "black"}, + "5,6" => %{"type" => "pawn", "colour" => "black"}, + "6,6" => %{"type" => "pawn", "colour" => "black"}, + "7,6" => %{"type" => "pawn", "colour" => "black"}, - "0,1" => %{type: :pawn, colour: :white}, - "1,1" => %{type: :pawn, colour: :white}, - "2,1" => %{type: :pawn, colour: :white}, - "3,1" => %{type: :pawn, colour: :white}, - "4,1" => %{type: :pawn, colour: :white}, - "5,1" => %{type: :pawn, colour: :white}, - "6,1" => %{type: :pawn, colour: :white}, - "7,1" => %{type: :pawn, colour: :white}, + "0,1" => %{"type" => "pawn", "colour" => "white"}, + "1,1" => %{"type" => "pawn", "colour" => "white"}, + "2,1" => %{"type" => "pawn", "colour" => "white"}, + "3,1" => %{"type" => "pawn", "colour" => "white"}, + "4,1" => %{"type" => "pawn", "colour" => "white"}, + "5,1" => %{"type" => "pawn", "colour" => "white"}, + "6,1" => %{"type" => "pawn", "colour" => "white"}, + "7,1" => %{"type" => "pawn", "colour" => "white"}, - "0,0" => %{type: :rook, colour: :white}, - "1,0" => %{type: :knight, colour: :white}, - "2,0" => %{type: :bishop, colour: :white}, - "3,0" => %{type: :queen, colour: :white}, - "4,0" => %{type: :king, colour: :white}, - "5,0" => %{type: :bishop, colour: :white}, - "6,0" => %{type: :knight, colour: :white}, - "7,0" => %{type: :rook, colour: :white} + "0,0" => %{"type" => "rook", "colour" => "white"}, + "1,0" => %{"type" => "knight", "colour" => "white"}, + "2,0" => %{"type" => "bishop", "colour" => "white"}, + "3,0" => %{"type" => "queen", "colour" => "white"}, + "4,0" => %{"type" => "king", "colour" => "white"}, + "5,0" => %{"type" => "bishop", "colour" => "white"}, + "6,0" => %{"type" => "knight", "colour" => "white"}, + "7,0" => %{"type" => "rook", "colour" => "white"} } end end diff --git a/lib/chess/moves.ex b/lib/chess/moves.ex index 60be54a..ae0ff6c 100644 --- a/lib/chess/moves.ex +++ b/lib/chess/moves.ex @@ -7,7 +7,7 @@ defmodule Chess.Moves do piece = board["#{file},#{rank}"] case piece do - %{type: :pawn} -> + %{"type" => "pawn"} -> Pawn.moves(board, {file, rank}) end end diff --git a/lib/chess/moves/pawn.ex b/lib/chess/moves/pawn.ex index 460d450..8419a21 100644 --- a/lib/chess/moves/pawn.ex +++ b/lib/chess/moves/pawn.ex @@ -5,12 +5,12 @@ defmodule Chess.Moves.Pawn do piece = board["#{file},#{rank}"] case piece do - %{colour: :white} -> + %{"colour" => "white"} -> case rank do 1 -> [{file, rank + 1}, {file, rank + 2}] _ -> [{file, rank + 1}] end - %{colour: :black} -> + %{"colour" => "black"} -> case rank do 6 -> [{file, rank - 1}, {file, rank - 2}] _ -> [{file, rank - 1}] diff --git a/lib/chess_web/channels/game_channel.ex b/lib/chess_web/channels/game_channel.ex index 51970b1..87f2b5d 100644 --- a/lib/chess_web/channels/game_channel.ex +++ b/lib/chess_web/channels/game_channel.ex @@ -5,6 +5,7 @@ defmodule ChessWeb.GameChannel do alias Chess.Store.Game alias Chess.Board + alias Chess.Moves import Chess.Auth, only: [current_user: 1] @@ -47,10 +48,32 @@ defmodule ChessWeb.GameChannel do {:ok, game} -> send_update(game) - {:noreply, socket} + {:noreply, socket} 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 payload = %{ board: Board.transform(game.board), diff --git a/test/chess/moves/pawn_test.exs b/test/chess/moves/pawn_test.exs index 142ec80..f70d03d 100644 --- a/test/chess/moves/pawn_test.exs +++ b/test/chess/moves/pawn_test.exs @@ -18,7 +18,7 @@ defmodule Chess.Moves.PawnTest do end 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}) expected_moves = [{4, 3}] @@ -26,7 +26,7 @@ defmodule Chess.Moves.PawnTest do end 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}) expected_moves = [{4, 4}]