Игра "Овцы и Волки"🐑🐺
Игра "Овцы и Волки"
В данном примере мы рассмотрим модель, суть симуляции которой заключена в отображении сценария "Хищник-Жертва-Растения". Эта агент-ориентированная модель, которая имитирует экосистему с тремя ключевыми компонентами:
- Овцы (жертвы) - питаются растениями, размножаются при достаточной энергии
- Волки (хищники) - охотятся на овец, требуют больше энергии для выживания
- Растения - восстанавливаются со временем, служат пищей для овец
Ключевые механизмы здесь - это циклы питания, представленный следующим образом Волки → Овцы → Растения и динамика популяций, регуляция которой происходит автоматически через, потребление энергии (голод) и размножение при достижении пороговых значений энергии, а так же случайное перемещение агентов
Модель демонстрирует классические принципы экологии:
- Пищевые цепи
- Конкуренцию за ресурсы
- Динамическое равновесие популяций
- Эффект "бутылочного горлышка" при вымирании видов
Pkg.add("StatsBase")
using Random
using StatsBase
Теперь объявим основные параметры и структуры объектов нашей модели.
-
Параметры мира:
WIDTH
иHEIGHT
- размеры двумерного мира (в клетках)MAX_STEPS
- лимит итераций симуляцииSAVE_EVERY
- частота сохранения состояния для визуализации
-
Агенты:
- Все агенты имеют координаты (x,y) и наследуют от базового типа
Agent
Sheep
иWolf
содержат параметрenergy
- ключевой показатель для:- Выживания (при energy ≤ 0 агент умирает)
- Размножения (требуется достаточный уровень энергии)
Bush
имеет флагactive
- может ли быть съеден
- Все агенты имеют координаты (x,y) и наследуют от базового типа
-
Модель:
- Содержит массивы всех агентов (
sheep
,wolves
) - Матрицу
bushes
(растения) размером WIDTH×HEIGHT - Счетчик шагов
step
- Словарь
params
с настраиваемыми параметрами:- Вероятности событий (размножение, восстановление растений)
- Энергетические параметры (потребление, расход, пороги)
- Баланс экосистемы (можно регулировать сложность выживания)
- Содержит массивы всех агентов (
Эти структуры образуют каркас для:
- Моделирования передвижения агентов
- Системы питания (волки→овцы→растения)
- Механизмов размножения и смерти
- Визуализации состояния экосистемы
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
создает начальное состояние модели экосистемы с тремя типами агентов: овцами, волками и растениями.
Что делает функция:
-
Инициализирует мир, создает матрицу растений размером WIDTH×HEIGHT (количество задано в
n_bushes
) -
Создает агентов: овец (
n_sheep
штук) со стартовой энергиейsheep_energy
, волков (n_wolves
штук) со стартовой энергиейwolf_energy
, случайно размещает их на карте -
Настраивает параметры модели через словарь
params
:- Вероятности восстановления растений и размножения животных
- Расход энергии при голодании
- Прирост энергии при питании
- Пороги энергии для размножения
Возвращает: Объект типа Model
с начальным состоянием экосистемы, все эти параметры функции позволяют гибко настраивать баланс экосистемы перед запуском.
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
Функция move_agent!
перемещает агента (овцу или волка) на карте случайным образом. Она генерирует случайное смещение dx
и dy
принимают значения -1, 0 или 1 (шаг в любом направлении или на месте) и вычисляет новые координаты, mod1
обеспечивает "заворачивание" границ - если агент выходит за край карты, появляется с противоположной стороны.
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
Функция 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
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
Далее аналогичные функции для волков, ключевые отличия от овец:
-
Волки охотятся на овец (а не на растения)
-
Используют свои параметры (
wolf_eat_gain
,wolf_reprod
и т.д.) -
Механика размножения аналогична, но с другими числовыми значениями
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
Функция regenerate_bushes!(model)
, восстанавливает съеденные растения (кусты) на карте с определенной вероятностью, эта функция проходит по всем клеткам игрового поля (WIDTH × HEIGHT), для каждого неактивного куста (bush.active == false
) с вероятностью bush_regrowth
(из параметров модели) делает куст активным, вероятность проверяется сравнением случайного числа с 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
Функция step!(model)
выполняет один шаг симуляции экосистемы, обновляя состояние всех агентов и проверяя условия завершения.
Основные этапы работы:
-
Инкремент счетчика шагов
model.step += 1
-
Обработка овец (в цикле):
- Перемещение (
move_agent!
) - Питание (если не удалось поесть - уменьшение энергии на
sheep_hunger
) - Попытка размножения (
reproduce!
)
- Перемещение (
-
Обработка волков (в цикле):
- Перемещение (
move_agent!
) - Охота (если не удалось - уменьшение энергии на
wolf_hunger
) - Попытка размножения (
reproduce!
)
- Перемещение (
-
Фильтрация умерших агентов:
- Удаление овец с энергией ≤ 0
- Удаление волков с энергией ≤ 0
-
Восстановление растений:
regenerate_bushes!
- возобновление съеденных кустов
-
Проверка условий завершения:
- Вымирание всех овец или всех волков
- Достижение максимального числа шагов (
MAX_STEPS
) - Вывод соответствующих сообщений
-
Логирование прогресса: каждый
SAVE_EVERY
шагов выводит текущую статистику -
Возвращаемое значение:
false
- если симуляция должна завершитьсяtrue
- если симуляция продолжается
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
Функция plot_model(model) в
изуализирует текущее состояние экосистемы в виде цветной карты, отображая расположение всех агентов и растений.
- Каждый агент занимает ровно одну клетку на карте
- Приоритет отображения: волки > овцы > растения (если находятся в одной клетке)
- Цвета выбраны для интуитивно понятной интерпретации:
- Красный - опасность (хищники)
- Белый - нейтральный (жертвы)
- Зеленый - растения
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
Функция run_simulation
главная функция, которая запускает и управляет всей симуляцией экосистемы.
Включает в себя следующие этапы:
-
Инициализация:
-
Основной цикл симуляции по шагам включает в себя визуализацию текущего состояния (
plot_model
), выполнение шага симуляции (step!
), сохраняет текущие данные о популяциях и прерывается при выполнении условий остановки. -
Создание анимации и сохраняет процесс симуляции в GIF-файл "wolf_sheep_sim.gif"
-
Вывод результата.
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
Далее мы выполняем запуск, для вашего удобства были применены маски кодовых ячеек, в соответствии с примером ниже вы при помоще слайдеров можети управлять всей моделью.
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 # Минимальная энергия для размножения волка
)
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}
)
Вывод
Подводя итоги хотелось бы рассмотреть положительное влияние этой модели на сообщество:
-
Эта модель полезна для обучения – отлично демонстрирует основы:
- Экологических систем (пищевые цепи, динамику популяций)
- Агентного моделирования (поведение автономных объектов)
- Балансировки параметров в симуляциях
-
Для исследований – позволяет изучать:
- Устойчивость экосистем к изменениям
- Эффект вымирания видов
- Влияние начальных условий на развитие системы
-
Для разработки – пример построения:
- Дискретных симуляций
- Визуализации данных в реальном времени
- Анимации процессов