A simple simulation of the game Tag.
> git clone https://github.com/jvallikivi/tag.git
> cd tag
> cargo run --release
Currently, actions are defined as follows (and can be added/modified as shown in the collapsible section below):
- One step left/right/up/down
- Stand still
- Tag someone
As defined in ./src/actions.rs, action definitions follow a generic PDDL (Planning Domain Definition Language) approach. Action parameters are not included due to the simple nature of the game.
Every agent holds a weight (preference) for every possible action, which is correlated with the probability that the agent chooses it. These preferences change over time in a random, yet mean reverting fashion.
Show me how to add/modify an action!
Feel free to add/modify actions in ActionContext::new in ./src/actions.rs, where the 6 existing actions are defined. Below is an example of an action that could be added which moves the agent left and up in one move. Make sure that the agents don't leave the grid! Take care of data races as the simulation runs on multiple threads. For example if two taggers show the intent of tagging the same untagged agent, running tag_effect may panic.
pub fn new() -> ActionContext {
/* Other already defined actions go above here */
// The precondition for moving left and up by one:
// The closure is given the agent id, the agent manager and grid
let left_up_step_precond: Precondition = |id, am, grid| {
let position = am.get_position(id);
// Make sure that the agent is not already standing on
// the left-most or top-most edge of the grid
if position.x == 0 || position.y == 0 {
return false;
}
if COLLSION_DETECTION {
// Check that the destination has no other
// agents around its vicinity.
// In grid.rs, check out the following methods
// for searching the grid:
// 'is_subgrid_free', 'is_subgrid_occupied',
// 'get_subgrid_occupiers'
grid.is_subgrid_free(
// Destination
Position {
x: position.x - 1,
y: position.y - 1,
},
// See the Parameters secion in Readme
STEP_SG_SIDE,
STEP_SG_SIDE,
// Agent ids which should be ignored in
// checking whether the vicinity is free
vec![id],
// Optional closure of type
// Option<&dyn Fn(Id) -> bool>
// which checks if given an id of
// an agent inside the defined vicinity
// should it be ignored or not.
// Check out the tag_precond function
// which includes a closure which
// tells grid to ignore other agents
// which are already tagged
None,
)
} else {
true
}
};
// The effect of executing the action of moving
// left and up by one, calling this realises the change
// in the simulation
let left_up_step_effect: Effect = |id, am, _grid| {
let mut position = am.get_position(id);
position.x -= 1;
position.y -= 1;
am.set_position(id, position);
};
// Create the action by combining the precondition and effect
let left_up_step: Action = Action {
precond: left_up_step_precond,
effect: left_up_step_effect,
};
// Put all actions (including the new, 7th action) in a vector
let actions: Vec<Action> = vec![
left_step,
right_step,
up_step,
down_step,
do_nothing,
tag,
left_up_step,
];
let action_count = actions.len();
ActionContext {
actions,
// Define some weights for each action (in the same order
// as in 'actions') indicating what preferences should
// agents have on average. For example 0.5 for left_up_step
// shows that it should, in a longer timeframe, be picked
// as often as left_step, right_step, up_step and down_step
mean_preferences: vec![0.5, 0.5, 0.5, 0.5, 0.1, 0.9, 0.5],
action_count,
}
}The parameters that can be played with with are in ./src/main.rs. After every modification it is important to build (cargo build --release) again.
pub const USE_VIEWER: bool = true;
pub const COLLSION_DETECTION: bool = true;
pub const STEP_SG_SIDE: usize = 21;
pub const TAG_SG_SIDE: usize = 31;
pub const GRID_SIDE: usize = 800;
pub const NUM_STEPS: usize = 20000;
pub const NUM_AGENTS: usize = 1000;
pub const NUM_AGENTS_IT: usize = 2;
USE_VIEWER: Whether to visualise the simulation (not recommended for benchmarking)COLLISION_DETECION: If true, the simulation does not allow agents too close to each other (overlapping agents), meaning agents act as movement barriers to each other - a more life-like approach. However it results in a simulation which is approximately twice as slow as a simulation without any collision detectionSTEP_SG_SIDE: If an agent wants to step into a grid location x, a square of side lengthSTEP_SG_SIDEwith center at x, must not contain any other agents. This is only used ifCOLLISION_DETECTIONis trueTAG_SG_SIDE: If an agent wants to tag a target agent (no tag-backs (see More) or tagging someone who is already it (in games with multiple agents being it at the same time)) then the target agent must be in the square of side lengthTAG_SG_SIDEwith center at the agent who wants to tag. This is to simulate the proximity requirement of tagging someoneGRID_SIDE: The environment is a square grid with side lengthGRID_SIDENUM_AGENTS: Number of agents in the simulation. Note that if the propsed number of agents exceeds the upper boundAGENT_NUM_UPPER_BOUND, then the exceeding agents will not be addedNUM_AGENTS_IT: Number of agents that initially are tagged (it)