Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added: Animal, AnimalTree, Genotype, GenotypeTests. #3

Merged
merged 2 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/main/java/agh/ics/oop/model/Animal.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package agh.ics.oop.model;

import agh.ics.oop.model.movement.MapDirection;
import agh.ics.oop.model.movement.Vector2d;
import agh.ics.oop.model.util.AnimalTree;
import agh.ics.oop.model.util.RandomInteger;
import agh.ics.oop.model.worldMaps.Globe;

public class Animal {
private final Globe globe;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no reason to keep Globe in Animal.
You use it to:

  1. Get specific values in constructor - they can just be passed
  2. Check genotype length - you can pass it to Genotype as attribute or check the length of parent's genotype

private Vector2d position;
private MapDirection direction;
private Genotype genotype;
private int energy;
private int daysLived=0;
private int eatenPlants=0;
private int dayOfDeath;
private int currentGeneIndex;
private AnimalTree animalTree;


public Animal(Globe globe) {
this.globe=globe;
this.position=new Vector2d(RandomInteger.getRandomInt(globe.getWidth()) + 1, RandomInteger.getRandomInt(globe.getHeight()) + 1);
this.direction=MapDirection.values()[RandomInteger.getRandomInt(8)];
this.energy=globe.getAnimalsStartingEnergy();
this.genotype=new Genotype(globe.getGenotypeLength());
this.currentGeneIndex=RandomInteger.getRandomInt(globe.getGenotypeLength());
this.animalTree=new AnimalTree(this);
}

public Animal(Animal mother, Animal father) {
this.globe=mother.getGlobe();
this.position=mother.getPosition();
this.direction=MapDirection.values()[RandomInteger.getRandomInt(8)];
this.energy=globe.getEnergyUsedToReproduce()*2;
this.genotype=new Genotype(mother,father);
this.currentGeneIndex=RandomInteger.getRandomInt(globe.getGenotypeLength());
mother.useEnergy(globe.getEnergyUsedToReproduce());
mother.animalTree.addChild(this.animalTree);
father.useEnergy(globe.getEnergyUsedToReproduce());
father.animalTree.addChild(this.animalTree);
}

private void useEnergy(int energyUsedToReproduce) {
this.energy-=energyUsedToReproduce;
}
public void nextGene(){
this.currentGeneIndex=(this.currentGeneIndex+1)%globe.getGenotypeLength();
}

public boolean canReproduce(){
return this.energy>=globe.getMinEnergyToReproduce();
}

public int getNumberOfChildren(){
return animalTree.getChildrenCount();
}
public int getNumberOfDescendants(){
return animalTree.getDescendantsCount();
}

public Vector2d getPosition() {
return position;
}

public int getEnergy() {
return energy;
}

public Genotype getGenotype() {
return genotype;
}

public Globe getGlobe() {
return globe;
}


}
69 changes: 69 additions & 0 deletions src/main/java/agh/ics/oop/model/Genotype.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package agh.ics.oop.model;

import agh.ics.oop.model.util.RandomInteger;

import java.util.ArrayList;
import java.util.List;

public class Genotype {
private List<Integer> genes = new ArrayList<>();

public Genotype(int genotypeLength) {
for (int i = 0; i < genotypeLength; i++) {
this.genes.add(RandomInteger.getRandomInt(8));
}
}

public Genotype(Animal mother, Animal father) {
validateParents(mother, father);

int divisionPoint = (int) (((double) mother.getEnergy() / (father.getEnergy() + mother.getEnergy())) * mother.getGenotype().genes.size());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You access genotype length twice in this constructor. Imo it would be better to either add an attribute for it or create a local variable to make code more readable.

boolean chooseLeftSide = RandomInteger.getRandomBoolean();

for (int i = 0; i < mother.getGlobe().getGenotypeLength(); i++) {
if ((chooseLeftSide && i < divisionPoint) || (!chooseLeftSide && i >= divisionPoint)) {
genes.add(mother.getGenotype().genes.get(i));
} else {
genes.add(father.getGenotype().genes.get(i));
}
}

mutate(mother.getGlobe().getMinNumberOfMutations(), mother.getGlobe().getMaxNumberOfMutations());
}

private void validateParents(Animal mother, Animal father) {
if (mother == null || father == null) {
throw new IllegalArgumentException("Both mother and father must not be null");
}
}

private void mutate(int minNumberOfMutation, int maxNumberOfMutation) {
int numberOfMutations = RandomInteger.getRandomInt(minNumberOfMutation, maxNumberOfMutation);

for (int i = 0; i < numberOfMutations; i++) {
switchGenes(RandomInteger.getRandomInt(genes.size()), RandomInteger.getRandomInt(genes.size())); //opcja symulacji z podmianką
randomGene(RandomInteger.getRandomInt(genes.size())); //opcja symulacji pełna losowość
}
}

public void switchGenes(int firstGeneIndex, int secondGeneIndex) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use Collections.swap() to do this.

int temp = this.genes.get(firstGeneIndex);
this.genes.set(firstGeneIndex, this.genes.get(secondGeneIndex));
this.genes.set(secondGeneIndex, temp);
}
public void randomGene(int geneIndex) {
genes.set(geneIndex, RandomInteger.getRandomInt(8));
}

