From c0facfa4d50a6d87bd994e6dc8ad6936f0f9b175 Mon Sep 17 00:00:00 2001 From: Dan Barber Date: Sat, 24 Feb 2018 15:52:22 -0500 Subject: [PATCH] Players take turns Restricts players to only be able to move their own pieces and only when it's their turn. --- assets/js/components/chess-board-square.js | 28 +++++++++++++++---- assets/js/components/chess-board.js | 5 ++-- assets/js/reducers/chess-board.js | 9 ++---- assets/js/store/actions.js | 11 ++++---- assets/js/store/default-state.js | 4 +-- lib/chess/board.ex | 9 ++++++ lib/chess/store/game.ex | 17 +++++++++-- .../controllers/api/game_controller.ex | 19 +++++-------- .../migrations/20161209105511_create_game.exs | 1 - .../20180223223018_add_turn_to_game.exs | 9 ++++++ test/chess/store/game_test.exs | 14 ++++++++-- 11 files changed, 87 insertions(+), 39 deletions(-) create mode 100644 priv/repo/migrations/20180223223018_add_turn_to_game.exs diff --git a/assets/js/components/chess-board-square.js b/assets/js/components/chess-board-square.js index 7f3dea9..ea8a124 100644 --- a/assets/js/components/chess-board-square.js +++ b/assets/js/components/chess-board-square.js @@ -3,7 +3,7 @@ import _ from "lodash"; import $ from "jquery"; import classNames from "classnames"; -import { selectPiece, setBoard } from "../store/actions"; +import { selectPiece, setGame } from "../store/actions"; class ChessBoardSquare extends React.Component { constructor(props) { @@ -16,19 +16,35 @@ class ChessBoardSquare extends React.Component { selectSquare() { var { piece, store } = this.props; - var { gameId, selectedSquare } = store.getState(); + var { gameId, selectedSquare, player } = store.getState(); - if (selectedSquare != null) { + if (selectedSquare != null && this.moveIsValid()) { $.ajax({ method: "PATCH", url: "/api/games/" + gameId, data: { move: { from: selectedSquare, to: this.squareCoords() } } - }).then((data) => store.dispatch(setBoard(data))); + }).then((data) => store.dispatch(setGame(data))); } - else if (piece != undefined) { + else if (selectedSquare != null) { + store.dispatch(selectPiece(null)); + } + else if (this.playerCanSelectPiece(player, piece)) { store.dispatch(selectPiece(this.squareCoords())); } - }; + } + + moveIsValid() { + return !this.isSelectedSquare(); + } + + playerCanSelectPiece(player, piece) { + var { store } = this.props; + var { turn } = store.getState(); + + return piece != undefined && + piece.colour == player && + player == turn; + } isSelectedSquare() { var { store } = this.props; diff --git a/assets/js/components/chess-board.js b/assets/js/components/chess-board.js index e0e2e8e..9f8ddbb 100644 --- a/assets/js/components/chess-board.js +++ b/assets/js/components/chess-board.js @@ -2,7 +2,7 @@ import React from "react"; import _ from "lodash"; import $ from "jquery"; import { connect } from "react-redux"; -import { setBoard, setPlayer, setGameId } from "../store/actions"; +import { setGame, setGameId } from "../store/actions"; import ChessBoardSquare from "./chess-board-square"; @@ -14,8 +14,7 @@ class ChessBoard extends React.Component { $.ajax({ method: "GET", url: `/api/games/${gameId}` }) .then((data) => { - store.dispatch(setBoard(data.board)); - store.dispatch(setPlayer(data.player)); + store.dispatch(setGame(data)); }); } diff --git a/assets/js/reducers/chess-board.js b/assets/js/reducers/chess-board.js index 4f5c595..5a7a9d8 100644 --- a/assets/js/reducers/chess-board.js +++ b/assets/js/reducers/chess-board.js @@ -4,15 +4,12 @@ import defaultState from "../store/default-state"; const chessBoardReducer = (state = defaultState, action) => { switch (action.type) { - case "SET_BOARD": + case "SET_GAME": return Immutable.fromJS(state) .set("board", action.board) - .set("selectedSquare", null) - .toJS(); - - case "SET_PLAYER": - return Immutable.fromJS(state) .set("player", action.player) + .set("turn", action.turn) + .set("selectedSquare", null) .toJS(); case "SET_GAME_ID": diff --git a/assets/js/store/actions.js b/assets/js/store/actions.js index 497047e..8c414ab 100644 --- a/assets/js/store/actions.js +++ b/assets/js/store/actions.js @@ -1,12 +1,13 @@ -const SET_BOARD = "SET_BOARD"; -const SET_PLAYER = "SET_PLAYER"; +const SET_GAME = "SET_GAME"; const SET_GAME_ID = "SET_GAME_ID"; const SELECT_PIECE = "SELECT_PIECE"; -export const setBoard = (board) => { +export const setGame = (data) => { return { - type: SET_BOARD, - board: board + type: SET_GAME, + board: data.board, + player: data.player, + turn: data.turn } } diff --git a/assets/js/store/default-state.js b/assets/js/store/default-state.js index 00026e8..52b2f0a 100644 --- a/assets/js/store/default-state.js +++ b/assets/js/store/default-state.js @@ -1,8 +1,8 @@ const defaultState = { selectedSquare: null, - player: "white", - turn: "white", + player: null, + turn: null, board: { 8: { 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 47dbcd8..683ae94 100644 --- a/lib/chess/board.ex +++ b/lib/chess/board.ex @@ -9,6 +9,15 @@ defmodule Chess.Board do end) end + def move_piece(board, move_params) do + [from_file, from_rank] = move_params["from"] + [to_file, to_rank] = move_params["to"] + + {piece, board} = Map.pop(board, "#{from_file},#{from_rank}") + + Map.put(board, "#{to_file},#{to_rank}", piece) + end + def default do %{ "0,7" => %{type: :rook, colour: :black}, diff --git a/lib/chess/store/game.ex b/lib/chess/store/game.ex index 33c58e5..5ac9368 100644 --- a/lib/chess/store/game.ex +++ b/lib/chess/store/game.ex @@ -12,6 +12,7 @@ defmodule Chess.Store.Game do schema "games" do field :board, :map + field :turn, :string belongs_to :user, Chess.Auth.User belongs_to :opponent, Chess.Auth.User, references: :id @@ -24,7 +25,8 @@ defmodule Chess.Store.Game do |> cast(params, required_attrs()) |> foreign_key_constraint(:user_id) |> foreign_key_constraint(:opponent_id) - |> put_change(:board, Board.default) + |> put_change(:board, Board.default()) + |> put_change(:turn, default_turn()) |> validate_required(required_attrs()) end @@ -36,6 +38,13 @@ defmodule Chess.Store.Game do |> validate_required(required_attrs()) end + def change_turn(turn) do + case turn do + "white" -> "black" + "black" -> "white" + end + end + def for_user(user) do from game in Game, where: game.user_id == ^user.id, @@ -47,5 +56,9 @@ defmodule Chess.Store.Game do |> order_by([game], desc: game.inserted_at) end - defp required_attrs, do: ~w[board user_id opponent_id]a + defp required_attrs, do: ~w[board turn user_id opponent_id]a + + defp default_turn do + "white" + end end diff --git a/lib/chess_web/controllers/api/game_controller.ex b/lib/chess_web/controllers/api/game_controller.ex index 1cfbc0a..db94300 100644 --- a/lib/chess_web/controllers/api/game_controller.ex +++ b/lib/chess_web/controllers/api/game_controller.ex @@ -25,20 +25,24 @@ defmodule ChessWeb.Api.GameController do |> Repo.get!(id) changeset = Game.changeset( - game, %{board: new_board(game.board, move_params)} + game, %{ + board: Board.move_piece(game.board, move_params), + turn: Game.change_turn(game.turn) + } ) case Repo.update(changeset) do {:ok, game} -> conn - |> json(Board.transform(game.board)) + |> json(game_attrs(conn, game)) end end defp game_attrs(conn, game) do %{ board: Board.transform(game.board), - player: player(conn, game) + player: player(conn, game), + turn: game.turn } end @@ -49,13 +53,4 @@ defmodule ChessWeb.Api.GameController do "black" end end - - defp new_board(board, move_params) do - [from_file, from_rank] = move_params["from"] - [to_file, to_rank] = move_params["to"] - - {piece, board} = Map.pop(board, "#{from_file},#{from_rank}") - - Map.put(board, "#{to_file},#{to_rank}", piece) - end end diff --git a/priv/repo/migrations/20161209105511_create_game.exs b/priv/repo/migrations/20161209105511_create_game.exs index f00f953..bb97a27 100644 --- a/priv/repo/migrations/20161209105511_create_game.exs +++ b/priv/repo/migrations/20161209105511_create_game.exs @@ -7,6 +7,5 @@ defmodule Chess.Repo.Migrations.CreateGame do timestamps() end - end end diff --git a/priv/repo/migrations/20180223223018_add_turn_to_game.exs b/priv/repo/migrations/20180223223018_add_turn_to_game.exs new file mode 100644 index 0000000..44d4767 --- /dev/null +++ b/priv/repo/migrations/20180223223018_add_turn_to_game.exs @@ -0,0 +1,9 @@ +defmodule Chess.Repo.Migrations.AddTurnToGame do + use Ecto.Migration + + def change do + alter table("games") do + add :turn, :string, default: "white" + end + end +end diff --git a/test/chess/store/game_test.exs b/test/chess/store/game_test.exs index 899bc34..41ead26 100644 --- a/test/chess/store/game_test.exs +++ b/test/chess/store/game_test.exs @@ -11,7 +11,12 @@ defmodule Chess.GameTest do user = create_user("link", "ilovezelda") opponent = create_user("zelda", "ganonsucks") - attrs = %{board: %{}, user_id: user.id, opponent_id: opponent.id} + attrs = %{ + board: %{}, + user_id: user.id, + opponent_id: opponent.id, + turn: "white" + } changeset = Game.changeset(%Game{}, attrs) assert changeset.valid? @@ -19,7 +24,12 @@ defmodule Chess.GameTest do end test "game cannot be saved if the user or opponent do not exist" do - attrs = %{board: %{}, user_id: 1, opponent_id: 2} + attrs = %{ + board: %{}, + user_id: 1, + opponent_id: 2, + turn: "white" + } changeset = Game.changeset(%Game{}, attrs) assert changeset.valid?