defmodule Tabletop.Actions do @moduledoc """ This module provides the fundamental actions involved in any tabletop game. Each action consists of a key and then an arguments value: * `:move => {from, to}` * `:add => {piece, position}` * `:remove => position` * `:assign => {position, attributes}` A game will take a combination of these actions which will then form a turn. For more information see the `Tabletop.take_turn/2` function. """ import Tabletop.Grid, only: [add: 2] import Kernel, except: [apply: 3] @doc """ Applies a particular action to the `board`. The type of action is determined by the atom provided with `arg2`. This will then be mapped to a specific behaviour such as Moving or Adding a piece. If the type of action has not been implemented then the board will be returned with no changes made. ## Moving a Piece * `arg2` => `:move` * `arguments` => `{from, to}` Moves the piece at the `from` position to the `to` position. Any existing pieces at the `to` position will be removed from the board and replaced by the moving piece. * `arg2` => `:step` * `arguments` => `{piece_id, direction}` Moves the first piece matching `piece_id` from its current position and in the provided `direction`. In the case that there is no piece to move, nothing will happen and the unchanged board struct will be returned. ## Adding a Piece * `arg2` => `:add` * `arguments` => `{%Tabletop.Piece{}, position}` Adds the provided `piece` to the `position` on the `board`. Existing pieces will be removed from the `board` and replaced. If the `position` is not within the bounds of the `board`, the unchanged `board` will be returned. ## Removing a Piece * `arg2` => `:remove` * `arguments` => `position` Removes the piece at the provided `position` if it is occupied. Does nothing to the `board` if the `position` does not contain a piece. ## Assigning Attributes to a Piece * `arg2` => `:assign` * `arguments` => `%{}` Assigns the provided `attributes` to the piece at the provided `position`. If it does not exist then the unchanged `board` will be returned. """ def apply(board, :move, {from, to}) do case Tabletop.get_piece(board, from) do %Tabletop.Piece{} = piece -> updated_pieces = Map.merge board.pieces, %{ from => nil, to => piece } %Tabletop.Board{board | pieces: updated_pieces} nil -> board end end def apply(board, :add, {%Tabletop.Piece{} = piece, position}) do if Tabletop.in_bounds?(board, position) do updated_pieces = Map.merge(board.pieces, %{position => piece}) %Tabletop.Board{board | pieces: updated_pieces} else board end end def apply(board, :remove, position) do if Tabletop.occupied?(board, position) do updated_pieces = Map.merge(board.pieces, %{position => nil}) %Tabletop.Board{board | pieces: updated_pieces} else board end end def apply(board, :assign, {position, attributes}) do case Tabletop.get_piece(board, position) do %Tabletop.Piece{} = piece -> updated_pieces = Map.merge(board.pieces, %{ position => Tabletop.Piece.assign(piece, attributes) }) %Tabletop.Board{board | pieces: updated_pieces} nil -> board end end def apply(board, :step, {piece_id, direction}) do case Tabletop.position_of(board, Tabletop.Piece.new(piece_id)) do nil -> board position -> next_position = add(position, direction) if Tabletop.in_bounds?(board, next_position) do apply(board, :move, {position, next_position}) else board end end end def apply(board, _key, _args) do board end end