The Sheep and Wolves Game
In this example, we will consider a model, the essence of the simulation of which is to display the "Predator-Prey-Plants" scenario. This is an agent-based model that simulates an ecosystem with three key components:
- Sheep (victims) - they feed on plants and reproduce with sufficient energy
- Wolves (predators) - hunt sheep, require more energy to survive
- Plants - regenerate over time, serve as food for sheep
The key mechanisms here are nutrition cycles, represented as follows Wolves → Sheep → Plants and population dynamics, which are regulated automatically through energy consumption (starvation) and reproduction when energy thresholds are reached, as well as random movement of agents
The model demonstrates the classical principles of ecology:
- Food chains
- Competition for resources
- Dynamic population equilibrium
- The bottleneck effect of species extinction
Pkg.add("StatsBase")
using Random
using StatsBase
Now we will declare the main parameters and structures of the objects of our model.
- 
World Parameters: - WIDTHand- HEIGHT- dimensions of the two-dimensional world (in cells)
- MAX_STEPS- limit of simulation iterations
- SAVE_EVERY- frequency of saving state for visualization
 
- 
Agents: - All agents have coordinates (x,y) and inherit from the base type Agent
- Sheepand- Wolfcontain the parameter- energy- a key indicator for:- Survival (at energy < 0, the agent dies)
- Reproduction (requires sufficient energy level)
 
- Bushhas a flag- active- can it be eaten
 
- All agents have coordinates (x,y) and inherit from the base type 
- 
Model: - Contains arrays of all agents (sheep,wolves)
 
