Сообщество Engee

Игра "Овцы и Волки"🐑🐺

Автор
avatar-yurevyurev
Notebook

Игра "Овцы и Волки"

В данном примере мы рассмотрим модель, суть симуляции которой заключена в отображении сценария "Хищник-Жертва-Растения". Эта агент-ориентированная модель, которая имитирует экосистему с тремя ключевыми компонентами:

  1. Овцы (жертвы) - питаются растениями, размножаются при достаточной энергии
  2. Волки (хищники) - охотятся на овец, требуют больше энергии для выживания
  3. Растения - восстанавливаются со временем, служат пищей для овец

Ключевые механизмы здесь - это циклы питания, представленный следующим образом Волки → Овцы → Растения и динамика популяций, регуляция которой происходит автоматически через, потребление энергии (голод) и размножение при достижении пороговых значений энергии, а так же случайное перемещение агентов

Модель демонстрирует классические принципы экологии:

  • Пищевые цепи
  • Конкуренцию за ресурсы
  • Динамическое равновесие популяций
  • Эффект "бутылочного горлышка" при вымирании видов
In [ ]:
Pkg.add("StatsBase")
using Random
using StatsBase
   Resolving package versions...
  No Changes to `~/.project/Project.toml`
  No Changes to `~/.project/Manifest.toml`

Теперь объявим основные параметры и структуры объектов нашей модели.

  1. Параметры мира:

    • WIDTH и HEIGHT - размеры двумерного мира (в клетках)
    • MAX_STEPS - лимит итераций симуляции
    • SAVE_EVERY - частота сохранения состояния для визуализации
  2. Агенты:

    • Все агенты имеют координаты (x,y) и наследуют от базового типа Agent
    • Sheep и Wolf содержат параметр energy - ключевой показатель для:
      • Выживания (при energy ≤ 0 агент умирает)
      • Размножения (требуется достаточный уровень энергии)
    • Bush имеет флаг active - может ли быть съеден
  3. Модель:

    • Содержит массивы всех агентов (sheep, wolves)
    • Матрицу bushes (растения) размером WIDTH×HEIGHT
    • Счетчик шагов step
    • Словарь params с настраиваемыми параметрами:
      • Вероятности событий (размножение, восстановление растений)
      • Энергетические параметры (потребление, расход, пороги)
      • Баланс экосистемы (можно регулировать сложность выживания)

Эти структуры образуют каркас для:

  • Моделирования передвижения агентов
  • Системы питания (волки→овцы→растения)
  • Механизмов размножения и смерти
  • Визуализации состояния экосистемы
In [ ]:
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

Функция init_model создает начальное состояние модели экосистемы с тремя типами агентов: овцами, волками и растениями.

Что делает функция:

  1. Инициализирует мир, создает матрицу растений размером WIDTH×HEIGHT (количество задано в n_bushes)

  2. Создает агентов: овец (n_sheep штук) со стартовой энергией sheep_energy, волков (n_wolves штук) со стартовой энергией wolf_energy, случайно размещает их на карте

  3. Настраивает параметры модели через словарь params:

    • Вероятности восстановления растений и размножения животных
    • Расход энергии при голодании
    • Прирост энергии при питании
    • Пороги энергии для размножения

Возвращает: Объект типа Model с начальным состоянием экосистемы, все эти параметры функции позволяют гибко настраивать баланс экосистемы перед запуском.

In [ ]:
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
Out[0]:
init_model (generic function with 1 method)

Функция move_agent! перемещает агента (овцу или волка) на карте случайным образом. Она генерирует случайное смещение dx и dy принимают значения -1, 0 или 1 (шаг в любом направлении или на месте) и вычисляет новые координаты, mod1 обеспечивает "заворачивание" границ - если агент выходит за край карты, появляется с противоположной стороны.

In [ ]:
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
Out[0]:
move_agent! (generic function with 1 method)

Функция eat!(sheep::Sheep, model) позволяет овце съесть растение на своей текущей клетке. Она проверяет куст под овцой (model.bushes[sheep.x, sheep.y]), если куст активен (active=true), делает куст неактивным (съедает его) и увеличивает энергию овцы на значение sheep_eat_gain из параметров, возвращает true (успешное питание), если куст неактивен - возвращает false

Функция reproduce!(agent::Sheep, model), осуществляет размножение овцы при выполнении условий. Она проверяет два условия: случайное число меньше вероятности размножения (sheep_reprod) и энергия овцы выше порогового значения (sheep_reprod_thresh), если условия выполнены: делит энергию овцы пополам, создает новую овцу с такими же координатами и половиной энергии, добавляет ее в модель, возвращает true (успешное размножение), если условия не выполнены - возвращает false

In [ ]:
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
Out[0]:
reproduce! (generic function with 2 methods)

Далее аналогичные функции для волков, ключевые отличия от овец:

  • Волки охотятся на овец (а не на растения)

  • Используют свои параметры (wolf_eat_gain, wolf_reprod и т.д.)

  • Механика размножения аналогична, но с другими числовыми значениями

In [ ]:
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
Out[0]:
reproduce! (generic function with 2 methods)

