Skip to main content
Version: 🚧 Alpha 🚧

2. The Forager Agent

By Killian Trouillet


Content​

This second step adds a forager agent that moves randomly on the grid. The forager is displayed as a blue circle and moves one cell per simulation step in a random direction (up, right, down, or left). It cannot walk through obstacles or outside the grid boundaries.

Formulation​

  • Definition of the forager species with a my_cell attribute linking it to the grid
  • Definition of a random_move reflex for movement
  • Display of the forager as a blue circle
  • Boundary and obstacle collision checking

Model Definition​

Forager species​

We define a new species called forager. The key attribute is my_cell, which stores a reference to the grid cell the forager currently occupies. This links the agent to the grid topology.

species forager {
world_cell my_cell;
  • world_cell my_cell: A variable of type world_cell (our grid species). This acts as the forager's "position" on the grid.

Random movement reflex​

A reflex is a behavior that is automatically executed at every simulation step. Here, we pick a random direction (0–3), compute the target coordinates, check boundaries and obstacles, and move.

reflex random_move {
int direction <- rnd(3);
int new_x <- my_cell.grid_x;
int new_y <- my_cell.grid_y;

switch direction {
match 0 { new_y <- new_y - 1; } // up
match 1 { new_x <- new_x + 1; } // right
match 2 { new_y <- new_y + 1; } // down
match 3 { new_x <- new_x - 1; } // left
}

if (new_x >= 0 and new_x < grid_size and new_y >= 0 and new_y < grid_size) {
world_cell target <- world_cell grid_at {new_x, new_y};
if (not target.is_obstacle) {
my_cell <- target;
location <- my_cell.location;
}
}
}
  • rnd(3): Returns a random integer between 0 and 3 (inclusive).
  • switch/match: A control structure to execute different code depending on the value. This is cleaner than a chain of if/else.
  • my_cell.grid_x / my_cell.grid_y: Built-in attributes of grid cells giving their column and row indices.
  • location <- my_cell.location: Updates the forager's geometric position to the center of the target cell, so it is displayed correctly.

Aspect​

An aspect defines how the agent is displayed.

aspect default {
draw circle(0.8) color: #blue;
}
}
  • draw circle(0.8): Draws a circle with radius 0.8, large enough to be clearly visible within a grid cell.
  • The default aspect is used automatically when no specific aspect is requested.

Creating the forager​

In the global init block, we create one forager and place it at position (0, 0):

create forager number: 1 {
my_cell <- world_cell grid_at {0, 0};
location <- my_cell.location;
}

Updated display​

We add the forager species to the display. Order matters: agents drawn later appear on top.

display "Grid World" {
grid world_cell border: #lightgray;
species forager;
graphics "food" {
ask world_cell where each.is_food {
draw circle(5) color: rgb(50, 180, 50);
}
}
}

Complete Model​

/**
* Name: SmartForager - Step 2: The Forager Agent
* Author: Killian Trouillet
* Description: This second step adds a forager agent that moves randomly on the grid.
* The forager cannot walk into obstacles or outside the grid boundaries.
* Tags: reinforcement-learning, grid, agent, movement, tutorial
*/

model SmartForager

global {
int grid_size <- 10;
int food_x <- 9;
int food_y <- 9;
list<point> obstacle_positions <- [{2,2}, {3,2}, {2,3}, {6,4}, {7,4}, {7,5}];

init {
ask world_cell grid_at {food_x, food_y} {
is_food <- true;
}
loop pos over: obstacle_positions {
ask world_cell grid_at pos {
is_obstacle <- true;
}
}
// Create the forager at position (0, 0)
create forager number: 1 {
my_cell <- world_cell grid_at {0, 0};
location <- my_cell.location;
}
}
}

grid world_cell width: 10 height: 10 neighbors: 4 {
bool is_food <- false;
bool is_obstacle <- false;
rgb color <- #white update: is_obstacle ? rgb(60, 60, 60) : #white;
}

species forager {
world_cell my_cell;

reflex random_move {
// Pick a random direction: 0=up, 1=right, 2=down, 3=left
int direction <- rnd(3);
int new_x <- my_cell.grid_x;
int new_y <- my_cell.grid_y;

switch direction {
match 0 { new_y <- new_y - 1; } // up
match 1 { new_x <- new_x + 1; } // right
match 2 { new_y <- new_y + 1; } // down
match 3 { new_x <- new_x - 1; } // left
}

// Check boundaries and obstacles
if (new_x >= 0 and new_x < grid_size and new_y >= 0 and new_y < grid_size) {
world_cell target <- world_cell grid_at {new_x, new_y};
if (not target.is_obstacle) {
my_cell <- target;
location <- my_cell.location;
}
}
}

aspect default {
draw circle(0.8) color: #blue;
}
}

experiment smart_forager type: gui {
output {
display "Grid World" {
grid world_cell border: #lightgray;
species forager;
graphics "food" {
ask world_cell where each.is_food {
draw circle(5) color: rgb(50, 180, 50);
}
}
}
}
}