defmodule Cartographer.Board do defstruct( tiles: %{}, height: nil, width: nil, wrap: false ) def new_board(height, width, :wrap), do: _new_board(height, width, true) def new_board(height, width, :no_wrap), do: _new_board(height, width, false) defp _new_board(height, width, wrap) do %__MODULE__{ height: height, width: width, wrap: wrap, } end def in_bounds(%__MODULE__{:height => height, :width => width}, x, y) do x >= 0 and x < width and y >= 0 and y < height end def teleport(%__MODULE__{:height => height, :width => width}, x, y) do {_teleport(x, width), _teleport(y, height)} end defp _teleport(n, range) when n >= range, do: rem(n, range) defp _teleport(n, range) when rem(n, range) == 0, do: 0 defp _teleport(n, range) when n < 0, do: range + rem(n, range) defp _teleport(n, _range), do: n def coordinate_range(center_x, center_y, range) do for x <- (center_x - range)..(center_x + range), y <- (center_y - range)..(center_y + range), do: {x, y} end def height(board), do: board.height def width(board), do: board.width def get(board), do: board def get(board, x, y) do Map.get(board.tiles, {x, y}) end def get(board = %{wrap: false}, center_x, center_y, range) do coordinate_range(center_x, center_y, range) |> Enum.filter(fn({x, y}) -> in_bounds(board, x, y) end) |> Enum.reduce(%{}, &(Map.put(&2, &1, Map.get(board.tiles, &1)))) end def get(board = %{wrap: true}, center_x, center_y, range) do coordinate_range(center_x, center_y, range) |> Enum.map(fn({x, y}) -> teleport(board, x, y) end) |> Enum.reduce(%{}, &(Map.put(&2, &1, Map.get(board.tiles, &1)))) end def set(board, x, y, data) do in_bounds(board, x, y) |> _set(board, x, y, data) end defp _set(_in_bounds = true, board, x, y, data) do tiles = board.tiles |> Map.put({x, y}, data) board |> Map.put(:tiles, tiles) end defp _set(_not_in_bounds, board, _x, _y, _data), do: board def neighbors(board = %{wrap: false}, center_x, center_y) do coordinate_range(center_x, center_y, 1) |> List.delete({center_x, center_y}) |> Enum.filter(fn({x, y}) -> in_bounds(board, x, y) end) |> Enum.reduce(%{}, &(Map.put(&2, &1, Map.get(board.tiles, &1)))) end def neighbors(board = %{wrap: true}, center_x, center_y) do coordinate_range(center_x, center_y, 1) |> List.delete({center_x, center_y}) |> Enum.map(fn({x, y}) -> teleport(board, x, y) end) |> Enum.reduce(%{}, &(Map.put(&2, &1, Map.get(board.tiles, &1)))) end end