From 0b364f9b4788ea588e50056998d39e86d089d3df Mon Sep 17 00:00:00 2001 From: dlesignac <damien.lesignac@dreev.com> Date: Sun, 24 Apr 2022 12:32:54 +0200 Subject: [PATCH] add base structure --- .gitignore | 4 + README.md | 91 -------------- pom.xml | 30 +++++ .../lotka_volterra/AppConfiguration.java | 50 ++++++++ .../fr/takima/lotka_volterra/EntryPoint.java | 93 ++++++++++++++ .../common/StringIdGenerator.java | 7 ++ .../lotka_volterra/common/measure/Angle.java | 15 +++ .../common/measure/Distance.java | 23 ++++ .../common/measure/Probability.java | 24 ++++ .../lotka_volterra/common/measure/Vector.java | 47 +++++++ .../common/measure/VectorComponent.java | 11 ++ .../common/value/BigDecimalValue.java | 41 ++++++ .../lotka_volterra/common/value/IntValue.java | 40 ++++++ .../common/value/PositiveInt.java | 19 +++ .../common/value/StringValue.java | 40 ++++++ .../lotka_volterra/mating/ChildMaking.java | 9 ++ .../takima/lotka_volterra/mating/Couple.java | 5 + .../DefaultPredatorMatingConfigurer.java | 38 ++++++ .../mating/DefaultPreyMatingConfigurer.java | 38 ++++++ .../takima/lotka_volterra/mating/Mating.java | 80 ++++++++++++ .../mating/MatingConfigurer.java | 16 +++ .../lotka_volterra/mating/MatingEntity.java | 7 ++ .../mating/PredatorMatingProperties.java | 27 ++++ .../mating/PreyMatingProperties.java | 27 ++++ .../moving/DefaultMapConfigurer.java | 40 ++++++ .../DefaultPredatorMovingConfigurer.java | 29 +++++ .../moving/DefaultPreyMovingConfigurer.java | 29 +++++ .../lotka_volterra/moving/MapConfigurer.java | 13 ++ .../lotka_volterra/moving/MapProperties.java | 45 +++++++ .../takima/lotka_volterra/moving/Moving.java | 53 ++++++++ .../moving/MovingConfigurer.java | 13 ++ .../lotka_volterra/moving/MovingEntity.java | 17 +++ .../lotka_volterra/moving/Position.java | 30 +++++ .../moving/PositionComponent.java | 38 ++++++ .../lotka_volterra/moving/PositionUpdate.java | 7 ++ .../moving/PredatorMovingProperties.java | 18 +++ .../moving/PreyMovingProperties.java | 18 +++ .../predation/DefaultPredationConfigurer.java | 53 ++++++++ .../lotka_volterra/predation/Predation.java | 82 ++++++++++++ .../predation/PredationConfigurer.java | 21 ++++ .../predation/PredationProperties.java | 45 +++++++ .../predation/PredationResult.java | 16 +++ .../predator/DefaultPredatorIdGenerator.java | 20 +++ .../lotka_volterra/predator/Predator.java | 46 +++++++ .../predator/PredatorChildMaking.java | 32 +++++ .../lotka_volterra/predator/PredatorId.java | 18 +++ .../predator/PredatorIdGenerator.java | 7 ++ .../predator/PredatorPositionUpdate.java | 15 +++ .../lotka_volterra/predator/Satiety.java | 40 ++++++ .../prey/DefaultPreyIdGenerator.java | 20 +++ .../fr/takima/lotka_volterra/prey/Prey.java | 33 +++++ .../lotka_volterra/prey/PreyChildMaking.java | 35 ++++++ .../fr/takima/lotka_volterra/prey/PreyId.java | 18 +++ .../lotka_volterra/prey/PreyIdGenerator.java | 7 ++ .../prey/PreyPositionUpdate.java | 15 +++ .../DefaultSimulationConfigurer.java | 106 ++++++++++++++++ .../simulation/Populations.java | 21 ++++ .../lotka_volterra/simulation/Simulation.java | 31 +++++ .../simulation/SimulationConfigurer.java | 35 ++++++ .../simulation/SimulationProperties.java | 36 ++++++ .../lotka_volterra/simulation/step/Step.java | 37 ++++++ .../lotka_volterra/simulation/step/Steps.java | 53 ++++++++ .../validator/GreaterThanValidator.java | 20 +++ .../validator/LowerThanValidator.java | 20 +++ .../validator/NotNullValidator.java | 16 +++ .../lotka_volterra/validator/Validator.java | 18 +++ src/main/resources/application.yml | 26 ++++ .../moving/MovingEntityTest.java | 52 ++++++++ .../predator/PredatorChildMakingTest.java | 51 ++++++++ .../lotka_volterra/predator/PredatorTest.java | 117 ++++++++++++++++++ .../prey/PreyChildMakingTest.java | 46 +++++++ .../takima/lotka_volterra/prey/PreyTest.java | 64 ++++++++++ 72 files changed, 2313 insertions(+), 91 deletions(-) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/fr/takima/lotka_volterra/AppConfiguration.java create mode 100644 src/main/java/fr/takima/lotka_volterra/EntryPoint.java create mode 100644 src/main/java/fr/takima/lotka_volterra/common/StringIdGenerator.java create mode 100644 src/main/java/fr/takima/lotka_volterra/common/measure/Angle.java create mode 100644 src/main/java/fr/takima/lotka_volterra/common/measure/Distance.java create mode 100644 src/main/java/fr/takima/lotka_volterra/common/measure/Probability.java create mode 100644 src/main/java/fr/takima/lotka_volterra/common/measure/Vector.java create mode 100644 src/main/java/fr/takima/lotka_volterra/common/measure/VectorComponent.java create mode 100644 src/main/java/fr/takima/lotka_volterra/common/value/BigDecimalValue.java create mode 100644 src/main/java/fr/takima/lotka_volterra/common/value/IntValue.java create mode 100644 src/main/java/fr/takima/lotka_volterra/common/value/PositiveInt.java create mode 100644 src/main/java/fr/takima/lotka_volterra/common/value/StringValue.java create mode 100644 src/main/java/fr/takima/lotka_volterra/mating/ChildMaking.java create mode 100644 src/main/java/fr/takima/lotka_volterra/mating/Couple.java create mode 100644 src/main/java/fr/takima/lotka_volterra/mating/DefaultPredatorMatingConfigurer.java create mode 100644 src/main/java/fr/takima/lotka_volterra/mating/DefaultPreyMatingConfigurer.java create mode 100644 src/main/java/fr/takima/lotka_volterra/mating/Mating.java create mode 100644 src/main/java/fr/takima/lotka_volterra/mating/MatingConfigurer.java create mode 100644 src/main/java/fr/takima/lotka_volterra/mating/MatingEntity.java create mode 100644 src/main/java/fr/takima/lotka_volterra/mating/PredatorMatingProperties.java create mode 100644 src/main/java/fr/takima/lotka_volterra/mating/PreyMatingProperties.java create mode 100644 src/main/java/fr/takima/lotka_volterra/moving/DefaultMapConfigurer.java create mode 100644 src/main/java/fr/takima/lotka_volterra/moving/DefaultPredatorMovingConfigurer.java create mode 100644 src/main/java/fr/takima/lotka_volterra/moving/DefaultPreyMovingConfigurer.java create mode 100644 src/main/java/fr/takima/lotka_volterra/moving/MapConfigurer.java create mode 100644 src/main/java/fr/takima/lotka_volterra/moving/MapProperties.java create mode 100644 src/main/java/fr/takima/lotka_volterra/moving/Moving.java create mode 100644 src/main/java/fr/takima/lotka_volterra/moving/MovingConfigurer.java create mode 100644 src/main/java/fr/takima/lotka_volterra/moving/MovingEntity.java create mode 100644 src/main/java/fr/takima/lotka_volterra/moving/Position.java create mode 100644 src/main/java/fr/takima/lotka_volterra/moving/PositionComponent.java create mode 100644 src/main/java/fr/takima/lotka_volterra/moving/PositionUpdate.java create mode 100644 src/main/java/fr/takima/lotka_volterra/moving/PredatorMovingProperties.java create mode 100644 src/main/java/fr/takima/lotka_volterra/moving/PreyMovingProperties.java create mode 100644 src/main/java/fr/takima/lotka_volterra/predation/DefaultPredationConfigurer.java create mode 100644 src/main/java/fr/takima/lotka_volterra/predation/Predation.java create mode 100644 src/main/java/fr/takima/lotka_volterra/predation/PredationConfigurer.java create mode 100644 src/main/java/fr/takima/lotka_volterra/predation/PredationProperties.java create mode 100644 src/main/java/fr/takima/lotka_volterra/predation/PredationResult.java create mode 100644 src/main/java/fr/takima/lotka_volterra/predator/DefaultPredatorIdGenerator.java create mode 100644 src/main/java/fr/takima/lotka_volterra/predator/Predator.java create mode 100644 src/main/java/fr/takima/lotka_volterra/predator/PredatorChildMaking.java create mode 100644 src/main/java/fr/takima/lotka_volterra/predator/PredatorId.java create mode 100644 src/main/java/fr/takima/lotka_volterra/predator/PredatorIdGenerator.java create mode 100644 src/main/java/fr/takima/lotka_volterra/predator/PredatorPositionUpdate.java create mode 100644 src/main/java/fr/takima/lotka_volterra/predator/Satiety.java create mode 100644 src/main/java/fr/takima/lotka_volterra/prey/DefaultPreyIdGenerator.java create mode 100644 src/main/java/fr/takima/lotka_volterra/prey/Prey.java create mode 100644 src/main/java/fr/takima/lotka_volterra/prey/PreyChildMaking.java create mode 100644 src/main/java/fr/takima/lotka_volterra/prey/PreyId.java create mode 100644 src/main/java/fr/takima/lotka_volterra/prey/PreyIdGenerator.java create mode 100644 src/main/java/fr/takima/lotka_volterra/prey/PreyPositionUpdate.java create mode 100644 src/main/java/fr/takima/lotka_volterra/simulation/DefaultSimulationConfigurer.java create mode 100644 src/main/java/fr/takima/lotka_volterra/simulation/Populations.java create mode 100644 src/main/java/fr/takima/lotka_volterra/simulation/Simulation.java create mode 100644 src/main/java/fr/takima/lotka_volterra/simulation/SimulationConfigurer.java create mode 100644 src/main/java/fr/takima/lotka_volterra/simulation/SimulationProperties.java create mode 100644 src/main/java/fr/takima/lotka_volterra/simulation/step/Step.java create mode 100644 src/main/java/fr/takima/lotka_volterra/simulation/step/Steps.java create mode 100644 src/main/java/fr/takima/lotka_volterra/validator/GreaterThanValidator.java create mode 100644 src/main/java/fr/takima/lotka_volterra/validator/LowerThanValidator.java create mode 100644 src/main/java/fr/takima/lotka_volterra/validator/NotNullValidator.java create mode 100644 src/main/java/fr/takima/lotka_volterra/validator/Validator.java create mode 100644 src/main/resources/application.yml create mode 100644 src/test/java/fr/takima/lotka_volterra/moving/MovingEntityTest.java create mode 100644 src/test/java/fr/takima/lotka_volterra/predator/PredatorChildMakingTest.java create mode 100644 src/test/java/fr/takima/lotka_volterra/predator/PredatorTest.java create mode 100644 src/test/java/fr/takima/lotka_volterra/prey/PreyChildMakingTest.java create mode 100644 src/test/java/fr/takima/lotka_volterra/prey/PreyTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb922b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +*.iml + +**/target diff --git a/README.md b/README.md index 04b9e78..aa1ad38 100644 --- a/README.md +++ b/README.md @@ -1,92 +1 @@ # lotka-volterra - - - -## Getting started - -To make it easy for you to get started with GitLab, here's a list of recommended next steps. - -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! - -## Add your files - -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: - -``` -cd existing_repo -git remote add origin https://gitlab.takima.io/dlesignac/lotka-volterra.git -git branch -M main -git push -uf origin main -``` - -## Integrate with your tools - -- [ ] [Set up project integrations](https://gitlab.takima.io/dlesignac/lotka-volterra/-/settings/integrations) - -## Collaborate with your team - -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) - -## Test and Deploy - -Use the built-in continuous integration in GitLab. - -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) - -*** - -# Editing this README - -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. - -## Suggestions for a good README -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. - -## Name -Choose a self-explaining name for your project. - -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. - -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. - -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. - -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. - -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. - -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. - -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. - -## Contributing -State if you are open to contributions and what your requirements are for accepting them. - -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. - -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. - -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. - -## License -For open source projects, say how it is licensed. - -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..30be752 --- /dev/null +++ b/pom.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>fr.takima</groupId> + <artifactId>lotka-volterra</artifactId> + <version>1.0.0-SNAPSHOT</version> + + <properties> + <maven.compiler.target>17</maven.compiler.target> + <maven.compiler.source>17</maven.compiler.source> + </properties> + + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter</artifactId> + <version>2.6.6</version> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <version>2.6.6</version> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/src/main/java/fr/takima/lotka_volterra/AppConfiguration.java b/src/main/java/fr/takima/lotka_volterra/AppConfiguration.java new file mode 100644 index 0000000..474d0ec --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/AppConfiguration.java @@ -0,0 +1,50 @@ +package fr.takima.lotka_volterra; + +import fr.takima.lotka_volterra.mating.PredatorMatingProperties; +import fr.takima.lotka_volterra.mating.PreyMatingProperties; +import fr.takima.lotka_volterra.moving.MapProperties; +import fr.takima.lotka_volterra.moving.PredatorMovingProperties; +import fr.takima.lotka_volterra.moving.PreyMovingProperties; +import fr.takima.lotka_volterra.predation.PredationProperties; +import fr.takima.lotka_volterra.predator.DefaultPredatorIdGenerator; +import fr.takima.lotka_volterra.predator.PredatorIdGenerator; +import fr.takima.lotka_volterra.prey.DefaultPreyIdGenerator; +import fr.takima.lotka_volterra.prey.PreyIdGenerator; +import fr.takima.lotka_volterra.simulation.SimulationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Random; + +@Configuration +@EnableConfigurationProperties({ + MapProperties.class, + PredationProperties.class, + PredatorMatingProperties.class, + PredatorMovingProperties.class, + PreyMatingProperties.class, + PreyMovingProperties.class, + SimulationProperties.class +}) +public class AppConfiguration { + + private static int PREY_ID = 0; + private static int PREDATOR_ID = 0; + + @Bean + public PreyIdGenerator preyIdGenerator() { + return new DefaultPreyIdGenerator(() -> String.valueOf(PREY_ID++)); + } + + @Bean + public PredatorIdGenerator predatorIdGenerator() { + return new DefaultPredatorIdGenerator(() -> String.valueOf(PREDATOR_ID++)); + } + + @Bean + public Random random() { + return new Random(); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/EntryPoint.java b/src/main/java/fr/takima/lotka_volterra/EntryPoint.java new file mode 100644 index 0000000..32f8601 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/EntryPoint.java @@ -0,0 +1,93 @@ +package fr.takima.lotka_volterra; + +import fr.takima.lotka_volterra.common.value.PositiveInt; +import fr.takima.lotka_volterra.moving.MapConfigurer; +import fr.takima.lotka_volterra.moving.Position; +import fr.takima.lotka_volterra.moving.PositionComponent; +import fr.takima.lotka_volterra.predator.Predator; +import fr.takima.lotka_volterra.predator.PredatorIdGenerator; +import fr.takima.lotka_volterra.predator.Satiety; +import fr.takima.lotka_volterra.prey.Prey; +import fr.takima.lotka_volterra.prey.PreyIdGenerator; +import fr.takima.lotka_volterra.simulation.Simulation; +import fr.takima.lotka_volterra.simulation.SimulationConfigurer; +import fr.takima.lotka_volterra.simulation.step.Step; +import fr.takima.lotka_volterra.simulation.step.Steps; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import java.util.HashSet; +import java.util.Set; + +@SpringBootApplication +public class EntryPoint implements CommandLineRunner { + + private static final Logger log = LoggerFactory.getLogger(EntryPoint.class); + + public static void main(String[] args) { + SpringApplication.run(EntryPoint.class, args); + } + + private final SimulationConfigurer simulationConfigurer; + private final PreyIdGenerator preyIdGenerator; + private final PredatorIdGenerator predatorIdGenerator; + + public EntryPoint(SimulationConfigurer simulationConfigurer, + PreyIdGenerator preyIdGenerator, + PredatorIdGenerator predatorIdGenerator) { + this.simulationConfigurer = simulationConfigurer; + this.preyIdGenerator = preyIdGenerator; + this.predatorIdGenerator = predatorIdGenerator; + } + + @Override + public void run(String... args) throws Exception { + var preys = getInitialPreys(simulationConfigurer.initialPreyCount(), simulationConfigurer.map()); + var predators = getInitialPredators(simulationConfigurer.initialPredatorCount(), simulationConfigurer.map()); + var step = new Step(new Step.Number(0), preys, predators); + var simulation = new Simulation(simulationConfigurer, new Steps(Set.of(step))); + run(simulation, simulationConfigurer.stepCount()); + } + + private void run(Simulation simulation, PositiveInt stepCount) { + for (var i = 0; i < stepCount.value(); i++) { + simulation = simulation.runNextStep(); + } + var populations = simulation.steps().last().populations(); + log.info("preys: {}, predators: {}", populations.preys().value(), populations.predators().value()); + } + + private Set<Prey> getInitialPreys(PositiveInt preyCount, MapConfigurer configurer) { + var preys = new HashSet<Prey>(); + + for (var i = 0; i < preyCount.value(); i++) { + var preyId = preyIdGenerator.generate(); + var x = new PositionComponent(0, configurer.minX(), configurer.maxX()); + var y = new PositionComponent(0, configurer.minY(), configurer.maxY()); + var position = new Position(x, y); + var prey = new Prey(preyId, position); + preys.add(prey); + } + + return preys; + } + + private Set<Predator> getInitialPredators(PositiveInt predatorCount, MapConfigurer configurer) { + var predators = new HashSet<Predator>(); + + for (var i = 0; i < predatorCount.value(); i++) { + var predatorId = predatorIdGenerator.generate(); + var x = new PositionComponent(0, configurer.minX(), configurer.maxX()); + var y = new PositionComponent(0, configurer.minY(), configurer.maxY()); + var position = new Position(x, y); + var predator = new Predator(predatorId, Satiety.MAX, position); + predators.add(predator); + } + + return predators; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/common/StringIdGenerator.java b/src/main/java/fr/takima/lotka_volterra/common/StringIdGenerator.java new file mode 100644 index 0000000..8711386 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/common/StringIdGenerator.java @@ -0,0 +1,7 @@ +package fr.takima.lotka_volterra.common; + +public interface StringIdGenerator { + + String generate(); + +} diff --git a/src/main/java/fr/takima/lotka_volterra/common/measure/Angle.java b/src/main/java/fr/takima/lotka_volterra/common/measure/Angle.java new file mode 100644 index 0000000..78e16b2 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/common/measure/Angle.java @@ -0,0 +1,15 @@ +package fr.takima.lotka_volterra.common.measure; + +import fr.takima.lotka_volterra.common.value.PositiveInt; +import fr.takima.lotka_volterra.validator.LowerThanValidator; + +public class Angle extends PositiveInt { + + public static final int MAX_VALUE = 360; + + public Angle(Integer value) { + super(value); + new LowerThanValidator<>("value", value, MAX_VALUE).validate(); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/common/measure/Distance.java b/src/main/java/fr/takima/lotka_volterra/common/measure/Distance.java new file mode 100644 index 0000000..11da308 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/common/measure/Distance.java @@ -0,0 +1,23 @@ +package fr.takima.lotka_volterra.common.measure; + +import fr.takima.lotka_volterra.common.value.BigDecimalValue; +import fr.takima.lotka_volterra.validator.GreaterThanValidator; + +import java.math.BigDecimal; + +public final class Distance extends BigDecimalValue { + + public Distance(BigDecimal value) { + super(value); + new GreaterThanValidator<>("value", value, BigDecimal.ZERO).validate(); + } + + public Distance(Integer value) { + this(new BigDecimal(value)); + } + + public boolean isLessThan(Distance other) { + return value.compareTo(other.value) <= 0; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/common/measure/Probability.java b/src/main/java/fr/takima/lotka_volterra/common/measure/Probability.java new file mode 100644 index 0000000..21b7458 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/common/measure/Probability.java @@ -0,0 +1,24 @@ +package fr.takima.lotka_volterra.common.measure; + +import fr.takima.lotka_volterra.common.value.BigDecimalValue; +import fr.takima.lotka_volterra.validator.GreaterThanValidator; +import fr.takima.lotka_volterra.validator.LowerThanValidator; + +import java.math.BigDecimal; + +public final class Probability extends BigDecimalValue { + + public Probability(BigDecimal value) { + super(value); + new GreaterThanValidator<>("value", value, BigDecimal.ZERO).validate(); + new LowerThanValidator<>("value", value, BigDecimal.ONE).validate(); + } + + @Override + public String toString() { + return "Probability{" + + "value=" + value + + '}'; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/common/measure/Vector.java b/src/main/java/fr/takima/lotka_volterra/common/measure/Vector.java new file mode 100644 index 0000000..614a577 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/common/measure/Vector.java @@ -0,0 +1,47 @@ +package fr.takima.lotka_volterra.common.measure; + +import fr.takima.lotka_volterra.validator.NotNullValidator; + +import java.math.BigDecimal; +import java.util.function.Function; + +public class Vector { + + private static VectorComponent getVectorComponent(Angle angle, Distance distance, Function<Integer, Double> function) { + var value = distance.value().multiply(BigDecimal.valueOf(function.apply(angle.value()))); + return new VectorComponent(value.intValue()); + } + + private static VectorComponent getVectorX(Angle angle, Distance distance) { + return getVectorComponent(angle, distance, Math::sin); + } + + private static VectorComponent getVectorY(Angle angle, Distance distance) { + return getVectorComponent(angle, distance, Math::cos); + } + + public static Vector of(Angle angle, Distance distance) { + var deltaX = getVectorX(angle, distance); + var deltaY = getVectorY(angle, distance); + return new Vector(deltaX, deltaY); + } + + private final VectorComponent x; + private final VectorComponent y; + + public Vector(VectorComponent x, VectorComponent y) { + new NotNullValidator("x", x).validate(); + new NotNullValidator("y", y).validate(); + this.x = x; + this.y = y; + } + + public VectorComponent x() { + return x; + } + + public VectorComponent y() { + return y; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/common/measure/VectorComponent.java b/src/main/java/fr/takima/lotka_volterra/common/measure/VectorComponent.java new file mode 100644 index 0000000..30bdc3c --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/common/measure/VectorComponent.java @@ -0,0 +1,11 @@ +package fr.takima.lotka_volterra.common.measure; + +import fr.takima.lotka_volterra.validator.NotNullValidator; + +public record VectorComponent(Integer value) { + + public VectorComponent { + new NotNullValidator("value", value).validate(); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/common/value/BigDecimalValue.java b/src/main/java/fr/takima/lotka_volterra/common/value/BigDecimalValue.java new file mode 100644 index 0000000..03a64ee --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/common/value/BigDecimalValue.java @@ -0,0 +1,41 @@ +package fr.takima.lotka_volterra.common.value; + +import fr.takima.lotka_volterra.validator.NotNullValidator; + +import java.math.BigDecimal; +import java.util.Objects; + +public abstract class BigDecimalValue { + + protected final BigDecimal value; + + public BigDecimalValue(BigDecimal value) { + new NotNullValidator("value", value).validate(); + this.value = value; + } + + public final BigDecimal value() { + return value; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (BigDecimalValue) o; + return value.equals(that.value); + } + + @Override + public final int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "BigDecimalValue{" + + "value=" + value + + '}'; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/common/value/IntValue.java b/src/main/java/fr/takima/lotka_volterra/common/value/IntValue.java new file mode 100644 index 0000000..08722a9 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/common/value/IntValue.java @@ -0,0 +1,40 @@ +package fr.takima.lotka_volterra.common.value; + +import fr.takima.lotka_volterra.validator.NotNullValidator; + +import java.util.Objects; + +public abstract class IntValue { + + protected final Integer value; + + public IntValue(Integer value) { + new NotNullValidator("value", value).validate(); + this.value = value; + } + + public final Integer value() { + return value; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var intValue = (IntValue) o; + return value.equals(intValue.value); + } + + @Override + public final int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "IntValue{" + + "value=" + value + + '}'; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/common/value/PositiveInt.java b/src/main/java/fr/takima/lotka_volterra/common/value/PositiveInt.java new file mode 100644 index 0000000..cbaf648 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/common/value/PositiveInt.java @@ -0,0 +1,19 @@ +package fr.takima.lotka_volterra.common.value; + +import fr.takima.lotka_volterra.validator.GreaterThanValidator; + +public class PositiveInt extends IntValue { + + public PositiveInt(Integer value) { + super(value); + new GreaterThanValidator<>("value", value, 0).validate(); + } + + @Override + public String toString() { + return "PositiveInt{" + + "value=" + value + + '}'; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/common/value/StringValue.java b/src/main/java/fr/takima/lotka_volterra/common/value/StringValue.java new file mode 100644 index 0000000..6e17252 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/common/value/StringValue.java @@ -0,0 +1,40 @@ +package fr.takima.lotka_volterra.common.value; + +import fr.takima.lotka_volterra.validator.NotNullValidator; + +import java.util.Objects; + +public abstract class StringValue { + + protected final String value; + + public StringValue(String value) { + new NotNullValidator("value", value).validate(); + this.value = value; + } + + public final String value() { + return value; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (StringValue) o; + return value.equals(that.value); + } + + @Override + public final int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "StringValue{" + + "value='" + value + '\'' + + '}'; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/mating/ChildMaking.java b/src/main/java/fr/takima/lotka_volterra/mating/ChildMaking.java new file mode 100644 index 0000000..4043358 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/mating/ChildMaking.java @@ -0,0 +1,9 @@ +package fr.takima.lotka_volterra.mating; + +import java.util.Set; + +public interface ChildMaking<T extends MatingEntity<T>> { + + Set<T> run(Couple<T> couple); + +} diff --git a/src/main/java/fr/takima/lotka_volterra/mating/Couple.java b/src/main/java/fr/takima/lotka_volterra/mating/Couple.java new file mode 100644 index 0000000..f018de0 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/mating/Couple.java @@ -0,0 +1,5 @@ +package fr.takima.lotka_volterra.mating; + +public record Couple<T extends MatingEntity<T>>(T mate1, T mate2) { + +} diff --git a/src/main/java/fr/takima/lotka_volterra/mating/DefaultPredatorMatingConfigurer.java b/src/main/java/fr/takima/lotka_volterra/mating/DefaultPredatorMatingConfigurer.java new file mode 100644 index 0000000..7ec8b5c --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/mating/DefaultPredatorMatingConfigurer.java @@ -0,0 +1,38 @@ +package fr.takima.lotka_volterra.mating; + +import fr.takima.lotka_volterra.common.measure.Probability; +import fr.takima.lotka_volterra.common.measure.Distance; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.Random; + +@Component +public class DefaultPredatorMatingConfigurer implements MatingConfigurer { + + private final Random random; + private final Distance maxDistance; + private final Probability successProbability; + + public DefaultPredatorMatingConfigurer(Random random, PredatorMatingProperties properties) { + this.random = random; + this.maxDistance = new Distance(properties.getMaxDistance()); + this.successProbability = new Probability(BigDecimal.valueOf(properties.getSuccessProbability())); + } + + @Override + public Random random() { + return random; + } + + @Override + public Distance maxDistance() { + return maxDistance; + } + + @Override + public Probability successProbability() { + return successProbability; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/mating/DefaultPreyMatingConfigurer.java b/src/main/java/fr/takima/lotka_volterra/mating/DefaultPreyMatingConfigurer.java new file mode 100644 index 0000000..b671d95 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/mating/DefaultPreyMatingConfigurer.java @@ -0,0 +1,38 @@ +package fr.takima.lotka_volterra.mating; + +import fr.takima.lotka_volterra.common.measure.Probability; +import fr.takima.lotka_volterra.common.measure.Distance; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.Random; + +@Component +public class DefaultPreyMatingConfigurer implements MatingConfigurer { + + private final Random random; + private final Distance maxDistance; + private final Probability successProbability; + + public DefaultPreyMatingConfigurer(Random random, PreyMatingProperties properties) { + this.random = random; + this.maxDistance = new Distance(properties.getMaxDistance()); + this.successProbability = new Probability(BigDecimal.valueOf(properties.getSuccessProbability())); + } + + @Override + public Random random() { + return random; + } + + @Override + public Distance maxDistance() { + return maxDistance; + } + + @Override + public Probability successProbability() { + return successProbability; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/mating/Mating.java b/src/main/java/fr/takima/lotka_volterra/mating/Mating.java new file mode 100644 index 0000000..6c3787a --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/mating/Mating.java @@ -0,0 +1,80 @@ +package fr.takima.lotka_volterra.mating; + +import fr.takima.lotka_volterra.validator.NotNullValidator; + +import java.math.BigDecimal; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.util.Collections.unmodifiableSet; + +public final class Mating<T extends MatingEntity<T>> { + + private static final int MAX_SUCCESS_THRESHOLD_INT = 100; + private static final BigDecimal MAX_SUCCESS_THRESHOLD_BIG_DECIMAL = new BigDecimal(MAX_SUCCESS_THRESHOLD_INT); + + private final MatingConfigurer configurer; + private final Set<T> matingEntities; + private final ChildMaking<T> childMaking; + private final BigDecimal successThreshold; + + public Mating(MatingConfigurer configurer, Set<T> matingEntities, ChildMaking<T> childMaking) { + new NotNullValidator("configurer", configurer).validate(); + new NotNullValidator("matingEntities", matingEntities).validate(); + new NotNullValidator("childMaking", childMaking).validate(); + this.configurer = configurer; + this.matingEntities = matingEntities; + this.childMaking = childMaking; + this.successThreshold = getSuccessThreshold(); + } + + public Set<T> run() { + var couples = new HashSet<Couple<T>>(); + var matingCandidates = new HashSet<>(matingEntities); + for (var entity : matingEntities) { + var isCandidate = matingCandidates.contains(entity); + var iterator = matingCandidates.iterator(); + var nextMatingCandidates = new HashSet<T>(); + while (isCandidate && iterator.hasNext()) { + var other = iterator.next(); + if (!entity.equals(other)) { + if (entity.isWithin(configurer.maxDistance(), other)) { + var matingSucceeded = computeSuccess(); + if (matingSucceeded) { + couples.add(new Couple<>(entity, other)); + isCandidate = false; + } else { + nextMatingCandidates.add(other); + } + } else { + nextMatingCandidates.add(other); + } + } + } + matingCandidates = nextMatingCandidates; + } + + var children = couples.stream() + .map(childMaking::run) + .flatMap(Set::stream) + .collect(Collectors.toSet()); + + var next = new HashSet<T>(); + next.addAll(matingEntities); + next.addAll(children); + + return unmodifiableSet(next); + } + + private boolean computeSuccess() { + var rand = new BigDecimal(configurer.random().nextInt(MAX_SUCCESS_THRESHOLD_INT)); + return rand.compareTo(successThreshold) < 0; + } + + private BigDecimal getSuccessThreshold() { + var successProbabilityValue = configurer.successProbability().value(); + return successProbabilityValue.multiply(MAX_SUCCESS_THRESHOLD_BIG_DECIMAL); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/mating/MatingConfigurer.java b/src/main/java/fr/takima/lotka_volterra/mating/MatingConfigurer.java new file mode 100644 index 0000000..aea7579 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/mating/MatingConfigurer.java @@ -0,0 +1,16 @@ +package fr.takima.lotka_volterra.mating; + +import fr.takima.lotka_volterra.common.measure.Probability; +import fr.takima.lotka_volterra.common.measure.Distance; + +import java.util.Random; + +public interface MatingConfigurer { + + Random random(); + + Distance maxDistance(); + + Probability successProbability(); + +} diff --git a/src/main/java/fr/takima/lotka_volterra/mating/MatingEntity.java b/src/main/java/fr/takima/lotka_volterra/mating/MatingEntity.java new file mode 100644 index 0000000..4b30422 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/mating/MatingEntity.java @@ -0,0 +1,7 @@ +package fr.takima.lotka_volterra.mating; + +import fr.takima.lotka_volterra.moving.MovingEntity; + +public interface MatingEntity<T extends MatingEntity<T>> extends MovingEntity { + +} diff --git a/src/main/java/fr/takima/lotka_volterra/mating/PredatorMatingProperties.java b/src/main/java/fr/takima/lotka_volterra/mating/PredatorMatingProperties.java new file mode 100644 index 0000000..3906652 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/mating/PredatorMatingProperties.java @@ -0,0 +1,27 @@ +package fr.takima.lotka_volterra.mating; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("predator.mating") +public class PredatorMatingProperties { + + private Integer maxDistance; + private Double successProbability; + + public Integer getMaxDistance() { + return maxDistance; + } + + public void setMaxDistance(Integer maxDistance) { + this.maxDistance = maxDistance; + } + + public Double getSuccessProbability() { + return successProbability; + } + + public void setSuccessProbability(Double successProbability) { + this.successProbability = successProbability; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/mating/PreyMatingProperties.java b/src/main/java/fr/takima/lotka_volterra/mating/PreyMatingProperties.java new file mode 100644 index 0000000..80e2910 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/mating/PreyMatingProperties.java @@ -0,0 +1,27 @@ +package fr.takima.lotka_volterra.mating; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("prey.mating") +public class PreyMatingProperties { + + private Integer maxDistance; + private Double successProbability; + + public Integer getMaxDistance() { + return maxDistance; + } + + public void setMaxDistance(Integer maxDistance) { + this.maxDistance = maxDistance; + } + + public Double getSuccessProbability() { + return successProbability; + } + + public void setSuccessProbability(Double successProbability) { + this.successProbability = successProbability; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/moving/DefaultMapConfigurer.java b/src/main/java/fr/takima/lotka_volterra/moving/DefaultMapConfigurer.java new file mode 100644 index 0000000..1c28c50 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/moving/DefaultMapConfigurer.java @@ -0,0 +1,40 @@ +package fr.takima.lotka_volterra.moving; + +import org.springframework.stereotype.Component; + +@Component +public final class DefaultMapConfigurer implements MapConfigurer { + + private final Integer minX; + private final Integer maxX; + private final Integer minY; + private final Integer maxY; + + public DefaultMapConfigurer(MapProperties properties) { + this.minX = properties.getMinX(); + this.maxX = properties.getMaxX(); + this.minY = properties.getMinY(); + this.maxY = properties.getMaxY(); + } + + @Override + public Integer minX() { + return minX; + } + + @Override + public Integer maxX() { + return maxX; + } + + @Override + public Integer minY() { + return minY; + } + + @Override + public Integer maxY() { + return maxY; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/moving/DefaultPredatorMovingConfigurer.java b/src/main/java/fr/takima/lotka_volterra/moving/DefaultPredatorMovingConfigurer.java new file mode 100644 index 0000000..d291f0a --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/moving/DefaultPredatorMovingConfigurer.java @@ -0,0 +1,29 @@ +package fr.takima.lotka_volterra.moving; + +import fr.takima.lotka_volterra.common.measure.Distance; +import org.springframework.stereotype.Component; + +import java.util.Random; + +@Component +public class DefaultPredatorMovingConfigurer implements MovingConfigurer { + + private final Random random; + private final Distance maxDistance; + + public DefaultPredatorMovingConfigurer(Random random, PredatorMovingProperties properties) { + this.random = random; + this.maxDistance = new Distance(properties.getMaxDistance()); + } + + @Override + public Random random() { + return random; + } + + @Override + public Distance maxDistance() { + return maxDistance; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/moving/DefaultPreyMovingConfigurer.java b/src/main/java/fr/takima/lotka_volterra/moving/DefaultPreyMovingConfigurer.java new file mode 100644 index 0000000..6c800b2 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/moving/DefaultPreyMovingConfigurer.java @@ -0,0 +1,29 @@ +package fr.takima.lotka_volterra.moving; + +import fr.takima.lotka_volterra.common.measure.Distance; +import org.springframework.stereotype.Component; + +import java.util.Random; + +@Component +public class DefaultPreyMovingConfigurer implements MovingConfigurer { + + private final Random random; + private final Distance maxDistance; + + public DefaultPreyMovingConfigurer(Random random, PreyMovingProperties properties) { + this.random = random; + this.maxDistance = new Distance(properties.getMaxDistance()); + } + + @Override + public Random random() { + return random; + } + + @Override + public Distance maxDistance() { + return maxDistance; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/moving/MapConfigurer.java b/src/main/java/fr/takima/lotka_volterra/moving/MapConfigurer.java new file mode 100644 index 0000000..e28570e --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/moving/MapConfigurer.java @@ -0,0 +1,13 @@ +package fr.takima.lotka_volterra.moving; + +public interface MapConfigurer { + + Integer minX(); + + Integer maxX(); + + Integer minY(); + + Integer maxY(); + +} diff --git a/src/main/java/fr/takima/lotka_volterra/moving/MapProperties.java b/src/main/java/fr/takima/lotka_volterra/moving/MapProperties.java new file mode 100644 index 0000000..b2e5210 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/moving/MapProperties.java @@ -0,0 +1,45 @@ +package fr.takima.lotka_volterra.moving; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("map") +public class MapProperties { + + private Integer minX; + private Integer maxX; + private Integer minY; + private Integer maxY; + + public Integer getMinX() { + return minX; + } + + public void setMinX(Integer minX) { + this.minX = minX; + } + + public Integer getMaxX() { + return maxX; + } + + public void setMaxX(Integer maxX) { + this.maxX = maxX; + } + + public Integer getMinY() { + return minY; + } + + public void setMinY(Integer minY) { + this.minY = minY; + } + + public Integer getMaxY() { + return maxY; + } + + public void setMaxY(Integer maxY) { + this.maxY = maxY; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/moving/Moving.java b/src/main/java/fr/takima/lotka_volterra/moving/Moving.java new file mode 100644 index 0000000..9971985 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/moving/Moving.java @@ -0,0 +1,53 @@ +package fr.takima.lotka_volterra.moving; + +import fr.takima.lotka_volterra.common.measure.Angle; +import fr.takima.lotka_volterra.common.measure.Distance; +import fr.takima.lotka_volterra.common.measure.Vector; +import fr.takima.lotka_volterra.validator.NotNullValidator; + +import java.util.HashSet; +import java.util.Set; + +public final class Moving<T extends MovingEntity> { + + private final MovingConfigurer configurer; + private final Set<T> entities; + private final PositionUpdate<T> positionUpdate; + + public Moving(MovingConfigurer configurer, Set<T> entities, PositionUpdate<T> positionUpdate) { + new NotNullValidator("configurer", configurer).validate(); + new NotNullValidator("entities", entities).validate(); + new NotNullValidator("positionUpdate", positionUpdate).validate(); + this.configurer = configurer; + this.entities = entities; + this.positionUpdate = positionUpdate; + } + + public Set<T> run() { + var next = new HashSet<T>(); + + for (var entity : entities) { + var randomVector = getRandomVector(); + var newPosition = entity.position().apply(randomVector); + var updated = positionUpdate.apply(entity, newPosition); + next.add(updated); + } + + return next; + } + + private Vector getRandomVector() { + return Vector.of(getRandomAngle(), getRandomDistance()); + } + + private Angle getRandomAngle() { + var rand = configurer.random().nextInt(Angle.MAX_VALUE); + return new Angle(rand); + } + + private Distance getRandomDistance() { + var rand = configurer.random().nextInt(configurer.maxDistance().value().intValue()); + return new Distance(rand); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/moving/MovingConfigurer.java b/src/main/java/fr/takima/lotka_volterra/moving/MovingConfigurer.java new file mode 100644 index 0000000..da217e6 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/moving/MovingConfigurer.java @@ -0,0 +1,13 @@ +package fr.takima.lotka_volterra.moving; + +import fr.takima.lotka_volterra.common.measure.Distance; + +import java.util.Random; + +public interface MovingConfigurer { + + Random random(); + + Distance maxDistance(); + +} diff --git a/src/main/java/fr/takima/lotka_volterra/moving/MovingEntity.java b/src/main/java/fr/takima/lotka_volterra/moving/MovingEntity.java new file mode 100644 index 0000000..2e0f4dd --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/moving/MovingEntity.java @@ -0,0 +1,17 @@ +package fr.takima.lotka_volterra.moving; + +import fr.takima.lotka_volterra.common.measure.Distance; + +public interface MovingEntity { + + Position position(); + + default Distance distanceTo(MovingEntity other) { + return position().distanceTo(other.position()); + } + + default boolean isWithin(Distance distance, MovingEntity other) { + return distanceTo(other).isLessThan(distance); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/moving/Position.java b/src/main/java/fr/takima/lotka_volterra/moving/Position.java new file mode 100644 index 0000000..00198ed --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/moving/Position.java @@ -0,0 +1,30 @@ +package fr.takima.lotka_volterra.moving; + +import fr.takima.lotka_volterra.common.measure.Distance; +import fr.takima.lotka_volterra.common.measure.Vector; +import fr.takima.lotka_volterra.validator.NotNullValidator; + +import java.math.BigDecimal; +import java.math.MathContext; + +import static java.lang.Math.pow; + +public record Position(PositionComponent x, PositionComponent y) { + + public Position { + new NotNullValidator("x", x).validate(); + new NotNullValidator("y", y).validate(); + } + + public Position apply(Vector vector) { + return new Position(x.apply(vector.x()), y.apply(vector.y())); + } + + public Distance distanceTo(Position other) { + var xPart = pow(x.value() - other.x.value(), 2); + var yPart = pow(y.value() - other.y.value(), 2); + var distanceValue = new BigDecimal(xPart + yPart).sqrt(MathContext.DECIMAL32); + return new Distance(distanceValue); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/moving/PositionComponent.java b/src/main/java/fr/takima/lotka_volterra/moving/PositionComponent.java new file mode 100644 index 0000000..722934e --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/moving/PositionComponent.java @@ -0,0 +1,38 @@ +package fr.takima.lotka_volterra.moving; + +import fr.takima.lotka_volterra.common.measure.VectorComponent; +import fr.takima.lotka_volterra.common.value.IntValue; +import fr.takima.lotka_volterra.validator.GreaterThanValidator; +import fr.takima.lotka_volterra.validator.LowerThanValidator; + +import static java.lang.Math.max; +import static java.lang.Math.min; + + +public final class PositionComponent extends IntValue { + + private final Integer minValue; + private final Integer maxValue; + + public PositionComponent(Integer value, Integer minValue, Integer maxValue) { + super(value); + new GreaterThanValidator<>("value", value, minValue).validate(); + new LowerThanValidator<>("value", value, maxValue).validate(); + this.minValue = minValue; + this.maxValue = maxValue; + } + + public PositionComponent apply(VectorComponent vectorComponent) { + var sum = value + vectorComponent.value(); + var newValue = max(0, min(sum, maxValue)); + return new PositionComponent(newValue, minValue, maxValue); + } + + @Override + public String toString() { + return "PositionComponent{" + + "value=" + value + + '}'; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/moving/PositionUpdate.java b/src/main/java/fr/takima/lotka_volterra/moving/PositionUpdate.java new file mode 100644 index 0000000..9329897 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/moving/PositionUpdate.java @@ -0,0 +1,7 @@ +package fr.takima.lotka_volterra.moving; + +public interface PositionUpdate<T extends MovingEntity> { + + T apply(T entity, Position newPosition); + +} diff --git a/src/main/java/fr/takima/lotka_volterra/moving/PredatorMovingProperties.java b/src/main/java/fr/takima/lotka_volterra/moving/PredatorMovingProperties.java new file mode 100644 index 0000000..ff228df --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/moving/PredatorMovingProperties.java @@ -0,0 +1,18 @@ +package fr.takima.lotka_volterra.moving; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("predator.moving") +public class PredatorMovingProperties { + + private Integer maxDistance; + + public Integer getMaxDistance() { + return maxDistance; + } + + public void setMaxDistance(Integer maxDistance) { + this.maxDistance = maxDistance; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/moving/PreyMovingProperties.java b/src/main/java/fr/takima/lotka_volterra/moving/PreyMovingProperties.java new file mode 100644 index 0000000..f509187 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/moving/PreyMovingProperties.java @@ -0,0 +1,18 @@ +package fr.takima.lotka_volterra.moving; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("prey.moving") +public class PreyMovingProperties { + + private Integer maxDistance; + + public Integer getMaxDistance() { + return maxDistance; + } + + public void setMaxDistance(Integer maxDistance) { + this.maxDistance = maxDistance; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/predation/DefaultPredationConfigurer.java b/src/main/java/fr/takima/lotka_volterra/predation/DefaultPredationConfigurer.java new file mode 100644 index 0000000..07124e2 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/predation/DefaultPredationConfigurer.java @@ -0,0 +1,53 @@ +package fr.takima.lotka_volterra.predation; + +import fr.takima.lotka_volterra.common.measure.Probability; +import fr.takima.lotka_volterra.common.measure.Distance; +import fr.takima.lotka_volterra.predator.Satiety; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.util.Random; + +@Component +public class DefaultPredationConfigurer implements PredationConfigurer { + + private final Random random; + private final Distance maxDistance; + private final Probability successProbability; + private final Satiety successSatietyGain; + private final Satiety failureSatietyLoss; + + public DefaultPredationConfigurer(Random random, PredationProperties properties) { + this.random = random; + this.maxDistance = new Distance(properties.getMaxDistance()); + this.successProbability = new Probability(BigDecimal.valueOf(properties.getSuccessProbability())); + this.successSatietyGain = new Satiety(properties.getSuccessSatietyGain()); + this.failureSatietyLoss = new Satiety(properties.getFailureSatietyLoss()); + } + + @Override + public Random random() { + return random; + } + + @Override + public Distance maxDistance() { + return maxDistance; + } + + @Override + public Probability successProbability() { + return successProbability; + } + + @Override + public Satiety successSatietyGain() { + return successSatietyGain; + } + + @Override + public Satiety failureSatietyLoss() { + return failureSatietyLoss; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/predation/Predation.java b/src/main/java/fr/takima/lotka_volterra/predation/Predation.java new file mode 100644 index 0000000..8cac754 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/predation/Predation.java @@ -0,0 +1,82 @@ +package fr.takima.lotka_volterra.predation; + +import fr.takima.lotka_volterra.predator.Predator; +import fr.takima.lotka_volterra.prey.Prey; +import fr.takima.lotka_volterra.validator.NotNullValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigDecimal; +import java.util.HashSet; +import java.util.Set; + +public final class Predation { + + private static final Logger log = LoggerFactory.getLogger(Predation.class); + private static final int MAX_SUCCESS_THRESHOLD_INT = 100; + private static final BigDecimal MAX_SUCCESS_THRESHOLD_BIG_DECIMAL = new BigDecimal(MAX_SUCCESS_THRESHOLD_INT); + + private final PredationConfigurer configurer; + private final Set<Prey> preys; + private final Set<Predator> predators; + private final BigDecimal successThreshold; + + public Predation(PredationConfigurer configurer, Set<Prey> preys, Set<Predator> predators) { + new NotNullValidator("configurer", configurer).validate(); + new NotNullValidator("preys", preys).validate(); + new NotNullValidator("predators", predators).validate(); + this.configurer = configurer; + this.preys = preys; + this.predators = predators; + this.successThreshold = getSuccessThreshold(); + } + + public PredationResult run() { + var nextPredators = new HashSet<Predator>(); + var nextPreys = new HashSet<>(preys); + + for (var predator : predators) { + var remainingPreys = new HashSet<Prey>(); + var alreadyAte = false; + for (var prey : nextPreys) { + if (!alreadyAte && predator.isWithin(configurer.maxDistance(), prey)) { + var predationSucceeded = computeSuccess(); + if (predationSucceeded) { + var updatedPredator = predator.withSatietyGain(configurer.successSatietyGain()); + nextPredators.add(updatedPredator); + alreadyAte = true; + log.info("predator '{}' ate prey '{}'", predator.id().value(), prey.id().value()); + } else { + remainingPreys.add(prey); + } + } else { + remainingPreys.add(prey); + } + } + + if (!alreadyAte) { + var updatedPredator = predator.withSatietyLoss(configurer.failureSatietyLoss()); + if (!updatedPredator.starvedToDeath()) { + nextPredators.add(updatedPredator); + } else { + log.info("predator '{}' starved to death", predator.id().value()); + } + } + + nextPreys = remainingPreys; + } + + return new PredationResult(nextPreys, nextPredators); + } + + private boolean computeSuccess() { + var rand = new BigDecimal(configurer.random().nextInt(MAX_SUCCESS_THRESHOLD_INT)); + return rand.compareTo(successThreshold) < 0; + } + + private BigDecimal getSuccessThreshold() { + var successProbabilityValue = configurer.successProbability().value(); + return successProbabilityValue.multiply(MAX_SUCCESS_THRESHOLD_BIG_DECIMAL); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/predation/PredationConfigurer.java b/src/main/java/fr/takima/lotka_volterra/predation/PredationConfigurer.java new file mode 100644 index 0000000..36f939b --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/predation/PredationConfigurer.java @@ -0,0 +1,21 @@ +package fr.takima.lotka_volterra.predation; + +import fr.takima.lotka_volterra.common.measure.Probability; +import fr.takima.lotka_volterra.common.measure.Distance; +import fr.takima.lotka_volterra.predator.Satiety; + +import java.util.Random; + +public interface PredationConfigurer { + + Random random(); + + Distance maxDistance(); + + Probability successProbability(); + + Satiety successSatietyGain(); + + Satiety failureSatietyLoss(); + +} diff --git a/src/main/java/fr/takima/lotka_volterra/predation/PredationProperties.java b/src/main/java/fr/takima/lotka_volterra/predation/PredationProperties.java new file mode 100644 index 0000000..7000fdc --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/predation/PredationProperties.java @@ -0,0 +1,45 @@ +package fr.takima.lotka_volterra.predation; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("predation") +public class PredationProperties { + + private Integer maxDistance; + private Double successProbability; + private Integer successSatietyGain; + private Integer failureSatietyLoss; + + public Integer getMaxDistance() { + return maxDistance; + } + + public void setMaxDistance(Integer maxDistance) { + this.maxDistance = maxDistance; + } + + public Double getSuccessProbability() { + return successProbability; + } + + public void setSuccessProbability(Double successProbability) { + this.successProbability = successProbability; + } + + public Integer getSuccessSatietyGain() { + return successSatietyGain; + } + + public void setSuccessSatietyGain(Integer successSatietyGain) { + this.successSatietyGain = successSatietyGain; + } + + public Integer getFailureSatietyLoss() { + return failureSatietyLoss; + } + + public void setFailureSatietyLoss(Integer failureSatietyLoss) { + this.failureSatietyLoss = failureSatietyLoss; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/predation/PredationResult.java b/src/main/java/fr/takima/lotka_volterra/predation/PredationResult.java new file mode 100644 index 0000000..384531c --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/predation/PredationResult.java @@ -0,0 +1,16 @@ +package fr.takima.lotka_volterra.predation; + +import fr.takima.lotka_volterra.predator.Predator; +import fr.takima.lotka_volterra.prey.Prey; +import fr.takima.lotka_volterra.validator.NotNullValidator; + +import java.util.Set; + +public record PredationResult(Set<Prey> preys, Set<Predator> predators) { + + public PredationResult { + new NotNullValidator("preys", preys).validate(); + new NotNullValidator("predators", predators).validate(); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/predator/DefaultPredatorIdGenerator.java b/src/main/java/fr/takima/lotka_volterra/predator/DefaultPredatorIdGenerator.java new file mode 100644 index 0000000..568d562 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/predator/DefaultPredatorIdGenerator.java @@ -0,0 +1,20 @@ +package fr.takima.lotka_volterra.predator; + +import fr.takima.lotka_volterra.common.StringIdGenerator; +import fr.takima.lotka_volterra.validator.NotNullValidator; + +public final class DefaultPredatorIdGenerator implements PredatorIdGenerator { + + private final StringIdGenerator generator; + + public DefaultPredatorIdGenerator(StringIdGenerator generator) { + new NotNullValidator("generator", generator).validate(); + this.generator = generator; + } + + @Override + public PredatorId generate() { + return new PredatorId(generator.generate()); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/predator/Predator.java b/src/main/java/fr/takima/lotka_volterra/predator/Predator.java new file mode 100644 index 0000000..fabbee3 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/predator/Predator.java @@ -0,0 +1,46 @@ +package fr.takima.lotka_volterra.predator; + +import fr.takima.lotka_volterra.mating.MatingEntity; +import fr.takima.lotka_volterra.moving.Position; +import fr.takima.lotka_volterra.validator.NotNullValidator; + +import java.util.Objects; + +public record Predator(PredatorId id, Satiety satiety, Position position) implements MatingEntity<Predator> { + + public Predator { + new NotNullValidator("id", id).validate(); + new NotNullValidator("satiety", satiety).validate(); + new NotNullValidator("position", position).validate(); + } + + public Predator withSatietyGain(Satiety gain) { + return new Predator(id, satiety.add(gain), position); + } + + public Predator withSatietyLoss(Satiety loss) { + return new Predator(id, satiety.subtract(loss), position); + } + + public boolean starvedToDeath() { + return satiety.equals(Satiety.MIN); + } + + public Predator withPosition(Position newPosition) { + return new Predator(id, satiety, newPosition); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var predator = (Predator) o; + return id.equals(predator.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/predator/PredatorChildMaking.java b/src/main/java/fr/takima/lotka_volterra/predator/PredatorChildMaking.java new file mode 100644 index 0000000..6e3de60 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/predator/PredatorChildMaking.java @@ -0,0 +1,32 @@ +package fr.takima.lotka_volterra.predator; + +import fr.takima.lotka_volterra.mating.ChildMaking; +import fr.takima.lotka_volterra.mating.Couple; +import fr.takima.lotka_volterra.validator.NotNullValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.util.Set; + +@Component +public final class PredatorChildMaking implements ChildMaking<Predator> { + + private static final Logger log = LoggerFactory.getLogger(PredatorChildMaking.class); + + private final PredatorIdGenerator idGenerator; + + public PredatorChildMaking(PredatorIdGenerator idGenerator) { + new NotNullValidator("idGenerator", idGenerator).validate(); + this.idGenerator = idGenerator; + } + + @Override + public Set<Predator> run(Couple<Predator> couple) { + var child = new Predator(idGenerator.generate(), Satiety.MAX, couple.mate1().position()); + var children = Set.of(child); + log.info("predators '{}' and '{}' made children", couple.mate1().id().value(), couple.mate2().id().value()); + return children; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/predator/PredatorId.java b/src/main/java/fr/takima/lotka_volterra/predator/PredatorId.java new file mode 100644 index 0000000..037e521 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/predator/PredatorId.java @@ -0,0 +1,18 @@ +package fr.takima.lotka_volterra.predator; + +import fr.takima.lotka_volterra.common.value.StringValue; + +public final class PredatorId extends StringValue { + + public PredatorId(String value) { + super(value); + } + + @Override + public String toString() { + return "PredatorId{" + + "value='" + value + '\'' + + '}'; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/predator/PredatorIdGenerator.java b/src/main/java/fr/takima/lotka_volterra/predator/PredatorIdGenerator.java new file mode 100644 index 0000000..99355b1 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/predator/PredatorIdGenerator.java @@ -0,0 +1,7 @@ +package fr.takima.lotka_volterra.predator; + +public interface PredatorIdGenerator { + + PredatorId generate(); + +} diff --git a/src/main/java/fr/takima/lotka_volterra/predator/PredatorPositionUpdate.java b/src/main/java/fr/takima/lotka_volterra/predator/PredatorPositionUpdate.java new file mode 100644 index 0000000..40b53a5 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/predator/PredatorPositionUpdate.java @@ -0,0 +1,15 @@ +package fr.takima.lotka_volterra.predator; + +import fr.takima.lotka_volterra.moving.Position; +import fr.takima.lotka_volterra.moving.PositionUpdate; +import org.springframework.stereotype.Component; + +@Component +public final class PredatorPositionUpdate implements PositionUpdate<Predator> { + + @Override + public Predator apply(Predator entity, Position newPosition) { + return entity.withPosition(newPosition); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/predator/Satiety.java b/src/main/java/fr/takima/lotka_volterra/predator/Satiety.java new file mode 100644 index 0000000..0f0d71c --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/predator/Satiety.java @@ -0,0 +1,40 @@ +package fr.takima.lotka_volterra.predator; + +import fr.takima.lotka_volterra.common.value.IntValue; +import fr.takima.lotka_volterra.validator.GreaterThanValidator; +import fr.takima.lotka_volterra.validator.LowerThanValidator; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +public final class Satiety extends IntValue { + + private static final int MIN_VALUE = 0; + private static final int MAX_VALUE = 100; + public static final Satiety MIN = new Satiety(MIN_VALUE); + public static final Satiety MAX = new Satiety(MAX_VALUE); + + public Satiety(Integer value) { + super(value); + new GreaterThanValidator<>("value", value, MIN_VALUE).validate(); + new LowerThanValidator<>("value", value, MAX_VALUE).validate(); + } + + public Satiety add(Satiety satiety) { + var sumValue = min(value + satiety.value, MAX_VALUE); + return new Satiety(sumValue); + } + + public Satiety subtract(Satiety satiety) { + var differenceValue = max(value - satiety.value, MIN_VALUE); + return new Satiety(differenceValue); + } + + @Override + public String toString() { + return "Satiety{" + + "value=" + value + + '}'; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/prey/DefaultPreyIdGenerator.java b/src/main/java/fr/takima/lotka_volterra/prey/DefaultPreyIdGenerator.java new file mode 100644 index 0000000..06b4299 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/prey/DefaultPreyIdGenerator.java @@ -0,0 +1,20 @@ +package fr.takima.lotka_volterra.prey; + +import fr.takima.lotka_volterra.common.StringIdGenerator; +import fr.takima.lotka_volterra.validator.NotNullValidator; + +public final class DefaultPreyIdGenerator implements PreyIdGenerator { + + private final StringIdGenerator generator; + + public DefaultPreyIdGenerator(StringIdGenerator generator) { + new NotNullValidator("generator", generator).validate(); + this.generator = generator; + } + + @Override + public PreyId generate() { + return new PreyId(generator.generate()); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/prey/Prey.java b/src/main/java/fr/takima/lotka_volterra/prey/Prey.java new file mode 100644 index 0000000..2fa5354 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/prey/Prey.java @@ -0,0 +1,33 @@ +package fr.takima.lotka_volterra.prey; + +import fr.takima.lotka_volterra.mating.MatingEntity; +import fr.takima.lotka_volterra.moving.Position; +import fr.takima.lotka_volterra.validator.NotNullValidator; + +import java.util.Objects; + +public record Prey(PreyId id, Position position) implements MatingEntity<Prey> { + + public Prey { + new NotNullValidator("id", id).validate(); + new NotNullValidator("position", position).validate(); + } + + public Prey withPosition(Position newPosition) { + return new Prey(id, newPosition); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var prey = (Prey) o; + return id.equals(prey.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/prey/PreyChildMaking.java b/src/main/java/fr/takima/lotka_volterra/prey/PreyChildMaking.java new file mode 100644 index 0000000..bfcf060 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/prey/PreyChildMaking.java @@ -0,0 +1,35 @@ +package fr.takima.lotka_volterra.prey; + +import fr.takima.lotka_volterra.mating.ChildMaking; +import fr.takima.lotka_volterra.mating.Couple; +import fr.takima.lotka_volterra.validator.NotNullValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.util.Set; + +@Component +public final class PreyChildMaking implements ChildMaking<Prey> { + + private static final Logger log = LoggerFactory.getLogger(PreyChildMaking.class); + + private final PreyIdGenerator idGenerator; + + public PreyChildMaking(PreyIdGenerator idGenerator) { + new NotNullValidator("idGenerator", idGenerator).validate(); + this.idGenerator = idGenerator; + } + + @Override + public Set<Prey> run(Couple<Prey> couple) { + var position = couple.mate1().position(); + var child1 = new Prey(idGenerator.generate(), position); + var child2 = new Prey(idGenerator.generate(), position); + var child3 = new Prey(idGenerator.generate(), position); + var children = Set.of(child1, child2, child3); + log.info("preys '{}' and '{}' made children", couple.mate1().id().value(), couple.mate2().id().value()); + return children; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/prey/PreyId.java b/src/main/java/fr/takima/lotka_volterra/prey/PreyId.java new file mode 100644 index 0000000..6b2203d --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/prey/PreyId.java @@ -0,0 +1,18 @@ +package fr.takima.lotka_volterra.prey; + +import fr.takima.lotka_volterra.common.value.StringValue; + +public final class PreyId extends StringValue { + + public PreyId(String value) { + super(value); + } + + @Override + public String toString() { + return "PreyId{" + + "value='" + value + '\'' + + '}'; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/prey/PreyIdGenerator.java b/src/main/java/fr/takima/lotka_volterra/prey/PreyIdGenerator.java new file mode 100644 index 0000000..adc1c65 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/prey/PreyIdGenerator.java @@ -0,0 +1,7 @@ +package fr.takima.lotka_volterra.prey; + +public interface PreyIdGenerator { + + PreyId generate(); + +} diff --git a/src/main/java/fr/takima/lotka_volterra/prey/PreyPositionUpdate.java b/src/main/java/fr/takima/lotka_volterra/prey/PreyPositionUpdate.java new file mode 100644 index 0000000..fbf775d --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/prey/PreyPositionUpdate.java @@ -0,0 +1,15 @@ +package fr.takima.lotka_volterra.prey; + +import fr.takima.lotka_volterra.moving.Position; +import fr.takima.lotka_volterra.moving.PositionUpdate; +import org.springframework.stereotype.Component; + +@Component +public final class PreyPositionUpdate implements PositionUpdate<Prey> { + + @Override + public Prey apply(Prey entity, Position newPosition) { + return entity.withPosition(newPosition); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/simulation/DefaultSimulationConfigurer.java b/src/main/java/fr/takima/lotka_volterra/simulation/DefaultSimulationConfigurer.java new file mode 100644 index 0000000..c6b94d4 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/simulation/DefaultSimulationConfigurer.java @@ -0,0 +1,106 @@ +package fr.takima.lotka_volterra.simulation; + +import fr.takima.lotka_volterra.common.value.PositiveInt; +import fr.takima.lotka_volterra.mating.DefaultPredatorMatingConfigurer; +import fr.takima.lotka_volterra.mating.DefaultPreyMatingConfigurer; +import fr.takima.lotka_volterra.mating.MatingConfigurer; +import fr.takima.lotka_volterra.moving.*; +import fr.takima.lotka_volterra.predation.DefaultPredationConfigurer; +import fr.takima.lotka_volterra.predation.PredationConfigurer; +import fr.takima.lotka_volterra.predator.PredatorChildMaking; +import fr.takima.lotka_volterra.prey.PreyChildMaking; +import org.springframework.stereotype.Component; + +@Component +public class DefaultSimulationConfigurer implements SimulationConfigurer { + + private final PositiveInt initialPreyCount; + private final PositiveInt initialPredatorCount; + private final PositiveInt stepCount; + private final MapConfigurer map; + private final PreyChildMaking preyChildMaking; + private final PredatorChildMaking predatorChildMaking; + private final MatingConfigurer preyMating; + private final MatingConfigurer predatorMating; + private final MovingConfigurer preyMoving; + private final MovingConfigurer predatorMoving; + private final PredationConfigurer predation; + + public DefaultSimulationConfigurer(SimulationProperties properties, + DefaultMapConfigurer map, + PreyChildMaking preyChildMaking, + PredatorChildMaking predatorChildMaking, + DefaultPreyMatingConfigurer preyMating, + DefaultPredatorMatingConfigurer predatorMating, + DefaultPreyMovingConfigurer preyMoving, + DefaultPredatorMovingConfigurer predatorMoving, + DefaultPredationConfigurer predation) { + this.initialPreyCount = new PositiveInt(properties.getInitialPreyCount()); + this.initialPredatorCount = new PositiveInt(properties.getInitialPredatorCount()); + this.stepCount = new PositiveInt(properties.getStepCount()); + this.map = map; + this.preyChildMaking = preyChildMaking; + this.predatorChildMaking = predatorChildMaking; + this.preyMating = preyMating; + this.predatorMating = predatorMating; + this.preyMoving = preyMoving; + this.predatorMoving = predatorMoving; + this.predation = predation; + } + + @Override + public PositiveInt initialPreyCount() { + return initialPreyCount; + } + + @Override + public PositiveInt initialPredatorCount() { + return initialPredatorCount; + } + + @Override + public PositiveInt stepCount() { + return stepCount; + } + + @Override + public PreyChildMaking preyChildMaking() { + return preyChildMaking; + } + + @Override + public PredatorChildMaking predatorChildMaking() { + return predatorChildMaking; + } + + @Override + public MatingConfigurer preyMating() { + return preyMating; + } + + @Override + public MatingConfigurer predatorMating() { + return predatorMating; + } + + @Override + public MovingConfigurer preyMoving() { + return preyMoving; + } + + @Override + public MovingConfigurer predatorMoving() { + return predatorMoving; + } + + @Override + public PredationConfigurer predation() { + return predation; + } + + @Override + public MapConfigurer map() { + return map; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/simulation/Populations.java b/src/main/java/fr/takima/lotka_volterra/simulation/Populations.java new file mode 100644 index 0000000..237efd1 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/simulation/Populations.java @@ -0,0 +1,21 @@ +package fr.takima.lotka_volterra.simulation; + +import fr.takima.lotka_volterra.common.value.PositiveInt; +import fr.takima.lotka_volterra.validator.NotNullValidator; + +public record Populations( + PositiveInt preys, + PositiveInt predators) { + + public Populations { + new NotNullValidator("preys", preys).validate(); + new NotNullValidator("predators", predators).validate(); + } + + @Override + public String toString() { + return "preys=" + preys + + ", predators=" + predators; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/simulation/Simulation.java b/src/main/java/fr/takima/lotka_volterra/simulation/Simulation.java new file mode 100644 index 0000000..c36eaa3 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/simulation/Simulation.java @@ -0,0 +1,31 @@ +package fr.takima.lotka_volterra.simulation; + +import fr.takima.lotka_volterra.predator.PredatorPositionUpdate; +import fr.takima.lotka_volterra.prey.PreyPositionUpdate; +import fr.takima.lotka_volterra.mating.Mating; +import fr.takima.lotka_volterra.moving.Moving; +import fr.takima.lotka_volterra.predation.Predation; +import fr.takima.lotka_volterra.simulation.step.Steps; +import fr.takima.lotka_volterra.validator.NotNullValidator; + +public record Simulation(SimulationConfigurer configurer, Steps steps) { + + public Simulation { + new NotNullValidator("configurer", configurer).validate(); + new NotNullValidator("steps", steps).validate(); + } + + public Simulation runNextStep() { + var lastStep = steps.last(); + var initialPreys = lastStep.preys(); + var initialPredators = lastStep.predators(); + + var predationResult = new Predation(configurer.predation(), initialPreys, initialPredators).run(); + var preyMatingResult = new Mating<>(configurer.preyMating(), predationResult.preys(), configurer().preyChildMaking()).run(); + var predatorMatingResult = new Mating<>(configurer.predatorMating(), predationResult.predators(), configurer().predatorChildMaking()).run(); + var preyMovingResult = new Moving<>(configurer.preyMoving(), preyMatingResult, new PreyPositionUpdate()).run(); + var predatorMovingResult = new Moving<>(configurer.predatorMoving(), predatorMatingResult, new PredatorPositionUpdate()).run(); + return new Simulation(configurer, steps.add(preyMovingResult, predatorMovingResult)); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/simulation/SimulationConfigurer.java b/src/main/java/fr/takima/lotka_volterra/simulation/SimulationConfigurer.java new file mode 100644 index 0000000..7cf2540 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/simulation/SimulationConfigurer.java @@ -0,0 +1,35 @@ +package fr.takima.lotka_volterra.simulation; + +import fr.takima.lotka_volterra.common.value.PositiveInt; +import fr.takima.lotka_volterra.mating.MatingConfigurer; +import fr.takima.lotka_volterra.moving.MapConfigurer; +import fr.takima.lotka_volterra.moving.MovingConfigurer; +import fr.takima.lotka_volterra.predation.PredationConfigurer; +import fr.takima.lotka_volterra.predator.PredatorChildMaking; +import fr.takima.lotka_volterra.prey.PreyChildMaking; + +public interface SimulationConfigurer { + + PositiveInt initialPreyCount(); + + PositiveInt initialPredatorCount(); + + PositiveInt stepCount(); + + PreyChildMaking preyChildMaking(); + + PredatorChildMaking predatorChildMaking(); + + MatingConfigurer preyMating(); + + MatingConfigurer predatorMating(); + + MovingConfigurer preyMoving(); + + MovingConfigurer predatorMoving(); + + PredationConfigurer predation(); + + MapConfigurer map(); + +} diff --git a/src/main/java/fr/takima/lotka_volterra/simulation/SimulationProperties.java b/src/main/java/fr/takima/lotka_volterra/simulation/SimulationProperties.java new file mode 100644 index 0000000..26d84bb --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/simulation/SimulationProperties.java @@ -0,0 +1,36 @@ +package fr.takima.lotka_volterra.simulation; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("simulation") +public class SimulationProperties { + + private Integer initialPredatorCount; + private Integer initialPreyCount; + private Integer stepCount; + + public Integer getInitialPredatorCount() { + return initialPredatorCount; + } + + public void setInitialPredatorCount(Integer initialPredatorCount) { + this.initialPredatorCount = initialPredatorCount; + } + + public Integer getInitialPreyCount() { + return initialPreyCount; + } + + public void setInitialPreyCount(Integer initialPreyCount) { + this.initialPreyCount = initialPreyCount; + } + + public Integer getStepCount() { + return stepCount; + } + + public void setStepCount(Integer stepCount) { + this.stepCount = stepCount; + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/simulation/step/Step.java b/src/main/java/fr/takima/lotka_volterra/simulation/step/Step.java new file mode 100644 index 0000000..04f577e --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/simulation/step/Step.java @@ -0,0 +1,37 @@ +package fr.takima.lotka_volterra.simulation.step; + +import fr.takima.lotka_volterra.common.value.PositiveInt; +import fr.takima.lotka_volterra.predator.Predator; +import fr.takima.lotka_volterra.prey.Prey; +import fr.takima.lotka_volterra.simulation.Populations; +import fr.takima.lotka_volterra.validator.NotNullValidator; + +import java.util.Set; + +public record Step(Number number, Set<Prey> preys, Set<Predator> predators) { + + public static final class Number extends PositiveInt { + + public Number(Integer value) { + super(value); + } + + public Number increment() { + return new Number(value + 1); + } + + } + + public Step { + new NotNullValidator("number", number).validate(); + new NotNullValidator("preys", preys).validate(); + new NotNullValidator("predators", predators).validate(); + } + + public Populations populations() { + var preysPopulation = new PositiveInt(preys.size()); + var predatorsPopulation = new PositiveInt(predators.size()); + return new Populations(preysPopulation, predatorsPopulation); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/simulation/step/Steps.java b/src/main/java/fr/takima/lotka_volterra/simulation/step/Steps.java new file mode 100644 index 0000000..27281f7 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/simulation/step/Steps.java @@ -0,0 +1,53 @@ +package fr.takima.lotka_volterra.simulation.step; + +import fr.takima.lotka_volterra.predator.Predator; +import fr.takima.lotka_volterra.prey.Prey; +import fr.takima.lotka_volterra.validator.NotNullValidator; + +import java.util.*; + +import static java.util.stream.IntStream.range; + +public final class Steps { + + private final List<Step> items; + + public Steps(Collection<Step> items) { + new NotNullValidator("items", items).validate(); + this.items = new ArrayList<>(); + range(0, items.size()) + .forEach(i -> addStepToItems(items, new Step.Number(i))); + } + + public int size() { + return items.size(); + } + + public Step last() { + return items.get(size() - 1); + } + + public Steps add(Set<Prey> preys, Set<Predator> predators) { + var nextNumber = last().number().increment(); + var step = new Step(nextNumber, preys, predators); + var newSteps = new ArrayList<>(items); + newSteps.add(step); + return new Steps(newSteps); + } + + private void addStepToItems(Collection<Step> items, Step.Number stepNumber) { + get(items, stepNumber).ifPresentOrElse( + this.items::add, + () -> { + var message = "could not find step " + stepNumber.value(); + throw new IllegalArgumentException(message); + }); + } + + private static Optional<Step> get(Collection<Step> items, Step.Number stepNumber) { + return items.stream() + .filter(it -> it.number().equals(stepNumber)) + .findAny(); + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/validator/GreaterThanValidator.java b/src/main/java/fr/takima/lotka_volterra/validator/GreaterThanValidator.java new file mode 100644 index 0000000..cb352e8 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/validator/GreaterThanValidator.java @@ -0,0 +1,20 @@ +package fr.takima.lotka_volterra.validator; + +public class GreaterThanValidator<T extends Comparable<T>> extends Validator<T> { + + private final T min; + + public GreaterThanValidator(String name, T value, T min) { + super(name, value); + new NotNullValidator("min", min).validate(); + this.min = min; + } + + @Override + public void validate() { + if (value.compareTo(min) < 0) { + throw new IllegalArgumentException(name + " must be greater than or equal to " + min); + } + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/validator/LowerThanValidator.java b/src/main/java/fr/takima/lotka_volterra/validator/LowerThanValidator.java new file mode 100644 index 0000000..b439415 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/validator/LowerThanValidator.java @@ -0,0 +1,20 @@ +package fr.takima.lotka_volterra.validator; + +public class LowerThanValidator<T extends Comparable<T>> extends Validator<T> { + + private final T max; + + public LowerThanValidator(String name, T value, T max) { + super(name, value); + new NotNullValidator("max", max).validate(); + this.max = max; + } + + @Override + public void validate() { + if (value.compareTo(max) > 0) { + throw new IllegalArgumentException(name + " must be lower than or equal to " + max); + } + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/validator/NotNullValidator.java b/src/main/java/fr/takima/lotka_volterra/validator/NotNullValidator.java new file mode 100644 index 0000000..267cef5 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/validator/NotNullValidator.java @@ -0,0 +1,16 @@ +package fr.takima.lotka_volterra.validator; + +public final class NotNullValidator extends Validator<Object> { + + public NotNullValidator(String name, Object value) { + super(name, value); + } + + @Override + public void validate() { + if (value == null) { + throw new IllegalArgumentException(name + " must not be null"); + } + } + +} diff --git a/src/main/java/fr/takima/lotka_volterra/validator/Validator.java b/src/main/java/fr/takima/lotka_volterra/validator/Validator.java new file mode 100644 index 0000000..523e7e2 --- /dev/null +++ b/src/main/java/fr/takima/lotka_volterra/validator/Validator.java @@ -0,0 +1,18 @@ +package fr.takima.lotka_volterra.validator; + +public abstract class Validator<T> { + + protected final String name; + protected final T value; + + protected Validator(String name, T value) { + if (name == null) { + throw new IllegalArgumentException("name must not be null"); + } + this.name = name; + this.value = value; + } + + public abstract void validate(); + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..195671f --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,26 @@ +map: + min-x: -100 + max-x: 100 + min-y: -100 + max-y: 100 +predation: + failure-satiety-loss: 20 + max-distance: 50 + success-probability: 0.25 + success-satiety-gain: 50 +predator: + mating: + max-distance: 50 + success-probability: 0.25 + moving: + max-distance: 50 +prey: + mating: + max-distance: 30 + success-probability: 0.5 + moving: + max-distance: 30 +simulation: + initial-predator-count: 100 + initial-prey-count: 10000 + step-count: 200 diff --git a/src/test/java/fr/takima/lotka_volterra/moving/MovingEntityTest.java b/src/test/java/fr/takima/lotka_volterra/moving/MovingEntityTest.java new file mode 100644 index 0000000..62cc56f --- /dev/null +++ b/src/test/java/fr/takima/lotka_volterra/moving/MovingEntityTest.java @@ -0,0 +1,52 @@ +package fr.takima.lotka_volterra.moving; + + +import fr.takima.lotka_volterra.common.measure.Distance; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.math.MathContext; + +import static org.assertj.core.api.Assertions.assertThat; + +class MovingEntityTest { + + private static final Position POSITION_0_0 = new Position(new PositionComponent(0, -100, 100), new PositionComponent(0, -100, 100)); + private static final Position POSITION_1_1 = new Position(new PositionComponent(1, -100, 100), new PositionComponent(1, -100, 100)); + private static final FakeMovingEntity ENTITY_1 = new FakeMovingEntity(POSITION_0_0); + private static final FakeMovingEntity ENTITY_2 = new FakeMovingEntity(POSITION_1_1); + + @Nested + class DistanceToTest { + + @Test + void ok() { + var actual = ENTITY_1.distanceTo(ENTITY_2); + assertThat(actual).isEqualTo(new Distance(new BigDecimal(2).sqrt(MathContext.DECIMAL32))); + } + + } + + @Nested + class IsWithinTest { + + @Test + void _true() { + var actual = ENTITY_1.isWithin(new Distance(2), ENTITY_2); + assertThat(actual).isTrue(); + } + + @Test + void _false() { + var actual = ENTITY_1.isWithin(new Distance(1), ENTITY_2); + assertThat(actual).isFalse(); + } + + } + + record FakeMovingEntity(Position position) implements MovingEntity { + + } + +} diff --git a/src/test/java/fr/takima/lotka_volterra/predator/PredatorChildMakingTest.java b/src/test/java/fr/takima/lotka_volterra/predator/PredatorChildMakingTest.java new file mode 100644 index 0000000..e0f1fa1 --- /dev/null +++ b/src/test/java/fr/takima/lotka_volterra/predator/PredatorChildMakingTest.java @@ -0,0 +1,51 @@ +package fr.takima.lotka_volterra.predator; + +import fr.takima.lotka_volterra.mating.Couple; +import fr.takima.lotka_volterra.moving.Position; +import fr.takima.lotka_volterra.moving.PositionComponent; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; + +@ExtendWith(MockitoExtension.class) +class PredatorChildMakingTest { + + private static final Position PARENT_1_POSITION = new Position(new PositionComponent(0, -100, 100), new PositionComponent(0, -100, 100)); + private static final Position PARENT_2_POSITION = new Position(new PositionComponent(1, -100, 100), new PositionComponent(1, -100, 100)); + private static final Predator PARENT_1 = new Predator( + new PredatorId("1"), + Satiety.MAX, + PARENT_1_POSITION); + private static final Predator PARENT_2 = new Predator( + new PredatorId("2"), + Satiety.MAX, + PARENT_2_POSITION); + private static final PredatorId CHILD_ID = new PredatorId("3"); + private static final Predator CHILD = new Predator( + CHILD_ID, + Satiety.MAX, + PARENT_1_POSITION); + private static final Couple<Predator> COUPLE = new Couple<>(PARENT_1, PARENT_2); + + @Mock + private PredatorIdGenerator idGenerator; + + @InjectMocks + private PredatorChildMaking predatorChildMaking; + + @Test + void ok() { + doReturn(CHILD_ID) + .when(idGenerator).generate(); + + var actual = predatorChildMaking.run(COUPLE); + assertThat(actual).usingRecursiveFieldByFieldElementComparator() + .containsExactlyInAnyOrder(CHILD); + } + +} diff --git a/src/test/java/fr/takima/lotka_volterra/predator/PredatorTest.java b/src/test/java/fr/takima/lotka_volterra/predator/PredatorTest.java new file mode 100644 index 0000000..3d33b4f --- /dev/null +++ b/src/test/java/fr/takima/lotka_volterra/predator/PredatorTest.java @@ -0,0 +1,117 @@ +package fr.takima.lotka_volterra.predator; + +import fr.takima.lotka_volterra.moving.Position; +import fr.takima.lotka_volterra.moving.PositionComponent; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class PredatorTest { + + private static final Position POSITION_0_0 = new Position(new PositionComponent(0, -100, 100), new PositionComponent(0, -100, 100)); + private static final Position POSITION_1_1 = new Position(new PositionComponent(1, -100, 100), new PositionComponent(1, -100, 100)); + private static final Predator ENTITY_1 = new Predator(new PredatorId("1"), new Satiety(50), POSITION_0_0); + private static final Predator ENTITY_1_WITH_SATIETY_GAIN = new Predator(new PredatorId("1"), Satiety.MAX, POSITION_0_0); + private static final Predator ENTITY_1_WITH_SATIETY_LOSS = new Predator(new PredatorId("1"), Satiety.MIN, POSITION_0_0); + private static final Predator ENTITY_1_WITH_POSITION = new Predator(new PredatorId("1"), Satiety.MAX, POSITION_1_1); + private static final Predator ENTITY_2 = new Predator(new PredatorId("2"), Satiety.MAX, POSITION_0_0); + + @Nested + class WithSatietyGainTest { + + @Test + void nominal() { + var actual = ENTITY_1.withSatietyGain(new Satiety(50)); + assertThat(actual).usingRecursiveComparison().isEqualTo(ENTITY_1_WITH_SATIETY_GAIN); + } + + @Test + void above_max() { + var actual = ENTITY_1.withSatietyGain(new Satiety(51)); + assertThat(actual).usingRecursiveComparison().isEqualTo(ENTITY_1_WITH_SATIETY_GAIN); + } + + } + + @Nested + class WithSatietyLossTest { + + @Test + void nominal() { + var actual = ENTITY_1.withSatietyLoss(new Satiety(50)); + assertThat(actual).usingRecursiveComparison().isEqualTo(ENTITY_1_WITH_SATIETY_LOSS); + } + + @Test + void under_min() { + var actual = ENTITY_1.withSatietyLoss(new Satiety(51)); + assertThat(actual).usingRecursiveComparison().isEqualTo(ENTITY_1_WITH_SATIETY_LOSS); + } + + } + + @Nested + class StarvedToDeathTest { + + @Test + void _false() { + var actual = ENTITY_1.starvedToDeath(); + assertThat(actual).isFalse(); + } + + @Test + void _true() { + var actual = ENTITY_1_WITH_SATIETY_LOSS.starvedToDeath(); + assertThat(actual).isTrue(); + } + + } + + @Nested + class WithPositionTest { + + @Test + void ok() { + var actual = ENTITY_1.withPosition(POSITION_1_1); + assertThat(actual).isEqualTo(ENTITY_1_WITH_POSITION); + } + + } + + @Nested + class EqualsTest { + + @Test + void with_itself_returns_true() { + var actual = ENTITY_1.equals(ENTITY_1); + assertThat(actual).isTrue(); + } + + @Test + void with_null_returns_false() { + var actual = ENTITY_1.equals(null); + assertThat(actual).isFalse(); + } + + @Test + void with_other_class_returns_null() { + var actual = ENTITY_1.equals(""); + assertThat(actual).isFalse(); + } + + @Test + void with_other_id_returns_false() { + var actual = ENTITY_1.equals(ENTITY_2); + assertThat(actual).isFalse(); + } + + @Test + void with_same_id_returns_true() { + var actual = ENTITY_1.equals(ENTITY_1_WITH_POSITION); + assertThat(actual).isTrue(); + } + + } + +} diff --git a/src/test/java/fr/takima/lotka_volterra/prey/PreyChildMakingTest.java b/src/test/java/fr/takima/lotka_volterra/prey/PreyChildMakingTest.java new file mode 100644 index 0000000..ff90fa4 --- /dev/null +++ b/src/test/java/fr/takima/lotka_volterra/prey/PreyChildMakingTest.java @@ -0,0 +1,46 @@ +package fr.takima.lotka_volterra.prey; + +import fr.takima.lotka_volterra.mating.Couple; +import fr.takima.lotka_volterra.moving.Position; +import fr.takima.lotka_volterra.moving.PositionComponent; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; + +@ExtendWith(MockitoExtension.class) +class PreyChildMakingTest { + + private static final Position PARENT_1_POSITION = new Position(new PositionComponent(0, -100, 100), new PositionComponent(0, -100, 100)); + private static final Position PARENT_2_POSITION = new Position(new PositionComponent(1, -100, 100), new PositionComponent(1, -100, 100)); + private static final Prey PARENT_1 = new Prey(new PreyId("1"), PARENT_1_POSITION); + private static final Prey PARENT_2 = new Prey(new PreyId("2"), PARENT_2_POSITION); + private static final PreyId CHILD_1_ID = new PreyId("3"); + private static final Prey CHILD_1 = new Prey(CHILD_1_ID, PARENT_1_POSITION); + private static final PreyId CHILD_2_ID = new PreyId("4"); + private static final Prey CHILD_2 = new Prey(CHILD_2_ID, PARENT_1_POSITION); + private static final PreyId CHILD_3_ID = new PreyId("5"); + private static final Prey CHILD_3 = new Prey(CHILD_3_ID, PARENT_1_POSITION); + private static final Couple<Prey> COUPLE = new Couple<>(PARENT_1, PARENT_2); + + @Mock + private PreyIdGenerator idGenerator; + + @InjectMocks + private PreyChildMaking preyChildMaking; + + @Test + void ok() { + doReturn(CHILD_1_ID, CHILD_2_ID, CHILD_3_ID) + .when(idGenerator).generate(); + + var actual = preyChildMaking.run(COUPLE); + assertThat(actual).usingRecursiveFieldByFieldElementComparator() + .containsExactlyInAnyOrder(CHILD_1, CHILD_2, CHILD_3); + } + +} diff --git a/src/test/java/fr/takima/lotka_volterra/prey/PreyTest.java b/src/test/java/fr/takima/lotka_volterra/prey/PreyTest.java new file mode 100644 index 0000000..64ede10 --- /dev/null +++ b/src/test/java/fr/takima/lotka_volterra/prey/PreyTest.java @@ -0,0 +1,64 @@ +package fr.takima.lotka_volterra.prey; + +import fr.takima.lotka_volterra.moving.Position; +import fr.takima.lotka_volterra.moving.PositionComponent; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class PreyTest { + + private static final Position POSITION_0_0 = new Position(new PositionComponent(0, -100, 100), new PositionComponent(0, -100, 100)); + private static final Position POSITION_1_1 = new Position(new PositionComponent(0, -100, 100), new PositionComponent(0, -100, 100)); + private static final Prey ENTITY_1 = new Prey(new PreyId("1"), POSITION_0_0); + private static final Prey ENTITY_1_WITH_POSITION = new Prey(new PreyId("1"), POSITION_1_1); + private static final Prey ENTITY_2 = new Prey(new PreyId("2"), POSITION_1_1); + + @Nested + class WithPositionTest { + + @Test + void ok() { + var actual = ENTITY_1.withPosition(POSITION_1_1); + assertThat(actual).isEqualTo(ENTITY_1_WITH_POSITION); + } + + } + + @Nested + class EqualsTest { + + @Test + void with_itself_returns_true() { + var actual = ENTITY_1.equals(ENTITY_1); + assertThat(actual).isTrue(); + } + + @Test + void with_null_returns_false() { + var actual = ENTITY_1.equals(null); + assertThat(actual).isFalse(); + } + + @Test + void with_other_class_returns_null() { + var actual = ENTITY_1.equals(""); + assertThat(actual).isFalse(); + } + + @Test + void with_other_id_returns_false() { + var actual = ENTITY_1.equals(ENTITY_2); + assertThat(actual).isFalse(); + } + + @Test + void with_same_id_returns_true() { + var actual = ENTITY_1.equals(ENTITY_1_WITH_POSITION); + assertThat(actual).isTrue(); + } + + } + +} -- GitLab