On this page:
9.1 Generics
gen:  rlgrid
rlgrid-ref
rlgrid-set
rlgrid-multi-set
rlgrid-width
rlgrid-height
rlgrid?
rlgrid-inside?
in-rlgrid
9.2 dtgrid:   Deduplicated Tree Based Grid
dtgrid?
make-dtgrid
dtgrid-stats
rlgrid->dtgrid
9.3 vgrid:   Vector-Based Grid
vgrid?
make-vgrid
rlgrid->vgdir
9.4 Utilities
rlgrid-values
rlgrid-ref-vec2
rlgrid-set-vec2
rlgrid-component
rlgrid-clip-rect
rlgrid-set-border
rlgrid-set-filter
rlgrid-dead-ends
rlgrid-pass-neighbors
9.5 Discrete Differential Analysis
dda
9.6 Visibility Arcs
varc
full-varc
varc-empty?
varcs-intersections-with
varcs-subtract
varc-size
varcs-size
9.7 Computing Visibility Fans in Grids
vfan-iter
in-vfan
8.10

9 RRLL: Functional Grids

Dominik Pantůček <dominik.pantucek@trustica.cz>

Racket Rogue-Like Library: Functional Grids

These modules provide the purely functional immutable grid implementations. The main differences are memory consumption and the time and memory complexity of referencing or setting a tile to a new value.

9.1 Generics

 (require rrll/grid/generics) package: rrll-grid

The generic interface deals only with accessing and modifying the grid. On top of this interface more common functionality is added.

value

gen:rlgrid : any/c

A generic interface that provides access methods to tile of an underlying grid. The following methods must be implemented:

The structs implementing this generic interface can - and probably will - implement other means of inspection. Construction of the underlying data structures is not covered by this generic interface.

procedure

(rlgrid-ref rlg x y)  any/c

  rlg : rlgrid?
  x : nonnegative-integer?
  y : nonnegative-integer?
Returns a tile at location given by x and y coordinates.

procedure

(rlgrid-set rlg x y v)  rlgrid?

  rlg : rlgrid?
  x : nonnegative-integer?
  y : nonnegative-integer?
  v : any/c
Returns the same implementation of gen:rlgrid with the tile at coordinates x and y being changed to a new value.

procedure

(rlgrid-multi-set rlg x y v ...)  rlgrid?

  rlg : rlgrid?
  x : nonnegative-integer?
  y : nonnegative-integer?
  v : any/c
Used to set multiple cells in one tree update. The remaining arguments are (x y value) triplets.

procedure

(rlgrid-width rlg)  positive-integer?

  rlg : rlgrid?
Returns the width of given grid.

procedure

(rlgrid-height rlg)  positive-integer?

  rlg : rlgrid?
Returns the height of given grid.

procedure

(rlgrid? v)  boolean?

  v : any/c
Returns #t if given value v is an instance of any implementation of gen:rlgrid.

procedure

(rlgrid-inside? rlg x y)  boolean?

  rlg : rlgrid?
  x : nonnegative-integer?
  y : nonnegative-integer?
Returns #t if given tile coordinates lie inside the given grid.

syntax

