diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dfd344f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.gradle/ +/.idea/ \ No newline at end of file diff --git a/build.gradle b/build.gradle index 3cae2d7..94d20ae 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'java' + id 'org.openjfx.javafxplugin' version '0.0.13' } group 'org.example' @@ -9,9 +10,16 @@ repositories { mavenCentral() } +javafx { + version = "17" + modules = [ 'javafx.controls' ] +} + dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' + implementation 'com.googlecode.json-simple:json-simple:1.1.1' + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.4' } test { diff --git a/build/classes/java/main/org/example/Main.class b/build/classes/java/main/org/example/Main.class new file mode 100644 index 0000000..73e6fa2 Binary files /dev/null and b/build/classes/java/main/org/example/Main.class differ diff --git a/build/classes/java/main/org/example/gui/App.class b/build/classes/java/main/org/example/gui/App.class new file mode 100644 index 0000000..fa14582 Binary files /dev/null and b/build/classes/java/main/org/example/gui/App.class differ diff --git a/build/classes/java/main/org/example/gui/GuiElementBox.class b/build/classes/java/main/org/example/gui/GuiElementBox.class new file mode 100644 index 0000000..9f70f16 Binary files /dev/null and b/build/classes/java/main/org/example/gui/GuiElementBox.class differ diff --git a/build/classes/java/main/org/example/gui/IEngineRefreshObserver.class b/build/classes/java/main/org/example/gui/IEngineRefreshObserver.class new file mode 100644 index 0000000..67a8eaf Binary files /dev/null and b/build/classes/java/main/org/example/gui/IEngineRefreshObserver.class differ diff --git a/build/classes/java/main/org/example/gui/SimulationEngine.class b/build/classes/java/main/org/example/gui/SimulationEngine.class new file mode 100644 index 0000000..71671f8 Binary files /dev/null and b/build/classes/java/main/org/example/gui/SimulationEngine.class differ diff --git a/build/classes/java/main/org/example/gui/SimulationStage.class b/build/classes/java/main/org/example/gui/SimulationStage.class new file mode 100644 index 0000000..02fac6f Binary files /dev/null and b/build/classes/java/main/org/example/gui/SimulationStage.class differ diff --git a/build/classes/java/main/org/example/map/EdgeEarth.class b/build/classes/java/main/org/example/map/EdgeEarth.class new file mode 100644 index 0000000..4f428a6 Binary files /dev/null and b/build/classes/java/main/org/example/map/EdgeEarth.class differ diff --git a/build/classes/java/main/org/example/map/EdgeHellPortal.class b/build/classes/java/main/org/example/map/EdgeHellPortal.class new file mode 100644 index 0000000..814cc84 Binary files /dev/null and b/build/classes/java/main/org/example/map/EdgeHellPortal.class differ diff --git a/build/classes/java/main/org/example/map/Statistics.class b/build/classes/java/main/org/example/map/Statistics.class new file mode 100644 index 0000000..6223bc2 Binary files /dev/null and b/build/classes/java/main/org/example/map/Statistics.class differ diff --git a/build/classes/java/main/org/example/map/WorldMap.class b/build/classes/java/main/org/example/map/WorldMap.class new file mode 100644 index 0000000..ed93f8f Binary files /dev/null and b/build/classes/java/main/org/example/map/WorldMap.class differ diff --git a/build/classes/java/main/org/example/map/objects/animal/Animal$1.class b/build/classes/java/main/org/example/map/objects/animal/Animal$1.class new file mode 100644 index 0000000..4dd098f Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/animal/Animal$1.class differ diff --git a/build/classes/java/main/org/example/map/objects/animal/Animal.class b/build/classes/java/main/org/example/map/objects/animal/Animal.class new file mode 100644 index 0000000..fdc01fa Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/animal/Animal.class differ diff --git a/build/classes/java/main/org/example/map/objects/animal/AnimalStatistics.class b/build/classes/java/main/org/example/map/objects/animal/AnimalStatistics.class new file mode 100644 index 0000000..072506b Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/animal/AnimalStatistics.class differ diff --git a/build/classes/java/main/org/example/map/objects/animal/IAnimalObserver.class b/build/classes/java/main/org/example/map/objects/animal/IAnimalObserver.class new file mode 100644 index 0000000..fd3444b Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/animal/IAnimalObserver.class differ diff --git a/build/classes/java/main/org/example/map/objects/animal/behavior/AnimalBehaviorCrazy.class b/build/classes/java/main/org/example/map/objects/animal/behavior/AnimalBehaviorCrazy.class new file mode 100644 index 0000000..1ed00c5 Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/animal/behavior/AnimalBehaviorCrazy.class differ diff --git a/build/classes/java/main/org/example/map/objects/animal/behavior/AnimalBehaviorPredestination.class b/build/classes/java/main/org/example/map/objects/animal/behavior/AnimalBehaviorPredestination.class new file mode 100644 index 0000000..b9f9009 Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/animal/behavior/AnimalBehaviorPredestination.class differ diff --git a/build/classes/java/main/org/example/map/objects/animal/behavior/IAnimalBehavior.class b/build/classes/java/main/org/example/map/objects/animal/behavior/IAnimalBehavior.class new file mode 100644 index 0000000..2ebe906 Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/animal/behavior/IAnimalBehavior.class differ diff --git a/build/classes/java/main/org/example/map/objects/animal/genes/GeneChoice.class b/build/classes/java/main/org/example/map/objects/animal/genes/GeneChoice.class new file mode 100644 index 0000000..2c3d294 Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/animal/genes/GeneChoice.class differ diff --git a/build/classes/java/main/org/example/map/objects/animal/genes/Genes.class b/build/classes/java/main/org/example/map/objects/animal/genes/Genes.class new file mode 100644 index 0000000..78dcf6a Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/animal/genes/Genes.class differ diff --git a/build/classes/java/main/org/example/map/objects/animal/genes/GenesFactory.class b/build/classes/java/main/org/example/map/objects/animal/genes/GenesFactory.class new file mode 100644 index 0000000..e6277ee Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/animal/genes/GenesFactory.class differ diff --git a/build/classes/java/main/org/example/map/objects/animal/genes/GenesFullRandom.class b/build/classes/java/main/org/example/map/objects/animal/genes/GenesFullRandom.class new file mode 100644 index 0000000..970cd54 Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/animal/genes/GenesFullRandom.class differ diff --git a/build/classes/java/main/org/example/map/objects/animal/genes/GenesSlightCorrection.class b/build/classes/java/main/org/example/map/objects/animal/genes/GenesSlightCorrection.class new file mode 100644 index 0000000..dd7186f Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/animal/genes/GenesSlightCorrection.class differ diff --git a/build/classes/java/main/org/example/map/objects/plants/CannotPlacePlantException.class b/build/classes/java/main/org/example/map/objects/plants/CannotPlacePlantException.class new file mode 100644 index 0000000..2cb379a Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/plants/CannotPlacePlantException.class differ diff --git a/build/classes/java/main/org/example/map/objects/plants/IPlantObserver.class b/build/classes/java/main/org/example/map/objects/plants/IPlantObserver.class new file mode 100644 index 0000000..9992e37 Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/plants/IPlantObserver.class differ diff --git a/build/classes/java/main/org/example/map/objects/plants/IPlants.class b/build/classes/java/main/org/example/map/objects/plants/IPlants.class new file mode 100644 index 0000000..cb54e19 Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/plants/IPlants.class differ diff --git a/build/classes/java/main/org/example/map/objects/plants/Plant.class b/build/classes/java/main/org/example/map/objects/plants/Plant.class new file mode 100644 index 0000000..7515ca3 Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/plants/Plant.class differ diff --git a/build/classes/java/main/org/example/map/objects/plants/PlantsEquator.class b/build/classes/java/main/org/example/map/objects/plants/PlantsEquator.class new file mode 100644 index 0000000..065e04c Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/plants/PlantsEquator.class differ diff --git a/build/classes/java/main/org/example/map/objects/plants/PlantsToxicCorpses.class b/build/classes/java/main/org/example/map/objects/plants/PlantsToxicCorpses.class new file mode 100644 index 0000000..5925d9e Binary files /dev/null and b/build/classes/java/main/org/example/map/objects/plants/PlantsToxicCorpses.class differ diff --git a/build/classes/java/main/org/example/map/options/IEdge.class b/build/classes/java/main/org/example/map/options/IEdge.class new file mode 100644 index 0000000..a1a0790 Binary files /dev/null and b/build/classes/java/main/org/example/map/options/IEdge.class differ diff --git a/build/classes/java/main/org/example/map/options/IMapElement.class b/build/classes/java/main/org/example/map/options/IMapElement.class new file mode 100644 index 0000000..c312f80 Binary files /dev/null and b/build/classes/java/main/org/example/map/options/IMapElement.class differ diff --git a/build/classes/java/main/org/example/utils/GetFromFile.class b/build/classes/java/main/org/example/utils/GetFromFile.class new file mode 100644 index 0000000..92de4be Binary files /dev/null and b/build/classes/java/main/org/example/utils/GetFromFile.class differ diff --git a/build/classes/java/main/org/example/utils/MapDirection$1.class b/build/classes/java/main/org/example/utils/MapDirection$1.class new file mode 100644 index 0000000..aaac0d4 Binary files /dev/null and b/build/classes/java/main/org/example/utils/MapDirection$1.class differ diff --git a/build/classes/java/main/org/example/utils/MapDirection.class b/build/classes/java/main/org/example/utils/MapDirection.class new file mode 100644 index 0000000..2015e41 Binary files /dev/null and b/build/classes/java/main/org/example/utils/MapDirection.class differ diff --git a/build/classes/java/main/org/example/utils/Preferences.class b/build/classes/java/main/org/example/utils/Preferences.class new file mode 100644 index 0000000..2861698 Binary files /dev/null and b/build/classes/java/main/org/example/utils/Preferences.class differ diff --git a/build/classes/java/main/org/example/utils/Vector2d.class b/build/classes/java/main/org/example/utils/Vector2d.class new file mode 100644 index 0000000..de0cce6 Binary files /dev/null and b/build/classes/java/main/org/example/utils/Vector2d.class differ diff --git a/build/resources/main/conf1.json b/build/resources/main/conf1.json new file mode 100644 index 0000000..80ce2e8 --- /dev/null +++ b/build/resources/main/conf1.json @@ -0,0 +1,18 @@ +{ + "width": 1, + "height": 1, + "iEdge": "earth", + "numberOfPlantsAtStart": 10, + "plantEnergy": 0, + "plantsSeededEachDay": 2, + "iPlants": "toxic", + "numberOfAnimalsAtStart": 1, + "animalEnergy": 2000000, + "animalEnergyBreedingThreshold": 50, + "animalBreedingCost": 50, + "animalMinMutations": 2, + "animalMaxMutations": 5, + "genesLength": 10, + "isCrazyBehavior": false, + "isFullRandom": true +} \ No newline at end of file diff --git a/build/resources/main/conf2.json b/build/resources/main/conf2.json new file mode 100644 index 0000000..ee3f088 --- /dev/null +++ b/build/resources/main/conf2.json @@ -0,0 +1,18 @@ +{ + "width": 10, + "height": 16, + "iEdge": "earth", + "numberOfPlantsAtStart": 20, + "plantEnergy": 50, + "plantsSeededEachDay": 6, + "iPlants": "equator", + "numberOfAnimalsAtStart": 6, + "animalEnergy": 100, + "animalEnergyBreedingThreshold": 50, + "animalBreedingCost": 50, + "animalMinMutations": 2, + "animalMaxMutations": 5, + "genesLength": 32, + "isCrazyBehavior": true, + "isFullRandom": true +} \ No newline at end of file diff --git a/build/resources/main/conf3.json b/build/resources/main/conf3.json new file mode 100644 index 0000000..9996460 --- /dev/null +++ b/build/resources/main/conf3.json @@ -0,0 +1,18 @@ +{ + "width": 10, + "height": 10, + "iEdge": "earth", + "numberOfPlantsAtStart": 20, + "plantEnergy": 50, + "plantsSeededEachDay": 6, + "iPlants": "equator", + "numberOfAnimalsAtStart": 11, + "animalEnergy": 100, + "animalEnergyBreedingThreshold": 50, + "animalBreedingCost": 50, + "animalMinMutations": 2, + "animalMaxMutations": 5, + "genesLength": 1, + "isCrazyBehavior": false, + "isFullRandom": false +} \ No newline at end of file diff --git a/build/resources/main/strawberry_resized_32.png b/build/resources/main/strawberry_resized_32.png new file mode 100644 index 0000000..b4ac8b2 Binary files /dev/null and b/build/resources/main/strawberry_resized_32.png differ diff --git a/build/tmp/compileJava/previous-compilation-data.bin b/build/tmp/compileJava/previous-compilation-data.bin new file mode 100644 index 0000000..6e714bb Binary files /dev/null and b/build/tmp/compileJava/previous-compilation-data.bin differ diff --git a/src/main/java/org/example/Main.java b/src/main/java/org/example/Main.java index 407f157..8993b15 100644 --- a/src/main/java/org/example/Main.java +++ b/src/main/java/org/example/Main.java @@ -1,7 +1,11 @@ package org.example; +import javafx.application.Application; +import org.example.gui.App; + + public class Main { public static void main(String[] args) { - System.out.println("Hello world!"); + Application.launch(App.class, args); } } \ No newline at end of file diff --git a/src/main/java/org/example/gui/App.java b/src/main/java/org/example/gui/App.java new file mode 100644 index 0000000..8ac1526 --- /dev/null +++ b/src/main/java/org/example/gui/App.java @@ -0,0 +1,82 @@ +package org.example.gui; + +import javafx.application.Application; +import javafx.geometry.HPos; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; +import javafx.stage.Stage; +import org.example.utils.GetFromFile; +import org.example.utils.Preferences; + +import java.io.IOException; + +public class App extends Application { + + private final GridPane layout = new GridPane(); + private final Scene configScene = new Scene(this.layout, 500, 500); + + private final GetFromFile getFromFile = new GetFromFile(); + + private final TextField textField = new TextField("Give path to file"); + + + public void start(Stage primaryStage) { + Button startSimulationButton1 = getButton("src/main/resources/conf1.json", 1); + Button startSimulationButton2 = getButton("src/main/resources/conf2.json", 2); + Button startSimulationButton3 = getButton("src/main/resources/conf3.json", 3); + + // Position buttons at center. + GridPane.setHalignment(startSimulationButton1, HPos.CENTER); + GridPane.setHalignment(startSimulationButton2, HPos.CENTER); + GridPane.setHalignment(startSimulationButton3, HPos.CENTER); + + layout.addRow(1, startSimulationButton1); + layout.addRow(2, startSimulationButton2); + layout.addRow(3, startSimulationButton3); + customPath(layout); + + primaryStage.setTitle("EvolutionSimulator"); + primaryStage.setScene(configScene); + primaryStage.show(); + + } + + private Button getButton(String path, int number) { + Button startSimulationButton = new Button("Go to simulation " + number); + startSimulationButton.setOnMouseClicked(event -> { + Preferences preferences; + try { + preferences = getFromFile.getPreferencesFromFile(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + + var simulationStage = new SimulationStage(preferences); + simulationStage.setTitle("World Map"); + +// primaryStage.close(); + }); + return startSimulationButton; + } + + private void customPath(GridPane layout) { + + Button button = new Button("Read simulation from file"); + GridPane.setHalignment(button, HPos.CENTER); + button.setOnMouseClicked(event -> { + Preferences preferences; + try { + preferences = getFromFile.getPreferencesFromFile(textField.getText()); + var simulationStage = new SimulationStage(preferences); + simulationStage.setTitle("World Map"); + } catch (IOException e) { + textField.setText("Wrong path"); + } + + }); + + layout.addRow(4, textField, button); + } +} diff --git a/src/main/java/org/example/gui/GuiElementBox.java b/src/main/java/org/example/gui/GuiElementBox.java new file mode 100644 index 0000000..d27548f --- /dev/null +++ b/src/main/java/org/example/gui/GuiElementBox.java @@ -0,0 +1,42 @@ +package org.example.gui; + +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.VBox; +import org.example.map.options.IMapElement; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +public class GuiElementBox { + private final IMapElement mapElement; + + public GuiElementBox(IMapElement mapElement) { + this.mapElement = mapElement; + } + + public VBox getGuiVisualization() { + if (this.mapElement == null) + return new VBox(); + + Image image = null; + try { + image = new Image(new FileInputStream(this.mapElement.getSourceAddress())); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + ImageView imageView = new ImageView(image); + imageView.setFitWidth(30); + imageView.setFitHeight(30); + + Label label = new Label(this.mapElement.toString()); + VBox vBox = new VBox(imageView, label); + vBox.setAlignment(Pos.CENTER); + + return vBox; + } +} + + diff --git a/src/main/java/org/example/gui/IEngineRefreshObserver.java b/src/main/java/org/example/gui/IEngineRefreshObserver.java new file mode 100644 index 0000000..bc87553 --- /dev/null +++ b/src/main/java/org/example/gui/IEngineRefreshObserver.java @@ -0,0 +1,6 @@ +package org.example.gui; + +public interface IEngineRefreshObserver { + // TODO can we delete this? + void refreshNeeded(); +} diff --git a/src/main/java/org/example/gui/SimulationEngine.java b/src/main/java/org/example/gui/SimulationEngine.java new file mode 100644 index 0000000..998e999 --- /dev/null +++ b/src/main/java/org/example/gui/SimulationEngine.java @@ -0,0 +1,46 @@ +package org.example.gui; + + +import javafx.application.Platform; +import org.example.map.Statistics; +import org.example.map.WorldMap; +import org.example.map.objects.animal.AnimalStatistics; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class SimulationEngine extends Thread { + private final WorldMap map; + private final SimulationStage simulationStage; + private final List statistics = new ArrayList<>(); + private final int dayCounter = 0; + + public SimulationEngine(WorldMap map, SimulationStage simulationStage) { + this.map = map; + this.simulationStage = simulationStage; + } + + private void day() { + Statistics currentStats = map.day(); + Optional animalStatistics = map.getTrackedAnimalStatistics(); + + Platform.runLater(() -> { + simulationStage.displayMap(); + simulationStage.displayStats(currentStats); + animalStatistics.ifPresent(simulationStage::displayAnimalStats); + }); + } + + @Override + public void run() { + while (true) { + day(); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + return; + } + } + } +} diff --git a/src/main/java/org/example/gui/SimulationStage.java b/src/main/java/org/example/gui/SimulationStage.java new file mode 100644 index 0000000..867c9fd --- /dev/null +++ b/src/main/java/org/example/gui/SimulationStage.java @@ -0,0 +1,308 @@ +package org.example.gui; + +import javafx.application.Platform; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import org.example.map.Statistics; +import org.example.map.WorldMap; +import org.example.map.objects.animal.Animal; +import org.example.map.objects.animal.AnimalStatistics; +import org.example.utils.Preferences; +import org.example.utils.Vector2d; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + + +public class SimulationStage extends Stage implements IEngineRefreshObserver { + private final GridPane gridPane = new GridPane(); + private final VBox layout = new VBox(); + private final Scene scene = new Scene(layout, 1280, 720); + private final Label dominantGenotype = new Label(); + private final Button saveToFileButton = new Button("save to file"); + int mapHeight = 50; + int mapWidth = 50; + WorldMap map; + + SimulationEngine engine; + Thread engineThread; + private final Label numberOfLivingAnimals = new Label(""); + private final Label numberOfPlants = new Label(""); + private final Label numberOfFreeFields = new Label(""); + private final Label mostPopularGenotype = new Label(""); + private final Label averageEnergy = new Label(""); + private final Label averageLifespan = new Label(""); + private final Label day = new Label(""); + private final Label trackedInfo = new Label("Click on animal to see its details:"); + private final Label trackedGenome = new Label(""); + private final Label trackedDirection = new Label(""); + private final Label trackedEnergy = new Label(""); + private final Label trackedPlants = new Label(""); + private final Label trackedChildren = new Label(""); + private final Label trackedAge = new Label(""); + private final Label trackedBornDay = new Label(""); + + public SimulationStage(Preferences preferences) { + map = preferences.toWorldMap(); + + setOnCloseRequest(e -> { + if (engine != null) { + engine.interrupt(); + } + }); + + Button startSimulationButton = new Button("Pause"); + startSimulationButton.setOnMouseClicked(event -> { + if (engine != null && engine.isAlive()) { + engine.interrupt(); + saveToFileButton.setDisable(false); + startSimulationButton.setText("Play"); + displayMapWithTracking(); + + + } else { + engine = new SimulationEngine(map, this); + engine.start(); + saveToFileButton.setDisable(true); + startSimulationButton.setText("Pause"); + } + + }); + saveToFileButton.setDisable(true); + saveToFileButton.setOnAction(event -> { + FileChooser fileChooser = new FileChooser(); + + //Set extension filter for csv files + FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("CSV files (*.csv)", "*.csv"); + fileChooser.getExtensionFilters().add(extFilter); + + //Show save file dialog + File file = fileChooser.showSaveDialog(this); + + if (file != null) { + if (!file.getName().endsWith(".csv")) { + file = new File(file.getAbsolutePath() + ".csv"); + } + saveStatisticsToFile(file); + } + }); +// +// + HBox main = new HBox(); + main.setPadding(new Insets(10, 10, 10, 10)); + VBox right = new VBox(); + right.setSpacing(10); + right.setPadding(new Insets(10, 10, 10, 10)); + right.getChildren().addAll(startSimulationButton, + numberOfLivingAnimals, + numberOfPlants, + numberOfFreeFields, + mostPopularGenotype, + averageEnergy, + averageLifespan, + day, + saveToFileButton, + trackedInfo, + trackedGenome, + trackedDirection, + trackedEnergy, + trackedPlants, + trackedChildren, + trackedBornDay, + trackedAge); + main.getChildren().addAll(gridPane, right); + layout.getChildren().add(main); + + engine = new SimulationEngine(map, this); + displayMap(); + engine.start(); + setScene(scene); + show(); + } + + private String nameMapLabels(Vector2d lowerLeft, + Vector2d upperRight, int borderMargin, + int x_position, int y_position) { + + int mapBorderX = lowerLeft.x + x_position - borderMargin; + int mapBorderY = upperRight.y - y_position + borderMargin; + + if (x_position == 0 && y_position == 0) { + return "y/x"; + } else if (x_position == 0) { + return Integer.toString(mapBorderY); + } else if (y_position == 0) { + return Integer.toString(mapBorderX); + } else { + Vector2d position = new Vector2d(mapBorderX, mapBorderY); + return map.contentLabel(position); + } + } + + public void displayStats(Statistics statistics) { + // generate labels for stats + numberOfLivingAnimals.setText("Number of living animals: " + statistics.numberOfAllAnimals()); + numberOfPlants.setText("Number of plants: " + statistics.numberOfAllPlants()); + numberOfFreeFields.setText("Number of free fields: " + statistics.freeField()); + mostPopularGenotype.setText("Most popular genotype: " + statistics.mostPopularGenotypes()); + averageEnergy.setText("Average energy: " + statistics.averageEnergy()); + averageLifespan.setText("Average lifespan: " + statistics.averageDeadLifespan()); + day.setText("Day: " + statistics.dayCounter()); + } + + public void displayAnimalStats(AnimalStatistics statistics) { + trackedGenome.setText("Tracked Genome: " + statistics.genome()); + trackedDirection.setText("Activated Genome (Direction): " + statistics.direction()); + trackedEnergy.setText("Energy: " + statistics.energy()); + trackedPlants.setText("Plants eaten: " + statistics.plantsEaten()); + trackedChildren.setText("Children: " + statistics.children()); + trackedBornDay.setText("Born day: " + statistics.bornDay()); + trackedAge.setText("Age: " + statistics.age()); + } + public void displayMapWithTracking() { + gridPane.setGridLinesVisible(false); + gridPane.getColumnConstraints().clear(); + gridPane.getRowConstraints().clear(); + gridPane.getChildren().clear(); + gridPane.setGridLinesVisible(true); + + Vector2d lowerLeft = map.getLowerLeft(); + Vector2d upperRight = map.getUpperRight(); + + int borderMargin = 1; + int maxValueX = upperRight.x - lowerLeft.x + borderMargin; + int maxValueY = upperRight.y - lowerLeft.y + borderMargin; + + // Add map labels to gridPane. + for (int y = 0; y <= maxValueY; y++) { + for (int x = 0; x <= maxValueX; x++) { + int mapBorderX = lowerLeft.x + x - borderMargin; + int mapBorderY = upperRight.y - y + borderMargin; + Vector2d pos = new Vector2d(mapBorderX, mapBorderY); + StackPane cell = new StackPane(); + if (x != 0 && y != 0) { + cell.setBackground(new Background(new BackgroundFill(Color.GREEN, CornerRadii.EMPTY, Insets.EMPTY))); + cell.onMouseClickedProperty().set(event -> { + map.setTrackedAnimal(new Vector2d(pos.x, pos.y)); + map.getTrackedAnimalStatistics().ifPresent(this::displayAnimalStats);}); + } + + String name = nameMapLabels(lowerLeft, upperRight, borderMargin, x, y); + Label label = new Label(name); + cell.getChildren().add(label); + + gridPane.add(cell, x, y, 1, 1); + GridPane.setHalignment(cell, HPos.CENTER); + } + } + + map.animalsWithDominantGenes().stream().map(Animal::getPosition).forEach(position -> { + StackPane cell = new StackPane(); + cell.setBackground(new Background(new BackgroundFill(Color.YELLOW, CornerRadii.EMPTY, Insets.EMPTY))); + String name = nameMapLabels(lowerLeft, upperRight, borderMargin, position.x+1, map.getHeight() - position.y); + Label label = new Label(name); + cell.getChildren().add(label); + gridPane.add(cell, position.x+1, map.getHeight() - position.y, 1, 1); + cell.onMouseClickedProperty().set(event -> { map.setTrackedAnimal(new Vector2d(position.x, position.y)); + map.getTrackedAnimalStatistics().ifPresent(this::displayAnimalStats); + map.getTrackedAnimalStatistics().ifPresent(this::displayAnimalStats); + }); + GridPane.setHalignment(cell, HPos.CENTER); + }); + + // Format gridPane as well as set constraints on columns and rows. + for (int y = 0; y <= maxValueY; y++) { + gridPane.getRowConstraints().add(new RowConstraints(this.mapHeight)); + } + + for (int x = 0; x <= maxValueX; x++) { + gridPane.getColumnConstraints().add(new ColumnConstraints(this.mapWidth)); + } + } + + public void displayMap() { + gridPane.setGridLinesVisible(false); + gridPane.getColumnConstraints().clear(); + gridPane.getRowConstraints().clear(); + gridPane.getChildren().clear(); + gridPane.setGridLinesVisible(true); + + Vector2d lowerLeft = map.getLowerLeft(); + Vector2d upperRight = map.getUpperRight(); + + int borderMargin = 1; + int maxValueX = upperRight.x - lowerLeft.x + borderMargin; + int maxValueY = upperRight.y - lowerLeft.y + borderMargin; + + // Add map labels to gridPane. + for (int y = 0; y <= maxValueY; y++) { + for (int x = 0; x <= maxValueX; x++) { + int mapBorderX = lowerLeft.x + x - borderMargin; + int mapBorderY = upperRight.y - y + borderMargin; + Vector2d pos = new Vector2d(mapBorderX, mapBorderY); + StackPane cell = new StackPane(); + if (x != 0 && y != 0) { + cell.setBackground(new Background(new BackgroundFill(Color.GREEN, CornerRadii.EMPTY, Insets.EMPTY))); + } + + String name = nameMapLabels(lowerLeft, upperRight, borderMargin, x, y); + Label label = new Label(name); + cell.getChildren().add(label); + + gridPane.add(cell, x, y, 1, 1); + GridPane.setHalignment(cell, HPos.CENTER); + } + } + + map.animalsWithDominantGenes().stream().map(Animal::getPosition).forEach(position -> { + StackPane cell = new StackPane(); + cell.setBackground(new Background(new BackgroundFill(Color.YELLOW, CornerRadii.EMPTY, Insets.EMPTY))); + String name = nameMapLabels(lowerLeft, upperRight, borderMargin, position.x+1, map.getHeight() - position.y); + Label label = new Label(name); + cell.getChildren().add(label); + gridPane.add(cell, position.x+1, map.getHeight() - position.y, 1, 1); + GridPane.setHalignment(cell, HPos.CENTER); + }); + + // Format gridPane as well as set constraints on columns and rows. + for (int y = 0; y <= maxValueY; y++) { + gridPane.getRowConstraints().add(new RowConstraints(this.mapHeight)); + } + + for (int x = 0; x <= maxValueX; x++) { + gridPane.getColumnConstraints().add(new ColumnConstraints(this.mapWidth)); + } + } + @Override + public void refreshNeeded() { + Platform.runLater(this::displayMap); + } + + private void saveStatisticsToFile(File file) { + try { + FileWriter out = new FileWriter(file); + out.append(Statistics.csvHeader()); + + map.getAllStatistics().forEach(statistics -> { + try { + out.append(statistics.toCsvRow()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + out.flush(); + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/example/map/EdgeEarth.java b/src/main/java/org/example/map/EdgeEarth.java new file mode 100644 index 0000000..61d5fee --- /dev/null +++ b/src/main/java/org/example/map/EdgeEarth.java @@ -0,0 +1,30 @@ +package org.example.map; + +import org.example.map.options.IEdge; +import org.example.utils.Vector2d; +import org.example.map.objects.animal.Animal; + +public class EdgeEarth implements IEdge { + + private final int width; + private final int height; + + public EdgeEarth(int width, int height) { + this.width = width; + this.height = height; + } + + @Override + public Vector2d handleMove(Animal animal) { + Vector2d position = animal.getNewPosition(); + if (position.y < 0 || position.y >= height) { + animal.turn(); + return animal.getPosition(); + } else if (position.x >= width) { + return new Vector2d(0, position.y); + } else if (position.x < 0) { + return new Vector2d(width - 1, position.y); + } + return position; + } +} diff --git a/src/main/java/org/example/map/EdgeHellPortal.java b/src/main/java/org/example/map/EdgeHellPortal.java new file mode 100644 index 0000000..92213cc --- /dev/null +++ b/src/main/java/org/example/map/EdgeHellPortal.java @@ -0,0 +1,31 @@ +package org.example.map; + +import org.example.map.objects.animal.Animal; +import org.example.map.options.IEdge; +import org.example.utils.Vector2d; + +public class EdgeHellPortal implements IEdge { + + private final Vector2d lowerLeft; + private final Vector2d upperRight; + + public EdgeHellPortal(Vector2d lowerLeft, Vector2d upperRight) { + this.lowerLeft = lowerLeft; + this.upperRight = upperRight; + } + + @Override + public Vector2d handleMove(Animal animal) { + Vector2d position = animal.getNewPosition(); + if (position.follows(lowerLeft) && position.precedes(upperRight)) { + return position; + } else { + // Return random position if out of map. + if (position.x < lowerLeft.x || position.x > upperRight.x) { + position = new Vector2d((int) (Math.random() * (upperRight.x - lowerLeft.x + 1) + lowerLeft.x), + (int) (Math.random() * (upperRight.y - lowerLeft.y + 1) + lowerLeft.y)); + } + } + return position; + } +} diff --git a/src/main/java/org/example/map/Statistics.java b/src/main/java/org/example/map/Statistics.java new file mode 100644 index 0000000..3e347e3 --- /dev/null +++ b/src/main/java/org/example/map/Statistics.java @@ -0,0 +1,28 @@ +package org.example.map; + +import java.util.List; + +public record Statistics( + int numberOfAllAnimals, + int numberOfAllPlants, + int freeField, + List> mostPopularGenotypes, + double averageEnergy, + double averageDeadLifespan, + int dayCounter) { + + public static String csvHeader() { + return "numberOfAllAnimals,numberOfAllPlants,freeField,mostPopularGenotypes,averageEnergy,averageDeadLifespan,dayCounter\n"; + } + + public String toCsvRow() { + return String.format("%d,%d,%d,%s,%f,%f,%d\n", + numberOfAllAnimals, + numberOfAllPlants, + freeField, + mostPopularGenotypes, + averageEnergy, + averageDeadLifespan, + dayCounter); + } +} diff --git a/src/main/java/org/example/map/WorldMap.java b/src/main/java/org/example/map/WorldMap.java new file mode 100644 index 0000000..ff35a1c --- /dev/null +++ b/src/main/java/org/example/map/WorldMap.java @@ -0,0 +1,354 @@ +package org.example.map; + +import org.example.map.objects.animal.Animal; +import org.example.map.objects.animal.AnimalStatistics; +import org.example.map.objects.animal.IAnimalObserver; +import org.example.map.objects.animal.genes.Genes; +import org.example.map.objects.animal.genes.GenesFactory; +import org.example.map.objects.plants.*; +import org.example.map.options.IEdge; +import org.example.utils.Vector2d; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class WorldMap implements IAnimalObserver, IPlantObserver { + // Map parameters + private final int width; + private final int height; + private final IEdge iEdge; + private final Vector2d lowerLeft; + private final Vector2d upperRight; + + // Plants parameters + private final int numberOfPlantsAtStart; + private final int plantEnergy; + private final int plantsSeededEachDay; + private final IPlants iPlants; + + // Animals parameters + private final int numberOfAnimalsAtStart; + private final int animalEnergyAtStart; + private final int animalEnergyBreedingThreshold; + private final int animalBreedingCost; + private final GenesFactory genesFactory; + + private int dayCounter = 0; + + // State parameters + private final Map> animals = new HashMap<>(); + private final Map plants = new HashMap<>(); + private final List deadAnimals = new LinkedList<>(); + private final Random generator = new Random(); + + private int numberOfAnimals = 0; + + private final List animalList = new ArrayList<>(); + private final List statistics = new ArrayList<>(); + + private Optional trackedAnimal = Optional.empty(); + + + public WorldMap(int width, + int height, + IEdge iEdge, + int numberOfPlantsAtStart, + int plantEnergy, + int plantsSeededEachDay, + IPlants iPlants, + int numberOfAnimalsAtStart, + int animalEnergy, + int animalEnergyBreedingThreshold, + int animalBreedingCost, + GenesFactory genesFactory) { + this.width = width; + this.height = height; + this.iEdge = iEdge; + this.lowerLeft = new Vector2d(0, 0); + this.upperRight = new Vector2d(width - 1, height - 1); + this.numberOfPlantsAtStart = numberOfPlantsAtStart; + this.plantEnergy = plantEnergy; + this.plantsSeededEachDay = plantsSeededEachDay; + this.iPlants = iPlants; + this.numberOfAnimalsAtStart = numberOfAnimalsAtStart; + this.animalEnergyAtStart = animalEnergy; + this.animalEnergyBreedingThreshold = animalEnergyBreedingThreshold; + this.animalBreedingCost = animalBreedingCost; + this.genesFactory = genesFactory; + placePlants(); + placeAnimals(); + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public Vector2d getLowerLeft() { + return lowerLeft; + } + + public Vector2d getUpperRight() { + return upperRight; + } + + private Vector2d plantPosition() throws CannotPlacePlantException { + boolean isPreferred = generator.nextInt(5) != 4; + try{ + return iPlants.grow(isPreferred); + } + catch (CannotPlacePlantException ex){ + return iPlants.grow(!isPreferred); + } + } + + private void placeOnePlant(){ + Plant plant; + try { + plant = new Plant(plantPosition(), plantEnergy); + plant.addObserver(this); + plant.addObserver((IPlantObserver) iPlants); + plant.place(); + } + catch(CannotPlacePlantException ignored){ + } + } + + private void placePlants(){ + for(int i = 0; i < numberOfPlantsAtStart; i++){ + placeOnePlant(); + } + } + + private Vector2d animalCoords(){ + int x, y; + Vector2d position; + do{ + x = generator.nextInt(width); + y = generator.nextInt(height); + position = new Vector2d(x, y); + }while(animals.getOrDefault(position, Collections.emptyList()).size() > 0); + return position; + } + + private void placeOneAnimal(){ + Vector2d position = animalCoords(); + Genes genotype = genesFactory.createGenes(); + Animal animal = new Animal(position, animalEnergyAtStart, genotype); + animal.addObserver(this); + if(iPlants instanceof IAnimalObserver) { + animal.addObserver((IAnimalObserver) iPlants); + } + animal.place(); + } + + private void placeAnimals(){ + for(int i=0; i(List.of(animal))); + } + numberOfAnimals++; + animalList.add(animal); + animal.calculateAge(this.dayCounter); + } + + @Override + public synchronized void animalMoved(Animal animal, Vector2d oldPosition) { + animals.get(oldPosition).remove(animal); + if(animals.get(oldPosition).isEmpty()){ + animals.remove(oldPosition); + } + + List animalsAtNewPosition = animals + .computeIfAbsent(animal.getPosition(), k -> new LinkedList<>()); + animalsAtNewPosition.add(animal); + } + + @Override + public synchronized void animalDied(Animal animal) { + animals.get(animal.getPosition()).remove(animal); + if (animals.get(animal.getPosition()).isEmpty()) { + animals.remove(animal.getPosition()); + } + deadAnimals.add(animal); + animalList.remove(animal); + numberOfAnimals--; + } + + @Override + public synchronized void plantEaten(Plant plant){ + plants.remove(plant.getPosition()); + } + + @Override + public synchronized void plantPlaced(Plant plant){ + plants.put(plant.getPosition(), plant); + } + + public void removeDeadAnimals(){ + List animalsIterator = new ArrayList<>(animalList); + animalsIterator.forEach(Animal::removeIfDied); + } + + public void eatPlants() { + List plantsIterator = new ArrayList<>(plants.values()); + plantsIterator.forEach(plant -> { + Vector2d position = plant.getPosition(); + List animalsAtPosition = animals.get(position); + if(animalsAtPosition != null && !animalsAtPosition.isEmpty()){ + animalsAtPosition.sort(Comparator.comparingInt(Animal::getEnergy)); + Animal strongest = animalsAtPosition.get(0); + plant.beEaten(strongest); + } + }); + } + + public void breeding() { + animals.values().forEach(animals -> { + if (animals.size() > 1) { + animals.sort(Comparator.comparingInt(Animal::getEnergy)); + Animal strongest = animals.get(0); + Animal secondStrongest = animals.get(1); + if (strongest.getEnergy() >= animalEnergyBreedingThreshold && secondStrongest.getEnergy() >= animalEnergyBreedingThreshold) { + Genes genes = genesFactory.createGenes(strongest, secondStrongest); + Animal child = new Animal(strongest.getPosition(), 2*animalBreedingCost, genes); + strongest.addEnergy(-animalBreedingCost); + secondStrongest.addEnergy(-animalBreedingCost); + child.addObserver(this); + if (iPlants instanceof PlantsToxicCorpses){ + child.addObserver((PlantsToxicCorpses) iPlants); + } + child.place(); + child.setBornDay(this.dayCounter); + child.calculateAge(this.dayCounter); + } + strongest.incrementChildren(); + secondStrongest.incrementChildren(); + } + }); + } + + public void growPlants() { + for(int i = 0; i < plantsSeededEachDay; i++){ + placeOnePlant(); + } + } + + + public int freeFields(){ + int usedFields = animals.size(); + for(Vector2d position: plants.keySet()){ + if(!animals.containsKey(position)){ + usedFields++; + } + } + return width * height - usedFields; + } + + public List> mostPopularGenotypes(){ + Map, Long> genotypesCounted = animalList.stream() + .map(Animal::getGenes) + .map(Genes::getGenotype) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + + int longest = genotypesCounted.values().stream() + .mapToInt(Long::intValue) + .max() + .orElse(0); + return genotypesCounted.entrySet().stream() + .filter(entry -> entry.getValue() == longest) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } + + public double averageEnergy(){ + return animalList.stream() + .mapToInt(Animal::getEnergy) + .average() + .orElse(0); + } + + public double averageDeadLifespan(){ + return deadAnimals.stream() + .mapToInt(Animal::getAge) + .average() + .orElse(0); + } + + public void moveAllAnimals(){ + animalList.forEach(this::moveAnimal); + } + + private void moveAnimal(Animal animal){ + Vector2d position = iEdge.handleMove(animal); + animal.move(position); + } + + public String contentLabel(Vector2d position){ + if(animals.containsKey(position) && !animals.get(position).isEmpty()){ + return animals.get(position).get(0).toString(); + } + if(plants.containsKey(position)){ + return plants.get(position).toString(); + } + return " "; + } + + public Statistics getStats(){ + return new Statistics( + numberOfAnimals, + plants.size(), + freeFields(), + mostPopularGenotypes(), + averageEnergy(), + averageDeadLifespan(), + dayCounter); + } + + public Statistics day(){ + dayCounter++; + removeDeadAnimals(); + moveAllAnimals(); + eatPlants(); + breeding(); + growPlants(); + Statistics currentStats = getStats(); + statistics.add(currentStats); + return currentStats; + } + + public List getAllStatistics(){ + return statistics; + } + + public List animalsWithDominantGenes() { + List> mostPopularGenotypes = mostPopularGenotypes(); + return animalList.stream() + .filter(animal -> mostPopularGenotypes.contains(animal.getGenes().getGenotype())) + .toList(); + } + + public Optional getTrackedAnimalStatistics(){ + return trackedAnimal.map(Animal::getStatistics); + } + + public void setTrackedAnimal(Vector2d position) { + if (animals.containsKey(position)) { + trackedAnimal = Optional.of(animals.get(position).get(0)); + } + } +} diff --git a/src/main/java/org/example/map/objects/animal/Animal.java b/src/main/java/org/example/map/objects/animal/Animal.java new file mode 100644 index 0000000..1c82643 --- /dev/null +++ b/src/main/java/org/example/map/objects/animal/Animal.java @@ -0,0 +1,138 @@ +package org.example.map.objects.animal; + +import org.example.map.objects.animal.genes.Genes; +import org.example.map.options.IMapElement; +import org.example.utils.MapDirection; +import org.example.utils.Vector2d; + +import java.util.ArrayList; +import java.util.List; + +public class Animal implements IMapElement { + private final List observers = new ArrayList<>(); + private Vector2d position; + private MapDirection direction; + private final Genes genes; + private int energy; + private int age; + private int bornDay; + private int children = 0; + private int plantsEaten; + + public Animal(Vector2d position, int energy, Genes genes) { + // Basic parameters. + this.age = 0; + this.position = position; + this.direction = MapDirection.NORTH.randomDirection(); + this.energy = energy; + this.genes = genes; + } + + @Override + public String toString() { + return switch (this.direction) { + case NORTH -> "↑"; + case EAST -> "→"; + case SOUTH -> "↓"; + case WEST -> "←"; + case NORTHEAST -> "↗"; + case NORTHWEST -> "↖"; + case SOUTHEAST -> "↘"; + case SOUTHWEST -> "↙"; + } + "\t" + energy; + } + + @Override + public String getSourceAddress() { + return null; + } + + @Override + public Vector2d getPosition() { + return position; + } + + public MapDirection getDirection() { + return direction; + } + + public int getEnergy() { + return energy; + } + + public Genes getGenes() { + return genes; + } + + public void setBornDay(int bornDay) { + this.bornDay = bornDay; + } + + public void incrementChildren() { + this.children++; + } + + public void addObserver(IAnimalObserver observer) { + observers.add(observer); + } + + public void deleteObserver(IAnimalObserver observer) { + observers.remove(observer); + } + + public void place() { + for (IAnimalObserver observer : observers) { + observer.animalPlaced(this); + } + } + + public void move(Vector2d newPosition) { + Vector2d oldPosition = this.position; + this.position = newPosition; + + for (IAnimalObserver observer : observers) { + observer.animalMoved(this, oldPosition); + } + age++; + energy--; + } + + public Vector2d getNewPosition() { + int nextGene = genes.getMoveDirection(); + MapDirection direction = MapDirection.fromInt(nextGene); + return position.add(direction.toUnitVector()); + } + + public void removeIfDied() { + if (!isAlive()) { + for (IAnimalObserver observer : observers) { + observer.animalDied(this); + } + } + } + + public boolean isAlive() { + return energy > 0; + } + + public void addEnergy(int energy) { + this.plantsEaten++; + this.energy += energy; + } + + public void turn() { + direction = direction.opposite(); + } + + public int getAge() { + return age; + } + + public void calculateAge(int day) { + this.age = Math.abs(day - bornDay); + } + + public AnimalStatistics getStatistics() { + return new AnimalStatistics(genes.getGenotype(), direction, energy, plantsEaten, children, age, bornDay); + } +} diff --git a/src/main/java/org/example/map/objects/animal/AnimalStatistics.java b/src/main/java/org/example/map/objects/animal/AnimalStatistics.java new file mode 100644 index 0000000..d67afe0 --- /dev/null +++ b/src/main/java/org/example/map/objects/animal/AnimalStatistics.java @@ -0,0 +1,16 @@ +package org.example.map.objects.animal; + +import org.example.utils.MapDirection; + +import java.util.List; + +public record AnimalStatistics( + List genome, + MapDirection direction, + int energy, + int plantsEaten, + int children, + int age, + int bornDay +) { +} diff --git a/src/main/java/org/example/map/objects/animal/IAnimalObserver.java b/src/main/java/org/example/map/objects/animal/IAnimalObserver.java new file mode 100644 index 0000000..34a8865 --- /dev/null +++ b/src/main/java/org/example/map/objects/animal/IAnimalObserver.java @@ -0,0 +1,10 @@ +package org.example.map.objects.animal; + +import org.example.utils.Vector2d; + +public interface IAnimalObserver { + // It lets Animals inform other classes about changes of their states + void animalPlaced(Animal animal); + void animalMoved(Animal animal, Vector2d oldPosition); + void animalDied(Animal animal); +} diff --git a/src/main/java/org/example/map/objects/animal/behavior/AnimalBehaviorCrazy.java b/src/main/java/org/example/map/objects/animal/behavior/AnimalBehaviorCrazy.java new file mode 100644 index 0000000..223ca10 --- /dev/null +++ b/src/main/java/org/example/map/objects/animal/behavior/AnimalBehaviorCrazy.java @@ -0,0 +1,16 @@ +package org.example.map.objects.animal.behavior; + +import java.util.Random; + +public class AnimalBehaviorCrazy implements IAnimalBehavior { + + private final Random randomGenerator = new Random(); + + @Override + public int nextGeneIndex(int current, int genesLength) { + if(randomGenerator.nextInt(5) <= 3){ + return (current + 1) % genesLength; + } + return randomGenerator.nextInt(genesLength); + } +} diff --git a/src/main/java/org/example/map/objects/animal/behavior/AnimalBehaviorPredestination.java b/src/main/java/org/example/map/objects/animal/behavior/AnimalBehaviorPredestination.java new file mode 100644 index 0000000..0099b5e --- /dev/null +++ b/src/main/java/org/example/map/objects/animal/behavior/AnimalBehaviorPredestination.java @@ -0,0 +1,8 @@ +package org.example.map.objects.animal.behavior; + +public class AnimalBehaviorPredestination implements IAnimalBehavior { + @Override + public int nextGeneIndex(int current, int genesLength) { + return (current + 1) % genesLength; + } +} diff --git a/src/main/java/org/example/map/objects/animal/behavior/IAnimalBehavior.java b/src/main/java/org/example/map/objects/animal/behavior/IAnimalBehavior.java new file mode 100644 index 0000000..2b49903 --- /dev/null +++ b/src/main/java/org/example/map/objects/animal/behavior/IAnimalBehavior.java @@ -0,0 +1,8 @@ +package org.example.map.objects.animal.behavior; + +public interface IAnimalBehavior { + // Deal with animal move variants: + // Full predestination or Crazy. + // Returns the index of next gene. + int nextGeneIndex(int current, int genesLength); +} diff --git a/src/main/java/org/example/map/objects/animal/genes/GeneChoice.java b/src/main/java/org/example/map/objects/animal/genes/GeneChoice.java new file mode 100644 index 0000000..f2ee981 --- /dev/null +++ b/src/main/java/org/example/map/objects/animal/genes/GeneChoice.java @@ -0,0 +1,34 @@ +package org.example.map.objects.animal.genes; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class GeneChoice { + /* A helper class to store indexes of non-mutated genes, + so we don't try to mutate already mutated ones. + */ + + private final List indexes = new ArrayList<>(); + private final Random randomGenerator = new Random(); + private final int genesLength; + + public GeneChoice(int genesLength){ + this.genesLength = genesLength; + for(int i = 0; i< genesLength; i++){ + indexes.add(i); + } + } + + public Integer next(){ + Integer chosenIndex = indexes.get(randomGenerator.nextInt(indexes.size())); + indexes.remove(chosenIndex); + if (indexes.size() == 0){ + for(int i=0; i genotype; + protected int currentGene; + protected final IAnimalBehavior makeMove; + protected final int genesLength; + + protected final int differentGenes = 8; + + + private List randomGenotype(){ + List newGenotype = new ArrayList<>(); + for (int i = 0; i < genesLength; i++) { + newGenotype.add(randomGenerator.nextInt(differentGenes)); + } + return newGenotype; + } + + private int relativeFatherEnergy(int fatherEnergy, int motherEnergy){ + int energySum = fatherEnergy + motherEnergy; + float relativeEnergy = (float) fatherEnergy / (float) energySum * genesLength; + return Math.round(relativeEnergy); + } + + private int relativeMotherEnergy(int fatherEnergy, int motherEnergy) { + int relativeFatherEnergy = relativeFatherEnergy(fatherEnergy, motherEnergy); + return genesLength - relativeFatherEnergy; + } + + private List inheritedGenotype(Genes fatherGenes, + Genes motherGenes, + int relativeFatherEnergy, + int relativeMotherEnergy) { + boolean chooseSide = randomGenerator.nextInt(100) < 50; + List newGenotype = new ArrayList<>(); + List fatherGenotype = fatherGenes.getGenotype(); + List motherGenotype = motherGenes.getGenotype(); + + if (chooseSide) { + for (int x = 0; x < relativeFatherEnergy; x++) newGenotype.add(fatherGenotype.get(x)); + for (int y = relativeFatherEnergy; y < genesLength; y++) newGenotype.add(motherGenotype.get(y)); + } else { + for (int x = 0; x < relativeMotherEnergy; x++) newGenotype.add(motherGenotype.get(x)); + for (int y = relativeMotherEnergy; y < genesLength; y++) newGenotype.add(fatherGenotype.get(y)); + } + Collections.sort(newGenotype); + return newGenotype; + } + + + public Genes(IAnimalBehavior makeMove, + int genesLength) { + // Constructor creates genotypes for first animals ever placed on map + this.makeMove = makeMove; + this.genesLength = genesLength; + + genotype = randomGenotype(); + } + + + public Genes(IAnimalBehavior makeMove, + Genes fatherGenes, + Genes motherGenes, + int fatherEnergy, + int motherEnergy, + int minMutations, + int maxMutations) { + // Constructor creates genotypes for descendants of first animals. + this.genesLength = fatherGenes.getGenesLength(); + this.makeMove = makeMove; + + genotype = inheritedGenotype(fatherGenes, + motherGenes, + relativeFatherEnergy(fatherEnergy, motherEnergy), + relativeMotherEnergy(fatherEnergy, motherEnergy)); + mutate(minMutations, maxMutations); + } + + private int getGenesLength() { + return genesLength; + } + + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + this.genotype.forEach(stringBuilder::append); + return stringBuilder.toString(); + } + + public Integer getMoveDirection() { + int lastGene = currentGene; + currentGene = makeMove.nextGeneIndex(currentGene, genesLength); + return genotype.get(lastGene); + } + @Override + public int hashCode() { + return Objects.hash(genotype); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Genes genome = (Genes) o; + return genotype.equals(genome.genotype); + } + + public List getGenotype(){ + return new ArrayList<>(genotype); + } + + + protected void mutate(int minMutations, int maxMutations) { + // change a gene to a different one, repeat + int mutations = randomGenerator.nextInt(maxMutations - minMutations) + minMutations; + GeneChoice geneChoice = new GeneChoice(genesLength); + for(int i = 0; i < mutations; i++) { + int index = geneChoice.next(); + Integer oldGene = genotype.get(index); + Integer newGene = mutatedGene(oldGene); + genotype.set(index, newGene); + } + } + + protected abstract Integer mutatedGene(Integer gene); + + + +} diff --git a/src/main/java/org/example/map/objects/animal/genes/GenesFactory.java b/src/main/java/org/example/map/objects/animal/genes/GenesFactory.java new file mode 100644 index 0000000..cba5180 --- /dev/null +++ b/src/main/java/org/example/map/objects/animal/genes/GenesFactory.java @@ -0,0 +1,65 @@ +package org.example.map.objects.animal.genes; + +import org.example.map.objects.animal.Animal; +import org.example.map.objects.animal.behavior.AnimalBehaviorCrazy; +import org.example.map.objects.animal.behavior.AnimalBehaviorPredestination; +import org.example.map.objects.animal.behavior.IAnimalBehavior; + +public class GenesFactory { + private final boolean isFullRandom; + private final IAnimalBehavior makeMove; + private final int genesLength; + private final int animalMinMutations; + private final int animalMaxMutations; + + private IAnimalBehavior behavior(boolean isCrazyBehavior){ + if (isCrazyBehavior){ + return new AnimalBehaviorCrazy(); + } + else{ + return new AnimalBehaviorPredestination(); + } + } + + public GenesFactory(boolean isFullRandom, + boolean isCrazyBehavior, + int genesLength, + int animalMinMutations, + int animalMaxMutations) { + this.isFullRandom = isFullRandom; + this.makeMove = behavior(isCrazyBehavior); + this.genesLength = genesLength; + this.animalMinMutations = animalMinMutations; + this.animalMaxMutations = animalMaxMutations; + } + + public Genes createGenes() { + if (isFullRandom) { + return new GenesFullRandom(makeMove, genesLength); + } else { + return new GenesSlightCorrection(makeMove, genesLength); + } + } + + public Genes createGenes(Animal father, Animal mother) { + if (isFullRandom) { + return new GenesFullRandom( + makeMove, + father.getGenes(), + mother.getGenes(), + father.getEnergy(), + mother.getEnergy(), + animalMinMutations, + animalMaxMutations); + } else { + return new GenesSlightCorrection( + makeMove, + father.getGenes(), + mother.getGenes(), + father.getEnergy(), + mother.getEnergy(), + animalMinMutations, + animalMaxMutations); + } + } +} diff --git a/src/main/java/org/example/map/objects/animal/genes/GenesFullRandom.java b/src/main/java/org/example/map/objects/animal/genes/GenesFullRandom.java new file mode 100644 index 0000000..4382a5d --- /dev/null +++ b/src/main/java/org/example/map/objects/animal/genes/GenesFullRandom.java @@ -0,0 +1,25 @@ +package org.example.map.objects.animal.genes; + +import org.example.map.objects.animal.behavior.IAnimalBehavior; + +public class GenesFullRandom extends Genes{ + + public GenesFullRandom(IAnimalBehavior makeMove, int genesLength) { + super(makeMove, genesLength); + } + + public GenesFullRandom(IAnimalBehavior makeMove, + Genes fatherGenes, + Genes motherGenes, + int fatherEnergy, + int motherEnergy, + int minMutations, + int maxMutations) { + super(makeMove, fatherGenes, motherGenes, fatherEnergy, motherEnergy, minMutations, maxMutations); + } + + @Override + protected Integer mutatedGene(Integer gene){ + return randomGenerator.nextInt(differentGenes); + } +} diff --git a/src/main/java/org/example/map/objects/animal/genes/GenesSlightCorrection.java b/src/main/java/org/example/map/objects/animal/genes/GenesSlightCorrection.java new file mode 100644 index 0000000..33581f6 --- /dev/null +++ b/src/main/java/org/example/map/objects/animal/genes/GenesSlightCorrection.java @@ -0,0 +1,28 @@ +package org.example.map.objects.animal.genes; + +import org.example.map.objects.animal.behavior.IAnimalBehavior; + +public class GenesSlightCorrection extends Genes{ + + public GenesSlightCorrection(IAnimalBehavior makeMove, int genesLength) { + super(makeMove, genesLength); + } + + public GenesSlightCorrection(IAnimalBehavior makeMove, + Genes fatherGenes, + Genes motherGenes, + int fatherEnergy, + int motherEnergy, + int minMutations, + int maxMutations) { + super(makeMove, fatherGenes, motherGenes, fatherEnergy, motherEnergy, minMutations, maxMutations); + } + + @Override + protected Integer mutatedGene(Integer gene){ + if(randomGenerator.nextBoolean()){ + return (gene + 1) % differentGenes; + } + return (gene + differentGenes - 1) % differentGenes; + } +} diff --git a/src/main/java/org/example/map/objects/plants/CannotPlacePlantException.java b/src/main/java/org/example/map/objects/plants/CannotPlacePlantException.java new file mode 100644 index 0000000..ea377e9 --- /dev/null +++ b/src/main/java/org/example/map/objects/plants/CannotPlacePlantException.java @@ -0,0 +1,7 @@ +package org.example.map.objects.plants; + +public class CannotPlacePlantException extends Exception{ + public CannotPlacePlantException(String message){ + super(message); + } +} diff --git a/src/main/java/org/example/map/objects/plants/IPlantObserver.java b/src/main/java/org/example/map/objects/plants/IPlantObserver.java new file mode 100644 index 0000000..02ca3aa --- /dev/null +++ b/src/main/java/org/example/map/objects/plants/IPlantObserver.java @@ -0,0 +1,8 @@ +package org.example.map.objects.plants; + + +public interface IPlantObserver { + // It lets Plants inform other classes about changes of their states + void plantEaten(Plant plant); + void plantPlaced(Plant plant); +} diff --git a/src/main/java/org/example/map/objects/plants/IPlants.java b/src/main/java/org/example/map/objects/plants/IPlants.java new file mode 100644 index 0000000..849af87 --- /dev/null +++ b/src/main/java/org/example/map/objects/plants/IPlants.java @@ -0,0 +1,12 @@ +package org.example.map.objects.plants; + +import org.example.utils.Vector2d; + +public interface IPlants { +// void setWorldMap(WorldMap map); + + // Deals with plants seeding variants: + // Forested equators or toxic corpses. + // Returns position of new plant to grow on. + Vector2d grow(boolean preferred) throws CannotPlacePlantException; +} diff --git a/src/main/java/org/example/map/objects/plants/Plant.java b/src/main/java/org/example/map/objects/plants/Plant.java new file mode 100644 index 0000000..66e1c86 --- /dev/null +++ b/src/main/java/org/example/map/objects/plants/Plant.java @@ -0,0 +1,50 @@ +package org.example.map.objects.plants; + +import org.example.map.objects.animal.Animal; +import org.example.utils.Vector2d; + +import java.util.ArrayList; +import java.util.List; + +public class Plant { + private final Vector2d plantPosition; + + private final List observers = new ArrayList<>(); + + private final int energy; + + public void addObserver(IPlantObserver observer){ + observers.add(observer); + } + + public void removeObserver(IPlantObserver observer){ + observers.remove(observer); + } + + public Plant(Vector2d plantPosition, int energy) { + this.plantPosition = plantPosition; + this.energy = energy; + } + + public Vector2d getPosition() { + return this.plantPosition; + } + + public void beEaten(Animal animal){ + animal.addEnergy(energy); + for(IPlantObserver observer: observers){ + observer.plantEaten(this); + } + } + + public void place(){ + for(IPlantObserver observer: observers){ + observer.plantPlaced(this); + } + } + + @Override + public String toString() { + return "#"; + } +} diff --git a/src/main/java/org/example/map/objects/plants/PlantsEquator.java b/src/main/java/org/example/map/objects/plants/PlantsEquator.java new file mode 100644 index 0000000..08ccdba --- /dev/null +++ b/src/main/java/org/example/map/objects/plants/PlantsEquator.java @@ -0,0 +1,108 @@ +package org.example.map.objects.plants; + +import org.example.utils.Vector2d; + +import java.util.*; + +public class PlantsEquator implements IPlants, IPlantObserver{ + + private final List equator = new ArrayList<>(); + + private final List nonEquator = new ArrayList<>(); + + private final int mapWidth, mapHeight; + + private int equatorStartRow, equatorEndRow; + + private final Random generator = new Random(); + + private void addRowsPositions(List positions, int startRow, int endRow){ + for(int y = startRow; y <= endRow; y++){ + for(int x = 0; x < mapWidth; x++){ + positions.add(new Vector2d(x ,y)); + } + } + } + + private void calcEquatorBorders(){ + int equatorRows = mapHeight / 5; + int rowsLeft = equatorRows; + if(mapHeight % 2 == 1){ + int middle = mapHeight / 2; + equatorStartRow = middle - equatorRows / 2; + rowsLeft -= equatorRows / 2 + 1; + equatorEndRow = middle + rowsLeft; + } + else{ + equatorEndRow = mapHeight / 2 + equatorRows / 2 - 1; + rowsLeft -= equatorRows / 2; + equatorStartRow = equatorEndRow - rowsLeft; + } + } + + private void prepareAvailablePositions(){ + addRowsPositions(equator, equatorStartRow, equatorEndRow); + addRowsPositions(nonEquator, 0, equatorStartRow - 1); + addRowsPositions(nonEquator, equatorEndRow + 1, mapHeight - 1); + } + + public PlantsEquator(int mapWidth, int mapHeight){ + this.mapWidth = mapWidth; + this.mapHeight = mapHeight; + calcEquatorBorders(); + prepareAvailablePositions(); + } + + private boolean equatorAccessible(){ + return equator.size() > 0; + } + + private boolean nonEquatorAccessible(){ + return nonEquator.size() > 0; + } + + private Vector2d randomPosition(List positions){ + return positions.get(generator.nextInt(positions.size())); + } + + + private synchronized Vector2d positionInsideEquator() throws CannotPlacePlantException{ + if(!equatorAccessible()){ + throw new CannotPlacePlantException("Can't place plant on preferred position"); + } + return randomPosition(equator); + } + + private synchronized Vector2d positionOutsideEquator() throws CannotPlacePlantException{ + if (!nonEquatorAccessible()) { + throw new CannotPlacePlantException("Can't place plant on non-preferred position"); + } + return randomPosition(nonEquator); + } + + @Override + public Vector2d grow(boolean preferred) throws CannotPlacePlantException{ + if(preferred){ + return positionInsideEquator(); + } + return positionOutsideEquator(); + } + + @Override + public void plantPlaced(Plant plant){ + Vector2d position = plant.getPosition(); + equator.remove(position); + nonEquator.remove(position); + } + + @Override + public void plantEaten(Plant plant){ + Vector2d position = plant.getPosition(); + if(position.y < equatorStartRow || position.y > equatorEndRow){ + nonEquator.add(position); + } + else{ + equator.add(position); + } + } +} diff --git a/src/main/java/org/example/map/objects/plants/PlantsToxicCorpses.java b/src/main/java/org/example/map/objects/plants/PlantsToxicCorpses.java new file mode 100644 index 0000000..5a4de6a --- /dev/null +++ b/src/main/java/org/example/map/objects/plants/PlantsToxicCorpses.java @@ -0,0 +1,103 @@ +package org.example.map.objects.plants; + +import org.example.map.objects.animal.Animal; +import org.example.map.objects.animal.IAnimalObserver; +import org.example.utils.Vector2d; + +import java.util.*; + +public class PlantsToxicCorpses implements IPlants, IAnimalObserver, IPlantObserver { + + private final int mapWidth, mapHeight; + + private final Map positionsUsed = new HashMap<>(); + + private final Map positionsNotUsed = new HashMap<>(); + private final Random generator = new Random(); + + private void positionsToChoose(){ + for(int y=0; y suitablePositions = positionsNotUsed.entrySet() + .stream() + .filter(entry-> Objects.equals(entry.getValue(), minDeaths)) + .map(Map.Entry::getKey) + .toList(); + return suitablePositions.get(generator.nextInt(suitablePositions.size())); + } + + private synchronized Vector2d nonPreferredPosition(){ + Integer minDeaths = Collections.min(positionsNotUsed.values()); + List suitablePositions = positionsNotUsed.entrySet() + .stream() + .filter(entry-> !Objects.equals(entry.getValue(), minDeaths)) + .map(Map.Entry::getKey) + .toList(); + return suitablePositions.get(generator.nextInt(suitablePositions.size())); + } + + public Vector2d grow(boolean preferred) throws CannotPlacePlantException{ + try { + if (preferred) { + return preferredPosition(); + } + return nonPreferredPosition(); + } + catch (NoSuchElementException | IllegalArgumentException ex){ + throw new CannotPlacePlantException("Can't place any new plants"); + } + } + +} diff --git a/src/main/java/org/example/map/options/IEdge.java b/src/main/java/org/example/map/options/IEdge.java new file mode 100644 index 0000000..e907a5a --- /dev/null +++ b/src/main/java/org/example/map/options/IEdge.java @@ -0,0 +1,10 @@ +package org.example.map.options; + +import org.example.map.objects.animal.Animal; +import org.example.utils.Vector2d; + +public interface IEdge { + // Deal with map variants: Earth, HellPortal. + // Return animal position after move. + Vector2d handleMove(Animal animal); +} diff --git a/src/main/java/org/example/map/options/IMapElement.java b/src/main/java/org/example/map/options/IMapElement.java new file mode 100644 index 0000000..b01d515 --- /dev/null +++ b/src/main/java/org/example/map/options/IMapElement.java @@ -0,0 +1,9 @@ +package org.example.map.options; + +import org.example.utils.Vector2d; + +public interface IMapElement { + Vector2d getPosition(); + String toString(); + String getSourceAddress(); +} diff --git a/src/main/java/org/example/utils/GetFromFile.java b/src/main/java/org/example/utils/GetFromFile.java new file mode 100644 index 0000000..3ab697b --- /dev/null +++ b/src/main/java/org/example/utils/GetFromFile.java @@ -0,0 +1,18 @@ +package org.example.utils; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.File; +import java.io.IOException; + +public class GetFromFile { + private final ObjectMapper mapper = new ObjectMapper(); + private Preferences preferences; + + public Preferences getPreferencesFromFile(String filename) throws IOException { + preferences = mapper.readValue(new File(filename), Preferences.class); + return preferences; + } + + +} diff --git a/src/main/java/org/example/utils/MapDirection.java b/src/main/java/org/example/utils/MapDirection.java new file mode 100644 index 0000000..2dd248c --- /dev/null +++ b/src/main/java/org/example/utils/MapDirection.java @@ -0,0 +1,72 @@ +package org.example.utils; + +import java.util.Random; + +public enum MapDirection { + NORTH, + SOUTH, + EAST, + WEST, + NORTHEAST, + NORTHWEST, + SOUTHEAST, + SOUTHWEST; + + public String toString() { + return switch(this) { + case NORTH -> "north"; + case SOUTH -> "south"; + case EAST -> "east"; + case WEST -> "west"; + case NORTHEAST -> "north-east"; + case NORTHWEST -> "north-west"; + case SOUTHEAST -> "south-east"; + case SOUTHWEST -> "south-west"; + }; + } + + public Vector2d toUnitVector(){ + return switch (this) { + case NORTH -> new Vector2d(0,1); + case SOUTH -> new Vector2d(0,-1); + case EAST -> new Vector2d(1,0); + case WEST -> new Vector2d(-1,0); + case NORTHEAST -> new Vector2d(1,1); + case NORTHWEST -> new Vector2d(-1,1); + case SOUTHEAST -> new Vector2d(1,-1); + case SOUTHWEST -> new Vector2d(-1,-1); + }; + } + + public static MapDirection fromInt(int value){ + return switch (value) { + case 0 -> MapDirection.NORTH; + case 1 -> MapDirection.NORTHEAST; + case 2 -> MapDirection.EAST; + case 3 -> MapDirection.SOUTHEAST; + case 4 -> MapDirection.SOUTH; + case 5 -> MapDirection.SOUTHWEST; + case 6 -> MapDirection.WEST; + case 7 -> MapDirection.NORTHWEST; + default -> throw new IllegalStateException("Unexpected value: " + value); + }; + } + + public MapDirection randomDirection(){ + Random random = new Random(); + return MapDirection.fromInt(random.nextInt(8)); + } + + public MapDirection opposite(){ + return switch (this) { + case NORTH -> SOUTH; + case SOUTH -> NORTH; + case EAST -> WEST; + case WEST -> EAST; + case NORTHWEST -> SOUTHEAST; + case NORTHEAST -> SOUTHWEST; + case SOUTHWEST -> NORTHEAST; + case SOUTHEAST -> NORTHWEST; + }; + } +} diff --git a/src/main/java/org/example/utils/Preferences.java b/src/main/java/org/example/utils/Preferences.java new file mode 100644 index 0000000..91a8504 --- /dev/null +++ b/src/main/java/org/example/utils/Preferences.java @@ -0,0 +1,71 @@ +package org.example.utils; + + +import org.example.map.EdgeEarth; +import org.example.map.EdgeHellPortal; +import org.example.map.WorldMap; +import org.example.map.objects.animal.genes.GenesFactory; +import org.example.map.objects.plants.IPlants; +import org.example.map.objects.plants.PlantsEquator; +import org.example.map.objects.plants.PlantsToxicCorpses; +import org.example.map.options.IEdge; + +public record Preferences( + // Map parameters + int width, + int height, + String iEdge, + + // Plants parameters + int numberOfPlantsAtStart, + int plantEnergy, + int plantsSeededEachDay, + String iPlants, + + // Animals parameters + int numberOfAnimalsAtStart, + int animalEnergy, + int animalEnergyBreedingThreshold, + int animalBreedingCost, + int animalMinMutations, + int animalMaxMutations, + int genesLength, + boolean isCrazyBehavior, + boolean isFullRandom +) { + public WorldMap toWorldMap() { + // TODO: add other gene parameters to Factory. + GenesFactory genesFactory = new GenesFactory(isFullRandom, + isCrazyBehavior, + genesLength, + animalMinMutations, + animalMaxMutations); + + IPlants iPlantsImplementation = switch(iPlants){ + case "equator" -> new PlantsEquator(width, height); + case "toxic" -> new PlantsToxicCorpses(width, height); + default -> throw new UnsupportedOperationException("Plants' variant " + iPlants + " not implemented"); + }; + + IEdge iEdgeImplementation = switch(iEdge){ + case "earth" -> new EdgeEarth(width, height); + case "portal" -> new EdgeHellPortal(new Vector2d(0, 0), new Vector2d(width-1, height-1)); + default -> throw new UnsupportedOperationException("Edges' variant " + iEdge + " not implemented"); + }; + + return new WorldMap(width, + height, + iEdgeImplementation, + numberOfPlantsAtStart, + plantEnergy, + plantsSeededEachDay, + iPlantsImplementation, + numberOfAnimalsAtStart, + animalEnergy, + animalEnergyBreedingThreshold, + animalBreedingCost, + genesFactory + ); + } + +} diff --git a/src/main/java/org/example/utils/Vector2d.java b/src/main/java/org/example/utils/Vector2d.java new file mode 100644 index 0000000..4e62c63 --- /dev/null +++ b/src/main/java/org/example/utils/Vector2d.java @@ -0,0 +1,63 @@ +package org.example.utils; + +import java.util.Objects; +import java.util.Random; + +public class Vector2d { + + public int x; + public int y; + + public Vector2d(int x, int y) { + this.x = x; + this.y = y; + } + + public String toString() { + return "(" + this.x + "," + this.y + ")"; + } + + public boolean precedes(Vector2d other) { + return this.x <= other.x && this.y <= other.y; + } + + public boolean follows(Vector2d other) { + return this.x >= other.x & this.y >= other.y; + } + + public Vector2d upperRight(Vector2d other) { + return new Vector2d(Math.max(this.x, other.x), Math.max(this.y, other.y)); + } + + public Vector2d lowerLeft(Vector2d other) { + return new Vector2d(Math.min(this.x, other.x), Math.min(this.y, other.y)); + } + + public Vector2d add(Vector2d other) { + return new Vector2d(this.x + other.x, this.y + other.y); + } + + public Vector2d subtract(Vector2d other) { + return new Vector2d(this.x - other.x, this.y - other.y); + } + + public Vector2d opposite() { + return new Vector2d(-this.x, -this.y); + } + + @Override + public boolean equals(Object other) { + if (this == other) + return true; + if (other == null || getClass() != other.getClass()) + return false; + Vector2d that = (Vector2d) other; + + return this.x == that.x && this.y == that.y; + } + + @Override + public int hashCode() { + return Objects.hash(this.x, this.y); + } +} \ No newline at end of file diff --git a/src/main/resources/conf1.json b/src/main/resources/conf1.json new file mode 100644 index 0000000..80ce2e8 --- /dev/null +++ b/src/main/resources/conf1.json @@ -0,0 +1,18 @@ +{ + "width": 1, + "height": 1, + "iEdge": "earth", + "numberOfPlantsAtStart": 10, + "plantEnergy": 0, + "plantsSeededEachDay": 2, + "iPlants": "toxic", + "numberOfAnimalsAtStart": 1, + "animalEnergy": 2000000, + "animalEnergyBreedingThreshold": 50, + "animalBreedingCost": 50, + "animalMinMutations": 2, + "animalMaxMutations": 5, + "genesLength": 10, + "isCrazyBehavior": false, + "isFullRandom": true +} \ No newline at end of file diff --git a/src/main/resources/conf2.json b/src/main/resources/conf2.json new file mode 100644 index 0000000..ee3f088 --- /dev/null +++ b/src/main/resources/conf2.json @@ -0,0 +1,18 @@ +{ + "width": 10, + "height": 16, + "iEdge": "earth", + "numberOfPlantsAtStart": 20, + "plantEnergy": 50, + "plantsSeededEachDay": 6, + "iPlants": "equator", + "numberOfAnimalsAtStart": 6, + "animalEnergy": 100, + "animalEnergyBreedingThreshold": 50, + "animalBreedingCost": 50, + "animalMinMutations": 2, + "animalMaxMutations": 5, + "genesLength": 32, + "isCrazyBehavior": true, + "isFullRandom": true +} \ No newline at end of file diff --git a/src/main/resources/conf3.json b/src/main/resources/conf3.json new file mode 100644 index 0000000..9996460 --- /dev/null +++ b/src/main/resources/conf3.json @@ -0,0 +1,18 @@ +{ + "width": 10, + "height": 10, + "iEdge": "earth", + "numberOfPlantsAtStart": 20, + "plantEnergy": 50, + "plantsSeededEachDay": 6, + "iPlants": "equator", + "numberOfAnimalsAtStart": 11, + "animalEnergy": 100, + "animalEnergyBreedingThreshold": 50, + "animalBreedingCost": 50, + "animalMinMutations": 2, + "animalMaxMutations": 5, + "genesLength": 1, + "isCrazyBehavior": false, + "isFullRandom": false +} \ No newline at end of file diff --git a/src/main/resources/strawberry_resized_32.png b/src/main/resources/strawberry_resized_32.png new file mode 100644 index 0000000..b4ac8b2 Binary files /dev/null and b/src/main/resources/strawberry_resized_32.png differ