diff --git a/project1/.gitignore b/project1/.gitignore index 3ac39be..4b7d0d7 100644 --- a/project1/.gitignore +++ b/project1/.gitignore @@ -33,4 +33,5 @@ out/ .vscode/ chart.png -data.csv \ No newline at end of file +data.csv +*.csv \ No newline at end of file diff --git a/project1/src/main/java/uk/co/cpsd/javaproject1/Animal.java b/project1/src/main/java/uk/co/cpsd/javaproject1/Animal.java index a997848..00aa58c 100644 --- a/project1/src/main/java/uk/co/cpsd/javaproject1/Animal.java +++ b/project1/src/main/java/uk/co/cpsd/javaproject1/Animal.java @@ -1,8 +1,11 @@ package uk.co.cpsd.javaproject1; import java.awt.Color; +import java.util.HashMap; import java.util.List; import java.awt.Point; +import java.util.Map; +import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; public abstract class Animal { @@ -14,6 +17,20 @@ public abstract class Animal { protected int lastReproductionTick = -1; private int age = 0; protected Point position; + protected int generation; // define generation of the animal , + protected double speed; // common traits among all Species + protected double reproductionPower; // common traits among all Species + protected int lastMoveTick=-1; + + protected boolean isPregnant = false; + protected int pregnancyStartTick = -1; + protected int pregnancyDurationTicks = 0; + protected Animal pregnancyPartner; + + protected DNA dna=new DNA(); + + Random random = new Random(); + private final Gender gender; @@ -36,6 +53,33 @@ public Animal(int x, int y, int energyLevel) { this.energyLevel = energyLevel; this.gender = Math.random() < .5 ? Gender.MALE : Gender.FEMALE; this.animalId = idCounter.getAndIncrement(); + + // Initialize DNA traits if not already present + if (!dna.hasTraits("reproductionPower")) { + double reproductionPower = 5.0 + random.nextDouble() * 2; // Random 5-7 + reproductionPower = Math.round(reproductionPower * 100.0) / 100.0; // Round to 2 decimals + dna.setTrait("reproductionPower", reproductionPower); + } + + if (!dna.hasTraits("speed")) { + double speed = 5.0 + random.nextDouble() * 2; // Random 5-7 + speed = Math.round(speed * 100.0) / 100.0; // Round to 2 decimals + dna.setTrait("speed", speed); + } + + if (!dna.hasTraits("generation")) { + dna.setTrait("generation", 1); // Initial generation + } + if (!dna.hasTraits("animalId")) { + dna.setTrait("animalId", animalId); // store as Integer + } + + if(!dna.hasTraits("parentsId")){ + dna.setTrait("parentId","f0m0"); + } + + reproductionPower = dna.getTrait("reproductionPower", Double.class); + speed = dna.getTrait("speed", Double.class); } public Gender getGender() { @@ -70,12 +114,14 @@ public int getEnergy() { public abstract DecisionInfo animalDecisionMaking(World world); + public abstract int moveCooldown(); + public void setPosition(Point point, int cost) { applyMovementCost(cost); this.position = new Point(point); // returns a copy } - public Animal reproduceWithTwo(Animal partner, int currentTick) { + public Animal reproduceWith(Animal partner, int currentTick) { // both animals are of the same species if (!this.getClass().equals(partner.getClass())) { throw new IllegalArgumentException("Animals must be of the same species to reproduce."); @@ -85,17 +131,32 @@ public Animal reproduceWithTwo(Animal partner, int currentTick) { this.lastReproductionTick = currentTick; partner.lastReproductionTick = currentTick; + // Only females get pregnant + Animal female = this.gender == Gender.FEMALE ? this : partner; + Animal male = this.gender == Gender.MALE ? this : partner; + + if (!female.isPregnant) { + female.isPregnant = true; + female.pregnancyStartTick = currentTick; + female.pregnancyDurationTicks = female.getPregnancyDuration(); + female.pregnancyPartner = male; + + // Start of preg + System.out.println("Pregnancy started for " + female.getClass().getSimpleName() + + " ID " + female.animalId + " at tick " + currentTick + + ", duration: " + female.pregnancyDurationTicks + " ticks"); + } + // Subtract energy based on species and gender this.energyLevel -= getReproductionEnergyCost(this.gender); partner.energyLevel -= getReproductionEnergyCost(partner.gender); - // Create baby - Animal baby = createBaby(this.position.x, this.position.y); - baby.energyLevel = getInitialBabyEnergy(); + return null; // Baby created later in act() - return baby; } + protected abstract int getPregnancyDuration(); + public int getLastReproductionTick() { return lastReproductionTick; } @@ -108,10 +169,80 @@ public boolean willMate(Animal otherAnimal, int currentTick) { boolean bothAnimalHaveEnergy = this.isFertile(currentTick) && otherAnimal.isFertile(currentTick); boolean isOppositeGender = this.gender != otherAnimal.gender; + boolean notPregnant = !this.isPregnant && !otherAnimal.isPregnant; + return bothAnimalHaveEnergy && isOppositeGender && notPregnant; + } - return bothAnimalHaveEnergy && isOppositeGender; + protected void handlePregnancy(World world, List babyAnimalHolder) { + + if (isPregnant && (world.getTotalTicks() - pregnancyStartTick >= pregnancyDurationTicks)) { + // Create baby + Animal baby = createBaby(position.x, position.y); + baby.energyLevel = getInitialBabyEnergy(); + + // Initialize baby DNA + // Map babyDNA = new HashMap<>(); + DNA babyDNA=new DNA(); + for (String key : dna.getTraitsName()) { + + if (key.equals("generation") || key.equals("animalId") || key.equals("parentsId")) { + continue; + } + + Object parent1Value = dna.getTrait(key, Object.class); // Mother's value + Object parent2Value = pregnancyPartner != null ? + pregnancyPartner.dna.getTrait(key, Object.class) : parent1Value; + + if (parent1Value instanceof Number && parent2Value instanceof Number) { + + double val1 = ((Number) parent1Value).doubleValue(); + double val2 = ((Number) parent2Value).doubleValue(); + double avgValue = (val1 + val2) / 2.0; + if (random.nextDouble() < 0.1) { + avgValue += random.nextGaussian(); + avgValue = Math.max(0, Math.min(avgValue, 12)); + } + avgValue = Math.round(avgValue * 100.0) / 100.0; + babyDNA.setTrait(key, avgValue); + } else { + babyDNA.setTrait(key, parent1Value); + } + } + + Integer parent1Gen = dna.getTrait("generation", Integer.class); + Integer parent2Gen = pregnancyPartner != null ? + pregnancyPartner.dna.getTrait("generation", Integer.class) : parent1Gen; + babyDNA.setTrait("generation", Math.max(parent1Gen, parent2Gen) + 1); + + babyDNA.setTrait("animalId", baby.animalId); + + //parentsId + String parentsId = "f" + this.animalId + "m" + (pregnancyPartner != null ? pregnancyPartner.animalId : 0); + babyDNA.setTrait("parentsId", parentsId); + + baby.dna = babyDNA; + + // baby birth check + System.out.println("Baby " + baby.getClass().getSimpleName() + " ID " + baby.animalId + + " born to " + this.getClass().getSimpleName() + " ID " + this.animalId + + " at tick " + world.getTotalTicks() + + " at position (" + position.x + "," + position.y + ")"); + + // Log the baby's traits to CSV + world.writeAnimalTraitsToCSV(baby); + + // Reset pregnancy + isPregnant = false; + pregnancyStartTick = -1; + pregnancyDurationTicks = 0; + pregnancyPartner = null; + + // Add baby to the world + babyAnimalHolder.add(baby); + } } + public abstract boolean isFertile(int tick); protected abstract int getReproductionEnergyCost(Gender gender); diff --git a/project1/src/main/java/uk/co/cpsd/javaproject1/DNA.java b/project1/src/main/java/uk/co/cpsd/javaproject1/DNA.java new file mode 100644 index 0000000..45baeb2 --- /dev/null +++ b/project1/src/main/java/uk/co/cpsd/javaproject1/DNA.java @@ -0,0 +1,29 @@ +package uk.co.cpsd.javaproject1; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class DNA { + + private final Map traits=new HashMap<>(); + + public void setTrait(String name, T value){ + traits.put(name,value); + } + + public T getTrait(String name, Class type){ + Object value=traits.get(name); + if(value==null) return null; + return type.cast(value); + + } + + public Set getTraitsName(){ + return traits.keySet(); + } + + public boolean hasTraits(String name){ + return traits.containsKey(name); + } +} diff --git a/project1/src/main/java/uk/co/cpsd/javaproject1/Goat.java b/project1/src/main/java/uk/co/cpsd/javaproject1/Goat.java index 9de4d42..33e12f0 100644 --- a/project1/src/main/java/uk/co/cpsd/javaproject1/Goat.java +++ b/project1/src/main/java/uk/co/cpsd/javaproject1/Goat.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.Map; import java.util.Random; -import java.util.stream.Collectors; import uk.co.cpsd.javaproject1.DecisionInfo.DecisionType; @@ -15,15 +14,23 @@ public class Goat extends Animal { public final int HUNGER_THRESHOLDS = 30; public final int GOAT_MAX_AGE = 60; + private double fleeingPower; public Goat(int x, int y) { super(x, y, 40); this.setLastReproductionTick(0); + Random random = new Random(); + if (!dna.hasTraits("fleeingPower")) { + double fleeingPower = 7.0 + new Random().nextDouble() * 2 - 1; // Random 6-8 + fleeingPower = Math.round(fleeingPower * 100.0) / 100.0; // Round to 2 decimals + dna.setTrait("fleeingPower", fleeingPower); + } + fleeingPower = dna.getTrait("fleeingPower", Double.class); } public void eatGrass() { this.energyLevel += 20; - System.out.println(energyLevel+"<=====Energy level===="); +// System.out.println(energyLevel+"<=====Energy level===="); } @Override @@ -38,15 +45,34 @@ public boolean isHungry() { @Override public void act(World world, List babyAnimalHolder, List removedAnimalsHolder) { - DecisionInfo decisionInfo = animalDecisionMaking(world); + // Handle pregnancy before other actions + handlePregnancy(world, babyAnimalHolder); + DecisionInfo decisionInfo = animalDecisionMaking(world); + int currentTick=world.getTotalTicks(); + boolean canMove= lastMoveTick==-1 || (currentTick-lastMoveTick)>moveCooldown(); + Point nextPos=decisionInfo.nextPos(); switch (decisionInfo.type()) { case EAT: - if (world.hasGrass(decisionInfo.nextPos().x, decisionInfo.nextPos().y)) { - eatGrass(); - world.removeGrass(decisionInfo.nextPos().x, decisionInfo.nextPos().y); - setPosition(decisionInfo.nextPos(), 1); - ; + if (world.hasGrass(nextPos.x, nextPos.y)) { + + if (!nextPos.equals(position)) { + // Check if goat can move due to cooldown + if (canMove) { + eatGrass(); + world.removeGrass(nextPos.x, nextPos.y); + setPosition(nextPos, isPregnant ? 2 : 1); + lastMoveTick = currentTick; + System.out.println("Goat ID " + animalId + " moved to eat at (" + nextPos.x + "," + nextPos.y + ") at tick " + currentTick); + } else { + setPosition(new Point(getX(), getY()), 0); // Stay put + System.out.println("Goat ID " + animalId + " cannot move (cooldown) at tick " + currentTick); + } + } else { + eatGrass(); + world.removeGrass(nextPos.x, nextPos.y); + setPosition(nextPos, 0); // No move, no cost + } } break; case REPRODUCE: @@ -54,17 +80,36 @@ public void act(World world, List babyAnimalHolder, List removed Animal partnerGoat = world.getAnimalAt(partnerLocation.x, partnerLocation.y); if (partnerGoat instanceof Goat otherGoat && this.willMate(otherGoat, world.getTotalTicks())) { - Animal babyGoat = this.reproduceWithTwo(otherGoat, world.getTotalTicks()); - babyAnimalHolder.add(babyGoat); + this.reproduceWith(otherGoat, world.getTotalTicks()); } break; case FLEE: - Point safeRandomPoint = decisionInfo.nextPos(); - setPosition(safeRandomPoint, 5); + if (!nextPos.equals(position)) { + if (canMove) { + setPosition(nextPos, isPregnant ? 7 : 5); + lastMoveTick = currentTick; + System.out.println("Goat ID " + animalId + " at tick " + currentTick); + } else { + setPosition(new Point(getX(), getY()), 0); + System.out.println("Goat ID " + animalId + " cannot flee at tick " + currentTick); + } + } else { + setPosition(nextPos, 0); // No move, no cost + } break; case WANDER: - Point randomMove = decisionInfo.nextPos(); - setPosition(randomMove, 1); + if (!nextPos.equals(position)) { + if (canMove) { + setPosition(nextPos, isPregnant ? 2 : 1); + lastMoveTick = currentTick; + System.out.println("Goat ID " + animalId + " wandered to (" + nextPos.x + "," + nextPos.y + ") at tick " + currentTick); + } else { + setPosition(new Point(getX(), getY()), 0); + System.out.println("Goat ID " + animalId + " cannot wander (cooldown) at tick " + currentTick); + } + } else { + setPosition(nextPos, 0); // Stay in place + } break; } @@ -73,17 +118,17 @@ public void act(World world, List babyAnimalHolder, List removed @Override public DecisionInfo animalDecisionMaking(World world) { - Map> scanedNeighbourHoodByGoat = world.scanNeighbour(getX(), getY()); + Map> scannedNeighbourHoodByGoat = world.scanNeighbour(getX(), getY()); // 1. Priority: Flee from danger - Point safe = findRandomSafePos(scanedNeighbourHoodByGoat); + Point safe = findRandomSafePos(scannedNeighbourHoodByGoat); if (!safe.equals(new Point(getX(), getY()))) { return new DecisionInfo(DecisionType.FLEE, safe); } // 2. Priority: Eat if hungry if (isHungry()) { - for (Map.Entry> entry : scanedNeighbourHoodByGoat.entrySet()) { + for (Map.Entry> entry : scannedNeighbourHoodByGoat.entrySet()) { if (entry.getValue().contains("grass")) { return new DecisionInfo(DecisionType.EAT, entry.getKey()); } @@ -91,7 +136,7 @@ public DecisionInfo animalDecisionMaking(World world) { } // 3. Priority: Reproduce (check nearby goats) - for (Map.Entry> entry : scanedNeighbourHoodByGoat.entrySet()) { + for (Map.Entry> entry : scannedNeighbourHoodByGoat.entrySet()) { for (Object obj : entry.getValue()) { if (obj instanceof Goat otherGoat && this.willMate(otherGoat, world.getTotalTicks())) { return new DecisionInfo(DecisionType.REPRODUCE, entry.getKey()); @@ -99,9 +144,35 @@ public DecisionInfo animalDecisionMaking(World world) { } } - // 4. Default: Random move - Point randomMove = findRandomPos(scanedNeighbourHoodByGoat); - return new DecisionInfo(DecisionType.WANDER, randomMove); + //4. Score tiles for wandering (grass=8 , safe tile preferred, lion=-10) + Point bestMove = null; + double bestScore = -1; + for (Map.Entry> entry : scannedNeighbourHoodByGoat.entrySet()) { + double score = 0; + List objectsAtTile = entry.getValue(); + if (objectsAtTile.contains("grass")) { + score += 8; + } + boolean hasLion = objectsAtTile.stream().anyMatch(obj -> obj instanceof Lion); + if (hasLion) { + score -= 10; + } else { + score += fleeingPower; + } + for (Object obj : objectsAtTile) { + if (obj instanceof Goat) { + score += 6; // Preference to stay near other goats + } + } + if (score > bestScore) { + bestScore = score; + bestMove = entry.getKey(); + } + } + + // 5. Wander: Use bestMove if available, otherwise fall back to random move + Point moveTo = bestMove != null ? bestMove : findRandomPos(scannedNeighbourHoodByGoat); + return new DecisionInfo(DecisionType.WANDER, moveTo); } public Point findRandomSafePos(Map> neighbourHoodPos) { @@ -159,12 +230,24 @@ public Point findRandomPos(Map> neighbourHoodPos) { return new Point(this.getX(), this.getY()); } + @Override + protected int getPregnancyDuration() { + return 5; // 5 ticks = 5 seconds + } + @Override public int getReproductionEnergyCost(Gender gender) { return gender == Gender.FEMALE ? 7 : 5; } + @Override + public int moveCooldown(){ + double speed=dna.getTrait("speed",Double.class); + return (int)Math.max(3,10-speed); + + } + @Override public int getInitialBabyEnergy() { return 10; @@ -175,9 +258,15 @@ public Animal createBaby(int x, int y) { return new Goat(x, y); } +// @Override +// public int getReproductionCooldown(Gender gender) { +// return gender == Gender.FEMALE ? 4 : 2; +// } + @Override public int getReproductionCooldown(Gender gender) { - return gender == Gender.FEMALE ? 4 : 2; + // Strong Males can reproduct soon + return gender==Gender.FEMALE?12:4-(int)(reproductionPower-5); } public boolean hasReachedEndOfLife() { diff --git a/project1/src/main/java/uk/co/cpsd/javaproject1/Lion.java b/project1/src/main/java/uk/co/cpsd/javaproject1/Lion.java index ac98abd..35604b7 100644 --- a/project1/src/main/java/uk/co/cpsd/javaproject1/Lion.java +++ b/project1/src/main/java/uk/co/cpsd/javaproject1/Lion.java @@ -11,18 +11,27 @@ public class Lion extends Animal { - public final int HUNGER_TRESHHOLDS = 50; - private static final int Lion_MAX_AGE = 60; + public final int HUNGER_THRESHOLDS = 40; + private static final int Lion_MAX_AGE = 45; + private double huntingPower; // specific trait for Lions public static int numOfEatenGoats = 0; public Lion(int x, int y) { - super(x, y, 20); + super(x, y, 40); + Random random=new Random(); + if (!dna.hasTraits("huntingPower")) { + double huntingPower = 7.0 + new Random().nextDouble() * 3; // Random 7-10 + huntingPower = Math.round(huntingPower * 100.0) / 100.0; // Round to 2 decimals + dna.setTrait("huntingPower", huntingPower); + } + + huntingPower=dna.getTrait("huntingPower",Double.class); } @Override public boolean isHungry() { - return energyLevel <= HUNGER_TRESHHOLDS; + return energyLevel <= HUNGER_THRESHOLDS; } @Override @@ -33,11 +42,11 @@ public Color getColor() { @Override public DecisionInfo animalDecisionMaking(World world) { - Map> scanedNeighbourHoodByLion = world.scanNeighbour(getX(), getY()); + Map> scannedNeighbourHoodByLion = world.scanNeighbour(getX(), getY()); // 1. Priority: Eat if hungry if (isHungry()) { - for (Map.Entry> entry : scanedNeighbourHoodByLion.entrySet()) { + for (Map.Entry> entry : scannedNeighbourHoodByLion.entrySet()) { for (Object obj : entry.getValue()) { if (obj instanceof Goat) { return new DecisionInfo(DecisionType.EAT, entry.getKey()); @@ -47,7 +56,7 @@ public DecisionInfo animalDecisionMaking(World world) { } // 2. Priority: Reproduce (check nearby lions) - for (Map.Entry> entry : scanedNeighbourHoodByLion.entrySet()) { + for (Map.Entry> entry : scannedNeighbourHoodByLion.entrySet()) { for (Object obj : entry.getValue()) { if (obj instanceof Lion otherLion && this.willMate(otherLion, world.getTotalTicks())) { return new DecisionInfo(DecisionType.REPRODUCE, entry.getKey()); @@ -55,10 +64,30 @@ public DecisionInfo animalDecisionMaking(World world) { } } - // 3. Default: Stay in place or Random move - boolean moveChance = Math.random() < 0.25; + // 3. New: Score tiles based on affinities (goat=10, lion=5) + Point bestMove = null; + double bestScore = -1; + for (Map.Entry> entry : scannedNeighbourHoodByLion.entrySet()) { + double score = 0; + for (Object obj : entry.getValue()) { + if (obj instanceof Goat) { +// score += 10; // Affinity for goats + score+=huntingPower; + } else if (obj instanceof Lion) { + score += 5; // Affinity for other lions + } + } + + if (score > bestScore) { + bestScore = score; + bestMove = entry.getKey(); + } + } + + // 4. Default: Stay in place or Random move + boolean moveChance = Math.random() < 0.2; if (moveChance) { - Point randomMove = findRandomPos(scanedNeighbourHoodByLion); + Point randomMove = findRandomPos(scannedNeighbourHoodByLion); return new DecisionInfo(DecisionType.WANDER, randomMove); } else { Point sameLocation = new Point(getX(), getY()); @@ -76,7 +105,7 @@ public Point findRandomPos(Map> neighbourHoodPos) { } public void eatGoat() { - this.energyLevel += 20; + this.energyLevel += 40; numOfEatenGoats++; // RoarPlayer.playSound("/roar.wav"); @@ -84,46 +113,78 @@ public void eatGoat() { @Override public void act(World world, List babyAnimalHolder, List removedAnimalsHolder) { + // Handle pregnancy before other actions + handlePregnancy(world, babyAnimalHolder); + DecisionInfo decisionInfo = animalDecisionMaking(world); Point nextPos = decisionInfo.nextPos(); + int currentTick=world.getTotalTicks(); + boolean canMove= lastMoveTick==-1 || (currentTick-lastMoveTick)>moveCooldown(); + switch (decisionInfo.type()) { case EAT -> { if (world.getAnimalAt(nextPos.x, nextPos.y) instanceof Goat) { int currentAliveGoats = world.getNumOfAliveGoats(); boolean successfulHunt = attemptHunting(world.getNumOfAliveGoats()) && currentAliveGoats > 10; if (successfulHunt) { - eatGoat(); - world.removeAnimal(nextPos.x, nextPos.y, removedAnimalsHolder); - setPosition(nextPos, 5); + if (!nextPos.equals(position)) { + if (canMove) { + eatGoat(); + world.removeAnimal(nextPos.x, nextPos.y, removedAnimalsHolder); + setPosition(nextPos, 3); + lastMoveTick = currentTick; + } else { + setPosition(new Point(getX(), getY()), 0); + } + } else { + eatGoat(); + world.removeAnimal(nextPos.x, nextPos.y, removedAnimalsHolder); + setPosition(nextPos, 0); + } + } else { Point samePosition = new Point(this.getX(), this.getY()); setPosition(samePosition, 5); - System.out.println("=============Hunting failed============="); } } } case REPRODUCE -> { - Point partnerlocation = decisionInfo.nextPos(); + Point partnerLocation = decisionInfo.nextPos(); Animal partnerLion = world - .getAnimalAt(partnerlocation.x, partnerlocation.y); + .getAnimalAt(partnerLocation.x, partnerLocation.y); - if (partnerLion instanceof Lion otherLion && this.willMate(otherLion, world.getTotalTicks())) { - Animal babyLion = this.reproduceWithTwo(otherLion, world.getTotalTicks()); - babyAnimalHolder.add(babyLion); + if (partnerLion instanceof Lion otherLion && this.willMate(otherLion, currentTick)) { + this.reproduceWith(otherLion, currentTick); } } + case FLEE -> { - Point safeRandomPoint = decisionInfo.nextPos(); - setPosition(safeRandomPoint, 5); + if (!nextPos.equals(position)) { + if (canMove) { + setPosition(nextPos, isPregnant ? 7 : 5); + lastMoveTick = currentTick; + System.out.println("Lion ID " + animalId + " fled to (" + nextPos.x + "," + nextPos.y + ") at tick " + currentTick); + } else { + setPosition(new Point(getX(), getY()), 0); + System.out.println("Lion ID " + animalId + " cannot flee (cooldown) at tick " + currentTick); + } + } else { + setPosition(nextPos, 0); + } + break; } case WANDER -> { - Point randomMove = decisionInfo.nextPos(); - if (randomMove.x == getX() && randomMove.y == getY()) { - setPosition(randomMove, 0); + if (!nextPos.equals(position)) { + if (canMove) { + setPosition(nextPos, isPregnant ? 2 : 1); + lastMoveTick = currentTick; + } else { + setPosition(new Point(getX(), getY()), 0); + } } else { - setPosition(randomMove, 1); + setPosition(nextPos, 0); } } @@ -136,15 +197,18 @@ public boolean hasReachedEndOfLife() { } public double getHuntingChance(int numOfAliveGoats) { - if (numOfAliveGoats <= 10) - return 0.0; - if (numOfAliveGoats <= 15) - return 0.3; - if (numOfAliveGoats <= 25) - return 0.6; - if (numOfAliveGoats <= 30) - return 0.8; - return 1.0; + double baseChance; + if (numOfAliveGoats <= 10) baseChance = 0.0; + else if (numOfAliveGoats <= 15) baseChance = 0.3; + else if (numOfAliveGoats <= 25) baseChance = 0.6; + else if (numOfAliveGoats <= 30) baseChance = 0.8; + else baseChance = 0.95; + return baseChance * (huntingPower / 9.5); + } + + @Override + protected int getPregnancyDuration() { + return 4; // 12 ticks = 12 seconds } public boolean attemptHunting(int numOfAliveGoats) { @@ -153,19 +217,21 @@ public boolean attemptHunting(int numOfAliveGoats) { } public int getReproductionEnergyCost(Gender gender) { - return gender == Gender.FEMALE ? 15 : 10; + return gender == Gender.FEMALE ? 10 : 7; }; + public int getReproductionCooldown(Gender gender) { + // Use reproductionPower to adjust cooldown + int baseCooldown = gender == Gender.FEMALE ? 12 : 2; + return (int)(baseCooldown / (reproductionPower) / 5.0); + } + public Animal createBaby(int x, int y) { return new Lion(x, y); }; public int getInitialBabyEnergy() { - return 20; - }; - - public int getReproductionCooldown(Gender gender) { - return gender == Gender.FEMALE ? 10 : 5; + return 30; }; @Override @@ -176,4 +242,12 @@ public boolean isFertile(int currentTick) { return sinceLastReproduce && hasEnergy; } + @Override + public int moveCooldown(){ + double speed=dna.getTrait("speed", Double.class); + // at the moment lions are slower than Goats + return (int)Math.max(4,10-speed); + + } + } diff --git a/project1/src/main/java/uk/co/cpsd/javaproject1/SimulatorFrame.java b/project1/src/main/java/uk/co/cpsd/javaproject1/SimulatorFrame.java index fad4411..39ee2cc 100644 --- a/project1/src/main/java/uk/co/cpsd/javaproject1/SimulatorFrame.java +++ b/project1/src/main/java/uk/co/cpsd/javaproject1/SimulatorFrame.java @@ -41,6 +41,8 @@ public SimulatorFrame(int tickLimitSimulation, int numOfGoats, int numOfLions, b } }); + + // Start the simulation timer.start(); diff --git a/project1/src/main/java/uk/co/cpsd/javaproject1/World.java b/project1/src/main/java/uk/co/cpsd/javaproject1/World.java index 85fd2f7..13bdabe 100644 --- a/project1/src/main/java/uk/co/cpsd/javaproject1/World.java +++ b/project1/src/main/java/uk/co/cpsd/javaproject1/World.java @@ -2,10 +2,7 @@ import java.io.FileWriter; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Stream; import java.awt.Point; @@ -22,15 +19,23 @@ public class World { private int numOfAliveGoats = 0; private boolean isGUIMode; private final AudioPlayer player; + private static boolean isFirstWrite = true; public World(int numOfGoats, int numOfLions, boolean isGUIMode, AudioPlayer player) { animals = new ArrayList<>(); for (int i = 0; i < numOfGoats; i++) { - animals.add(new Goat((int) (Math.random() * size), (int) (Math.random() * size))); +// animals.add(new Goat((int) (Math.random() * size), (int) (Math.random() * size))); + Animal goat = new Goat((int) (Math.random() * size), (int) (Math.random() * size)); + animals.add(goat); + writeAnimalTraitsToCSV(goat); // Log traits for goats + } for (int j = 0; j < numOfLions; j++) { - animals.add(new Lion((int) (Math.random() * size), (int) (Math.random() * size))); +// animals.add(new Lion((int) (Math.random() * size), (int) (Math.random() * size))); + Animal lion = new Lion((int) (Math.random() * size), (int) (Math.random() * size)); + animals.add(lion); + writeAnimalTraitsToCSV(lion); // Log traits for initial lions } this.isGUIMode = isGUIMode; this.player = player; @@ -215,4 +220,42 @@ public void removeAnimal(int x, int y, List removedAnimalsHolder) { public int getNumOfAliveGoats() { return numOfAliveGoats; } + + public void writeAnimalTraitsToCSV(Animal animal) { + try (FileWriter csvData = new FileWriter("animal_traits.csv", !isFirstWrite)) { + if (isFirstWrite) { + csvData.write("Tick,AnimalId,Species,generation,fleeingPower,huntingPower,speed,parentsId,reproductionPower\n"); + isFirstWrite = false; // Mark file as initialized + } + + StringBuilder row = new StringBuilder(); + row.append(totalTicks).append(","); + row.append(animal.getId()).append(","); + row.append(animal.getClass().getSimpleName()).append(","); + + Integer generation = animal.dna.getTrait("generation", Integer.class); + row.append(generation != null ? generation : 1).append(","); + + Double fleeingPower = animal.dna.getTrait("fleeingPower", Double.class); + row.append(fleeingPower != null ? fleeingPower : 0.0).append(","); + + Double huntingPower = animal.dna.getTrait("huntingPower", Double.class); + row.append(huntingPower != null ? huntingPower : 0.0).append(","); + + Double speed = animal.dna.getTrait("speed", Double.class); + row.append(speed != null ? speed : 0.0).append(","); + + String parentsId = animal.dna.getTrait("parentsId", String.class); + row.append(parentsId != null ? parentsId : "f0m0").append(","); + + Double reproductionPower = animal.dna.getTrait("reproductionPower", Double.class); + row.append(reproductionPower != null ? reproductionPower : 0.0); + + row.append("\n"); + csvData.write(row.toString()); + } catch (IOException e) { + System.out.println("Error writing to animal_traits.csv: " + e); + } + } + }