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