public String toString() {
StringBuilder genotypeToString = new StringBuilder();
for (Integer gene : genes) {
genotypeToString.append(gene.toString());
}
return genotypeToString.toString();
}

public Integer getCurrentGene(int currentGeneIndex) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case it is better to use int instead of Integer.

return genes.get(currentGeneIndex);
}
}
33 changes: 33 additions & 0 deletions src/main/java/agh/ics/oop/model/util/AnimalTree.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package agh.ics.oop.model.util;

import agh.ics.oop.model.Animal;

import java.util.ArrayList;
import java.util.List;

public class AnimalTree {
private final List<AnimalTree> children;
private final Animal animal;

public AnimalTree(Animal animal) {
this.animal = animal;
this.children = new ArrayList<>();
}

public void addChild(AnimalTree child) {
children.add(child);
}
public int getChildrenCount() {
return children.size();
}

public int getDescendantsCount() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea is that once you choose a specific animal, you get all of its children and add an observer to them that updates out count whenever a new animal is born (from an observed animal).

Not sure if it is the correct solution.

int descendantsCount = children.size(); // liczba bezpośrednich potomków

for (AnimalTree child : children) {
descendantsCount += child.getDescendantsCount(); // rekurencyjnie dodaj potomków potomka
}

return descendantsCount;
}
}
22 changes: 22 additions & 0 deletions src/main/java/agh/ics/oop/model/util/RandomInteger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package agh.ics.oop.model.util;

import java.util.Random;

public class RandomInteger {
private static final Random random = new Random();

public static int getRandomInt(int bound) {
return random.nextInt(bound);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is exclusive but it's overload with 2 variables is inclusive. It will lead to confusion and mistakes in the future.

}

public static int getRandomInt(int min, int max) {
if (min >= max) {
throw new IllegalArgumentException("Min value must be less than max value");
}
return random.nextInt(max - min + 1) + min;
}

public static boolean getRandomBoolean() {
return random.nextBoolean();
}
}
105 changes: 91 additions & 14 deletions src/main/java/agh/ics/oop/model/worldMaps/Globe.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package agh.ics.oop.model.worldMaps;

import agh.ics.oop.model.Animal;
import agh.ics.oop.model.Genotype;
import agh.ics.oop.model.movement.Vector2d;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.*;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep the named imports because they are more readable.


