DeepMind Lab2D: Lua Levels API
July 6, 2021 · View on GitHub
On construction of the environment, the levelName setting specifies a game
script file name. By default, game script files live in the level directory
game_scripts/levels and end in .lua.
There is a setting allowing the user to specify their own level directories.
called levelDirectory.
The level script file name is searched for in the following order.
<levelName>(If levelName has .lua suffix.)<levelDirectory>/<levelName>.luaiflevelDirectoryis specified.<levelDirectory>/<levelName>/init.luaiflevelDirectoryis specified.game_scripts/levels/<levelName>.luagame_scripts/levels/<levelName>/init.lua
The level script must consist of Lua code that either returns a table, or a
function which when called returns a table. Where the table optionally
implementing the API functions below. An extra argument may be supplied to the
function call by specifying a suffix :<argument> to the levelName.
Require statements in scripts are processed in the following order.
dirname(<levelName>).<levelDirectory>if specified.game_scripts/levels.
An example is described here: game_scripts/levels/examples/level_api.lua
The calling code that calls into these API functions can be found in:
- env_lua_api/env_lua_api.cc.
- env_lua_api/actions.cc.
- env_lua_api/episodes.cc.
- env_lua_api/observations.cc.
- env_lua_api/properties.cc.
Initialisation
Called at startup once.
init(settings)- Constructs the environment readying the specs.observationSpec()- Returns the list of observations available.discreteActionSpec()- Optional, returns the list of discrete actions.continuousActionSpec()- Optional, returns the list of continuous actions.textActionSpec()- Optional, returns the list of text actions.
Episode Start
Called at any time after initialisation.
start(episode, seed)- Starts an episode ready for stepping.
Frame Step
Called at any time after the first call to start.
observation(idx)- Returns observation at index matching spec, may be called for each observation the agent requires.
Called only while an episode is active i.e. after start or after a call to
advance where the episode has not been marked as ended.
discreteActions(actions)- Optional, called with a list of discrete actions.continuousActions(actions)- Optional, called with a list of continuous actions.textActions(actions)- Optional, called with a list of text actions.advance(frame)- Advance the frame of the environment returning whether the episode is still active and the reward for that frame.
Property API
Called at any time after initialisation.
writeProperty(key, value)- Optional, sets a property.readProperty(key)- Optional, reads a property.listProperty(key, callback)- Optional, lists properties.
init(settings)
Called at game startup with the table settings representing a key-value store for all settings passed at the start of the environment.
Example:
function api:init(settings)
print('Script Settings')
for k, v in pairs(settings) do
print(k .. ' = ' .. v)
end
io.flush()
end
observationSpec() → array
observation(index) → MatchSpec
observationSpec is called after init. Returns an array of observation types.
Each entry must contain a name, type and shape.
nameName of the observation reported by the environment.typeType of tensor returned by the environment. Must be one of:'tensor.DoubleTensor','tensor.ByteTensor','tensor.Int32Tensor','tensor.Int64Tensor','String'.shapeAn array of integers denoting the shape of the tensor. If the shape of any dimension can change between calls, it must be set to-1here. Use an empty list to denote a scalar. Shape does not need to be specified for string types.
observation(index) is called after start. Returns an observation matching
the spec of the same index. This will be either a tensor or a string. (Scalar
values are auto converted.)
function api:observationSpec()
local count = 2
return {
{name = 'HITS', type = 'tensor.Int32Tensor', shape = {count, count}},
{name = 'PICK_UP_VALUES', type = 'doubles', shape = {-1}},
{name = '1.REWARD', type = 'tensor.DoubleTensor', shape = {}},
{name = '2.REWARD', type = 'tensor.DoubleTensor', shape = {}},
{name = '1.MESSAGE_FROM2', type = 'String'},
{name = '2.MESSAGE_FROM1', type = 'String'},
}
end
local order = 'Find Apples!'
local observationTable = {
tensor.Int32Tensor{2, 2}, -- 'HITS'
tensor.DoubleTensor(4) -- 'PICK_UP_VALUES'
0, -- 1.REWARD
0, -- 2.REWARD
'', -- 1.MESSAGE_FROM2
'', -- 2.MESSAGE_FROM1
}
function api:observation(idx)
return observationTable[idx]
end
discreteActionSpec() → array
discreteActions(actions)
discreteActionSpec is called after init. Returns a list of integer action
specs.
Each action must contain a name, min and max.
nameName of the action reported by the environment.minMin value of actions.maxMax value of actions.
When specified discreteActions is called with an array of current actions for
that frame.
function api:actionSpec()
return {
{name = 'move', min = 0, max = 4},
{name = 'turn', min = -1, max = 1},
{name = 'fire', min = 0, max = 1},
}
end
local discreteActions = {
move = 0,
turn = 0,
fire = 0,
}
function api:discreteActions(actions)
discreteActions.move = actions[1]
discreteActions.turn = actions[2]
discreteActions.fire = actions[3]
end
continuousActionSpec() → array
continuousActions(actions)
continuousActionSpec is called after init. Returns a list of floating point
action specs.
Each action must contain a name, min and max.
nameName of the action reported by the environment.minMin value of actions.maxMax value of actions.
When specified continuousActions is called with an array of current actions
for that frame.
function api:actionSpec()
-- Choose between ship speed and shields.
return {{name = 'shieldSpeed', min = -1.0, max = 1.0}}
end
shieldSpeed = -0.5,
function api:continuousActions(actions)
shieldSpeed = actions[1]
end
textActionSpec() → array[string]
textActionSpec is called after init. Returns a list of names of text
actions.
When specified textActions is called with an array of current actions for that
frame.
function api:actionSpec()
-- Choose between ship speed and shields.
return {"talkToPlayer1", "talkToPlayer2"}
end
talkToPlayer1 = '',
talkToPlayer2 = '',
function api:textActions(actions)
talkToPlayer1 = actions[1]
talkToPlayer2 = actions[2]
end
start(episode, seed)
The environment calls this function at the start of each episode, with:
- A number episode, starting from 0.
- A number seed, the random seed of the game engine. Supplying the same seed is intended to result in reproducible behaviour.
Example:
local random = require 'system.random'
function api:start(episode, seed)
random:seed(seed)
print('Entering episode no. ' .. episode .. ' with seed ' .. seed)
io.flush()
end
advance(frameIdx) → boolean, reward
Advance the simulation by one frame. Returns whether the episode should continue and the total reward for this frame.
Properties
PROPERTY_RESULT is one of:
local properties = require 'system.properties'
assert(properties.SUCCESS == 0)
assert(properties.NOT_FOUND == 1)
assert(properties.PERMISSION_DENIED == 2)
assert(properties.INVALID_ARGUMENT == 3)
writeProperty(key, value) → PROPERTY_RESULT
Callback to support RlCApi's write_property. Return true if
property[key] is assigned value. Return false if property[key] cannot
hold value and return nil if property[key] does not exist.
readProperty(key) → string or PROPERTY_RESULT
Callback to support RlCApi's read_property. Return value of property[key]
if it exists otherwise return nil.
listProperty(key, callback) → PROPERTY_RESULT
Callback to support RlCApi's list_property. Return true if property[key]
is a list and call callback(key, type) for each sub-property of
property[key] where key is the full length key to the sub-property and type a
string containing any combination of 'r', 'w' and 'l'.
If the type contains:
- 'l' - Listable and
listProperty(key)maybe called. - 'r' - Readable and
readProperty(key)maybe called. - 'w' - Writable and
writeProperty(key, value)maybe called.
Event API
Events are cleared at the start of each call to advance and can be created using
the system.events module, which can be loaded using local events = require 'system.events'. The module provides the following function.
add(name[, observation1[, observation2 ... ]])
Adds events to be read by the user. Each event has a list of observations. Each observation may be one of String, ByteTensor, DoubleTensor, Int32Tensor or Int64Tensor.
Example script:
local events = require 'system.events'
local tensor = require 'system.tensor'
local api = {}
function api:start(episode, seed)
events:add('Event Name', 'Text', tensor.ByteTensor{3}, tensor.DoubleTensor{7})
end