Agentscape is a library for creating agent-based simulations. It provides a simple API for defining agents and their behavior, and for defining the environment in which the agents interact. Agentscape is designed to be flexible and extensible, allowing users to create a wide variety of simulations.
npm i agentscape
We will create a simple simulation where agents move randomly around a grid. We start by defining an agent.
An agent is a class that extends Entities.Agent
. The agent must implement an act
method that defines the agent's behavior for each step of the simulation.
import { Entities } from 'agentscape'
import { CellGrid } from 'agentscape/structures'
export default class Agent extends Entities.Agent {
constructor(opts: Entities.AgentConstructor) {
super(opts)
}
// agent moves to a random cell in its field of view
act(grid: CellGrid<Entities.Cell>) {
const potentialMoves = this.getCellsWithinCone(grid, 90, 5)
// if there are no cells in view (ie. agent is facing world boundary),
// then face a random cell and try again
if (potentialMoves.length == 0 ) {
this.faceCell(grid.random(this.rng))
return this.act(grid)
}
const destination = this.rng.pickRandom(potentialMoves)
this.faceCell(destination)
this.move(1, grid)
}
}
Next, we define a model that contains the agents and the grid. A model consists of zero or more agents and one grid of cells.
Agents are grouped into an AgentSet
and a Cell
is grouped into a CellGrid
. The Model
must implement methods to initialize agent sets and a cell grid, and an update
method that defines the behavior of the model for each step of the simulation.
import { Model, ModelConstructor} from 'agentscape'
import { Cell } from 'agentscape/entities'
import { CellGrid, AgentSet } from 'agentscape/structures'
import Agent from './Agent'
import { Render2D } from 'agentscape/ui'
export default class RandomWalk extends Model<Agent, CellGrid<Cell>> {
constructor(opts: ModelConstructor) {
super(opts)
}
initAgents() {
const _default = AgentSet.fromFactory(100, () => new Agent({
// init position is the center of the 100x100 grid
initialPosition: [50, 50]
}))
return {_default}
}
initGrid() {
// create a 100x100 grid
// the default grid of cells is sufficient for this example
return CellGrid.default(100)
}
update(): (renderer: Render2D) => void {
return (renderer: Render2D) => {
renderer.clear()
this.agents._default.step(this.grid)
renderer.drawAgentSet(this.agents._default)
}
}
}
Finally, we create an instance of the model and run the simulation.
import RandomWalkModel from './Model'
const documentRoot = document.getElementById('root') as HTMLDivElement
const model = new RandomWalkModel({
documentRoot,
renderWidth: 800,
id: 'random-walk',
autoPlay: false,
frameRate: 10,
})
model.start()
We can add parameters to the model that can be controlled by the user. Parameters are defined as an array of ControlVariable
objects and passed to the model constructor.
import { ControlVariable } from 'agentscape/ui'
const parameters: ControlVariable[] = [
{
label: 'Grid Size',
name: 'gridSize',
type: 'number',
default: 100
},
{
label: 'Number of Agents',
name: 'agentCount',
type: 'number',
default: 100
},
{
label: 'Random Seed',
name: 'randomSeed',
type: 'number',
default: 0
}
]
const model = new RandomWalkModel({
documentRoot,
renderWidth: 800,
title: 'Random Walk',
id: 'random-walk',
parameters,
autoPlay: false,
frameRate: 10,
})
The parameters can be accessed in the scope of the model class as properties by using the @ControlVariable
decorator.
export default class RandomWalk extends Model<Agent, CellGrid<Cell>> {
@ControlVariable('gridSize')
gridSize: number
@ControlVariable('agentCount')
agentCount: number
@ControlVariable('randomSeed')
randomSeed: number
constructor(opts: ModelConstructor) {
super(opts)
this.setRandomSeed(this.randomSeed)
}
initAgents() {
const _default = AgentSet.fromFactory(this.agentCount, () => new Agent({
// init position is the center of the 100x100 grid
initialPosition: [Math.floor(this.gridSize / 2), Math.floor(this.gridSize / 2)],
}))
return {_default}
}
initGrid() {
return CellGrid.default(this.gridSize)
}
update(): (renderer: Render2D) => void {
return (renderer: Render2D) => {
renderer.clear()
this.agents._default.step(this.grid)
renderer.drawAgentSet(this.agents._default)
}
}
}