public class Globe {
private final int width;
private final int height;

private static final Vector2d lowerLeftBoundary = new Vector2d(0, 0);

private final Vector2d upperRightBoundary;;
private int plantCount;
private final int energyPerPlant;
Expand All @@ -21,7 +22,9 @@ public class Globe {
private final int energyUsedToReproduce;
private final int minNumberOfMutations;
private final int maxNumberOfMutations;
private final int genomeLength;
private final int genotypeLength;
private final Map<Genotype, Integer> genotypeCount = new HashMap<>();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are using a map but there is neither equals nor hash in Genotype



private Map<Vector2d, List<Animal>> animals = new HashMap<Vector2d, ArrayList<Animal>>();
private Map<Vector2d, Plant> plants = new HashMap<Vector2d, Plant>();
Expand All @@ -34,7 +37,7 @@ private void checkIfNotNegative(int value) throws IllegalArgumentException{

public Globe(int width, int height, int energyPerPlant, int plantsPerDay, int numberOfStartingAnimals,
int animalsStartingEnergy,int minEnergyToReproduce, int energyUsedToReproduce,
int minNumberOfMutations, int maxNumberOfMutations, int genomeLength) throws IllegalArgumentException {
int minNumberOfMutations, int maxNumberOfMutations, int genotypeLength) throws IllegalArgumentException {

if (width < 1) throw new IllegalArgumentException("Map's width must be positive");
this.width = width;
Expand All @@ -51,12 +54,6 @@ public Globe(int width, int height, int energyPerPlant, int plantsPerDay, int nu
checkIfNotNegative(plantsPerDay);
this.plantsPerDay = plantsPerDay;

checkIfNotNegative(numberOfStartingAnimals);
Random random = new Random();
for (int i = 0; i < numberOfStartingAnimals; i++) {
place(new Animal(new Vector2d(random.nextInt(width) + 1, random.nextInt(height) + 1)));
}

checkIfNotNegative(animalsStartingEnergy);
this.animalsStartingEnergy = animalsStartingEnergy;

Expand All @@ -72,14 +69,21 @@ public Globe(int width, int height, int energyPerPlant, int plantsPerDay, int nu
throw new IllegalArgumentException("Maximum number of mutations must be greater than minimal");
}
this.maxNumberOfMutations = maxNumberOfMutations;
if (genomeLength < 1) throw new IllegalArgumentException("Genome length must be positive");
this.genomeLength = genomeLength;
if (genotypeLength < 1) throw new IllegalArgumentException("Genotype length must be positive");
this.genotypeLength = genotypeLength;

checkIfNotNegative(numberOfStartingAnimals);
for (int i = 0; i < numberOfStartingAnimals; i++) {
place(new Animal(this));
}
}

public boolean canMoveTo(Vector2d position) {
return position.precedes(upperRightBoundary) && position.follows(lowerLeftBoundary);
}



public void place(Animal animal) {
Vector2d position = animal.getPosition();
numberOfAnimals++;
Expand All @@ -88,5 +92,78 @@ public void place(Animal animal) {
animalsAtThisPosition.add(animal);

animals.put(animalsAtThisPosition);
updateGenotypeCount(animal);
}
public void remove(Animal animal) {
Vector2d position = animal.getPosition();
List<Animal> animalsAtThisPosition = animals.get(position);

if (animalsAtThisPosition != null) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would replace it with try catch because we expect that position in animal is kept correctly.

animalsAtThisPosition.remove(animal);
if (animalsAtThisPosition.isEmpty()) {
animals.remove(position);
}
}

removeGenotype(animal);
}
private void updateGenotypeCount(Animal animal) {
Genotype animalGenotype = animal.getGenotype();

genotypeCount.put(animalGenotype, genotypeCount.getOrDefault(animalGenotype, 0) + 1);
}
private void removeGenotype(Animal animal) {
Genotype animalGenotype = animal.getGenotype();

genotypeCount.put(animalGenotype, genotypeCount.getOrDefault(animalGenotype, 1) - 1);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above we expect that genotypes are also tracked correctly so no need for getOrDefault().


if (genotypeCount.get(animalGenotype) == 0) {
genotypeCount.remove(animalGenotype);
}
}
public Genotype findMostCommonGenotype() { //niedynamiczne- do poprawy
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me it sounds like heap.

Genotype mostCommonGenotype = null;
int maxCount = 0;

for (Map.Entry<Genotype, Integer> entry : genotypeCount.entrySet()) {
if (entry.getValue() > maxCount) {
mostCommonGenotype = entry.getKey();
maxCount = entry.getValue();
}
}

return mostCommonGenotype;
}
public int getAnimalsStartingEnergy() {
return animalsStartingEnergy;
}

public int getGenotypeLength() {
return genotypeLength;
}

public int getEnergyUsedToReproduce() {
return energyUsedToReproduce;
}

public int getMinNumberOfMutations() {
return minNumberOfMutations;
}

public int getMaxNumberOfMutations() {
return maxNumberOfMutations;
}

public int getMinEnergyToReproduce() {
return minimalEnergyToReproduce;
}

public int getWidth() {
return width;
}

public int getHeight() {
return height;
}
}

Loading