- Contains arrays of all agents (
- The matrix bushes(plants) of the size WIDTH×HEIGHT
- Step counter step- Dictionary paramswith configurable parameters:- Probabilities of events (reproduction, restoration of plants)
 
 
- Dictionary 
- Energy parameters (consumption, consumption, thresholds)
- Ecosystem balance (survival difficulty can be adjusted)
These structures form the framework for:
- Simulation of agent movement
- Food systems (wolves→sheep→plants)
- Mechanisms of reproduction and death
- Visualization of the ecosystem state
const WIDTH = 50
const HEIGHT = 50
const MAX_STEPS = 2000
const SAVE_EVERY = 100
abstract type Agent end
mutable struct Sheep <: Agent
    x::Int
    y::Int
    energy::Int
end
mutable struct Wolf <: Agent
    x::Int
    y::Int
    energy::Int
end
mutable struct Bush <: Agent
    x::Int
    y::Int
    active::Bool
end
mutable struct Model
    sheep::Vector{Sheep}
    wolves::Vector{Wolf}
    bushes::Matrix{Bush}
    step::Int
    params::Dict{Symbol,Float64}
end
Function init_model creates the initial state of an ecosystem model with three types of agents: sheep, wolves, and plants.
What does the function do:
- 
Initializes the world, creates a matrix of plants measuring WIDTH×HEIGHT (the number is set in n_bushes)
- 
Creates agents: sheep ( n_sheeppieces) with starting energysheep_energy, wolves (n_wolvespieces) with starting energywolf_energy, randomly places them on the map
- 
Adjusts the model parameters through the dictionary params:- Probabilities of plant recovery and animal reproduction
- Energy consumption during starvation
- Energy gain during nutrition
- Energy thresholds for reproduction
 
Returns: Object type Model With the initial state of the ecosystem, all these function parameters allow you to flexibly adjust the ecosystem balance before launch.
function init_model(;
    n_sheep = 50,
    n_wolves = 10,
    n_bushes = 500,
    sheep_energy = 10,
    wolf_energy = 50,
    bush_regrowth = 0.01,
    sheep_reprod = 0.04,
    wolf_reprod = 0.05,
    sheep_hunger = 1,
    wolf_hunger = 2,
    sheep_eat_gain = 5,
    wolf_eat_gain = 20,
    sheep_reprod_thresh = 15,
    wolf_reprod_thresh = 30
)
bushes = Matrix{Bush}(undef, WIDTH, HEIGHT)
for x in 1:WIDTH, y in 1:HEIGHT
    bushes[x,y] = Bush(x, y, false)
end
all_positions = [(x, y) for x in 1:WIDTH, y in 1:HEIGHT]
bush_positions = sample(all_positions, n_bushes, replace=false)
for (x, y) in bush_positions
    bushes[x,y].active = true
end
sheep = Sheep[]
for _ in 1:n_sheep
    x, y = rand(1:WIDTH), rand(1:HEIGHT)
    push!(sheep, Sheep(x, y, sheep_energy))
end
wolves = Wolf[]
for _ in 1:n_wolves
    x, y = rand(1:WIDTH), rand(1:HEIGHT)
    push!(wolves, Wolf(x, y, wolf_energy))
end
params = Dict(
    :bush_regrowth => bush_regrowth,
    :sheep_reprod => sheep_reprod,
    :wolf_reprod => wolf_reprod,
    :sheep_hunger => sheep_hunger,
    :wolf_hunger => wolf_hunger,
    :sheep_eat_gain => sheep_eat_gain,
    :wolf_eat_gain => wolf_eat_gain,
    :sheep_reprod_thresh => sheep_reprod_thresh,
    :wolf_reprod_thresh => wolf_reprod_thresh
)
Model(sheep, wolves, bushes, 0, params)
end
Function move_agent! Moves the agent (sheep or wolf) on the map randomly. It generates a random offset dx and dy takes the values -1, 0, or 1 (a step in any direction or in place) and calculates the new coordinates, mod1 provides "wrapping" of borders - if the agent goes beyond the edge of the map, it appears on the opposite side.
function move_agent!(agent::Agent, model)
    dx, dy = rand(-1:1, 2)
    new_x = mod1(agent.x + dx, WIDTH)
    new_y = mod1(agent.y + dy, HEIGHT)
    agent.x, agent.y = new_x, new_y
end
Function eat!(sheep::Sheep, model) allows a sheep to eat a plant in its current cage. She checks the bush under the sheep (model.bushes[sheep.x, sheep.y]), if the bush is active (active=true), makes the bush inactive (eats it) and increases the sheep's energy by sheep_eat_gain from the parameters, returns true (successful feeding), if the bush is inactive, returns false
Function reproduce!(agent::Sheep, model), carries out sheep breeding when conditions are met. It checks two conditions: a random number is less likely to reproduce (sheep_reprod) and the sheep 's energy is above the threshold value (sheep_reprod_thresh), if the conditions are met: divides the sheep's energy in half, creates a new sheep with the same coordinates and half the energy, adds it to the model, returns true (successful reproduction), if the conditions are not met, returns false
function eat!(sheep::Sheep, model)
    bush = model.bushes[sheep.x, sheep.y]
    if bush.active
        bush.active = false
        sheep.energy += model.params[:sheep_eat_gain]
        return true
    end
    false
end
function reproduce!(agent::Sheep, model)
    if rand() < model.params[:sheep_reprod] && agent.energy > model.params[:sheep_reprod_thresh]
        agent.energy ÷= 2
        new_sheep = Sheep(agent.x, agent.y, agent.energy)
        push!(model.sheep, new_sheep)
        return true
    end
    false
end
Then there are similar functions for wolves, the key differences from sheep:
- 
Wolves hunt sheep (not plants) 
- 
Use their own parameters ( wolf_eat_gain,wolf_reprodetc.)
- 
The mechanics of reproduction are similar, but with different numerical values 
function eat!(wolf::Wolf, model)
    sheep_idx = findfirst(s -> s.x == wolf.x && s.y == wolf.y, model.sheep)
    if !isnothing(sheep_idx)
        splice!(model.sheep, sheep_idx)
        wolf.energy += model.params[:wolf_eat_gain]
        return true
    end
    false
end
function reproduce!(agent::Wolf, model)
    if rand() < model.params[:wolf_reprod] && agent.energy > model.params[:wolf_reprod_thresh]
        agent.energy ÷= 2
        new_wolf = Wolf(agent.x, agent.y, agent.energy)
        push!(model.wolves, new_wolf)
        return true
    end
    false
end
Function regenerate_bushes!(model), restores eaten plants (bushes) on the map with a certain probability, this function runs through all cells of the playing field (WIDTH × HEIGHT), for each inactive bush (bush.active == false) with probability bush_regrowth (from the parameters of the model) makes the bush active, the probability is checked by comparing a random number with model.params[:bush_regrowth]
function regenerate_bushes!(model)
    for x in 1:WIDTH, y in 1:HEIGHT
        bush = model.bushes[x,y]
        if !bush.active && rand() < model.params[:bush_regrowth]
            bush.active = true
        end
    end
end
Function step!(model) performs one step of ecosystem simulation, updating the status of all agents and verifying completion conditions.
Main stages of work:
- 
Increment of the step counter model.step += 1
- 
Sheep handling (in a cycle): - Moving (move_agent!)
 
- Moving (
- Nutrition (if you can 't eat , reduce energy by sheep_hunger)
- An attempt at reproduction (reproduce!)
- 
Wolf treatment (in a cycle): - Moving (move_agent!)
 
- Moving (
- Hunting (if not successful , decrease energy by wolf_hunger)
- An attempt at reproduction (reproduce!)
- 
Filtering of deceased agents: - Removing sheep with energy less than 0
 
- Removing wolves with energy less than 0
- 
Plant restoration: - regenerate_bushes!- renewal of eaten bushes
 
- 
Checking completion conditions: - Extinction of all sheep or all wolves
 
- Reaching the maximum number of steps (MAX_STEPS)
- Output of relevant messages
- 
Progress logging: each SAVE_EVERYsteps displays the current statistics
- 
Return value: 
- false- if the simulation is to end
- true- if the simulation continues
function step!(model)
    model.step += 1
    for sheep in model.sheep
        move_agent!(sheep, model)
        if !eat!(sheep, model)
            sheep.energy -= model.params[:sheep_hunger]
        end
        reproduce!(sheep, model)
    end
    for wolf in model.wolves
        move_agent!(wolf, model)
        if !eat!(wolf, model)
            wolf.energy -= model.params[:wolf_hunger]
        end
        reproduce!(wolf, model)
    end
    filter!(s -> s.energy > 0, model.sheep)
    filter!(w -> w.energy > 0, model.wolves)
    regenerate_bushes!(model)
    if isempty(model.sheep) || isempty(model.wolves)
        if isempty(model.sheep)
            println("Симуляция остановлена на шаге $(model.step): все овцы вымерли.")
        else
            println("Симуляция остановлена на шаге $(model.step): все волки вымерли.")
        end
        return false
    end
    if model.step >= MAX_STEPS
        println("Симуляция завершена: достигнуто максимальное количество шагов ($MAX_STEPS).")
        return false
    end
    if model.step % SAVE_EVERY == 0
        println("Прогресс: завершен шаг $(model.step). Овец: $(length(model.sheep)), волков: $(length(model.wolves))")
    end
    true
end
Function plot_model(model) вIt visualizes the current state of the ecosystem in the form of a color map, displaying the location of all agents and plants.
- Each agent occupies exactly one square on the map
- Display priority: wolves > sheep > plants (if they are in the same cage)
- Colors are selected for intuitive interpretation:
- Red - danger (predators)
 
- White - neutral (prey)
- Green - plants
function plot_model(model)
    grid = zeros(Int, WIDTH, HEIGHT)
    for x in 1:WIDTH, y in 1:HEIGHT
        if model.bushes[x,y].active
            grid[x,y] = 3
        end
    end
    for sheep in model.sheep
        grid[sheep.x, sheep.y] = 2
    end
    for wolf in model.wolves
        grid[wolf.x, wolf.y] = 1
    end
    
    colors = [colorant"black", colorant"red", colorant"white", colorant"green"]
    heatmap(grid, 
        c=colors, 
        clim=(0,3),
        title="Шаг: $(model.step) | Овцы: $(length(model.sheep)) | Волки: $(length(model.wolves))",
        axis=false,
        legend=false
    )
end
Function run_simulation the main function that runs and manages the entire ecosystem simulation.
It includes the following steps::
- 
Initialization: 
- 
The main simulation cycle includes step-by-step visualization of the current state ( plot_model), performing the simulation step (step!), saves the current population data and is interrupted when the stop conditions are met.
- 
Create an animation and save the simulation process to a GIF file "wolf_sheep_sim.gif" 
- 
Output of the result. 
function run_simulation(;
    n_sheep = 50,
    n_wolves = 10,
    n_bushes = 500,
    sheep_energy = 10,
    wolf_energy = 50,
    bush_regrowth = 0.01,
    sheep_reprod = 0.04,
    wolf_reprod = 0.05,
    sheep_hunger = 1,
    wolf_hunger = 2,
    sheep_eat_gain = 5,
    wolf_eat_gain = 20,
    sheep_reprod_thresh = 15,
    wolf_reprod_thresh = 30
)
model = init_model(
    n_sheep = n_sheep,
    n_wolves = n_wolves,
    n_bushes = n_bushes,
    sheep_energy = sheep_energy,
    wolf_energy = wolf_energy,
    bush_regrowth = bush_regrowth,
    sheep_reprod = sheep_reprod,
    wolf_reprod = wolf_reprod,
    sheep_hunger = sheep_hunger,
    wolf_hunger = wolf_hunger,
    sheep_eat_gain = sheep_eat_gain,
    wolf_eat_gain = wolf_eat_gain,
    sheep_reprod_thresh = sheep_reprod_thresh,
    wolf_reprod_thresh = wolf_reprod_thresh
)
sheep_history = Int[]
wolf_history = Int[]
bush_history = Int[]
step_history = Int[]
anim = @animate for _ in 1:MAX_STEPS
    plot_model(model)
    should_continue = step!(model)
    push!(sheep_history, length(model.sheep))
    push!(wolf_history, length(model.wolves))
    push!(bush_history, sum(b.active for b in model.bushes))
    push!(step_history, model.step)
    
    should_continue || break
end
println("\nРезультаты симуляции:")
println("Финальный шаг: $(model.step)")
println("Количество овец: $(length(model.sheep))")
println("Количество волков: $(length(model.wolves))")
println("Количество растений: $(sum(b.active for b in model.bushes))")
plt = plot(step_history, [sheep_history, wolf_history, bush_history],
     label = ["Овцы" "Волки" "Растения"],
     xlabel = "Шаги симуляции",
     ylabel = "Количество",
     title = "Динамика популяций",
     linewidth = 2)
display(plt)
display(gif(anim, "wolf_sheep_sim.gif", fps=10))
end
Next, we run it, for your convenience, masks of code cells have been applied, in accordance with the example below, you can control the entire model using sliders.
run_simulation(
    # Basic population
parameters n_sheep = 100, # Initial number of sheep
    n_wolves = 5, # Initial number of wolves
n_bushes = 70, # Initial amount of grass
    
    # Energy parameters
    sheep_energy = 100, # Initial energy of sheep
    wolf_energy = 1000, # Initial energy of wolves
    
    # Resource Recovery Options
    bush_regrowth = 0.005, # Probability of grass restoration per step
    
    # Breeding Parameters
    sheep_reprod = 0.02, # Probability of sheep reproduction per step
    wolf_reprod = 0.1, # The probability of reproduction of a wolf per step
    
    # Fasting parameters
    sheep_hunger = 1, # Sheep's energy loss per step without food
    wolf_hunger = 3, # Loss of wolf energy per step without food
    
    # Power Parameters
    sheep_eat_gain = 8, # Sheep's energy gain when eating grass
    wolf_eat_gain = 25, # Wolf's energy gain when eating sheep
    
    # Breeding thresholds
    sheep_reprod_thresh = 20, # Minimum energy for sheep reproduction
    wolf_reprod_thresh = 35 # Minimum energy for wolf reproduction
)
run_simulation(
n_sheep = 100 # @param {type:"slider",min:0,max:1000,step:1}
,
n_wolves = 24 # @param {type:"slider",min:0,max:1000,step:1}
,
n_bushes = 70 # @param {type:"slider",min:0,max:1000,step:1}
,
sheep_energy = 100 # @param {type:"slider",min:0,max:1000,step:1}
,
wolf_energy = 79 # @param {type:"slider",min:0,max:1000,step:1}
,
bush_regrowth = 0.005 # @param {type:"slider",min:0,max:1,step:0.001}
,
sheep_reprod = 0.02 # @param {type:"slider",min:0,max:1,step:0.01}
,
wolf_reprod = 0.01 # @param {type:"slider",min:0,max:1,step:0.01}
,
sheep_hunger = 1 # @param {type:"slider",min:0,max:1000,step:1}
,
wolf_hunger = 3 # @param {type:"slider",min:0,max:1000,step:1}
,
sheep_eat_gain = 8 # @param {type:"slider",min:0,max:1000,step:1}
,
wolf_eat_gain = 25 # @param {type:"slider",min:0,max:1000,step:1}
,
sheep_reprod_thresh = 20 # @param {type:"slider",min:0,max:1000,step:1}
,
wolf_reprod_thresh = 35 # @param {type:"slider",min:0,max:1000,step:1}
)
Conclusion
To summarize, I would like to consider the positive impact of this model on the community.:
- 
This model is useful for learning – it perfectly demonstrates the basics: - Ecological systems (food chains, population dynamics)
 
- Agent-based modeling (behavior of autonomous objects)
- Balancing parameters in simulations
- 
For research – allows you to study: - Ecosystem resilience to changes
 
- The effect of species extinction
- The impact of initial conditions on the development of the system
- 
For development, an example of building: - Discrete simulations
- Real-time data visualization
 
- Process animations