diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a3417b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/out/ diff --git a/.idea/modules.xml b/.idea/modules.xml index 425ed52..5a06d60 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/JavaPlayground.iml b/evolutionary-altruism-simulation.iml similarity index 100% rename from JavaPlayground.iml rename to evolutionary-altruism-simulation.iml diff --git a/out/production/JavaPlayground/Simulation/Main.class b/out/production/JavaPlayground/Simulation/Main.class deleted file mode 100644 index 7e78fc6..0000000 Binary files a/out/production/JavaPlayground/Simulation/Main.class and /dev/null differ diff --git a/out/production/JavaPlayground/Simulation/Simulation$Altruist.class b/out/production/JavaPlayground/Simulation/Simulation$Altruist.class deleted file mode 100644 index 4cdd8f4..0000000 Binary files a/out/production/JavaPlayground/Simulation/Simulation$Altruist.class and /dev/null differ diff --git a/out/production/JavaPlayground/Simulation/Simulation$Coward.class b/out/production/JavaPlayground/Simulation/Simulation$Coward.class deleted file mode 100644 index 9b5b8c7..0000000 Binary files a/out/production/JavaPlayground/Simulation/Simulation$Coward.class and /dev/null differ diff --git a/out/production/JavaPlayground/Simulation/Simulation$Entity.class b/out/production/JavaPlayground/Simulation/Simulation$Entity.class deleted file mode 100644 index 68fbb01..0000000 Binary files a/out/production/JavaPlayground/Simulation/Simulation$Entity.class and /dev/null differ diff --git a/out/production/JavaPlayground/Simulation/Simulation.class b/out/production/JavaPlayground/Simulation/Simulation.class deleted file mode 100644 index 6c46855..0000000 Binary files a/out/production/JavaPlayground/Simulation/Simulation.class and /dev/null differ diff --git a/out/production/JavaPlayground/Simulation/Utils.class b/out/production/JavaPlayground/Simulation/Utils.class deleted file mode 100644 index 7d47889..0000000 Binary files a/out/production/JavaPlayground/Simulation/Utils.class and /dev/null differ diff --git a/src/Simulation/Entity.java b/src/Simulation/Entity.java new file mode 100644 index 0000000..b1c6378 --- /dev/null +++ b/src/Simulation/Entity.java @@ -0,0 +1,16 @@ +package Simulation; + +public class Entity { + // survival rate when enemy met + public float survivalRate; + + // petition to notify danger when enemy met + public float dangerNotifyChance; + + // number of entities born on reproduction + public int reproductionCountMin; + public int reproductionCountMax; + + public int nutrientsNecessaryForReproduction; + public int currentNutrients; +} \ No newline at end of file diff --git a/src/Simulation/Main.java b/src/Simulation/Main.java index e5ea5b5..36aa4e8 100644 --- a/src/Simulation/Main.java +++ b/src/Simulation/Main.java @@ -1,10 +1,56 @@ package Simulation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicReferenceArray; + public class Main { - public static void main(String[] args) { - System.out.println("Hello world!"); - final Simulation simulation = new Simulation(20, 20); - simulation.simulate(50); + + public static void main(String[] args) throws InterruptedException { + final Simulation simulation = new Simulation(); + simulation.setData(20, 20, 10); + simulation.simulate(10); + simulation.printData(); + + // fix seed +// final ThreadLocal random = ThreadLocal.withInitial(Random::new); +// random.get().setSeed(50); +// final ThreadLocalRandom random = ThreadLocalRandom.current(); +// random.setSeed(50); + +// final Random random = Utils.random; +// random.setSeed(500); +// final ArrayList objects = new ArrayList<>(); +// final ArrayList threads = new ArrayList<>(); +// +// for (int i = 0; i < 10; i++) { +// objects.add(i); +// } +// +// for (int i = 0; i < 10; i++) { +// final ArrayList clone = new ArrayList<>(objects); +// +// for (Object object : clone) { +// final Thread thread = new Thread(() -> { +// final float randomFloat = random.nextFloat(); +// if (randomFloat > 0.5f) { +// objects.remove(object); +// } +// }); +// threads.add(thread); +// thread.start(); +// } +// +// for (Thread thread : threads) { +// thread.join(); +// } +// +// Collections.sort(objects); +// System.out.println("objects = " + objects); +// } } } diff --git a/src/Simulation/Simulation.java b/src/Simulation/Simulation.java index ffae595..e403218 100644 --- a/src/Simulation/Simulation.java +++ b/src/Simulation/Simulation.java @@ -1,147 +1,278 @@ package Simulation; +import com.sun.source.tree.SynchronizedTree; +import jdk.jshell.execution.Util; + import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadLocalRandom; +/* + * Evolutionary Biological Altruism Simulation + * created by Daeva + */ public class Simulation { - // Basic Biological Altruism simulation - // Initial distribution - private final int initialAltruistCount; - private final int initialCowardCount; + // world properties + private final float enemyMeetingChance = 0.7f; + private ArrayList entities; - // Current distribution - private int currentAltruistCount; - private int currentCowardCount; + // statistics + private int initialAltruistCount; + private int initialCowardCount; + private int[] altruistCountDailyData; + private int[] cowardCountDailyData; - // World properties - private final float enemyChance; + // day counters + private int days; + private int currentDay; + private int simulationEndDay; - private ArrayList entities; + // current milestones + // TODO: 3/22/2023 make entity variables change by next generation + // TODO: 3/22/2023 entities have something like perception, altruist should deduce if "opponent" is altruist or coward + // TODO: 3/22/2023 fully implement nutrients parameter for entities + + private void resetData () { + this.currentDay = 0; + this.entities = new ArrayList<>(); - // TODO: 21.03.23 keep data in arrays so we could plot it + // initialise data containers + this.altruistCountDailyData = new int[days + 1]; + this.cowardCountDailyData = new int[days + 1]; + } - public Simulation (int initialAltruistCount, int initialCowardCount) { + public void setData (int initialAltruistCount, int initialCowardCount, int days) { this.initialAltruistCount = initialAltruistCount; this.initialCowardCount = initialCowardCount; + this.days = days; - this.currentAltruistCount = initialAltruistCount; - this.currentCowardCount = initialCowardCount; - - // TODO: 06.03.23 add as arg - this.enemyChance = 0.65f; + resetData(); + } - this.entities = new ArrayList<>(); + public void simulate (long seed) { +// Utils.random.get().setSeed(seed); + simulate(); } - public void simulate (int days) { - // Initialise entities + public void simulate () { + System.out.println("\nStarting simulation for " + days + " days\n"); + + // initialise entities for (int i = 0; i < initialAltruistCount; i++) { - this.entities.add(new Altruist()); + summonAltruist(); } for (int i = 0; i < initialCowardCount; i++) { - this.entities.add(new Coward()); + summonCoward(); } - // Simulate + // simulate + long startTime = System.currentTimeMillis(); + for (int currentDay = 1; currentDay <= days; currentDay++) { - // If no entities left end the simulation - if (entities.size() == 0) { - System.out.println("\nSimulation ended on day " + currentDay); - return; - } + // update current date + this.currentDay = currentDay; + + final int previousDay = currentDay - 1; + this.altruistCountDailyData[currentDay] = this.altruistCountDailyData[previousDay]; + this.cowardCountDailyData[currentDay] = this.cowardCountDailyData[previousDay]; + // copy entities array to modify further final ArrayList entitiesCopy = new ArrayList<>(entities); - // TODO: 21.03.23 loop over entity couples - for ( Entity entity : entities) { - // Approach to food source - // TODO: 06.03.23 later - - // TODO: 21.03.23 random one from couple is altruist other has 1 survival rate - // TODO: 21.03.23 perception changes this 1, - // like if perception is 0.2 if opponent is not altruist there is a 0.2 that it will not shout out - - // Handle event - final float enemySpawnChance = (float) Math.random(); - if (enemyChance > enemySpawnChance) { - // Enemy spawned - final float survivalRate = (float) Math.random(); - if (entity.getSurvivalRateWhenEnemyMet() > survivalRate) { - // Survived - // TODO: 06.03.23 - } else { - // Died - entitiesCopy.remove(entity); - if (entity instanceof Coward) { - currentCowardCount--; - } else if (entity instanceof Altruist) { - currentAltruistCount--; + + // shuffle entities to make couples + Collections.shuffle(entitiesCopy, Utils.random.get()); + + // create and start threads + final int couples = (int) Math.ceil(entitiesCopy.size() / 2.0f); + final int leftEnemyCount = entitiesCopy.size() % 2; + final ExecutorService executorService = Executors.newFixedThreadPool(couples + leftEnemyCount); + + System.out.println("couples " + (couples + leftEnemyCount)); + + // loop over entities with couples + for (int coupleIndex = 0; coupleIndex < couples - leftEnemyCount; coupleIndex++) { + final int firstEntityIndex = coupleIndex * 2; + final int secondEntityIndex = coupleIndex * 2 + 1; + + executorService.execute(() -> { + // get a couple + final Entity firstEntity = entitiesCopy.get(firstEntityIndex); + final Entity secondEntity = entitiesCopy.get(secondEntityIndex); + + // handle event + if (metDanger(enemyMeetingChance)) { + if (shouldNotify(firstEntity)) { + // second entity survived + + // check if first survives + handleDanger(firstEntity); + + // call it a day + return; } - continue; + + // first entity didn't notify + // TODO: 3/21/2023 figure out whether second entity should notify if first didn't + handleDanger(firstEntity); + handleDanger(secondEntity); + + // call it a day + return; } - } else { - // No enemy - // TODO: 06.03.23 - } - - // Try getting food - - // Return - // Repopulate - final int entityReproductionCount = entity.getReproductionCount(); - for (int i = 0; i < entityReproductionCount; i++) { - if (entity instanceof Coward) { - entitiesCopy.add(new Coward()); - currentCowardCount++; - } else if (entity instanceof Altruist) { - entitiesCopy.add(new Altruist()); - currentAltruistCount++; + + // no danger met -> get food + firstEntity.currentNutrients++; + secondEntity.currentNutrients++; + + // try to reproduce + handleReproduction(firstEntity); + handleReproduction(secondEntity); + System.out.print(" | "); + }); + } + + // case if there is an enemy with no couple + if (leftEnemyCount > 0) { + executorService.submit(() -> { + final Entity entity = entitiesCopy.get(entitiesCopy.size() - 1); + + if (metDanger(enemyMeetingChance)) { + handleDanger(entity); + } else { + handleReproduction(entity); } - } + System.out.print(" | "); + }); } - entities = entitiesCopy; - System.out.println("Current day " + currentDay); - System.out.println("Current Altruists Count is " + currentAltruistCount); - System.out.println("current Cowards Count is " + currentCowardCount); + executorService.shutdown(); + +// this.entities.sort(Utils.entityComparator); + System.out.println(); + System.out.println("Day " + currentDay + " passed"); + + // if no entities left end the simulation + if (entities.size() == 0) { + endSimulation(startTime, days); + return; + } } + + endSimulation(startTime, days); } - // TODO: 21.03.23 if make variables change by next generation - // TODO: 21.03.23 like if random didn't shout out - // TODO: 21.03.23 perception like something, like altruist should deduce if "opponent" is altruist or coward - public class Altruist extends Entity { - public float getSurvivalRateWhenShout () { - return 0.1f; - } + public void endSimulation (long startTime, int simulationEndDay) { + final long endTime = System.currentTimeMillis(); + this.simulationEndDay = simulationEndDay; - @Override - public float getSurvivalRateWhenEnemyMet() { - return 0.2f; - } + System.out.println(); + System.out.println("Simulation ended on day " + currentDay ); + System.out.println("Elapsed time is " + (endTime - startTime) + " mills"); + System.out.println(); + } + + // handling events + public void handleReproduction (Entity entity) { + final int reproductionCount = getReproductionCount(entity); + + if (reproductionCount <= 0) return; + + final boolean isAltruist = isAltruist(entity); + + for (int i = 0; i < reproductionCount; i++) { + if (isAltruist) + summonAltruist(); + else summonCoward(); - @Override - public int getReproductionCount() { - return Utils.getRandomNumberInclusive(1, 2); } + entity.currentNutrients -= entity.nutrientsNecessaryForReproduction; } - public class Coward extends Entity { - @Override - public float getSurvivalRateWhenEnemyMet() { - return 0.2f; - } + public void handleDanger (Entity entity) { + if (survivedDanger(entity)) return; - @Override - public int getReproductionCount() { - return Utils.getRandomNumberInclusive(1, 2); - } + killEntity(entity); + } + + // creating or destroying entities + public void summonAltruist () { + // create entity + final Entity entity = new Entity(); + entity.survivalRate = 0.1f; + entity.dangerNotifyChance = 1.0f; + entity.reproductionCountMin = 1; + entity.reproductionCountMax = 1; + + // update data + this.altruistCountDailyData[currentDay] = this.altruistCountDailyData[currentDay] + 1; + this.entities.add(entity); + } + + public void summonCoward () { + // create entity + final Entity entity = new Entity(); + entity.survivalRate = 0.2f; + entity.dangerNotifyChance = 0.0f; + entity.reproductionCountMin = 1; + entity.reproductionCountMax = 1; + + // update data + this.cowardCountDailyData[currentDay] = this.cowardCountDailyData[currentDay] + 1; + this.entities.add(entity); } - public abstract class Entity { - public abstract float getSurvivalRateWhenEnemyMet(); - // Number of entities born on reproduction - public abstract int getReproductionCount(); + public void killEntity (Entity entity) { + // remove from entities + this.entities.remove(entity); + + // update data + if (isAltruist(entity)) + this.altruistCountDailyData[currentDay] = this.altruistCountDailyData[currentDay] - 1; + else this.cowardCountDailyData[currentDay] = this.cowardCountDailyData[currentDay] - 1; + } + + // checking events + public boolean shouldNotify (Entity entity) { + // TODO: 3/21/2023 somehow check if "opponent" is altruist or not and add perception parameter respectively + return eventHappened(entity.dangerNotifyChance); + } + + public boolean metDanger (float dangerChance) { + return eventHappened(dangerChance); + } + + public boolean survivedDanger (Entity entity) { + return eventHappened(entity.survivalRate); + } + + private boolean eventHappened (float chance) { + final float random = Utils.random.get().nextFloat(); + return random <= chance; + } + + // entity utils + public int getReproductionCount (Entity entity) { + if (entity.nutrientsNecessaryForReproduction > entity.currentNutrients) return 0; + return Utils.getRandomNumberInclusive(entity.reproductionCountMin, entity.reproductionCountMax); + } + + public boolean isAltruist (Entity entity) { + return entity.dangerNotifyChance == 1.0f; + } + + // simulation utils + public void printData () { + for (int currentDay = 0; currentDay <= simulationEndDay; currentDay++) { + System.out.println("Current day " + currentDay); + System.out.println("Current Altruists Count is " + this.altruistCountDailyData[currentDay]); + System.out.println("current Cowards Count is " + this.cowardCountDailyData[currentDay]); + System.out.println(); + } } } diff --git a/src/Simulation/Utils.java b/src/Simulation/Utils.java index 2a2b590..299752e 100644 --- a/src/Simulation/Utils.java +++ b/src/Simulation/Utils.java @@ -1,11 +1,24 @@ package Simulation; +import java.util.Comparator; +import java.util.Random; + public class Utils { +// public static final Random random = new Random(); + public static final ThreadLocal random = ThreadLocal.withInitial(() -> new Random(50L)); + public static int getRandomNumber(int min, int max) { - return (int) ((Math.random() * (max - min)) + min); + return random.get().nextInt(max - min) + min; } public static int getRandomNumberInclusive(int min, int max) { return getRandomNumber(min, max + 1); } + + public static Comparator entityComparator = new Comparator() { + @Override + public int compare(Entity o1, Entity o2) { + return Float.compare(o1.dangerNotifyChance, o2.dangerNotifyChance); + } + }; }