Функция regenerate_bushes!(model), восстанавливает съеденные растения (кусты) на карте с определенной вероятностью, эта функция проходит по всем клеткам игрового поля (WIDTH × HEIGHT), для каждого неактивного куста (bush.active == false) с вероятностью bush_regrowth (из параметров модели) делает куст активным, вероятность проверяется сравнением случайного числа с model.params[:bush_regrowth]

In [ ]:
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
Out[0]:
regenerate_bushes! (generic function with 1 method)

Функция step!(model) выполняет один шаг симуляции экосистемы, обновляя состояние всех агентов и проверяя условия завершения.

Основные этапы работы:

  1. Инкремент счетчика шагов model.step += 1

  2. Обработка овец (в цикле):

    • Перемещение (move_agent!)
    • Питание (если не удалось поесть - уменьшение энергии на sheep_hunger)
    • Попытка размножения (reproduce!)
  3. Обработка волков (в цикле):

    • Перемещение (move_agent!)
    • Охота (если не удалось - уменьшение энергии на wolf_hunger)
    • Попытка размножения (reproduce!)
  4. Фильтрация умерших агентов:

    • Удаление овец с энергией ≤ 0
    • Удаление волков с энергией ≤ 0
  5. Восстановление растений:

    • regenerate_bushes! - возобновление съеденных кустов
  6. Проверка условий завершения:

    • Вымирание всех овец или всех волков
    • Достижение максимального числа шагов (MAX_STEPS)
    • Вывод соответствующих сообщений
  7. Логирование прогресса: каждый SAVE_EVERY шагов выводит текущую статистику

  8. Возвращаемое значение:

  • false - если симуляция должна завершиться
  • true - если симуляция продолжается
In [ ]:
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
Out[0]:
step! (generic function with 285 methods)

Функция plot_model(model) визуализирует текущее состояние экосистемы в виде цветной карты, отображая расположение всех агентов и растений.

  • Каждый агент занимает ровно одну клетку на карте
  • Приоритет отображения: волки > овцы > растения (если находятся в одной клетке)
  • Цвета выбраны для интуитивно понятной интерпретации:
    • Красный - опасность (хищники)
    • Белый - нейтральный (жертвы)
    • Зеленый - растения
In [ ]:
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
Out[0]:
plot_model (generic function with 1 method)

Функция run_simulation главная функция, которая запускает и управляет всей симуляцией экосистемы.

Включает в себя следующие этапы:

  1. Инициализация:

  2. Основной цикл симуляции по шагам включает в себя визуализацию текущего состояния (plot_model), выполнение шага симуляции (step!), сохраняет текущие данные о популяциях и прерывается при выполнении условий остановки.

  3. Создание анимации и сохраняет процесс симуляции в GIF-файл "wolf_sheep_sim.gif"

  4. Вывод результата.

In [ ]:
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
Out[0]:
run_simulation (generic function with 1 method)

Далее мы выполняем запуск, для вашего удобства были применены маски кодовых ячеек, в соответствии с примером ниже вы при помоще слайдеров можети управлять всей моделью.

run_simulation(
    # Базовые параметры популяции
    n_sheep = 100,         # Начальное количество овец
    n_wolves = 5,          # Начальное количество волков
    n_bushes = 70,        # Начальное количество травы
    
    # Энергетические параметры
    sheep_energy = 100,     # Начальная энергия овец
    wolf_energy = 1000,      # Начальная энергия волков
    
    # Параметры восстановления ресурсов
    bush_regrowth = 0.005,  # Вероятность восстановления травы за шаг
    
    # Параметры размножения
    sheep_reprod = 0.02,   # Вероятность размножения овцы за шаг
    wolf_reprod = 0.1,    # Вероятность размножения волка за шаг
    
    # Параметры голодания
    sheep_hunger = 1,      # Потеря энергии овцы за шаг без еды
    wolf_hunger = 3,       # Потеря энергии волка за шаг без еды
    
    # Параметры питания
    sheep_eat_gain = 8,    # Прирост энергии овцы при поедании травы
    wolf_eat_gain = 25,    # Прирост энергии волка при поедании овцы
    
    # Пороги размножения
    sheep_reprod_thresh = 20,  # Минимальная энергия для размножения овцы
    wolf_reprod_thresh = 35    # Минимальная энергия для размножения волка
)
In [ ]:
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}
)
Прогресс: завершен шаг 100. Овец: 113, волков: 1
Симуляция остановлена на шаге 101: все волки вымерли.

Результаты симуляции:
Финальный шаг: 101
Количество овец: 113
Количество волков: 0
Количество растений: 371
[ Info: Saved animation to /user/my_projects/Demo/Work/wolf_sheep_sim.gif
No description has been provided for this image

Вывод

Подводя итоги хотелось бы рассмотреть положительное влияние этой модели на сообщество:

  1. Эта модель полезна для обучения – отлично демонстрирует основы:

    • Экологических систем (пищевые цепи, динамику популяций)
    • Агентного моделирования (поведение автономных объектов)
    • Балансировки параметров в симуляциях
  2. Для исследований – позволяет изучать:

    • Устойчивость экосистем к изменениям
    • Эффект вымирания видов
    • Влияние начальных условий на развитие системы
  3. Для разработки – пример построения:

    • Дискретных симуляций
    • Визуализации данных в реальном времени
    • Анимации процессов