diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf27853 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# NewIsland + +Симуляция острова с животными и растениями. + +## 🚀 Запуск программы + +1. Открыть проект в IntelliJ IDEA +2. Найти класс `Main` +3. Запустить метод `main()` → Run + +## ⚙️ Настройки симуляции + +В классе `Main` можно изменить параметры (в коде есть комментарии): + +- размер острова (карты) +- количество животных и растений +- количество шагов симуляции + +После изменения параметров перезапусти программу. + +## 📌 Описание + +Программа моделирует жизнь экосистемы острова: +перемещение животных, размножение, питание и рост растений. \ No newline at end of file diff --git a/src/Fauna/AllHerbivores/Boar.java b/src/Fauna/AllHerbivores/Boar.java new file mode 100644 index 0000000..7069146 --- /dev/null +++ b/src/Fauna/AllHerbivores/Boar.java @@ -0,0 +1,22 @@ +package Fauna.AllHerbivores; + +import Fauna.Animal; +import MapEngine.Coordinate; + +public class Boar extends Herbivores{ + + + public Boar() { + super("Boar", 400, 50, 2, 50); + setMaxAge(10); + } + + @Override + protected Animal createChild() { + Boar baby = new Boar(); + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } +} diff --git a/src/Fauna/AllHerbivores/Buffalo.java b/src/Fauna/AllHerbivores/Buffalo.java new file mode 100644 index 0000000..60fadb3 --- /dev/null +++ b/src/Fauna/AllHerbivores/Buffalo.java @@ -0,0 +1,22 @@ +package Fauna.AllHerbivores; + +import Fauna.Animal; +import MapEngine.Coordinate; + +public class Buffalo extends Herbivores{ + + + public Buffalo() { + super("Buffalo", 700, 10, 3, 100); + setMaxAge(10); + } + + @Override + protected Animal createChild() { + Buffalo baby = new Buffalo(); + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } +} diff --git a/src/Fauna/AllHerbivores/Caterpillar.java b/src/Fauna/AllHerbivores/Caterpillar.java new file mode 100644 index 0000000..1555905 --- /dev/null +++ b/src/Fauna/AllHerbivores/Caterpillar.java @@ -0,0 +1,22 @@ +package Fauna.AllHerbivores; + +import Fauna.Animal; +import MapEngine.Coordinate; + +public class Caterpillar extends Herbivores{ + + + public Caterpillar() { + super("Caterpillar", 0.01, 1000, 0, 0); + setMaxAge(10); + } + + @Override + protected Animal createChild() { + Caterpillar baby = new Caterpillar(); + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } +} diff --git a/src/Fauna/AllHerbivores/Deer.java b/src/Fauna/AllHerbivores/Deer.java new file mode 100644 index 0000000..dc6952d --- /dev/null +++ b/src/Fauna/AllHerbivores/Deer.java @@ -0,0 +1,22 @@ +package Fauna.AllHerbivores; + +import Fauna.Animal; +import MapEngine.Coordinate; + +public class Deer extends Herbivores{ + + + public Deer() { + super("Deer", 300, 20, 4, 50); + setMaxAge(10); + } + + @Override + protected Animal createChild() { + Deer baby = new Deer(); + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } +} diff --git a/src/Fauna/AllHerbivores/Duck.java b/src/Fauna/AllHerbivores/Duck.java new file mode 100644 index 0000000..ec704b8 --- /dev/null +++ b/src/Fauna/AllHerbivores/Duck.java @@ -0,0 +1,22 @@ +package Fauna.AllHerbivores; + +import Fauna.Animal; +import MapEngine.Coordinate; + +public class Duck extends Herbivores{ + + + public Duck() { + super("Duck", 1, 200, 4, 0.15); + setMaxAge(10); + } + + @Override + protected Animal createChild() { + Duck baby = new Duck(); + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } +} diff --git a/src/Fauna/AllHerbivores/Goat.java b/src/Fauna/AllHerbivores/Goat.java new file mode 100644 index 0000000..9c5008e --- /dev/null +++ b/src/Fauna/AllHerbivores/Goat.java @@ -0,0 +1,22 @@ +package Fauna.AllHerbivores; + +import Fauna.Animal; +import MapEngine.Coordinate; + +public class Goat extends Herbivores{ + + + public Goat() { + super("Goat", 60, 140, 3, 10); + setMaxAge(10); + } + + @Override + protected Animal createChild() { + Goat baby = new Goat(); + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } +} diff --git a/src/Fauna/AllHerbivores/Herbivores.java b/src/Fauna/AllHerbivores/Herbivores.java new file mode 100644 index 0000000..4d57e73 --- /dev/null +++ b/src/Fauna/AllHerbivores/Herbivores.java @@ -0,0 +1,52 @@ +package Fauna.AllHerbivores; + +import Fauna.Herbs; +import Fauna.Animal; +import SupportClasses.Statistics; + +import java.util.List; + +public abstract class Herbivores extends Animal { + + public Herbivores(String name, double maxWeight, int maxCountOnCell, int speed, double foodNeed) { + super(name, maxWeight, maxCountOnCell, speed, foodNeed); + } + + /** + * Общая логика травоядных — они едят растения. + */ + @Override + public void eat(List others) { + for (Object other : others) { + if (other instanceof Herbs herb && herb.isAlive()) { + System.out.println(name + " ate some grass 🌿"); + herb.beEaten(); + hunger = 1.0; + + // ✅ добавляем запись в статистику + Statistics.markGrassEaten(this); + + return; + } + } + } + + /** + * Попытка съесть траву (из отдельного списка растений, если он есть). + */ + public boolean tryEatPlants(List plants) { + if (plants != null && !plants.isEmpty()) { + Herbs herb = plants.remove(0); // съели один пучок + if (herb != null && herb.isAlive()) { +// System.out.println(name + " ate some grass 🌿"); + hunger = 1.0; + + // ✅ статистика + Statistics.markGrassEaten(this); + + return true; + } + } + return false; + } +} diff --git a/src/Fauna/AllHerbivores/Horse.java b/src/Fauna/AllHerbivores/Horse.java new file mode 100644 index 0000000..f81d2b9 --- /dev/null +++ b/src/Fauna/AllHerbivores/Horse.java @@ -0,0 +1,22 @@ +package Fauna.AllHerbivores; + +import Fauna.Animal; +import MapEngine.Coordinate; + +public class Horse extends Herbivores{ + + + public Horse() { + super("Horse", 400, 20, 4, 60); + setMaxAge(10); + } + + @Override + protected Animal createChild() { + Horse baby = new Horse(); + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } +} diff --git a/src/Fauna/AllHerbivores/Mouse.java b/src/Fauna/AllHerbivores/Mouse.java new file mode 100644 index 0000000..b14489b --- /dev/null +++ b/src/Fauna/AllHerbivores/Mouse.java @@ -0,0 +1,22 @@ +package Fauna.AllHerbivores; + +import Fauna.Animal; +import MapEngine.Coordinate; + +public class Mouse extends Herbivores{ + + + public Mouse() { + super("Mouse", 0.05, 500, 1, 0.01); + setMaxAge(10); + } + + @Override + protected Animal createChild() { + Mouse baby = new Mouse(); + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } +} diff --git a/src/Fauna/AllHerbivores/Rabbit.java b/src/Fauna/AllHerbivores/Rabbit.java new file mode 100644 index 0000000..b660998 --- /dev/null +++ b/src/Fauna/AllHerbivores/Rabbit.java @@ -0,0 +1,21 @@ +package Fauna.AllHerbivores; + +import Fauna.Animal; +import MapEngine.Coordinate; + +public class Rabbit extends Herbivores { + + public Rabbit() { + super("Rabbit", 2, 150, 2, 0.45); + setMaxAge(10); + } + + @Override + protected Animal createChild() { + Rabbit baby = new Rabbit(); + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } +} diff --git a/src/Fauna/AllHerbivores/Sheep.java b/src/Fauna/AllHerbivores/Sheep.java new file mode 100644 index 0000000..37f7f95 --- /dev/null +++ b/src/Fauna/AllHerbivores/Sheep.java @@ -0,0 +1,22 @@ +package Fauna.AllHerbivores; + +import Fauna.Animal; +import MapEngine.Coordinate; + +public class Sheep extends Herbivores{ + + + public Sheep() { + super("Sheep", 70, 140, 3, 15); + setMaxAge(10); + } + + @Override + protected Animal createChild() { + Sheep baby = new Sheep(); + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } +} diff --git a/src/Fauna/AllPredators/Bear.java b/src/Fauna/AllPredators/Bear.java new file mode 100644 index 0000000..ceff2fb --- /dev/null +++ b/src/Fauna/AllPredators/Bear.java @@ -0,0 +1,35 @@ +package Fauna.AllPredators; + +import Fauna.Animal; +import MapEngine.Coordinate; + +public class Bear extends Predators{ + + + public Bear() { + super("Bear", 500, 5, 2, 80); + setMaxAge(50); + } + + @Override + protected double getEatChance(Animal prey) { + // Упрощённая таблица вероятностей + return switch (prey.getName()) { + case "Mouse"-> 0.9; + case "Rabbit","Boa","Deer" -> 0.8; + case "Goat", "Sheep" -> 0.7; + case "Horse" -> 0.4; + default -> 0.0; + }; + } + + @Override + protected Animal createChild() { + Bear baby = new Bear(); + // Копируем позицию родителя, если она установлена + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } +} diff --git a/src/Fauna/AllPredators/Boa.java b/src/Fauna/AllPredators/Boa.java new file mode 100644 index 0000000..c3d82a8 --- /dev/null +++ b/src/Fauna/AllPredators/Boa.java @@ -0,0 +1,34 @@ + +package Fauna.AllPredators; +import Fauna.Animal; +import MapEngine.Coordinate; + + public class Boa extends Predators { + + public Boa() { + super("Boa", 15, 30, 1, 3); + setMaxAge(40); + } + + @Override + protected double getEatChance(Animal prey) { + return switch (prey.getName()) { + case "Rabbit" -> 0.2; + case "Mouse" -> 0.4; + case "Fox" -> 0.15; + + default -> 0.0; + }; + } + + @Override + protected Animal createChild() { + Boa baby = new Boa(); + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } + } + + diff --git a/src/Fauna/AllPredators/Eagle.java b/src/Fauna/AllPredators/Eagle.java new file mode 100644 index 0000000..f646c25 --- /dev/null +++ b/src/Fauna/AllPredators/Eagle.java @@ -0,0 +1,33 @@ +package Fauna.AllPredators; + +import Fauna.Animal; +import MapEngine.Coordinate; + +public class Eagle extends Predators{ + + + public Eagle() { + super("Eagle", 6, 20, 3, 1); + setMaxAge(50); + } + + @Override + protected double getEatChance(Animal prey) { + // Упрощённая таблица вероятностей + return switch (prey.getName()) { + case "Rabbit","Mouse" -> 0.9; + case "Fox" -> 0.1; + default -> 0.0; + }; + } + + @Override + protected Animal createChild() { + Eagle baby = new Eagle(); + // Копируем позицию родителя, если она установлена + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } +} diff --git a/src/Fauna/AllPredators/Fox.java b/src/Fauna/AllPredators/Fox.java new file mode 100644 index 0000000..22d5e73 --- /dev/null +++ b/src/Fauna/AllPredators/Fox.java @@ -0,0 +1,31 @@ +package Fauna.AllPredators; + +import Fauna.Animal; +import MapEngine.Coordinate; + +public class Fox extends Predators { + + public Fox() { + super("Fox", 8, 30, 2, 2); + setMaxAge(40); + } + + @Override + protected double getEatChance(Animal prey) { + return switch (prey.getName()) { + case "Rabbit" -> 0.8; + case "Mouse" -> 0.7; + case "Goat", "Sheep" -> 0.5; + default -> 0.0; + }; + } + + @Override + protected Animal createChild() { + Fox baby = new Fox(); + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } +} diff --git a/src/Fauna/AllPredators/Predators.java b/src/Fauna/AllPredators/Predators.java new file mode 100644 index 0000000..d68508f --- /dev/null +++ b/src/Fauna/AllPredators/Predators.java @@ -0,0 +1,56 @@ +package Fauna.AllPredators; + +import Fauna.AllHerbivores.Herbivores; +import Fauna.Animal; +import SupportClasses.Statistics; + +import java.util.List; +import java.util.Random; + +public abstract class Predators extends Animal { + + protected final Random random = new Random(); + + public Predators(String name, double maxWeight, int maxCountOnCell, int speed, double foodNeed) { + super(name, maxWeight, maxCountOnCell, speed, foodNeed); + } + + /** + * Логика кормления хищника: ищет травоядных в списке и ест. + */ + @Override + public void eat(List others) { + for (Animal target : others) { + if (target instanceof Herbivores && target.isAlive()) { + double chance = getEatChance(target); + if (random.nextDouble() < chance) { +// System.out.println(getName() + " attacked and ate the " + target.getName()); + setHunger(1.0); // полностью сытый + target.setAlive(false); + + // травоядное помечаем как съеденное + Statistics.markDeath(target, true); + + // хищник увеличивает счетчик съеденных животных + Statistics.markAnimalEatenByPredator(this); + + return; + } + } + } + + // если никого не съел — становится голоднее + setHunger(getHunger() - 0.2); + } + + /** + * Вероятность съесть конкретного травоядного. + * Каждый вид хищника должен переопределить этот метод. + */ + protected abstract double getEatChance(Animal prey); + + /** + * Метод для создания потомства конкретного вида хищника. + */ + protected abstract Animal createChild(); +} diff --git a/src/Fauna/AllPredators/Wolf.java b/src/Fauna/AllPredators/Wolf.java new file mode 100644 index 0000000..e23246f --- /dev/null +++ b/src/Fauna/AllPredators/Wolf.java @@ -0,0 +1,33 @@ +package Fauna.AllPredators; + +import Fauna.Animal; +import MapEngine.Coordinate; + +public class Wolf extends Predators { + + public Wolf() { + super("Wolf", 50, 30, 3, 8); + setMaxAge(50); + } + + @Override + protected double getEatChance(Animal prey) { + // Упрощённая таблица вероятностей + return switch (prey.getName()) { + case "Rabbit" -> 0.6; + case "Mouse" -> 0.8; + case "Goat", "Sheep" -> 0.7; + default -> 0.0; + }; + } + + @Override + protected Animal createChild() { + Wolf baby = new Wolf(); + // Копируем позицию родителя, если она установлена + if (this.getPosition() != null) { + baby.setPosition(new Coordinate(this.getPosition().x, this.getPosition().y)); + } + return baby; + } +} diff --git a/src/Fauna/Animal.java b/src/Fauna/Animal.java new file mode 100644 index 0000000..0965bab --- /dev/null +++ b/src/Fauna/Animal.java @@ -0,0 +1,201 @@ +package Fauna; + +import MapEngine.Coordinate; +import MapEngine.Island; +import SupportClasses.Statistics; + +import java.util.List; +import java.util.Random; + +public abstract class Animal { + // ==== Основные характеристики ==== + protected String name; + protected double weight; + protected double maxWeight; + protected int maxCountOnCell; + protected int speed; + protected double foodNeed; + protected boolean alive = true; + + // ==== Состояние ==== + protected double hunger; // 1.0 = сыт, 0.0 = умирает + protected int age = 0; + protected int maxAge; + protected Coordinate position; + + protected final Random random = new Random(); + + // ==== Конструктор ==== + public Animal(String name, double maxWeight, int maxCountOnCell, int speed, double foodNeed) { + this.name = name; + this.maxWeight = maxWeight; + this.weight = maxWeight; + this.maxCountOnCell = maxCountOnCell; + this.speed = speed; + this.foodNeed = foodNeed; + this.hunger = 1.0; + + // ✅ Регистрируем животное в статистике + Statistics.registerAnimal(this); + } + + // ==== Логика жизни ==== + + /** Один шаг жизни животного */ + public void liveCycle(Island island) { + if (!alive) return; + + age++; + decreaseHunger(); + move(island); + deathCheck(); + } + + /** Уменьшаем уровень сытости каждый шаг */ + protected void decreaseHunger() { + hunger -= 0.1; // теряет 10% сытости за шаг + if (hunger < 0) hunger = 0; + } + + /** Проверка смерти от старости или голода */ + protected void deathCheck() { + if (!alive) return; + + if (hunger <= 0) { + alive = false; + Statistics.markDeath(this, false); + // System.out.println(name + " died of hunger ☠️"); + } else if (age >= maxAge) { + alive = false; + Statistics.markDeath(this, false); + // System.out.println(name + " died of old age 🕯️"); + } + } + + // ==== Основные действия ==== + + /** Двигается в случайном направлении */ + public void move(Island island) { + if (!alive || position == null) return; + + int dx = random.nextInt(speed * 2 + 1) - speed; + int dy = random.nextInt(speed * 2 + 1) - speed; + + int newX = Math.max(0, Math.min(island.getWidth() - 1, position.x + dx)); + int newY = Math.max(0, Math.min(island.getHeight() - 1, position.y + dy)); + + position = new Coordinate(newX, newY); + } + + /** Еда — реализуется в наследниках */ + public abstract void eat(List others); + + /** Размножение — если рядом есть партнёр */ + public Animal reproduce(List sameSpecies) { + if (!alive || sameSpecies.size() < 2) return null; + + if (random.nextDouble() < 0.3) { // шанс 30% + Animal child = createChild(); + + // ✅ ставим ту же позицию, что у родителя + if (this.position != null) { + child.setPosition(new Coordinate(this.position.x, this.position.y)); + } + + // ✅ регистрируем в статистике + Statistics.markReproduce(child); + + return child; + } + return null; + } + + /** Абстрактный метод создания детёныша */ + protected abstract Animal createChild(); + + // ==== Геттеры и сеттеры ==== + + public String getName() { + return name; + } + + public boolean isAlive() { + return alive; + } + + public void setAlive(boolean alive) { + this.alive = alive; + } + + public double getHunger() { + return hunger; + } + + public void setHunger(double hunger) { + this.hunger = hunger; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public Coordinate getPosition() { + return position; + } + + public void setPosition(Coordinate position) { + this.position = position; + } + + public double getWeight() { + return weight; + } + + public void setWeight(double weight) { + this.weight = weight; + } + + public double getMaxWeight() { + return maxWeight; + } + + public void setMaxWeight(double maxWeight) { + this.maxWeight = maxWeight; + } + + public int getMaxCountOnCell() { + return maxCountOnCell; + } + + public void setMaxCountOnCell(int maxCountOnCell) { + this.maxCountOnCell = maxCountOnCell; + } + + public int getSpeed() { + return speed; + } + + public void setSpeed(int speed) { + this.speed = speed; + } + + public double getFoodNeed() { + return foodNeed; + } + + public void setFoodNeed(double foodNeed) { + this.foodNeed = foodNeed; + } + + public int getMaxAge() { + return maxAge; + } + + public void setMaxAge(int maxAge) { + this.maxAge = maxAge; + } +} diff --git a/src/Fauna/Herbs.java b/src/Fauna/Herbs.java new file mode 100644 index 0000000..6236e7a --- /dev/null +++ b/src/Fauna/Herbs.java @@ -0,0 +1,28 @@ +package Fauna; + +import MapEngine.Island; + +public class Herbs { + private final String name = "Herb"; + private double weight = 1.0; + private boolean alive = true; + + public Herbs() {} + + public String getName() { + return name; + } + + public double getWeight() { + return weight; + } + + public boolean isAlive() { + return alive; + } + + public void beEaten() { + this.alive = false; + System.out.println("🌿 The herb was eaten."); + } +} diff --git a/src/Fauna/Main.java b/src/Fauna/Main.java new file mode 100644 index 0000000..f6be035 --- /dev/null +++ b/src/Fauna/Main.java @@ -0,0 +1,109 @@ +package Fauna; + +import Fauna.AllHerbivores.*; +import Fauna.AllPredators.*; +import MapEngine.Island; +import SupportClasses.Statistics; + +import java.util.Random; + +public class Main { + + public static void main(String[] args) { + // 🏝️ Создаём остров + Island island = new Island(100, 20); + Random random = new Random(); + + // 🔄 Сбрасываем старую статистику + Statistics.reset(); + + // 🌿 Добавляем траву + addHerbs(island, 200, random); + + // 🐾 Добавляем животных + addAnimals(island, 30, Wolf.class, random, "🐺"); + addAnimals(island, 150, Rabbit.class, random, "🐇"); + addAnimals(island, 30, Fox.class, random, "🦊"); + addAnimals(island, 30, Boa.class, random, "🐍"); + addAnimals(island, 5, Bear.class, random, "🐻"); + addAnimals(island, 20, Eagle.class, random, "🦅"); + addAnimals(island, 20, Horse.class, random, "🐎"); + addAnimals(island, 20, Deer.class, random, "🦌"); + addAnimals(island, 100, Mouse.class, random, "🐁"); + addAnimals(island, 140, Goat.class, random, "🐐"); + addAnimals(island, 140, Sheep.class, random, "🐑"); + addAnimals(island, 50, Boar.class, random, "🐗"); + addAnimals(island, 50, Buffalo.class, random, "🐃"); + addAnimals(island, 50, Duck.class, random, "🦆"); + addAnimals(island, 50, Caterpillar.class, random, "🐛"); + + // 🕒 Симуляция количества шагов + for (int i = 1; i <= 100; i++) { + System.out.println("\n===== Step " + i + " ====="); + + // Сбрасываем счётчик травы перед шагом + Statistics.resetGrassCounter(); + + // 🚀 Запускаем шаг симуляции + island.simulateStep(); + + // 📊 Выводим статистику + Statistics.print(); + + // 🗺️ Отображаем карту + island.printMap(); + + // 💀 Подсчёт смертей + System.out.println("💀 " + Statistics.getDeathsThisStep() + " animals died this step."); + + // 🌱 Регенерация травы + regenerateGrass(island, 100, random); + } + + // 🧾 Финальный отчёт + System.out.println("\n===== FINAL SUMMARY ====="); + Statistics.print(); + System.out.println("========================="); + + // 🔚 Завершаем пул потоков + island.shutdown(); + } + + // 🌿 Метод для добавления травы + private static void addHerbs(Island island, int count, Random random) { + for (int i = 0; i < count; i++) { + Herbs herbs = new Herbs(); + int x = random.nextInt(island.getWidth()); + int y = random.nextInt(island.getHeight()); + island.addPlant(herbs, x, y); + } + System.out.println("🌿 Added " + count + " grass patches to the map."); + } + + // 🌱 Метод для регенерации травы каждый шаг + private static void regenerateGrass(Island island, int count, Random random) { + for (int i = 0; i < count; i++) { + Herbs herbs = new Herbs(); + int x = random.nextInt(island.getWidth()); + int y = random.nextInt(island.getHeight()); + island.addPlant(herbs, x, y); + } + System.out.println("🌱 Grass regrew by " + count + " patches."); + } + + // 🐾 Метод для добавления животных + private static void addAnimals(Island island, int count, Class animalClass, Random random, String emoji) { + for (int i = 0; i < count; i++) { + try { + Animal animal = animalClass.getDeclaredConstructor().newInstance(); + int x = random.nextInt(island.getWidth()); + int y = random.nextInt(island.getHeight()); + island.addAnimal(animal, x, y); + Statistics.registerAnimal(animal); + } catch (Exception e) { + e.printStackTrace(); + } + } + System.out.println(emoji + " Added " + count + " " + animalClass.getSimpleName() + "(s) to the map."); + } +} diff --git a/src/Main.java b/src/Main.java deleted file mode 100644 index 3d87d6e..0000000 --- a/src/Main.java +++ /dev/null @@ -1,13 +0,0 @@ -//TIP To Run code, press or -// click the icon in the gutter. -void main() { - //TIP Press with your caret at the highlighted text - // to see how IntelliJ IDEA suggests fixing it. - IO.println(String.format("Hello and welcome!")); - - for (int i = 1; i <= 5; i++) { - //TIP Press to start debugging your code. We have set one breakpoint - // for you, but you can always add more by pressing . - IO.println("i = " + i); - } -} diff --git a/src/MapEngine/Cell.java b/src/MapEngine/Cell.java new file mode 100644 index 0000000..1961921 --- /dev/null +++ b/src/MapEngine/Cell.java @@ -0,0 +1,164 @@ +package MapEngine; + +import Fauna.Animal; +import Fauna.Herbs; +import Fauna.AllHerbivores.Herbivores; + +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; + +public class Cell { + + private final Coordinate coordinate; // координаты клетки + private final List animals = new ArrayList<>(); // животные в клетке + private final List plants = new ArrayList<>(); // растения в клетке + private final Queue movedIn = new ConcurrentLinkedQueue<>(); // очередь прибывших + + private final ReentrantLock lock = new ReentrantLock(); // 🔒 для синхронизации + + public Cell(Coordinate coordinate) { + this.coordinate = coordinate; + } + + /** Добавить животное в клетку (безопасно для многопоточности) */ + public void addAnimal(Animal animal) { + if (animal == null) return; + + lock.lock(); + try { + long sameTypeCount = animals.stream() + .filter(a -> a.getClass() == animal.getClass()) + .count(); + + if (sameTypeCount < animal.getMaxCountOnCell()) { + animals.add(animal); + animal.setPosition(coordinate); + } + } finally { + lock.unlock(); + } + } + + /** Используется другими потоками для прибытия животных */ + public void enqueueIncoming(Animal animal) { + if (animal != null) movedIn.add(animal); + } + + /** Добавить растение (потокобезопасно) */ + public void addPlant(Herbs herb) { + if (herb != null) { + lock.lock(); + try { + plants.add(herb); + } finally { + lock.unlock(); + } + } + } + + public List getAnimals() { + lock.lock(); + try { + return new ArrayList<>(animals); // возвращаем копию + } finally { + lock.unlock(); + } + } + + public List getPlants() { + lock.lock(); + try { + return new ArrayList<>(plants); + } finally { + lock.unlock(); + } + } + + /** Один цикл жизни клетки (выполняется в отдельном потоке) */ + public void liveCycle(Island island) { + lock.lock(); + try { + // 1️⃣ Еда + List snapshot = new ArrayList<>(animals); + for (Animal animal : snapshot) { + if (!animal.isAlive()) continue; + + if (animal instanceof Herbivores herbivore) { + boolean ate = herbivore.tryEatPlants(plants); + if (!ate) { + herbivore.eat(snapshot); + } + } else { + animal.eat(snapshot); + } + } + + // 2️⃣ Размножение + List newborns = new ArrayList<>(); + Map, List> byType = animals.stream() + .filter(Animal::isAlive) + .collect(Collectors.groupingBy(Object::getClass)); + + for (List group : byType.values()) { + for (Animal parent : group) { + Animal child = parent.reproduce(group); + if (child != null) { + child.setPosition(coordinate); // сразу ставим координаты + newborns.add(child); + } + } + } + for (Animal baby : newborns) addAnimal(baby); + + // 3️⃣ Передвижение — только решение, без изменения чужих клеток + List toMove = new ArrayList<>(); + for (Animal animal : new ArrayList<>(animals)) { + if (!animal.isAlive()) continue; + + Coordinate oldPos = animal.getPosition(); + animal.move(island); + Coordinate newPos = animal.getPosition(); + + if (oldPos.x != newPos.x || oldPos.y != newPos.y) { + toMove.add(animal); + } + } + + // Убираем уехавших + animals.removeAll(toMove); + + // Передаём их в новые клетки через очередь + for (Animal moving : toMove) { + Cell target = island.getCell(moving.getPosition().x, moving.getPosition().y); + if (target != null) { + target.enqueueIncoming(moving); + } + } + + // 4️⃣ Старение и смерть + Iterator iterator = animals.iterator(); + while (iterator.hasNext()) { + Animal a = iterator.next(); + a.liveCycle(island); + if (!a.isAlive()) { + iterator.remove(); + } + } + + // 5️⃣ Приём прибывших животных (в конце фазы) + Animal arrived; + while ((arrived = movedIn.poll()) != null) { + addAnimal(arrived); + } + + } finally { + lock.unlock(); + } + } + + public Coordinate getCoordinate() { + return coordinate; + } +} diff --git a/src/MapEngine/Coordinate.java b/src/MapEngine/Coordinate.java new file mode 100644 index 0000000..72241ac --- /dev/null +++ b/src/MapEngine/Coordinate.java @@ -0,0 +1,11 @@ +package MapEngine; + +public class Coordinate { + public final int x; + public final int y; + + public Coordinate(int x, int y) { + this.x = x; + this.y = y; + } +} diff --git a/src/MapEngine/Island.java b/src/MapEngine/Island.java new file mode 100644 index 0000000..cd3c148 --- /dev/null +++ b/src/MapEngine/Island.java @@ -0,0 +1,149 @@ +package MapEngine; + +import Fauna.Animal; +import Fauna.Herbs; + +import java.util.concurrent.*; + +public class Island { + + private final int width; + private final int height; + private final Cell[][] cells; + + // Один общий пул потоков для острова + private final ExecutorService pool; + + public Island(int width, int height) { + this.width = width; + this.height = height; + this.cells = new Cell[height][width]; + + // создаём клетки + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + cells[y][x] = new Cell(new Coordinate(x, y)); + } + } + + // создаём пул потоков под число процессоров + this.pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + } + + /** Возвращает клетку по координатам */ + public Cell getCell(int x, int y) { + if (x < 0 || y < 0 || x >= width || y >= height) return null; + return cells[y][x]; + } + + /** Симуляция одного шага (все клетки работают параллельно) */ + public void simulateStep() { + CompletionService completionService = new ExecutorCompletionService<>(pool); + + int taskCount = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell cell = cells[y][x]; + completionService.submit(() -> { + cell.liveCycle(this); + return null; + }); + taskCount++; + } + } + + for (int i = 0; i < taskCount; i++) { + try { + completionService.take(); // ждём, пока каждая задача завершится + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + } + + /** Добавить животное в клетку по координатам */ + public void addAnimal(Animal animal, int x, int y) { + Cell cell = getCell(x, y); + if (cell != null) { + cell.addAnimal(animal); + animal.setPosition(new Coordinate(x, y)); + } + } + + /** Добавить растение в клетку по координатам */ + public void addPlant(Herbs herb, int x, int y) { + Cell cell = getCell(x, y); + if (cell != null) { + cell.addPlant(herb); + } + } + + /** Печать карты (безопасный снимок данных) */ + public void printMap() { + System.out.println("\n===== ISLAND MAP ====="); + + for (int y = 0; y < height; y++) { + StringBuilder row = new StringBuilder(); + + for (int x = 0; x < width; x++) { + Cell cell = cells[y][x]; + String symbol = "⬜ "; + + var animals = cell.getAnimals(); + if (!animals.isEmpty()) { + symbol = getAnimalSymbol(animals.get(0)) + " "; + } else if (!cell.getPlants().isEmpty()) { + symbol = "🌿 "; + } + + row.append(symbol); + } + System.out.println(row); + } + } + + /** Эмодзи для животных */ + private String getAnimalSymbol(Animal animal) { + return switch (animal.getName()) { + case "Rabbit" -> "🐇"; + case "Fox" -> "🦊"; + case "Wolf" -> "🐺"; + case "Boa" -> "🐍"; + case "Bear" -> "\uD83D\uDC3B"; + case "Eagle" -> "\uD83E\uDD85"; + case "Horse" -> "\uD83D\uDC0E"; + case "Deer" -> "\uD83E\uDD8C"; + case "Mouse" -> "\uD83D\uDC01"; + case "Goat" -> "\uD83D\uDC10"; + case "Sheep" -> "\uD83D\uDC11"; + case "Boar" -> "\uD83D\uDC17"; + case "Buffalo" -> "\uD83D\uDC03"; + case "Duck" -> "\uD83E\uDD86"; + case "Caterpillar" -> "\uD83D\uDC1B"; + default -> "❓"; + }; + } + + /** Геттеры для размеров */ + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + /** Завершить все потоки (при остановке программы) */ + public void shutdown() { + pool.shutdown(); + try { + if (!pool.awaitTermination(5, TimeUnit.SECONDS)) { + pool.shutdownNow(); + } + } catch (InterruptedException e) { + pool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } diff --git a/src/SupportClasses/Statistics.java b/src/SupportClasses/Statistics.java new file mode 100644 index 0000000..68877f1 --- /dev/null +++ b/src/SupportClasses/Statistics.java @@ -0,0 +1,142 @@ +package SupportClasses; + +import Fauna.Animal; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; + +public class Statistics { + + public static class StatRecord { + public int left = 0; // живых животных + public int rip = 0; // умерших животных + public int reproduced = 0; // потомства + public int eaten = 0; // съедено животных + public int ateGrass = 0; // съедено травы + public int ateAnimals = 0; // сколько животных съел хищник + } + + // Основная статистика по видам + private static final Map stats = new IdentityHashMap<>(); + private static int deathsLastStep = 0; + private static int previousTotalDeaths = 0; + + + // Набор уже зарегистрированных объектов (уникальных экземпляров животных) + private static final Set registered = Collections.newSetFromMap(new IdentityHashMap<>()); + + /** Полный сброс статистики */ + public static void reset() { + stats.clear(); + registered.clear(); + } + + /** Регистрируем животное только один раз */ + public static void registerAnimal(Animal animal) { + if (!registered.add(animal)) return; // уже зарегистрировано, пропускаем + + stats.putIfAbsent(animal.getName(), new StatRecord()); + stats.get(animal.getName()).left++; + } + + /** Отмечаем смерть животного */ + public static void markDeath(Animal animal, boolean eaten) { + // удаляем из набора зарегистрированных + registered.remove(animal); + + StatRecord record = stats.computeIfAbsent(animal.getName(), n -> new StatRecord()); + record.left = Math.max(0, record.left - 1); + record.rip++; + if (eaten && canBeEaten(animal.getName())) { + record.eaten++; + } + } + + /** Отмечаем появление потомка */ + public static void markReproduce(Animal child) { + // потомок — это новый объект, его тоже регистрируем + registerAnimal(child); + + StatRecord record = stats.computeIfAbsent(child.getName(), n -> new StatRecord()); + record.reproduced++; + } + + /** Травоядное съело траву */ + public static void markGrassEaten(Animal animal) { + if (!isHerbivore(animal.getName())) return; + StatRecord record = stats.computeIfAbsent(animal.getName(), n -> new StatRecord()); + record.ateGrass++; + } + + /** Хищник съел животное */ + public static void markAnimalEatenByPredator(Animal predator) { + if (!predatorEatsAnimals(predator.getName())) return; + StatRecord record = stats.computeIfAbsent(predator.getName(), n -> new StatRecord()); + record.ateAnimals++; + } + + /** Обнуляем количество съеденной травы (например, раз в шаг) */ + public static void resetGrassCounter() { + for (StatRecord record : stats.values()) { + record.ateGrass = 0; + } + } + + /** Вывод всей статистики по видам */ + public static void print() { + for (Map.Entry entry : stats.entrySet()) { + String name = entry.getKey(); + StatRecord s = entry.getValue(); + + if (isHerbivore(name)) { + System.out.printf("%s: left - %d, RIP - %d, reproduced - %d, eaten - %d, ate grass - %d%n", + name, s.left, s.rip, s.reproduced, s.eaten, s.ateGrass); + } else if (predatorEatsAnimals(name) && !canBeEaten(name)) { + System.out.printf("%s: left - %d, RIP - %d, reproduced - %d, ate animals - %d%n", + name, s.left, s.rip, s.reproduced, s.ateAnimals); + } else { + System.out.printf("%s: left - %d, RIP - %d, reproduced - %d, eaten - %d, ate animals - %d%n", + name, s.left, s.rip, s.reproduced, s.eaten, s.ateAnimals); + } + } + } + + // --- вспомогательные методы определения типа животного --- + + private static boolean isHerbivore(String name) { + return switch (name) { + case "Rabbit", "Mouse", "Goat", "Sheep","Horse","Deer","Boar","Buffalo","Duck","Caterpillar" -> true; + default -> false; + }; + } + + private static boolean canBeEaten(String name) { + return switch (name) { + case "Rabbit", "Mouse", "Goat", "Sheep","Horse","Deer","Boar","Buffalo","Duck","Caterpillar", "Fox","Boa" -> true; + default -> false; + }; + } + + private static boolean predatorEatsAnimals(String name) { + return switch (name) { + case "Wolf", "Fox", "Boa","Bear","Eagle" -> true; + default -> false; + }; + } + public static int getTotalDeaths() { + int total = 0; + for (StatRecord record : stats.values()) { + total += record.rip; + } + return total; + } + + public static int getDeathsThisStep() { + int totalNow = getTotalDeaths(); + deathsLastStep = totalNow - previousTotalDeaths; + previousTotalDeaths = totalNow; + return Math.max(deathsLastStep, 0); + } + +}