Scriber.Level (Scriber v0.1.2)

Copy Markdown View Source

One stratum of the Lattice: a grid of tiles plus the fixtures placed in it.

A level is generated by build/2 from a generator and a depth, and is thereafter immutable except through unseal/1. Entities, items and the player live in Scriber.Game, not here.

Tiles are held in a map keyed by {x, y}; a coordinate with no entry reads as :rock. Every stratum is the same size, given by the struct's :width and :height.

What is in a stratum

Rooms joined by L-shaped corridors, and three fixtures:

  • a console at :console, in a room between the first and the last — where the gate is opened and shards are spent, see Scriber.Console
  • a gate at :gate, in the last room placed — the way down, solid until unseal/1 turns it into a :stair
  • the player's spawn at :spawn, the centre of the first room placed

A gate cannot be opened from here; Scriber.Console accepts the access code and the caller then invokes unseal/1.

Example

{level, rng} = Scriber.Level.build(Cauldron2D.Rng.new(7), 1)
Scriber.Level.at(level, level.spawn)
#=> :floor

Summary

Functions

The tile at point. Any coordinate with no tile, inside the level or out, is :rock.

Generate a stratum at depth, returning {level, rng} with the generator advanced.

The centre tile of a room rectangle, rounding down on both axes.

The level with the door at point opened: a floor tile, sight passing through. Any other tile is left alone.

Every walkable tile, sorted, so the same level always yields the same list.

Whether point lets sight through, as Cauldron2D.Grid.Fov asks it.

Open the gate: set unsealed? and turn the :gate tile into a :stair.

Whether point can be stood on.

Types

point()

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

rect()

@type rect() :: %{
  x: integer(),
  y: integer(),
  width: pos_integer(),
  height: pos_integer()
}

t()

@type t() :: %Scriber.Level{
  console: point() | nil,
  depth: pos_integer(),
  gate: point() | nil,
  height: pos_integer(),
  rooms: [rect()],
  spawn: point(),
  tiles: %{required(point()) => tile()},
  unsealed?: boolean(),
  width: pos_integer()
}

tile()

@type tile() :: :rock | :wall | :floor | :door | :console | :gate | :stair

Functions

at(level, point)

@spec at(t(), point()) :: tile()

The tile at point. Any coordinate with no tile, inside the level or out, is :rock.

build(rng, depth)

@spec build(Cauldron2D.Rng.t(), pos_integer()) :: {t(), Cauldron2D.Rng.t()}

Generate a stratum at depth, returning {level, rng} with the generator advanced.

Rooms are placed by rejection: a fixed number of candidate rectangles are tried and each is kept only if it clears every room already placed by one tile. Rooms are then joined in placement order by L-shaped corridors, fixtures are placed, and every floor tile with no neighbour gets a :wall. From stratum 2 the corridor tile at each room's threshold is a :door, which blocks sight until it is opened by walking through it. Generation never fails — a crowded draw yields fewer rooms, and the degenerate case of no rooms at all yields a level with spawn: {1, 1} and no gate or console.

The same rng and depth always produce the same level.

center(room)

@spec center(rect()) :: point()

The centre tile of a room rectangle, rounding down on both axes.

open(level, point)

@spec open(t(), point()) :: t()

The level with the door at point opened: a floor tile, sight passing through. Any other tile is left alone.

open_tiles(level)

@spec open_tiles(t()) :: [point()]

Every walkable tile, sorted, so the same level always yields the same list.

transparent?(level, point)

@spec transparent?(t(), point()) :: boolean()

Whether point lets sight through, as Cauldron2D.Grid.Fov asks it.

True for everything except :rock, :wall and a :door not yet opened. This is not the inverse of walkable?/2: a sealed :gate blocks movement but not sight.

unseal(level)

@spec unseal(t()) :: t()

Open the gate: set unsealed? and turn the :gate tile into a :stair.

Call this once the access code has been accepted; see Scriber.Lattice.unseal/2. A level with no gate is returned unchanged. Calling it twice is harmless.

walkable?(level, point)

@spec walkable?(t(), point()) :: boolean()

Whether point can be stood on.

True for :floor, :door, :console and :stair. A :gate is walkable only while the level is unsealed. :rock and :wall are never walkable.