# `Cauldron2D.Map`
[🔗](https://github.com/jaman/cauldron/blob/v0.1.3/cauldron_2d/lib/cauldron_2d/map.ex#L1)

A tile map written as text: a grid of characters, one per tile, with optional
`name: value` options ahead of it.

Two layouts are read:

  * a bare picture — every line is a row:

        ####
        #..#
        ####

  * the header format of XPilot's map files — options, then the grid between the
    multiline markers:

        mapwidth: 6
        mapheight: 3
        edgewrap: yes
        mapData: \multiline: EndOfMapdata
        xxxxxx
        x _  x
        xxxxxx
        EndOfMapdata

The engine attaches no meaning to any character, and to no option but the two that
size the grid (`mapwidth`, `mapheight`) and the one `parse/2` is told wraps it. A game
supplies a legend to `cells/2` and reads the options it honours with `option/4`. Option
names are matched without regard to case.

A map wraps when the option named by `:wrap` is true: `at/2` outside the grid then
reads the tile on the opposite side, and otherwise reads a space.

# `point`

```elixir
@type point() :: {integer(), integer()}
```

# `t`

```elixir
@type t() :: %Cauldron2D.Map{
  height: pos_integer(),
  options: %{required(String.t()) =&gt; String.t()},
  rows: tuple(),
  width: pos_integer(),
  wrap?: boolean()
}
```

# `at`

```elixir
@spec at(t(), point()) :: String.t()
```

The character at `{x, y}`; a space outside the grid, or the wrapped tile when the map wraps.

# `cells`

```elixir
@spec cells(t(), (String.t() -&gt; term())) :: %{required(point()) =&gt; term()}
```

Every tile the legend gives a value for, as `%{{x, y} => value}`.

`legend` is called with each character; a `nil` return leaves that tile out.

# `option`

```elixir
@spec option(t(), String.t(), :string | :integer | :float | :boolean, term()) ::
  term()
```

An option converted to `type`, or `default` when absent or not of that type.

`type` is `:string`, `:integer`, `:float` or `:boolean`. Booleans read `yes`, `true`,
`on` and `1` as true and `no`, `false`, `off` and `0` as false.

# `parse`

```elixir
@spec parse(String.t(), keyword()) :: {:ok, t()} | {:error, term()}
```

Parse map text.

Returns `{:ok, map}`, or `{:error, :empty}` for no rows at all. A multiline block with
no closing marker runs to the end of the text; a row wider than `mapwidth` is cut to it
and a shorter one padded with spaces; rows beyond `mapheight` are dropped and missing
ones are blank.

## Options

  * `:wrap` — the name of the header option that, when true, makes the map wrap
    (`"edgewrap"` in XPilot's files). Default: none, the map never wraps

# `parse_file`

```elixir
@spec parse_file(Path.t(), keyword()) :: {:ok, t()} | {:error, term()}
```

Read and parse a file, with `parse/2`'s options; a `.gz` file is decompressed first.

# `positions`

```elixir
@spec positions(t(), String.t()) :: [point()]
```

Where `char` occurs, row by row then left to right.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
