Grid World
July 13, 2021 · View on GitHub
local grid_world = require 'system.grid_world'
Library for defining and updating grid worlds.
Underlying C++ code is in dmlab2d/system/grid_world/...
World
Contains data that does not change between episodes. It contains hits, states, layers, groups, sprites, render_order and update_order.
local grid_world = require 'system.grid_world'
local world = grid_world.World{
-- Layers are declared implicitly in states, render layers are the order
-- that the sprites are placed when rendered. Sprites on layers other than
-- these are not visible.
renderOrder = {'layer0', 'layer1'},
-- A fixed number of named update phases. Each updater is called in order
-- during grid:update(...) and calls the equivalent function in onUpdate.
updateOrder = {'update0', {name = 'update1', func = 'funcName1'}},
-- Available beams for players to interact with other players at a distance.
-- There maybe a sprite associated with them.
hits = {
hitName0 = {
layer = 'hitLayer0',
sprite = 'AspriteName',
},
hitName1 = {
layer = 'hitLayer0',
sprite = 'BspriteNameH1',
},
},
-- List of piece states.
states = {
state0 = {
-- Layer the sprite is rendered on if in updateOrder. No two pieces
-- may be in the same location and layer.
layer = 'layer0',
-- Name of sprite to render.
sprite = '0spriteName',
-- Groups the state belongs to. All pieces belonging to a group can
-- be accessed and updated together.
groups = {'even', 'all'},
-- When one piece moves to or from the same location as another
-- piece (the pieces must be on different layers), that other piece
-- state's onContact is called with contactName and enter/leave.
contact = 'contactName0',
},
state1 = {
layer = 'layer1',
sprite = '1spriteName',
groups = {'odd', 'all'},
contact = 'contactName1',
},
state2 = {
layer = 'layer2',
sprite = '2spriteName',
groups = {'even', 'all'},
contact = 'contactName2',
}
},
-- This field is for custom sprites that are not associated with a state.
customSprites = {'customSprite1', 'customSprite2'},
-- When rendering the level from a certain location there can be locations
-- out of bounds of the described map. Render them with this sprite.
outOfBoundsSprite = 'OutOfBounds',
-- In certain rendering modes there are locations which are in the render
-- window but out of view for the avatar. Render them with this sprite.
outOfViewSprite = 'OutOfView',
}
world:createGrid{} → Grid, pieces
Returns a new grid with specified parameters. The second value pieces will
contain the pieces created during grid creation if a layout is specified. See
grid:createLayout.
local grid, peices = world:createGrid{
-- Either:
stateMap = stateMap,
layout = layout,
-- Or:
size = size,
--
stateCallbacks = stateCallbacks
topology = topology, -- Optional.
}
stateMap and layout
See grid:createLayout.
size
A table in the form {width = <inputWidth>, height = <inputHeight>}. An empty
grid is created with <inputWidth> by <inputHeight>.
topology
The topology of the grid. Must be one of:
grid_world.TOPOLOGY.BOUNDED(default) - Pieces may not move outside the bounds of the grid and rendered views will renderoutOfBoundsSprites outside of the playable area.grid_world.TOPOLOGY.TORUS- The left-right and top-bottom are joined together and the rendered view will loop too.
stateCallbacks
local stateCallbacks = {
state0 = {
onHit = true, -- Absorbs all beam weapons.
},
state1 = {
onAdd = function(grid, selfPiece) end,
onRemove = function(grid, selfPiece) end,
onBlocked = function(grid, selfPiece, blocker) end,
onContact = {
contactName1 = {
enter = function(grid, selfPiece, otherPiece) end,
leave = function(grid, selfPiece, otherPiece) end,
},
contactName2 = {
enter = function(grid, selfPiece, otherPiece) end,
leave = function(grid, selfPiece, otherPiece) end,
},
},
onUpdate = {
update0 = function(grid, selfPiece, framesOld) end,
funcName1 = function(grid, selfPiece, framesOld) end,
},
onHit = {
hitName0 = function(grid, selfPiece, hitterPiece)
return true --Returns whether the piece absorbs beam.
end,
hitName1 = function(grid, selfPiece, hitterPiece)
return true --Returns whether the piece absorbs beam.
end,
}
},
}
world:spriteNames() → array<string>
Returns a stable list of sprite names used when rendering grids.
world:createView(kwargs) → LayerView
Returns an object used for rendering sections of a Grid as layer observations around a selected cell. It can be used with system.tile_set to create RGB observations.
kwargs:
left(optional int) - number of cells left of selected cell to render.right(optional int) - number of cells right of selected cell to render.forward(optional int) - number of cells forward of selected cell to render.backward(optional int) - number of cells right of selected cell to render.layout(optional string) - Used instead ofleft,right,forwardandbackward. Calculates right and backwards such that the whole layout would be visible if the selected cell is (0, 0).centered- Whether to center observation such that all rotations do not move the selected cell visually.spriteMapmap[string][string] (Must be valid sprite names.) Remaps sprites for this layer viewer.
LayerView
LayerViews are designed to work with system.tile.Scene.
local tile = require 'system.tile'
local tile_set = require 'common.tile_set'
-- ...
local layerView = world:CreateView{left=5, right=5, forward=5, backward=5}
local set = tile_set(word, {width=8, height=8})
set:addShape('name', shapeData)
-- ...
local rgbScene= tile.Scene{
shape = layerView:gridSize(),
set = set,
}
-- ...
-- Scene view observation spec.
local rgbSceneSpec = {
name = 'RGB',
type = 'tensor.ByteTensor',
shape = rgbScene:shape(),
}
-- Render scene using layer view.
local rgbObservation = rgbScene:render(layerView:observation{grid = grid})
LayerView::observationSpec(name)
Returns the observation spec of the Layer view with a given name.
The returned value will be an int64 tensor with shape {H, W, L}. Where L is the
number of render layers specified in the world.
LayerView::gridSize() → {width=width, height=height}
Returns the size of the grid rendered by the layer view.
LayerView::observation(kwargs)
Returns the rendered view of the grid from specified cell and orientation.
kwargs:
gridThe grid to be rendered. (Must be created from the same world object.)transformoptional. The selected cell and orientation to render the grid from.pieceoptional. The piece handle to read the orientation and position from. (If the cell is off grid then the OutOfBounds sprite is rendered everywhere instead.)orientationoptional. Overides player orientation to render from a fixed orientation.
If no position or orientation is provided then it is assumed to render from: (0,0) North.
Grid
Terms:
positionrefers to a grid position of the form {x, y} starting at {0, 0} for the left-top and {gridWidth-1, gridHeight-1} for the right-bottom.orientationrefers to one of['N', 'E', 'S', 'W'].N- Piece relative forward or grid relative negative y.E- Piece relative right or grid relative positive x.S- Piece relative backwards or grid relative positive y.W- Piece relative left or grid relative negative x.
turnrefers to one of[0, 1, 2, 3]which represent the delta between two orientations.0- Turn 0 degrees.1- Turn 90 degrees counterclockwise.2- Turn 180 degrees.3- Turn 90 clockwise.
transformrefers to a piece position and orientation in the form:{pos=position, orientation=orientation}piecerefers to a piece created via createLayout or createPiece.randomrandom number generator returned by `require 'system.random'`` seeded at start of episode.
Grid removal
grid:destroy()
Forces the destruction of the grid and any references held by it.
if self._grid then
self._grid:destroy()
end
self._grid = self._world:createGrid{...}
Piece creation/removal.
grid:createLayout{} → pieces
local piecesCreated = grid:createLayout {
stateMap = stateMap,
layout = layout,
offset = offset,
}
stateMap
This a table of a character to state. Used by layout for building a grid.
local stateMap = {
['0'] = 'state0',
['1'] = 'state1',
['2'] = 'state2',
}
layout
A new-line separated string describing the grid layout and shape. New lines and
leading spaces are ignored. Characters not existing in stateMap are not
converted to pieces.
local layout = [[
..0.0.0.0..
.1.1.1.1.1.
2.2.2.2.2.2
]]
Assuming room for the pieces to be placed, it will have 4 pieces of state
state0, 5 of state state1 and 6 of state state2 and #piecesCreated will
be 15.
grid:createPiece(state, transform) → piece
Creates a piece at transform if the layer and location are available. Returns
a handle to that piece. Triggers the onAdd callback for state.
grid:removePiece(pieceHandle)
Removes a piece. pieceHandle is recycled and may appear in future calls to
createPiece().
User-State
grid:setUserState(piece, any)
Stores a reference to any, any Lua value/function, with piece.
-- Set table value
local userState = {1, 2, 3}
grid:setUserState(piece, userState)
assert(userState == grid:userState(piece))
-- Set string value
grid:setUserState(piece, "hello")
assert("hello" == grid:userState(piece))
-- Set number value
grid:setUserState(piece, 10)
assert(10 == grid:userState(piece))
-- Clear
grid:setUserState(piece, nil)
assert(nil == grid:userState(piece))
grid:userState(piece) → any
Retrieves user-state associated with piece if previously set, otherwise nil.
See grid:setUserState(piece, any).
Querying
grid:__tostring() → string
Debug utility for rendering a grid as a string using the first character of the sprite name for the highest-layer piece state at each location.
grid:transform(piece) → transform
Returns the transform of a piece.
grid:position(piece) → {x, y}
Returns the position of a piece. (Useful if the orientation is not needed.)
grid:state(piece) → string
Returns the current state of a piece.
grid:layer(piece) → string|nil
Returns the current layer of a piece or nil if it does not have one.
grid:frames(piece) → Number
Returns the number of frames a piece has been in its current state.
grid:rayCast(layer, positionStart, positionEnd) → hit, piece, position
Returns whether there is a piece on the line between positionStart and
positionEnd on a given layer, not including the start position. If hit is
true, the piece and position return values are the first piece found. In
torus topology positionEnd is reached by the shortest route available, using
the negative direction if two routes are equal. The resulting position may not
be normalised.
grid:rayCastDirection(layer, positionStart, direction) → hit, piece, offset
Returns whether there is a piece on a line between positionStart and
positionStart + direction on a given layer, not including the start
position, or whether the line is out of bounds. If a piece is found, the piece
and offset return values are the first piece found and its offset. In torus
topology rayCastDirection does not change direction, but the offset is not
normalised.
grid:queryPosition(layer, position) → piece or nil
Returns piece at given layer or nil.
grid:queryRectangle(layer, positionCorner1, positionCorner2) → table[piece, position]
Returns a table of all pieces to positions in rectangle on given layer between
positionCorner1 and positionCorner2 inclusive. In torus topology the
positions returned are not normalised.
grid:queryDiamond(layer, position, radius) → table[piece, position]
Returns a table of all pieces with an L1 distance to position less than or
equal to radius. In torus topology the positions returned are not normalised.
grid:queryDisc(layer, position, radius) → table[piece, position]
Returns a table of all pieces with an L2 distance to position less than or
equal to radius. In torus topology the positions returned are not normalised.
grid:groupCount(group) → Number
Returns the number of pieces belonging to a certain group.
grid:groupRandom(random, group) → piece
Returns a random piece belonging to a given group.
grid:groupShuffled(random, group) → array[piece]
Returns pieces belonging to a certain group in a random order.
grid:groupShuffledWithCount(random, group, count) → array[piece]
Returns count random pieces belonging to a certain group in a random order.
grid:groupShuffledWithProbability(random, group, probability) → array[piece]
Returns pieces belonging to a certain group in a random order, where each piece has the given probability of being returned.
Converting coordinates
Each piece has a position and an orientation (in absolute space). This determines for each piece a relative-space grid in which the piece is at the (relative) origin and its orientation is (relative) North.
For example, here is a piece (marked R) facing absolute East, and its relative
grid. A second piece (marked X) can be expressed in absolute terms or relative
to R:
grid origin
A-------------> E
| X-->u
| W
| ^
| |
| |
| S<-----R------->N
| |
| |
| v
v E
S
Here:
Ais the origin of the absolute-space grid (origin at top-left).Ris the origin of the relative-space grid whose position and orientation are relative to the given piece.Xis a test particle with orientationu.X's position is in the absolute South-East pointing absolute East. Relative toR, it is inR's relative South-West and pointing North.
grid:toAbsolutePosition(piece, position) → {x, y}
Converts position in piece relative-space to a position in absolute-space.
grid:toRelativePosition(piece, position) → {x, y}
Converts position in absolute-space to piece relative-space.
grid:toAbsoluteDirection(piece, direction) → {x, y}
Converts direction in piece relative-space to a direction in absolute-space.
grid:toRelativeDirection(piece, direction) → {x, y}
Converts direction in absolute-space to piece relative-space.
Updating
All updates are queued when called, then processed during grid:update(random, flushCount = 128). Callbacks may introduce new updates on the queue. These will
be flushed up to flushCount (128) times.
grid:setUpdater{update=update, group=group, probability=1.0, startFrame=0}
Sets the group of pieces to be updated during grid:update(random). The update
will trigger the callback onUpdate.update(grid, piece)
grid:update(random, flushCount = 128)
Updates the grid, processing all actions queued. If new actions are queued
during the update via callbacks they are flushed up to flushCount (128) times.
grid:moveAbs(piece, orientation)
Pushes a piece in the direction specified by orientation. World relative: the
piece's orientation is not taken into account. If there is a piece in the target
location at the time of the move then the piece stays where it is and
onBlocked is called.
Triggers onContact.contactName.leave callbacks for pieces at current location
and onContact.contactName.enter for pieces at the location moved to. Both
callbacks are triggered even if the move is not possible, in which case piece
effectively leaves and enters the same cell.
grid:moveRel(piece, orientation)
Pushes a piece in specified direction relative to the direction the piece is
facing. If there is a piece in the target location at the time of the move then
the piece stays where it is and onBlocked is called. Triggers
onContact.contactName.leave and onContact.contactName.enter callbacks in the
same way as grid:moveAbs(piece, orientation)
grid:teleport(piece, position)
Teleports the player to a given position. Triggers onContact.contactName.leave
and onContact.contactName.enter in the same way as grid:moveAbs(piece, orientation).
grid:turn(piece, turn)
Turns a piece by given value:
0-> No Op.1-> 90 degrees clockwise.2-> 180 degrees.3-> 90 degrees counterclockwise.
grid:setOrientation(piece, orientation)
Sets the orientation of a piece (the direction it is facing in grid space):
'N'-> North (decreasing y).'E'-> East (increasing x).'S'-> South (increasing y.'W'-> West (decreasing x).
grid:hitBeam(piece, hitName, width, radius)
Fires a beam with a given hitName with a given width and radius. Beams are
produced sideways then forwards and triggers onHit.hitName(pieceHit, hitter)
in order the pieces they hit. The callback may return a true in order for the
beam to not progress beyond that cell. Otherwise if the function returns false
or nothing the beam will progress to next cell. The beam will be visualised by
the given layer and sprite in the hits section of the world.
grid:setState(piece, state)
Sets the state of a piece. The transition to the new state will happen during an
update as soon as a layer becomes available. Triggers callback onRemove(piece)
for the previous state and onAdd(piece) for the new state. Also triggers
onContact.contactName.leave and onContact.contactName1.enter for pieces at
that location.
grid:teleportToGroup(piece, group, state[, orienationFlag])
Sets the position of a piece to an unoccupied layer and position in group
group. Calls the same callbacks as grid::setState(),
orienationFlag can be one of the following:
grid_world.TELEPORT_ORIENTATION.PICK_RANDOM: Default - Picks a random orientation.grid_world.TELEPORT_ORIENTATION.KEEP_ORIGINAL: Keeps piece's original orientation.grid_world.TELEPORT_ORIENTATION.MATCH_TARGET: Matches target piece's orientation.
To pick own orientation:
local KEEP_ORIENTATION = grid_world.TELEPORT_ORIENTATION.KEEP_ORIGINAL
grid:setOrientation(piece, orientation)
grid:teleportToGroup(piece, group, nil, KEEP_ORIENTATION)
Connecting
grid:connect(piece1, piece2)
Connects two piece together in such a way that if one piece is pushed they are all pushed.
grid:disconnect(piece)
Disconnect a previously connected piece.
grid:disconnectAll(piece)
Disconnects a piece and all transitively connected pieces.