(in-rlgrid maybe-modifier ... rlg (xstart 0) (ystart 0) (xend #f) (yend #f))

 
maybe-modifier = #:with-xy
  | #:default default
  | #:only-xy
 
  rlg : (generic-instance/c gen:rlgrid)
  xstart : integer?
  ystart : integer?
  xend : (or/c #f integer?)
  yend : (or/c #f integer?)
Creates a sequence (a stream) of tile values for given rectangular (sub)area of given grid.

If no #:default is provided, the are is clipped against the grid.

If the clipped or given rectangle has zero area, an empty stream is returned.

If #:default is provided, the sequence always iterates over the whole rectangle requested, but for tiles outside the grid it returns the default value provided.

When #:with-xy is given, the sequence will always return three values: x and y coordinates and value of the tile.

If #:only-xy is requested, only x and y coordinate values are provided by the sequence. This is mainly useful without #:default to iterate over a clipped rectangle.

If either xend or yend is #f, they are substituded for the width and height of given grid respectively.

To iterate over all tiles in given grid:

(for ((tile (in-rlgrid grid)))
  ...)

The same iteration with coordinates passed to the for loop body:

(for (((x y tile) (in-rlgrid #:with-xy grid)))
  ...)

Clipping the iteration rectangle to the grid (will iterate over 2x2 rectangle in the upper left corner of the grid):

(for (((x y tile) (in-rlgrid #:with-xy grid -10 -10 2 2)))
  ...)

With #:default specified, it is possible to substitute meaningful value for tiles outside the grid:

(for ((tile (in-rlgrid #:default #f -1 -1 2 2)))
  ...)

To only clip given rectangle to the grid and iterate over the coordinates:

(for (((x y) (in-rlgrid #:only-xy grid -1 -1 2 2)))
  ...)

9.2 dtgrid: Deduplicated Tree Based Grid

 (require rrll/grid/dtgrid) package: rrll-grid

This module implements gen:rlgrid that stores the grid in a binary tree where any node can be collapsed if its two child nodes contain values that are equal? to each other of if the child nodes are both collapsed and their collapsed value is the same.

procedure

(dtgrid? v)  boolean?

  v : any/c
Returns #t if given value v is a dtgrid.

procedure

(make-dtgrid width    
  height    
  default    
  [#:force force])  dtgrid?
  width : positive-integer?
  height : positive-integer?
  default : 
(or/c (not/c procedure?)
      (procedure-arity-includes/c 0)
      (procedure-arity-includes/c 2))
  force : boolean? = #f
Constructs a new dtgrid? using default as the initial value for all tiles.

If the default is a procedure, it is evaluated when given cell is accessed. If the procedure accepts two arguments, the coordinates of the tile being constructed are passed to it.

If the two tiles in the same cons cell are equal?, the cell is collapsed and separate cells are not allocated. If an internal tree node cons cell contains two collapsed values which are equal? it is collapsed recursively up to root if possible.

The construction takes constant time as only the collapsed root cell is allocated.

However, if force is #t and the default is a procedure?, all the tiles are created upon creation.

procedure

(dtgrid-stats dtg)  
nonnegative-integer?
nonnegative-integer?
nonnegative-integer?
nonnegative-integer?
  dtg : dtgrid?
Returns the number of actually allocated nodes of given dtgrid?.

The values are:

procedure

(rlgrid->dtgrid rlg)  dtgrid?

  rlg : rlgrid?
Converts any grid to dtgrid. If it is already a dtgrid?, it is returned as-is.

Because this procedure uses default value procedure which is evaluated lazily, it is actually a constant-time shallow copy of original rlg.

9.3 vgrid: Vector-Based Grid

 (require rrll/grid/vgrid) package: rrll-grid

Method

 

Time

 

Memory

Make

 

\Theta(n)

 

\Theta(n)

Ref

 

\Theta(1)

 

\Theta(1)

Set

 

\Theta(n)

 

\Theta(n)

This module implements gen:rlgrid that stores the grid in a one-dimensional vector. The tiles in a row are stored from left to right and the rows are concatenated from top to bottom linearly in the vector.

procedure

(vgrid? v)  boolean?

  v : any/c
Returns #t if given value v is a vgrid.

procedure

(make-vgrid width height default)  vgrid?

  width : positive-integer?
  height : positive-integer?
  default : 
(or/c (not/c procedure?)
      (procedure-arity-includes/c 0)
      (procedure-arity-includes/c 2))
Constructs a new vgrid? using default as the initial value for all tiles. If the default argument is a procedure, it is evaluated immediately for all tiles.

procedure

(rlgrid->vgdir rlg)  vgrid?

  rlg : rlgrid?
Converts any grid to vgrid. If it is already a vgrid?, it is returned as-is.

9.4 Utilities

 (require rrll/grid/utils) package: rrll-grid

This module contains simple utilities commonly used by various algorithms in other modules.

procedure

(rlgrid-values rlg    
  [xstart    
  ystart    
  xend    
  yend    
  #:default default    
  #:conv conv])  any
  rlg : rlgrid?
  xstart : fixnum? = 0
  ystart : fixnum? = 0
  xend : (or/c fixnum? #f) = #f
  yend : (or/c fixnum? #f) = #f
  default : any/c = #f
  conv : (-> any/c any/c) = (λ (v) v)
Helper procedure to get values of grid area as bindings. If the area is (partially) outside the grid, default value is substituted for all tiles that are not within the grid bounds.

(define-values (tl t tr l c r bl b cr)
  (rlgrid-values grid 0 0 3 3))

image

The conv routine is used to optionally convert the tiles as required.

procedure

(rlgrid-ref-vec2 rlg v)  any/c

  rlg : rlgrid?
  v : vec2?
A wrapper for rlgrid-ref:

(rlgrid-ref rlg (vec2-x v) (vec2-y v))

procedure

(rlgrid-set-vec2 rlg p v)  rlgrid?

  rlg : rlgrid?
  p : vec2?
  v : any/c
A wrapper for rlgrid-set:

(rlgrid-set rlg (vec2-x p) (vec2-y p) v)

procedure

(rlgrid-component rlg    
  x    
  y    
  [#:directions dirs    
  #:passable? passable?])  (set/c vec2?)
  rlg : rlgrid?
  x : fixnum?
  y : fixnum?
  dirs : (listof vec2?) = vec2s:grid
  passable? : (-> any/c boolean) = (λ (v) v)
Random-first search based algorithm for determining the set of vec2?s of connected component of given grid.

procedure

(rlgrid-clip-rect rlg x0 y0 [x1 y1])

  
fixnum? fixnum? fixnum? fixnum?
  rlg : rlgrid?
  x0 : fixnum?
  y0 : fixnum?
  x1 : (or/c #f fixnum?) = #f
  y1 : (or/c #f fixnum?) = #f
Clips given rectangle to be within given grid.

procedure

(rlgrid-set-border rlg    
  component    
  [#:directions dirs])  (set/c vec2?)
  rlg : rlgrid?
  component : (set/c vec2?)
  dirs : (listof vec2?) = vec2s:grid
Generates a set of vec2?s that is adjacent to given component (or any set of tiles) based on dirs major directions.

procedure

(rlgrid-set-filter rlg component pred?)  (set/c vec2?)

  rlg : rlgrid?
  component : (set/c vec2?)
  pred? : (-> any/c boolean?)
Filters given set (component) of vec2?s in given grid.

procedure

(rlgrid-dead-ends rlg)  (listof vec2?)

  rlg : rlgrid?
{ Scans the grid to find dead-ends. }

procedure

(rlgrid-pass-neighbors rlg    
  pos    
  [#:directions dirs    
  #:passable? passable?])  (listof vec2?)
  rlg : rlgrid?
  pos : vec2?
  dirs : (listof vec2?) = vec2s:grid
  passable? : (-> any/c boolean?) = (λ (v) v)
Returns passable neighbors from given pos in given dirs.

9.5 Discrete Differential Analysis

 (require rrll/grid/dda) package: rrll-grid

procedure

(dda rlg    
  start-x    
  start-y    
  ray-dx    
  ray-dy    
  [limit-t    
  wall?    
  skip-start-tile])  
(or/c #f flonum?) any/c (or/c #f vec2?)
  rlg : rlgrid?
  start-x : 
(and/c (or/c fixnum? flonum?)
       (not/c negative?))
  start-y : 
(and/c (or/c fixnum? flonum?)
       (not/c negative?))
  ray-dx : (or/c fixnum? flonum?)
  ray-dy : (or/c fixnum? flonum?)
  limit-t : (or/c fixnum? flonum?) = +inf.0
  wall? : (-> any/c boolean?) = (λ (v) (not v))
  skip-start-tile : boolean? = #t
Discrete differential analysis line algorithm iterates position on the line in discrete steps where each step lies on the underlying integer grid. The first impassable tile encountered is taken as a ray hit:

image

image

If any of the coordinates start-x, start-y, ray-dx, or ray-dy are integer?, they are adjusted to point to the center of given tile by adding 0.5 to their value.

The limit-t argument specifies maximum distance before giving up finding a collision and returning #f for all return values.

As the implementation expects a grid of boolean? values where #f means wall (impassable, opaque tile) and #t means free space (passable, transparent tile), the default predicate for wall? reflects that. This makes the algorithm easy to adapt to different representations.

If skip-start-tile is true, a hit within the starting tile is ignored.

The values returned are:

9.6 Visibility Arcs

 (require rrll/grid/varc) package: rrll-grid

This module implements visibility arcs, simple operations with them and method for determining tile arc from given center point.

It allows computing possibly visible tiles in a rlgrid?.

struct

(struct varc (start end))

  start : flonum?
  end : flonum?
This struct represents an arc of visibility by means of using starting and ending angle. If end is smalled than start, the arc goes over the 2\pi boundary.

image

The starting angle \alpha and ending angle \beta are shown in the figure.

value

full-varc : varc?

A visibility arc representing the full circle.

procedure

(varc-empty? arc)  boolean?

  arc : varc?
Returns true if start angle equals the end angle.

procedure

(varcs-intersections-with arcs arc)  (listof varc?)

  arcs : (listof varc?)
  arc : varc?
Updates the list of given visibility arcs by reducing them to intersections with the arc provided as second argument. Some of the arcs may be reduced to empty arcs.

procedure

(varcs-subtract arcs arc)  (listof varc?)

  arcs : (listof varc?)
  arc : varc?
Updates the list of given arcs by subtracting the arc given as second argument from them. Some arcs may be reduced to empty arcs.

procedure

(varc-size arc)  flonum?

  arc : varc?
Returns the size (in radians) of given visibility arc handling arcs that wrap around the 2\pi boundary correctly.

procedure

(varcs-size arcs)  flonum?

  arcs : (listof varc?)
Returns the sum of the sizes of all arcs given.

9.7 Computing Visibility Fans in Grids

 (require rrll/grid/vfan) package: rrll-grid

procedure

(vfan-iter rlg    
  start-x    
  start-y    
  proc    
  [#:wall? wall?    
  #:init-varc init-varc    
  #:with-tile with-tile    
  #:process? process?])  void?
  rlg : rlgrid?
  start-x : real?
  start-y : real?
  proc : (-> fixnum? fixnum? void?)
  wall? : (-> any/c boolean?) = (λ (v) (not v))
  init-varc : varc? = full-varc
  with-tile : boolean? = #f
  process? : (-> any/c boolean?) = (λ (v) #f)
Iterates through given rlgrid? in BFS-like manner starting at the tile containing the (possibly non-integer) point (start-x start-y).

For each tile covered by the visibility fan centered upon the starting point it evaluates given proc with the X and Y coordinate of such tile being the only arguments provided (for now).

The #:wall? keyword argument can be used to specify how the algorithm recognizes which walls are opaque for the purpose of visibility computation.

The init-varc argument can be used to limit the initial search arc to particular direction and field-of-view.

If with-tile is #t, the proc gets always evaluated with a third argument - the tile in question - as well.

When process? returns non-#f for any tile that is considered wall, it is processed with proc although it does not add any tiles to further iteration.

syntax

(in-vfan rlg start-x start-y option ...)

 
option = #:with-tile
  | #:wall? wall? = (λ (v) (not v))
  | #:process? process? = (λ (v) #f)
  | #:dir dir = #f
  | #:fov/2 fov/2 = #f
  | #:init-varc init-varc = full-varc
 
  rlg : rlgrid?
  start-x : flonum?
  start-y : flonum?
  wall? : (-> any/c boolean?)
  dir : (or/c #f flonum?)
  fov/2 : (or/c #f flonum?)
  init-varc : varc?
Uses racket/generator wrapper around vfan-iter to provide more convenient interface to visibility fan iteration.

The dir and fov/2 arguments have precedence over init-varc if they are both specified.