From 9065ef6af52ae66ef89d37fc88dccb4373b0f7ab Mon Sep 17 00:00:00 2001 From: Febbweiss Date: Thu, 4 Jan 2018 12:16:14 +0000 Subject: [PATCH] Feature: initial commit --- README.md | 23 ++ pom.xml | 63 ++++ src/main/java/fr/pavnay/rabbits/Main.java | 17 + .../fr/pavnay/rabbits/engine/HuntEngine.java | 202 ++++++++++++ .../java/fr/pavnay/rabbits/model/Burrow.java | 36 +++ .../fr/pavnay/rabbits/model/Character.java | 18 ++ .../java/fr/pavnay/rabbits/model/Forest.java | 295 ++++++++++++++++++ .../java/fr/pavnay/rabbits/model/Hunter.java | 129 ++++++++ .../java/fr/pavnay/rabbits/model/Rabbit.java | 91 ++++++ .../java/fr/pavnay/rabbits/model/Tree.java | 22 ++ .../fr/pavnay/rabbits/model/enums/Color.java | 22 ++ .../fr/pavnay/rabbits/model/enums/Speed.java | 25 ++ .../fr/pavnay/rabbits/model/enums/Status.java | 18 ++ .../model/predicate/RangePredicate.java | 25 ++ .../java/fr/pavnay/rabbits/ui/FormFrame.java | 115 +++++++ .../java/fr/pavnay/rabbits/ui/HuntWorker.java | 98 ++++++ .../java/fr/pavnay/rabbits/ui/MainFrame.java | 114 +++++++ .../java/fr/pavnay/rabbits/ui/UiUtils.java | 40 +++ .../fr/pavnay/rabbits/ui/panel/Canvas.java | 71 +++++ .../pavnay/rabbits/ui/panel/CanvasLegend.java | 54 ++++ .../rabbits/ui/panel/CharacterPanel.java | 98 ++++++ .../fr/pavnay/rabbits/ui/panel/LogPanel.java | 28 ++ src/main/resources/log4j.properties | 8 + .../pavnay/rabbits/engine/HuntEngineTest.java | 248 +++++++++++++++ .../fr/pavnay/rabbits/model/ForestTest.java | 246 +++++++++++++++ .../fr/pavnay/rabbits/model/HunterTest.java | 108 +++++++ .../fr/pavnay/rabbits/model/RabbitTest.java | 29 ++ 27 files changed, 2243 insertions(+) create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/fr/pavnay/rabbits/Main.java create mode 100644 src/main/java/fr/pavnay/rabbits/engine/HuntEngine.java create mode 100644 src/main/java/fr/pavnay/rabbits/model/Burrow.java create mode 100644 src/main/java/fr/pavnay/rabbits/model/Character.java create mode 100644 src/main/java/fr/pavnay/rabbits/model/Forest.java create mode 100644 src/main/java/fr/pavnay/rabbits/model/Hunter.java create mode 100644 src/main/java/fr/pavnay/rabbits/model/Rabbit.java create mode 100644 src/main/java/fr/pavnay/rabbits/model/Tree.java create mode 100644 src/main/java/fr/pavnay/rabbits/model/enums/Color.java create mode 100644 src/main/java/fr/pavnay/rabbits/model/enums/Speed.java create mode 100644 src/main/java/fr/pavnay/rabbits/model/enums/Status.java create mode 100644 src/main/java/fr/pavnay/rabbits/model/predicate/RangePredicate.java create mode 100644 src/main/java/fr/pavnay/rabbits/ui/FormFrame.java create mode 100644 src/main/java/fr/pavnay/rabbits/ui/HuntWorker.java create mode 100644 src/main/java/fr/pavnay/rabbits/ui/MainFrame.java create mode 100644 src/main/java/fr/pavnay/rabbits/ui/UiUtils.java create mode 100644 src/main/java/fr/pavnay/rabbits/ui/panel/Canvas.java create mode 100644 src/main/java/fr/pavnay/rabbits/ui/panel/CanvasLegend.java create mode 100644 src/main/java/fr/pavnay/rabbits/ui/panel/CharacterPanel.java create mode 100644 src/main/java/fr/pavnay/rabbits/ui/panel/LogPanel.java create mode 100644 src/main/resources/log4j.properties create mode 100644 src/test/java/fr/pavnay/rabbits/engine/HuntEngineTest.java create mode 100644 src/test/java/fr/pavnay/rabbits/model/ForestTest.java create mode 100644 src/test/java/fr/pavnay/rabbits/model/HunterTest.java create mode 100644 src/test/java/fr/pavnay/rabbits/model/RabbitTest.java diff --git a/README.md b/README.md new file mode 100644 index 0000000..07a8994 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Rabbits vs Hunter + +## Goal + +This projects manages a rabbit hunt. +It's possible to change some criteria such as trees, burrows and rabbits counts. + + +## Building + +Using maven, just execute the following command : +``` +mvn clean package +``` + +In the new _target_ folder, you will find the _rabbits-vs-hunter-[version]-jar-with-dependencies.jar_ jar file. This jar contains all dependencies. + +## Running + +Execute the built jar : +``` +java -jar rabbits-vs-hunter-[version]-jar-with-dependencies.jar +``` \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1a15a04 --- /dev/null +++ b/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + fr.pavnay + rabbits-vs-hunter + 1.0-SNAPSHOT + jar + + + + org.apache.commons + commons-collections4 + 4.0 + + + log4j + log4j + 1.2.17 + + + + + junit + junit + 4.12 + test + + + + 1.7 + 1.7 + + + + + maven-assembly-plugin + 3.1.0 + + + jar-with-dependencies + + + + fr.pavnay.rabbits.Main + + + + + + make-assembly + + package + + + single + + + + + + + \ No newline at end of file diff --git a/src/main/java/fr/pavnay/rabbits/Main.java b/src/main/java/fr/pavnay/rabbits/Main.java new file mode 100644 index 0000000..7e8b203 --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/Main.java @@ -0,0 +1,17 @@ +package fr.pavnay.rabbits; + +import fr.pavnay.rabbits.ui.FormFrame; + +/** + * + * The main class to run + * + */ +public class Main { + + public static void main(String[] args) { + + new FormFrame(); + + } +} diff --git a/src/main/java/fr/pavnay/rabbits/engine/HuntEngine.java b/src/main/java/fr/pavnay/rabbits/engine/HuntEngine.java new file mode 100644 index 0000000..cf9c768 --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/engine/HuntEngine.java @@ -0,0 +1,202 @@ +package fr.pavnay.rabbits.engine; + +import java.awt.Point; +import java.util.List; +import java.util.Random; + +import org.apache.log4j.Logger; + +import fr.pavnay.rabbits.model.Burrow; +import fr.pavnay.rabbits.model.Character; +import fr.pavnay.rabbits.model.Forest; +import fr.pavnay.rabbits.model.Hunter; +import fr.pavnay.rabbits.model.Rabbit; +import fr.pavnay.rabbits.model.enums.Speed; +import fr.pavnay.rabbits.model.enums.Status; + +/** + * + * This class provides the hunt mechanisms like moving, shooting, etc... + * + */ +public class HuntEngine { + + private static final Logger logger = Logger.getLogger(HuntEngine.class); + + private Forest forest; + private Hunter hunter; + + private int stepCount = 0; + private int lastShotStep = -4; + private Status status = Status.RUNNING; + + public HuntEngine(Forest forest, Hunter hunter) { + this.forest = forest; + this.hunter = hunter; + } + public Forest getForest() { + return forest; + } + public Hunter getHunter() { + return hunter; + } + public Status getStatus() { + return status; + } + + /** + * The core engine method. To have a full hunt cycle, call this method in a loop still the HuntEngine.getStatus() returns a value different than RUNNING. + * + * @return + */ + public Status step() { + logger.debug("Step " + stepCount); + boolean hunterHasShot = false; // Only one shot per step + int stepSinceLastShot = stepCount - lastShotStep; + + move(hunter); + + Random rand = new Random(); + for (Rabbit rabbit : forest.getRabbits() ) { + + if( stepSinceLastShot > 3 && !rabbit.isScared() ) { + if( rabbit.getSpeed() > Speed.MEDIUM.getSpeed() ) { + rabbit.slowDown(); + } else { + rabbit.setSpeed(rand.nextInt(Speed.RUNNING.getSpeed())); + } + } else if( stepSinceLastShot > 5 && rabbit.isScared() ) { + rabbit.setScared(false); + } + + move(rabbit); + + boolean canShoot = !hunterHasShot && stepSinceLastShot > 5; // One shot per step and only 5 steps after the last shot. + + if( canShoot && forest.canView( hunter, rabbit ) && hunter.isInRange(rabbit) ) { + hunterHasShot = true; + lastShotStep = stepCount; + if( hunter.shoot(rabbit) ) { + forest.killedRabbit(rabbit); + } else { + logger.info(String.format("Hunter missed a shot. %d ammos remaining", hunter.getAmmos())); + hunter.purchase(rabbit); + } + } + } + + // A shot occurs, so rabbits must escape + if( hunterHasShot ) { + logger.debug("Escape from the hunter"); + forest.escape(hunter); + } + + stepCount++; + return updateStatus(); + } + + /** + * The rabbit must choose a destination - Random, the last one or a burrow + * + * @param rabbit The rabbit to move + */ + protected void move( Rabbit rabbit ) { + Point location = rabbit.getLocation(); + Point destination = rabbit.getDestination(); + + if( location.distance(destination) <= rabbit.getSpeed() ) { + List refuges = rabbit.getRefuges(); + if( refuges.size() > 0 ) { // Purchased rabbit + Burrow refuge = refuges.get( refuges.size() -1 ); + if( refuge.isFree() ) { // Only one rabbit per burrow + refuge.setFree(false); + forest.savedRabbit( rabbit ); + } else { + Burrow burrow = forest.getNearestBurrow(hunter, rabbit); // Find another burrow + rabbit.setRefuge(burrow); // Keep in mind this burrow is full + rabbit.setDestination(burrow.getLocation()); + } + } else { + setRandomDestination(rabbit); + } + } + + if( rabbit.getDestination() != null ) { + go(rabbit); + } + } + + /** + * The hunter must choose a destination : the last one or a new randomized destination. + * + * @param hunter + */ + protected void move( Hunter hunter ) { + Point location = hunter.getLocation(); + Point destination = hunter.getDestination(); + + if( location.distance(destination) < hunter.getSpeed() ) { + destination = setRandomDestination(hunter); + } + + go(hunter); + } + + /** + * Move the given character (Hunter or Rabbit) + * + * @param character + */ + protected void go(Character character) { + Point location = character.getLocation(); + Point destination = character.getDestination(); + + double x = destination.getX() - location.getX(); + double y = destination.getY() - location.getY(); + + double rate = character.getSpeed() / location.distance(destination); + + double newX = Math.max(0, location.getX() + x * rate); + double newY = Math.max(0, location.getY() + y * rate); + Point newLocation = new Point((int) Math.min(newX, forest.getEdgeArea()), (int) Math.min(newY, forest.getEdgeArea())); + character.increaseDistance((int) location.distance(newLocation)); + location.setLocation( newLocation ); + } + + /** + * "Choose" a random destination for the given character (Hunter or Rabbit) + * + * @param character The character to move + * @return The new destination + */ + protected Point setRandomDestination(Character character) { + Random rand = new Random(); + Point destination = new Point(rand.nextInt(forest.getEdgeArea()), rand.nextInt(forest.getEdgeArea())); + character.setDestination(destination); + + return destination; + } + + /** + * Update the hunt status + * + * @return The new Status + */ + protected Status updateStatus() { + if( forest.getRabbits().size() == 0 ) { // No more running rabbits, so the hunt ends + if( (forest.getRabbits().size() + forest.getSavedRabbits().size()) < forest.getDeadRabbits().size() ) { // Too many dead rabbit, the hunter wins + status = Status.HUNTER_WINS; + } else { + status = Status.RABBITS_WIN; // else, rabbits win + } + logger.info("Hunt ended : " + status); + } else if( hunter.getAmmos() == 0 ) { // No more ammos, rabbits win + status = Status.RABBITS_WIN; + logger.info("Hunt ended : " + status); + } else { // Hunt still running + status = Status.RUNNING; + } + return status; + } + +} diff --git a/src/main/java/fr/pavnay/rabbits/model/Burrow.java b/src/main/java/fr/pavnay/rabbits/model/Burrow.java new file mode 100644 index 0000000..97a28a8 --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/model/Burrow.java @@ -0,0 +1,36 @@ +package fr.pavnay.rabbits.model; + +import java.awt.Point; + +/** + * + * A rabbit burrow - Only one rabbit per burrow. + * Burrows are filled when a rabbit is purchased. + * + */ +public class Burrow { + + private Point location; + private boolean free; + + public Burrow( Point location ) { + this.location = location; + free = true; + } + + public Point getLocation() { + return location; + } + public void setFree(boolean free) { + this.free = free; + } + public boolean isFree() { + return free; + } + + @Override + public String toString() { + return "Burrow [location=" + location + ", free=" + free + "]"; + } + +} diff --git a/src/main/java/fr/pavnay/rabbits/model/Character.java b/src/main/java/fr/pavnay/rabbits/model/Character.java new file mode 100644 index 0000000..9b0cc35 --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/model/Character.java @@ -0,0 +1,18 @@ +package fr.pavnay.rabbits.model; + +import java.awt.Point; + +/** + * + * An interface to manage hunter and rabbits in the same way. + * + */ +public interface Character { + + public Point getLocation(); + public Point getDestination(); + public void setDestination(Point destination); + + public int getSpeed(); + public void increaseDistance(int distance); +} diff --git a/src/main/java/fr/pavnay/rabbits/model/Forest.java b/src/main/java/fr/pavnay/rabbits/model/Forest.java new file mode 100644 index 0000000..18cc690 --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/model/Forest.java @@ -0,0 +1,295 @@ +package fr.pavnay.rabbits.model; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.log4j.Logger; + +import fr.pavnay.rabbits.model.predicate.RangePredicate; + +/** + * + * The main "entity" where rabbits live. + * + */ +public class Forest { + + private static final Logger logger = Logger.getLogger(Forest.class); + + private List trees = new ArrayList(); + private List rabbits = new CopyOnWriteArrayList(); + private List savedRabbits = new CopyOnWriteArrayList(); + private List deadRabbits = new CopyOnWriteArrayList(); + private List burrows = new ArrayList(); + + private int edgeArea; + + public Forest( int area, int treesCount, int burrowsCount, int rabbitsCount ) { + if( area <= 0 || area > 10) { + throw new IllegalArgumentException("Area must be between 1 and 10 "); + } + + if( treesCount <= 0 || treesCount > 1000) { + throw new IllegalArgumentException("Tree count must be between 1 and 1000 "); + } + + if( rabbitsCount <= 0 ) { + throw new IllegalArgumentException("Add some rabbits"); + } + + if( burrowsCount <= 0 || burrowsCount < rabbitsCount) { + throw new IllegalArgumentException("Add some burrows. Greater or equals than rabbits count."); + } + + + edgeArea = (int) Math.sqrt(area * 1000); + + populate(treesCount, burrowsCount, rabbitsCount); + } + + /** + * Add trees, burrows and rabbits to the forest. These entities have random location (and rabbits random destination) + * + * @param treesCount Number of trees to create + * @param burrowsCount Number of burrows to create + * @param rabbitsCount Number of rabbits to create + */ + private void populate(int treesCount, int burrowsCount, int rabbitsCount) { + Random rand = new Random(); + for( int i = 0; i < treesCount; i++ ) { + trees.add(new Tree(new Point(rand.nextInt(edgeArea), rand.nextInt(edgeArea)))); + } + + for( int i = 0; i < burrowsCount; i++ ) { + burrows.add(new Burrow(new Point(rand.nextInt(edgeArea), rand.nextInt(edgeArea)))); + } + + for( int i = 0; i < rabbitsCount; i++ ) { + Rabbit rabbit = new Rabbit(new Point(rand.nextInt(edgeArea), rand.nextInt(edgeArea))); + rabbit.setDestination(new Point(rand.nextInt(edgeArea), rand.nextInt(edgeArea))); + rabbits.add(rabbit); + } + } + + public int getEdgeArea() { + return edgeArea; + } + + public List getRabbits() { + return rabbits; + } + + /** + * + * @param hunter + * @return Rabbits in hunter range + */ + public List getRabbitsInRange(Hunter hunter) { + return (List) CollectionUtils.select(rabbits, new RangePredicate(hunter)); + } + + /** + * + * @return Dead rabbits + */ + public List getDeadRabbits() { + return deadRabbits; + } + + /** + * + * @return Rabbits in burrows + */ + public List getSavedRabbits() { + return savedRabbits; + } + + /** + * Move a rabbit from "running" to "dead" + * @param rabbit + */ + public void killedRabbit(Rabbit rabbit) { + if( rabbits.remove(rabbit) ) { + logger.info("Poor rabbit... Killed..."); + deadRabbits.add(rabbit); + } else { + throw new RuntimeException("Unable to kill a poor rabbit..."); + } + } + + /** + * Move a rabbit from "running" to "saved" + * @param rabbit + */ + public void savedRabbit(Rabbit rabbit) { + if( rabbits.remove(rabbit) ) { + logger.info("In a burrow... Rabbit saved"); + rabbit.setDestination(null); + savedRabbits.add(rabbit); + } else { + throw new RuntimeException("Unable to save a poor rabbit..."); + } + } + + public List getBurrows() { + return burrows; + } + + /** + * Rabbits in hunter range must escape + * @param hunter + */ + public void escape(Hunter hunter) { + for( Rabbit rabbit : getRabbitsInRange(hunter) ) { + escape(hunter, rabbit); + } + } + + /** + * Choose a rabbit destination to escape. If the given rabbit is scared (the one on which the hunter shot), it must find a burrow. + * The others must go at the opposite of hunter location. + * + * @param hunter + * @param rabbit + */ + protected void escape( Hunter hunter, Rabbit rabbit ) { + Point target = null; + if( rabbit.isScared() ) { + logger.debug("Rabbit's searching a burrow"); + Burrow refuge = getNearestBurrow(hunter, rabbit); + rabbit.setRefuge(refuge); + } else { + logger.debug("Go away from the hunter"); + // Hunter -> Rabbit vector + double a = rabbit.getLocation().getX() - hunter.getLocation().getX(); + double b = rabbit.getLocation().getY() - hunter.getLocation().getY(); + + // Keep in forest + double x = Math.max(0, rabbit.getLocation().getX() + a); + double y = Math.max(0, rabbit.getLocation().getY() + b); + target = new Point((int) Math.min(x, edgeArea), (int) Math.min(y, edgeArea)); + rabbit.setDestination(target); + } + + } + + /** + * Compute 2 nearest burrows. One with back to the hunter and another more safe. The last one is preferred + * + * @param hunter + * @param rabbit + * @return + */ + public Burrow getNearestBurrow(Hunter hunter, Rabbit rabbit) { + logger.debug("Searching the nearest burrow"); + double distance = Double.MAX_VALUE, worthDistance = Double.MAX_VALUE; + Burrow nearest = null, worthNearest = null; + + for( Burrow burrow : burrows ) { + if( rabbit.getRefuges().contains(burrow) ) { + continue; + } + double tmpDistance = burrow.getLocation().distance(rabbit.getLocation()); + double dotProduct = dotProduct(hunter.getLocation(), rabbit.getLocation(), burrow.getLocation()); + if( dotProduct <= 0 && tmpDistance < distance ) { + distance = tmpDistance; + nearest = burrow; + } + else if( dotProduct > 0 && tmpDistance < worthDistance ) { // Back to the hunter + worthDistance = tmpDistance; + worthNearest = burrow; + } + } + + return distance <= worthDistance ? nearest : worthNearest; + } + + public List getTrees() { + return trees; + } + + /** + * A rabbit is viewed when there are no trees between the hunter and it. + * + * @param hunter + * @param rabbit + * @return + */ + public boolean canView(Hunter hunter, Rabbit rabbit) { + for( Tree tree : trees ) { + if( isBetween(hunter.getLocation(), rabbit.getLocation(), tree.getLocation())) { + logger.debug("Rabbit is hidden"); + return false; + } + }; + + return true; + } + + private static final int EPSILON = 5; + + /** + * Checks if a tree is between a rabbit and the hunter. + * + * @param hunter + * @param rabbit + * @param tree + * @return + */ + protected boolean isBetween(Point hunter, Point rabbit, Point tree) { + double crossproduct = (tree.getY() - hunter.getY()) * (rabbit.getX() - hunter.getX()) - (tree.getX() - hunter.getX()) * (rabbit.getY() - hunter.getY()); + if (Math.abs(crossproduct) > EPSILON ) { + return false; + } + + double dotproduct = dotProduct(hunter, rabbit, tree); // Tree is far away + if ( dotproduct < 0 ) { + return false; + } + + double squaredlengthba = (rabbit.getX()- hunter.getX())*(rabbit.getX() - hunter.getX()) + (rabbit.getY() - hunter.getY())*(rabbit.getY() - hunter.getY()); + if ( dotproduct > squaredlengthba) { // Tree is too far + return false; + } + + return true; + } + + /** + * dotProduct to know if the tree is between the hunter and the rabbit. + * + * @param hunter + * @param rabbit + * @param tree + * @return If positive, the tree is between. + */ + private double dotProduct(Point hunter, Point rabbit, Point tree) { + return (tree.getX() - hunter.getX()) * (rabbit.getX() - hunter.getX()) + (tree.getY() - hunter.getY())*(rabbit.getY() - hunter.getY()); + } + + + public void printStats() { + logger.debug("Running rabbits"); + printRabbits(rabbits); + logger.debug("Saved rabbits"); + printRabbits(savedRabbits); + logger.debug("Dead rabbits"); + printRabbits(deadRabbits); + } + private void printRabbits(List rabbits) { + for( Rabbit rabbit : rabbits ) { + logger.debug(rabbit); + } + + } + @Override + public String toString() { + return "Forest [trees=" + trees + ", rabbits=" + rabbits + ", burrows=" + burrows + ", edgeArea=" + edgeArea + + "]"; + } + +} diff --git a/src/main/java/fr/pavnay/rabbits/model/Hunter.java b/src/main/java/fr/pavnay/rabbits/model/Hunter.java new file mode 100644 index 0000000..b105528 --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/model/Hunter.java @@ -0,0 +1,129 @@ +package fr.pavnay.rabbits.model; + +import java.awt.Point; + +import org.apache.log4j.Logger; + +import fr.pavnay.rabbits.model.enums.Speed; + +public class Hunter implements Character { + + private static final Logger logger = Logger.getLogger(Hunter.class); + + private final static int INITIAL_AMMOS = 10; + + private int ammos = INITIAL_AMMOS; + private int hungryLevel = 0; + private int distance = 0; + private Point location; + private int range; + private int speed; + + private Point destination; + + public Hunter( Point location, int range ) { + this.location = location; + this.range = range; + this.speed = Speed.MEDIUM.getSpeed(); + } + + public int getAmmos() { + return ammos; + } + public void setAmmos(int ammos) { + this.ammos = ammos; + } + public int getHungryLevel() { + return hungryLevel; + } + public void setHungryLevel(int hungryLevel) { + this.hungryLevel = hungryLevel; + } + public int getDistance() { + return distance; + } + @Override + public Point getLocation() { + return location; + } + @Override + public Point getDestination() { + return destination; + } + @Override + public void setDestination(Point destination) { + this.destination = destination; + } + @Override + public int getSpeed() { + return speed; + } + /** + * Increasing the walked distance increases the hungry level + * + */ + @Override + public void increaseDistance(int distance) { + this.distance += distance; + setHungryLevel(Math.min(10, (int) (this.distance / 15))); + } + + /** + * Tries to shoot a rabbit + * + * @param rabbit + * @return + */ + public boolean shoot(Rabbit rabbit) { + if( ammos == 0 ) { + logger.info("Click... No more ammos"); + return false; + } + double rate = getAccuracyRate(rabbit); + ammos--; + return Math.random() < rate; // If the random value is less than the computed accuracy, the shot is good + } + + /** + * The hunter finds a rabbit. He goes to its last position. + * + * @param rabbit + */ + public void purchase(Rabbit rabbit) { + rabbit.purchased(); + destination = rabbit.getLocation(); + } + + /** + * Accuracy depends to the distance with the target, the number of leaving ammos and the hungry level. + * + * @param rabbit + * @return + */ + protected double getAccuracyRate(Rabbit rabbit) { + double rate = 1 - getLocation().distance(rabbit.getLocation()) / range; + + rate -= 0.005 * (INITIAL_AMMOS - ammos); + rate -= 0.01 * hungryLevel; + + return rate; + } + + /** + * The given rabbit is in range if the distance between the hunter and the rabbit is less than the hunter range. + * + * @param rabbit + * @return + */ + public boolean isInRange(Rabbit rabbit) { + logger.debug(location.distance(rabbit.getLocation()) + "<=" + range); + return location.distance(rabbit.getLocation()) <= range; + } + + @Override + public String toString() { + return "Hunter [ammos=" + ammos + ", hungryLevel=" + hungryLevel + ", distance=" + distance + ", location=" + + location + ", range=" + range + "]"; + } + +} diff --git a/src/main/java/fr/pavnay/rabbits/model/Rabbit.java b/src/main/java/fr/pavnay/rabbits/model/Rabbit.java new file mode 100644 index 0000000..9d2a3c2 --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/model/Rabbit.java @@ -0,0 +1,91 @@ +package fr.pavnay.rabbits.model; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.List; + +import fr.pavnay.rabbits.model.enums.Color; +import fr.pavnay.rabbits.model.enums.Speed; + +public class Rabbit implements Character { + + private Point location; + private int speed; + private Color color; + private int distance; + + private Point destination; + + private List refuges = new ArrayList(); + private boolean scared; + + public Rabbit(Point location) { + this.color = Math.random() < 0.5 ? Color.BROWN : Color.WHITE; + this.location = location; + speed = Speed.SLOW.getSpeed(); + distance = 0; + } + + @Override + public Point getLocation() { + return location; + } + @Override + public int getSpeed() { + return speed; + } + @Override + public void increaseDistance(int distance) { + this.distance += distance; + } + public void setSpeed(int speed) { + this.speed = speed; + } + public Color getColor() { + return color; + } + public int getDistance() { + return distance; + } + public boolean isScared() { + return scared; + } + public void setScared(boolean scared) { + this.scared = scared; + } + @Override + public void setDestination(Point destination) { + this.destination = destination; + } + @Override + public Point getDestination() { + return destination; + } + + /** + * The rabbit finds a burrow. So it keeps in mind its location and to go it. + * + * @param refuge + */ + public void setRefuge(Burrow refuge) { + this.refuges.add( refuge ); + destination = refuge.getLocation(); + } + public List getRefuges() { + return refuges; + } + public void slowDown() { + speed = Math.max(0, speed - 1); + } + + public void purchased() { + scared = true; + } + + @Override + public String toString() { + return "Rabbit [location=" + location + ", speed=" + speed + ", color=" + color + ", distance=" + distance + + "]"; + } + +} diff --git a/src/main/java/fr/pavnay/rabbits/model/Tree.java b/src/main/java/fr/pavnay/rabbits/model/Tree.java new file mode 100644 index 0000000..060cfa6 --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/model/Tree.java @@ -0,0 +1,22 @@ +package fr.pavnay.rabbits.model; + +import java.awt.Point; + +public class Tree { + + private Point location; + + public Tree( Point location ) { + this.location = location; + } + + public Point getLocation() { + return location; + } + + @Override + public String toString() { + return "Tree [location=" + location + "]"; + } + +} diff --git a/src/main/java/fr/pavnay/rabbits/model/enums/Color.java b/src/main/java/fr/pavnay/rabbits/model/enums/Color.java new file mode 100644 index 0000000..ec65c18 --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/model/enums/Color.java @@ -0,0 +1,22 @@ +package fr.pavnay.rabbits.model.enums; + +/** + * + * An utility enum to give color to rabbits + * + */ +public enum Color { + + WHITE(java.awt.Color.WHITE), + BROWN(new java.awt.Color(149, 86, 40)); + + private java.awt.Color color; + + private Color( java.awt.Color color) { + this.color = color; + } + + public java.awt.Color getColor() { + return color; + } +} diff --git a/src/main/java/fr/pavnay/rabbits/model/enums/Speed.java b/src/main/java/fr/pavnay/rabbits/model/enums/Speed.java new file mode 100644 index 0000000..1cf0e91 --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/model/enums/Speed.java @@ -0,0 +1,25 @@ +package fr.pavnay.rabbits.model.enums; + +/** + * + * An utility enum to give some default speeds + * + */ +public enum Speed { + + STOPPED(0), + SLOW(1), + MEDIUM(5), + RUNNING(10); + + private int speed; + + private Speed(int speed) { + this.speed = speed; + } + + public int getSpeed() { + return speed; + } + +} diff --git a/src/main/java/fr/pavnay/rabbits/model/enums/Status.java b/src/main/java/fr/pavnay/rabbits/model/enums/Status.java new file mode 100644 index 0000000..c609fe6 --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/model/enums/Status.java @@ -0,0 +1,18 @@ +package fr.pavnay.rabbits.model.enums; + +/** + * + * The hunt status + * + */ +public enum Status { + + RUNNING, + HUNTER_WINS, + RABBITS_WIN; + + @Override + public String toString() { + return name().charAt(0) + name().substring(1).toLowerCase().replace('_', ' '); + } +} diff --git a/src/main/java/fr/pavnay/rabbits/model/predicate/RangePredicate.java b/src/main/java/fr/pavnay/rabbits/model/predicate/RangePredicate.java new file mode 100644 index 0000000..a5291f2 --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/model/predicate/RangePredicate.java @@ -0,0 +1,25 @@ +package fr.pavnay.rabbits.model.predicate; + +import org.apache.commons.collections4.Predicate; + +import fr.pavnay.rabbits.model.Hunter; +import fr.pavnay.rabbits.model.Rabbit; + +/** + * + * An utility class to check if a rabbit is in the hunter range + * + */ +public class RangePredicate implements Predicate { + + private Hunter hunter; + + public RangePredicate(Hunter hunter) { + this.hunter = hunter; + } + + public boolean evaluate(Rabbit rabbit) { + return hunter.isInRange(rabbit); + } + +} diff --git a/src/main/java/fr/pavnay/rabbits/ui/FormFrame.java b/src/main/java/fr/pavnay/rabbits/ui/FormFrame.java new file mode 100644 index 0000000..d2dee9f --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/ui/FormFrame.java @@ -0,0 +1,115 @@ +package fr.pavnay.rabbits.ui; + +import java.awt.Dimension; +import java.awt.GridBagLayout; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.text.NumberFormat; +import java.util.Random; + +import javax.swing.JButton; +import javax.swing.JFormattedTextField; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.text.NumberFormatter; + +import fr.pavnay.rabbits.engine.HuntEngine; +import fr.pavnay.rabbits.model.Forest; +import fr.pavnay.rabbits.model.Hunter; + +/** + * + * The entry point in which forest area, trees, burrows and rabbit numbers are set. + * + */ +public class FormFrame extends JFrame implements ActionListener, PropertyChangeListener { + + private static final long serialVersionUID = 4594334659003442873L; + + private JFormattedTextField areaInput; // Only number with specificities (min, max) + private JFormattedTextField treeInput; + private JFormattedTextField rabbitInput; + private JFormattedTextField burrowInput; + + private JButton validateBtn; + + public FormFrame() { + setTitle("Rabbits vs Hunter"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + setLayout(new GridBagLayout()); + UiUtils.add(this, new JLabel("Area"), 0, 0, 1, 1); + areaInput = new JFormattedTextField(getFormatter(1, 10)); + areaInput.setValue(10); + areaInput.addActionListener(this); + UiUtils.add(this, areaInput, 1, 0, 1, 1); + UiUtils.add(this, new JLabel("Trees"), 0, 1, 1, 1); + + treeInput = new JFormattedTextField(getFormatter(0, 300)); + treeInput.setValue(20); + treeInput.addActionListener(this); + UiUtils.add(this, treeInput, 1, 1, 1, 1); + UiUtils.add(this, new JLabel("Rabbits"), 0, 2, 1, 1); + rabbitInput = new JFormattedTextField(getFormatter(1, 20)); + rabbitInput.setValue(5); + rabbitInput.addActionListener(this); + UiUtils.add(this, rabbitInput, 1, 2, 1, 1); + UiUtils.add(this, new JLabel("Burrows"), 0, 3, 1, 1); + burrowInput = new JFormattedTextField(getFormatter(1, 20)); + burrowInput.setValue(10); + burrowInput.addPropertyChangeListener(this); + UiUtils.add(this, burrowInput, 1, 3, 1, 1); + + validateBtn = new JButton("Validate"); + validateBtn.addActionListener(this); + + UiUtils.add(this, validateBtn, 0, 4, 2, 1); + pack(); + setLocationRelativeTo(null); + setVisible(true); + } + + private NumberFormatter getFormatter( int min, int max) { + NumberFormat format = NumberFormat.getInstance(); + NumberFormatter formatter = new NumberFormatter(format); + formatter.setValueClass(Integer.class); + formatter.setMinimum(min); + formatter.setMaximum(max); + formatter.setAllowsInvalid(false); + return formatter; + } + + @Override + public void actionPerformed(ActionEvent event) { + if( Integer.parseInt(burrowInput.getText()) < Integer.parseInt(rabbitInput.getText()) ) { + JOptionPane.showMessageDialog(this, "Add some burrows. Greater or equals than rabbits count.", "Error", JOptionPane.ERROR_MESSAGE); + } else { + Forest forest = new Forest(Integer.parseInt(areaInput.getText()), Integer.parseInt(treeInput.getText()), + Integer.parseInt(burrowInput.getText()), Integer.parseInt(rabbitInput.getText())); + + Random rand = new Random(); + Hunter hunter = new Hunter(new Point(rand.nextInt(forest.getEdgeArea()), rand.nextInt(forest.getEdgeArea())), 20); + hunter.setDestination(new Point(rand.nextInt(forest.getEdgeArea()), rand.nextInt(forest.getEdgeArea()))); + HuntEngine engine = new HuntEngine(forest, hunter); + + new MainFrame(engine); + dispose(); + } + } + + @Override + public void propertyChange(PropertyChangeEvent arg0) { + validateBtn.setEnabled(!"".equals(areaInput.getText()) && !"".equals(treeInput.getText()) + && !"".equals(rabbitInput.getText()) && !"".equals(burrowInput.getText())); + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(250, 200); + } + +} diff --git a/src/main/java/fr/pavnay/rabbits/ui/HuntWorker.java b/src/main/java/fr/pavnay/rabbits/ui/HuntWorker.java new file mode 100644 index 0000000..e9721ca --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/ui/HuntWorker.java @@ -0,0 +1,98 @@ +package fr.pavnay.rabbits.ui; + +import java.util.List; + +import javax.swing.SwingWorker; + +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; + +import fr.pavnay.rabbits.engine.HuntEngine; +import fr.pavnay.rabbits.model.enums.Status; + +/** + * + * This worker provides the loop to play HuntEngine.step() until the status is different to RUNNING + * + */ +public class HuntWorker extends SwingWorker { + + private static final Logger logger = Logger.getLogger(HuntWorker.class); + + public final static long SLEEP_TIME = 500; + + private HuntEngine engine; + private MainFrame mainFrame; + + public HuntWorker(HuntEngine engine, MainFrame mainFrame ) { + this.engine = engine; + this.mainFrame = mainFrame; + HuntLogAppender appender = new HuntLogAppender(this); + LogManager.getLogger("fr.pavnay.rabbits").addAppender(appender); + } + + public void appendLog(String log) { + publish(log); + } + + /** + * Provides some hunt event from logs to UI + */ + @Override + protected void process(List logs) { + for(String log : logs) { + mainFrame.report(log); + } + } + + @Override + protected Status doInBackground() throws Exception { + Status status = engine.getStatus(); + try { + while( status == Status.RUNNING ) { + status = engine.step(); + Thread.sleep(SLEEP_TIME); + } + }catch( Exception e) { + logger.error(e.getMessage(), e); + } + logger.debug("Hunt ended : " + engine.getStatus()); + return engine.getStatus(); + } + + @Override + protected void done() { + super.done(); + logger.debug("Done - " + engine.getStatus()); + } + + /** + * + * A log appender to retrieve hunt logs and give them to UI. + * + */ + public class HuntLogAppender extends AppenderSkeleton { + private final HuntWorker worker; + + public HuntLogAppender( HuntWorker slurperWorker) { + this.worker = slurperWorker; + } + protected void append(LoggingEvent event) + { + if(event.getLevel().equals(Level.INFO)){ + worker.appendLog(event.getMessage().toString()); + } + } + public void close() + { + } + public boolean requiresLayout() + { + return false; + } + } + +} diff --git a/src/main/java/fr/pavnay/rabbits/ui/MainFrame.java b/src/main/java/fr/pavnay/rabbits/ui/MainFrame.java new file mode 100644 index 0000000..2d3c403 --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/ui/MainFrame.java @@ -0,0 +1,114 @@ +package fr.pavnay.rabbits.ui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingWorker; +import javax.swing.Timer; + +import org.apache.log4j.Logger; + +import fr.pavnay.rabbits.engine.HuntEngine; +import fr.pavnay.rabbits.ui.panel.Canvas; +import fr.pavnay.rabbits.ui.panel.CanvasLegend; +import fr.pavnay.rabbits.ui.panel.CharacterPanel; +import fr.pavnay.rabbits.ui.panel.LogPanel; + +/** + * + * The frame to see the hunt. + * + */ +public class MainFrame extends JFrame implements PropertyChangeListener, ActionListener { + + private static final long serialVersionUID = -5070100141993068939L; + + private static final Logger logger = Logger.getLogger(MainFrame.class); + + private HuntEngine engine; + + private Timer timer; // Timer used to refresh UI periodically + + private JPanel canvas; // The hunt panel + private CharacterPanel characterPanel; // Characters stats panel + private LogPanel logPanel; // Hunt event panel + private JLabel statusLabel; // Hunt status + + private HuntWorker worker; + + public MainFrame( HuntEngine engine ) { + setTitle("Rabbits vs Hunter"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + setLayout(new BorderLayout()); + + this.engine = engine; + + JPanel statusPanel = new JPanel(); + statusPanel.add(new JLabel("Status :")); + statusLabel = new JLabel(engine.getStatus().toString()); + statusPanel.add(statusLabel); + add(statusPanel, BorderLayout.NORTH); + + canvas = new Canvas(engine.getForest(), engine.getHunter()); + timer = new Timer((int) HuntWorker.SLEEP_TIME, this); + add(canvas, BorderLayout.CENTER); + + characterPanel = new CharacterPanel(engine.getHunter(), engine.getForest()); + logPanel = new LogPanel(); + + int VERT_GAP = 10; + int EB_GAP = 5; + JPanel sidePanel = new JPanel(); + sidePanel.setBorder(BorderFactory.createEmptyBorder(EB_GAP, EB_GAP, EB_GAP, EB_GAP)); + sidePanel.setLayout(new BoxLayout(sidePanel, BoxLayout.PAGE_AXIS)); + sidePanel.add(characterPanel); + sidePanel.add(Box.createVerticalStrut(VERT_GAP)); + sidePanel.add(Box.createVerticalStrut(VERT_GAP)); + sidePanel.add(new JScrollPane(logPanel)); + add(sidePanel, BorderLayout.EAST); + + add( new CanvasLegend(engine.getForest()), BorderLayout.SOUTH ); + + worker = new HuntWorker(engine, this); + worker.addPropertyChangeListener(this); + worker.execute(); + timer.start(); + + pack(); + setLocationRelativeTo(null); + setVisible(true); + } + + public void propertyChange(PropertyChangeEvent event) { + if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.DONE == event.getNewValue()) { // Worker ends + statusLabel.setText(engine.getStatus().toString()); + statusLabel.repaint(); + + logger.debug(engine.getHunter()); + engine.getForest().printStats(); + } + } + + public void actionPerformed(ActionEvent event) { + if(event.getSource()==timer){ + canvas.repaint(); + characterPanel.refresh(); + } + } + + public void report(String log) { + logPanel.addLog(log); + } + +} diff --git a/src/main/java/fr/pavnay/rabbits/ui/UiUtils.java b/src/main/java/fr/pavnay/rabbits/ui/UiUtils.java new file mode 100644 index 0000000..87dcddd --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/ui/UiUtils.java @@ -0,0 +1,40 @@ +package fr.pavnay.rabbits.ui; + +import java.awt.Container; +import java.awt.GridBagConstraints; +import java.awt.Insets; + +import javax.swing.JComponent; + +/** + * + * An utility class for UI + * + */ +public class UiUtils { + + /** + * Add a component in a container with a GridBagLayout + * + * @param container + * @param component + * @param x + * @param y + * @param w + * @param h + */ + public static void add(Container container, JComponent component, int x, int y, + int w, int h) { + GridBagConstraints bagConstraints = new GridBagConstraints(); + bagConstraints.gridx = x; + bagConstraints.gridy = y; + bagConstraints.gridwidth = w; + bagConstraints.gridheight = h; + bagConstraints.anchor = (x == 0) ? GridBagConstraints.EAST + : GridBagConstraints.WEST; + bagConstraints.fill = (x == 0) ? GridBagConstraints.BOTH + : GridBagConstraints.HORIZONTAL; + bagConstraints.insets = new Insets(5, 5, 5, 5); + container.add(component, bagConstraints); + } +} diff --git a/src/main/java/fr/pavnay/rabbits/ui/panel/Canvas.java b/src/main/java/fr/pavnay/rabbits/ui/panel/Canvas.java new file mode 100644 index 0000000..e6db8db --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/ui/panel/Canvas.java @@ -0,0 +1,71 @@ +package fr.pavnay.rabbits.ui.panel; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; + +import javax.swing.JPanel; + +import fr.pavnay.rabbits.model.Burrow; +import fr.pavnay.rabbits.model.Forest; +import fr.pavnay.rabbits.model.Hunter; +import fr.pavnay.rabbits.model.Rabbit; +import fr.pavnay.rabbits.model.Tree; + +/** + * + * This class draws all hunt elements : trees, burrows, hunter and rabbits + * + */ +public class Canvas extends JPanel { + + private static final long serialVersionUID = 4209640358977009595L; + + protected static int RATE = 5; + protected static int ITEM_RATIO = 2; + + private Forest forest; + private Hunter hunter; + + public Canvas( Forest forest, Hunter hunter) { + this.hunter = hunter; + this.forest = forest; + } + + public void paintComponent(Graphics graphics) { + super.paintComponent(graphics); + + graphics.setColor(Color.RED); + graphics.fillOval((int) hunter.getLocation().getX() * RATE, (int) hunter.getLocation().getY() * RATE, 3 * ITEM_RATIO, 3 * ITEM_RATIO); + + graphics.setColor(Color.GREEN); + for( Tree tree : forest.getTrees() ) { + graphics.fillOval((int) tree.getLocation().getX() * RATE, (int) tree.getLocation().getY() * RATE, 5 * ITEM_RATIO, 5 * ITEM_RATIO); + } + + for( Burrow burrow : forest.getBurrows() ) { + if( burrow.isFree() ) { + graphics.setColor(Color.GRAY); + } else { + graphics.setColor(Color.DARK_GRAY); + } + graphics.fillOval((int) burrow.getLocation().getX() * RATE, (int) burrow.getLocation().getY() * RATE, 4 * ITEM_RATIO, 2 * ITEM_RATIO); + } + + for( Rabbit rabbit : forest.getRabbits() ) { + if( rabbit.getColor() == fr.pavnay.rabbits.model.enums.Color.WHITE ) { + graphics.setColor(Color.BLACK); + graphics.drawOval((int) rabbit.getLocation().getX() * RATE, (int) rabbit.getLocation().getY() * RATE, 3 * ITEM_RATIO, 3 * ITEM_RATIO); + } else { + graphics.setColor(rabbit.getColor().getColor()); + graphics.fillOval((int) rabbit.getLocation().getX() * RATE, (int) rabbit.getLocation().getY() * RATE, 3 * ITEM_RATIO, 3 * ITEM_RATIO); + } + } + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(forest.getEdgeArea() * RATE, forest.getEdgeArea() * RATE); + } + +} diff --git a/src/main/java/fr/pavnay/rabbits/ui/panel/CanvasLegend.java b/src/main/java/fr/pavnay/rabbits/ui/panel/CanvasLegend.java new file mode 100644 index 0000000..660fad1 --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/ui/panel/CanvasLegend.java @@ -0,0 +1,54 @@ +package fr.pavnay.rabbits.ui.panel; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; + +import javax.swing.JPanel; + +import fr.pavnay.rabbits.model.Forest; + +/** + * + * This class draws the Canvas elements legend + * + */ +public class CanvasLegend extends JPanel { + + private static final long serialVersionUID = 1307357862666434838L; + + private int width; + + public CanvasLegend(Forest forest) { + this.width = forest.getEdgeArea() * Canvas.RATE; + } + + public void paintComponent(Graphics graphics) { + super.paintComponent(graphics); + + graphics.setColor(Color.RED); + graphics.fillOval(5, 10, 3 * Canvas.ITEM_RATIO, 3 * Canvas.ITEM_RATIO); + graphics.drawString("Hunter", 12, 15); + + graphics.setColor(Color.GREEN); + graphics.fillOval(width / 5 + 5, 10, 5 * Canvas.ITEM_RATIO, 5 * Canvas.ITEM_RATIO); + graphics.drawString("Trees", width / 5 + 12, 15); + + graphics.setColor(Color.GRAY); + graphics.fillOval(2 * width / 5 + 5, 10, 4 * Canvas.ITEM_RATIO, 2 * Canvas.ITEM_RATIO); + graphics.drawString("Burrows", 2 * width / 5 + 12, 15); + + graphics.setColor(Color.BLACK); + graphics.drawOval(3 * width / 5 + 5, 10, 3 * Canvas.ITEM_RATIO, 3 * Canvas.ITEM_RATIO); + graphics.drawString("Rabbits", 3 * width / 5 + 12, 15); + + graphics.setColor(fr.pavnay.rabbits.model.enums.Color.BROWN.getColor()); + graphics.fillOval(4 * width / 5 + 5, 10, 3 * Canvas.ITEM_RATIO, 3 * Canvas.ITEM_RATIO); + graphics.drawString("Rabbits", 4 * width / 5 + 12, 15); + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(width, 20); + } +} diff --git a/src/main/java/fr/pavnay/rabbits/ui/panel/CharacterPanel.java b/src/main/java/fr/pavnay/rabbits/ui/panel/CharacterPanel.java new file mode 100644 index 0000000..3a42bed --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/ui/panel/CharacterPanel.java @@ -0,0 +1,98 @@ +package fr.pavnay.rabbits.ui.panel; + +import java.awt.Color; +import java.awt.Font; +import java.awt.GridBagLayout; + +import javax.swing.JLabel; +import javax.swing.JPanel; + +import fr.pavnay.rabbits.model.Forest; +import fr.pavnay.rabbits.model.Hunter; +import fr.pavnay.rabbits.ui.UiUtils; + +/** + * + * This class gives some hunt statistics + * + */ +public class CharacterPanel extends JPanel { + + private static final long serialVersionUID = -7823773213443395686L; + + private Hunter hunter; + private Forest forest; + + private JLabel ammosCountLabel; + private JLabel hungryLevelLabel; + private JLabel distanceLabel; + + private JLabel runningRabbitsLabel; + private JLabel savedRabbitsLabel; + private JLabel deadRabbitsLabel; + + public CharacterPanel(Hunter hunter, Forest forest) { + + this.hunter = hunter; + this.forest = forest; + + JPanel innerPanel = new JPanel(new GridBagLayout()); + + UiUtils.add(innerPanel, new JLabel("Hunter"), 0, 0, 3, 1); + JPanel tmpPanel = new JPanel(new GridBagLayout()); + UiUtils.add(tmpPanel,new JLabel("Ammos"), 0, 0, 1, 1); + ammosCountLabel = new JLabel(String.valueOf(hunter.getAmmos())); + Font plainFont = ammosCountLabel.getFont().deriveFont(Font.PLAIN); + ammosCountLabel.setFont(plainFont); + UiUtils.add(tmpPanel, ammosCountLabel, 1, 0, 1, 1); + + UiUtils.add(tmpPanel, new JLabel("Hungry"), 2, 0, 1, 1); + hungryLevelLabel = new JLabel(String.valueOf(hunter.getHungryLevel())); + hungryLevelLabel.setFont(plainFont); + UiUtils.add(tmpPanel, hungryLevelLabel, 3, 0, 1, 1); + + UiUtils.add(tmpPanel, new JLabel("Distance"), 4, 0, 1, 1); + distanceLabel = new JLabel(String.valueOf(hunter.getDistance())); + distanceLabel.setFont(plainFont); + UiUtils.add(tmpPanel, distanceLabel, 5, 0, 1, 1); + + UiUtils.add(innerPanel, tmpPanel, 0, 1, 1, 1); + + UiUtils.add(innerPanel, new JLabel("Rabbits"), 0, 2, 3, 1); + tmpPanel = new JPanel(new GridBagLayout()); + UiUtils.add(tmpPanel,new JLabel("Running"), 0, 0, 1, 1); + runningRabbitsLabel = new JLabel(String.valueOf(forest.getRabbits().size())); + plainFont = runningRabbitsLabel.getFont().deriveFont(Font.PLAIN); + runningRabbitsLabel.setFont(plainFont); + runningRabbitsLabel.setForeground(Color.BLACK); + UiUtils.add(tmpPanel, runningRabbitsLabel, 1, 0, 1, 1); + + UiUtils.add(tmpPanel, new JLabel("Saved"), 2, 0, 1, 1); + savedRabbitsLabel = new JLabel(String.valueOf(forest.getSavedRabbits().size())); + savedRabbitsLabel.setFont(plainFont); + savedRabbitsLabel.setForeground(Color.GREEN); + UiUtils.add(tmpPanel, savedRabbitsLabel, 3, 0, 1, 1); + + UiUtils.add(tmpPanel, new JLabel("Dead"), 4, 0, 1, 1); + deadRabbitsLabel = new JLabel(String.valueOf(forest.getDeadRabbits().size())); + deadRabbitsLabel.setFont(plainFont); + deadRabbitsLabel.setForeground(Color.RED); + UiUtils.add(tmpPanel, deadRabbitsLabel, 5, 0, 1, 1); + + UiUtils.add(innerPanel, tmpPanel, 0, 3, 1, 1); + + add(innerPanel); + + } + + public void refresh() { + ammosCountLabel.setText(String.valueOf(hunter.getAmmos())); + hungryLevelLabel.setText(String.valueOf(hunter.getHungryLevel())); + distanceLabel.setText(String.valueOf(hunter.getDistance())); + + runningRabbitsLabel.setText(String.valueOf(forest.getRabbits().size())); + savedRabbitsLabel.setText(String.valueOf(forest.getSavedRabbits().size())); + deadRabbitsLabel.setText(String.valueOf(forest.getDeadRabbits().size())); + } + +} diff --git a/src/main/java/fr/pavnay/rabbits/ui/panel/LogPanel.java b/src/main/java/fr/pavnay/rabbits/ui/panel/LogPanel.java new file mode 100644 index 0000000..0d4f3af --- /dev/null +++ b/src/main/java/fr/pavnay/rabbits/ui/panel/LogPanel.java @@ -0,0 +1,28 @@ +package fr.pavnay.rabbits.ui.panel; + +import java.awt.Dimension; +import java.awt.Font; + +import javax.swing.JLabel; +import javax.swing.JPanel; + +/** + * + * This class provides some hunt events + * + */ +public class LogPanel extends JPanel { + + private static final long serialVersionUID = 3786097551382633033L; + + public void addLog(String log) { + JLabel label = new JLabel(log); + label.setFont(label.getFont().deriveFont(Font.PLAIN)); + add(label); + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(200, 400); + } +} diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 0000000..c5d6ab9 --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,8 @@ +log4j.rootLogger=INFO, CONSOLE + +# CONSOLE is set to be a ConsoleAppender using a PatternLayout +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d [%-5p] %c - %m%n + +log4j.logger.fr.pavnay.rabbits=INFO \ No newline at end of file diff --git a/src/test/java/fr/pavnay/rabbits/engine/HuntEngineTest.java b/src/test/java/fr/pavnay/rabbits/engine/HuntEngineTest.java new file mode 100644 index 0000000..d72aea2 --- /dev/null +++ b/src/test/java/fr/pavnay/rabbits/engine/HuntEngineTest.java @@ -0,0 +1,248 @@ +package fr.pavnay.rabbits.engine; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.awt.Point; + +import org.junit.Test; + +import fr.pavnay.rabbits.model.Burrow; +import fr.pavnay.rabbits.model.Forest; +import fr.pavnay.rabbits.model.Hunter; +import fr.pavnay.rabbits.model.Rabbit; +import fr.pavnay.rabbits.model.enums.Speed; +import fr.pavnay.rabbits.model.enums.Status; + +public class HuntEngineTest { + + @Test + public void testUpdateStatusRunning() { + Hunter hunter = new Hunter(new Point(3, 2), 10); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + engine.updateStatus(); + + assertEquals( "Default status is RUNNING", Status.RUNNING, engine.getStatus()); + } + + @Test + public void testUpdateStatusRabbitsWin() { + Hunter hunter = new Hunter(new Point(3, 2), 10); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + hunter.setAmmos(0); + engine.updateStatus(); + + assertEquals( "Default status is RUNNING", Status.RABBITS_WIN, engine.getStatus()); + } + + @Test + public void testUpdateStatusHunterWins() { + Hunter hunter = new Hunter(new Point(3, 2), 10); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + forest.getDeadRabbits().addAll(forest.getRabbits()); + forest.getRabbits().clear(); + engine.updateStatus(); + + assertEquals( "Default status is RUNNING", Status.HUNTER_WINS, engine.getStatus()); + } + + @Test + public void testUpdateStatusHunterLoose() { + Hunter hunter = new Hunter(new Point(3, 2), 10); + Forest forest = new Forest(10, 1, 2, 2); + HuntEngine engine = new HuntEngine(forest, hunter); + forest.getDeadRabbits().add(forest.getRabbits().get(0)); + forest.getSavedRabbits().add(forest.getRabbits().get(1)); + forest.getRabbits().clear(); + engine.updateStatus(); + + assertEquals( "Default status is RUNNING", Status.RABBITS_WIN, engine.getStatus()); + } + + @Test + public void testMoveHunterCloseToDestination() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + hunter.setDestination(new Point(2, 0)); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + + engine.move(hunter); + assertNotEquals("Destination has changed - X", 2, hunter.getDestination().getX()); + assertNotEquals("Destination has changed - Y", 0, hunter.getDestination().getY()); + } + + @Test + public void testMoveHunterFarFromDestination() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + hunter.setDestination(new Point(20, 0)); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + + engine.move(hunter); + assertNotEquals("Destination has changed - X", 20, hunter.getDestination().getX()); + assertNotEquals("Destination has changed - Y", 0, hunter.getDestination().getY()); + } + + @Test + public void testMoveRabbitCloseToFreeBurrow() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + + Burrow burrow = forest.getBurrows().get(0); + burrow.getLocation().setLocation(2, 0); + + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.getLocation().setLocation(new Point(0, 0)); + rabbit.setSpeed(Speed.MEDIUM.getSpeed()); + rabbit.setRefuge(burrow); + + engine.move(rabbit); + assertFalse("Lucky rabbit doesn't run anymore", forest.getRabbits().contains(rabbit)); + assertNull("No more destination", rabbit.getDestination()); + } + + @Test + public void testMoveRabbitCloseToNotFreeBurrow() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + + Burrow burrow = forest.getBurrows().get(0); + burrow.getLocation().setLocation(2, 0); + burrow.setFree(false); + + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.getLocation().setLocation(new Point(0, 0)); + rabbit.setSpeed(Speed.MEDIUM.getSpeed()); + rabbit.setRefuge(burrow); + + engine.move(rabbit); + assertTrue("Rabbit still running", forest.getRabbits().contains(rabbit)); + assertEquals("New destination : next burrow",forest.getBurrows().get(1).getLocation(), rabbit.getDestination()); + } + + @Test + public void testMoveRabbitToFarFromBurrow() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + + Burrow burrow = forest.getBurrows().get(0); + burrow.getLocation().setLocation(20, 0); + + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.getLocation().setLocation(new Point(0, 0)); + rabbit.setSpeed(Speed.MEDIUM.getSpeed()); + rabbit.setRefuge(burrow); + + engine.move(rabbit); + assertTrue("Rabbit still running", forest.getRabbits().contains(rabbit)); + assertEquals("Burrow still the destination", burrow.getLocation(), rabbit.getDestination()); + } + + @Test + public void testMoveRabbitCloseToDestination() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.getLocation().setLocation(new Point(0, 0)); + rabbit.setSpeed(Speed.MEDIUM.getSpeed()); + rabbit.setDestination(new Point(3, 0)); + engine.move(rabbit); + assertNotEquals("New destination", new Point(3, 0), rabbit.getDestination()); + } + + @Test + public void testMoveRabbitFarFromDestination() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.getLocation().setLocation(new Point(0, 0)); + rabbit.setSpeed(Speed.MEDIUM.getSpeed()); + rabbit.setDestination(new Point(13, 0)); + + engine.move(rabbit); + assertEquals("Same destination", new Point(13, 0), rabbit.getDestination()); + } + + @Test + public void testSetRandomDestinationHunter() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + hunter.setDestination(null); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + + engine.setRandomDestination(hunter); + assertNotNull("Destination set", hunter.getDestination()); + } + + @Test + public void testSetRandomDestinationRabbit() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.setDestination(null); + engine.setRandomDestination(rabbit); + assertNotNull("Destination set", rabbit.getDestination()); + } + + @Test + public void testGoHunter() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + hunter.setDestination(new Point(20, 20)); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + + engine.go(hunter); + + assertEquals("Location has changed", new Point(3, 3), hunter.getLocation()); + } + + @Test + public void testGoRabbit() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.getLocation().setLocation(new Point(0, 0)); + rabbit.setSpeed(Speed.MEDIUM.getSpeed()); + rabbit.setDestination(new Point(13, 13)); + + engine.go(rabbit); + + assertEquals("Location has changed", new Point(3, 3), rabbit.getLocation()); + } + + @Test + public void testSavedRabbit() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Forest forest = new Forest(10, 1, 2, 1); + HuntEngine engine = new HuntEngine(forest, hunter); + + Burrow burrow = forest.getBurrows().get(0); + burrow.getLocation().setLocation(2, 0); + + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.getLocation().setLocation(new Point(0, 0)); + rabbit.setSpeed(Speed.MEDIUM.getSpeed()); + rabbit.setRefuge(burrow); + + engine.go(rabbit); + + assertEquals("Destination has not changed", new Point(5, 0), rabbit.getLocation()); + } +} diff --git a/src/test/java/fr/pavnay/rabbits/model/ForestTest.java b/src/test/java/fr/pavnay/rabbits/model/ForestTest.java new file mode 100644 index 0000000..d36aa30 --- /dev/null +++ b/src/test/java/fr/pavnay/rabbits/model/ForestTest.java @@ -0,0 +1,246 @@ +package fr.pavnay.rabbits.model; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.awt.Point; +import java.util.List; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.matchers.JUnitMatchers; +import org.junit.rules.ExpectedException; + +@SuppressWarnings("deprecation") +public class ForestTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void testInitSuccess() { + Forest forest = new Forest(10, 1, 1, 1); + assertNotNull("Forest created", forest); + } + + @Test + public void testInitFailureAreaMin() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage(JUnitMatchers.containsString("Area")); + + new Forest(0, 1, 1, 1); + } + + @Test + public void testInitFailureAreaMax() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage(JUnitMatchers.containsString("Area")); + + new Forest(11, 1, 1, 1); + } + + @Test + public void testInitFailureTreesMin() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage(JUnitMatchers.containsString("Tree")); + + new Forest(1, 0, 1, 1); + } + + @Test + public void testInitFailureTreesMax() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage(JUnitMatchers.containsString("Tree")); + + new Forest(10, 1001, 1, 1); + } + + @Test + public void testInitFailureNoRabbits() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage(JUnitMatchers.containsString("rabbits")); + + new Forest(10, 100, 1, 0); + } + + @Test + public void testInitFailureNotEnoughBurrows() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage(JUnitMatchers.containsString("burrow")); + + new Forest(10, 100, 1, 2); + } + + @Test + public void testIsBetweenSuccess() { + Hunter hunter = new Hunter(new Point(0,0), 10); + Forest forest = new Forest(10, 1, 1, 1); + Rabbit rabbit = new Rabbit(new Point(4, 4)); + Tree tree = new Tree(new Point(2, 2)); + assertTrue("Tree is between hunter and rabbit", forest.isBetween(hunter.getLocation(), rabbit.getLocation(), tree.getLocation())); + } + + @Test + public void testCanViewSuccess() { + Hunter hunter = new Hunter(new Point(0,0), 10); + Forest forest = new Forest(10, 2, 1, 1); + forest.getTrees().get(0).getLocation().setLocation(10, 0); + forest.getTrees().get(1).getLocation().setLocation(20, 0); + Rabbit rabbit = new Rabbit(new Point(4, 4)); + assertTrue("No Tree between hunter and rabbit", forest.canView(hunter, rabbit)); + } + + @Test + public void testCanViewFailure() { + Hunter hunter = new Hunter(new Point(0,0), 10); + Forest forest = new Forest(10, 2, 1, 1); + forest.getTrees().get(0).getLocation().setLocation(2, 2); + forest.getTrees().get(1).getLocation().setLocation(2, 0); + Rabbit rabbit = new Rabbit(new Point(4, 4)); + assertFalse("Tree is between hunter and rabbit", forest.canView(hunter, rabbit)); + } + + @Test + public void testIsBetweenFailureTreeBehind() { + Hunter hunter = new Hunter(new Point(0,0), 10); + Forest forest = new Forest(10, 1, 1, 1); + Rabbit rabbit = new Rabbit(new Point(4, 4)); + Tree tree = new Tree(new Point(5, 5)); + assertFalse("Tree is behind the rabbit", forest.isBetween(hunter.getLocation(), rabbit.getLocation(), tree.getLocation())); + } + + @Test + public void testIsBetweenFailureTreeSomewhere() { + Hunter hunter = new Hunter(new Point(0,0), 10); + Forest forest = new Forest(10, 1, 1, 1); + Rabbit rabbit = new Rabbit(new Point(4, 4)); + Tree tree = new Tree(new Point(10, 5)); + assertFalse("Tree is not in the hunter-rabbit aligment", forest.isBetween(hunter.getLocation(), rabbit.getLocation(), tree.getLocation())); + } + + @Test + public void testKilledRabbitSuccess() { + Forest forest = new Forest(10, 1, 2, 2); + Rabbit rabbit = forest.getRabbits().get(0); + forest.killedRabbit(rabbit); + + assertFalse("Poor killed rabbit still running", forest.getRabbits().contains(rabbit)); + assertEquals("Still one rabbit alive", 1, forest.getRabbits().size()); + assertTrue("Poor killed rabbit can't run", forest.getDeadRabbits().contains(rabbit)); + assertEquals("One dead rabbit", 1, forest.getDeadRabbits().size()); + } + + @Test + public void testSavedRabbitSuccess() { + Forest forest = new Forest(10, 1, 1, 1); + Rabbit rabbit = forest.getRabbits().get(0); + forest.savedRabbit(rabbit); + + assertFalse("Poor saved rabbit still running", forest.getRabbits().contains(rabbit)); + assertTrue("Lucky rabbit can't run", forest.getSavedRabbits().contains(rabbit)); + } + + @Test + public void testGetRabbitsInRangeSuccess() { + Hunter hunter = new Hunter(new Point(0,0), 10); + Forest forest = new Forest(10, 1, 2, 2); + forest.getRabbits().get(0).getLocation().setLocation(0, 5); // First rabbit in range + forest.getRabbits().get(1).getLocation().setLocation(0, 15); // Seconde one out of range + + List rabbitsInRange = forest.getRabbitsInRange(hunter); + assertEquals("One rabbit in range", 1, rabbitsInRange.size()); + assertTrue("First rabbit in range", rabbitsInRange.contains(forest.getRabbits().get(0))); + assertFalse("Second rabbit out of range", rabbitsInRange.contains(forest.getRabbits().get(1))); + } + + @Test + public void testGetNearestBurrowFirst() { + Hunter hunter = new Hunter(new Point(0, 1), 10); + Forest forest = new Forest(10, 1, 2, 1); + forest.getBurrows().get(0).getLocation().setLocation(7, 2); + forest.getBurrows().get(1).getLocation().setLocation(9, 0); + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.getLocation().setLocation(4, 1); + + assertEquals("Second burrow is nearest from rabbit", forest.getBurrows().get(0), forest.getNearestBurrow(hunter, rabbit)); + } + + @Test + public void testGetNearestBurrowSecond() { + Hunter hunter = new Hunter(new Point(0, 1), 10); + Forest forest = new Forest(10, 1, 2, 1); + forest.getBurrows().get(0).getLocation().setLocation(9, 0); + forest.getBurrows().get(1).getLocation().setLocation(7, 2); + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.getLocation().setLocation(4, 1); + + assertEquals("Second burrow is nearest from rabbit", forest.getBurrows().get(1), forest.getNearestBurrow(hunter, rabbit)); + } + + @Test + public void testGetNearestBurrowWorstIsBetter() { + Hunter hunter = new Hunter(new Point(0, 1), 10); + Forest forest = new Forest(10, 1, 2, 1); + forest.getBurrows().get(0).getLocation().setLocation(7, 2); + forest.getBurrows().get(1).getLocation().setLocation(3, 2); + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.getLocation().setLocation(4, 1); + + assertEquals("Second burrow is nearest from rabbit", forest.getBurrows().get(1), forest.getNearestBurrow(hunter, rabbit)); + } + + @Test + public void testGetNearestBurrowNotVisited() { + Hunter hunter = new Hunter(new Point(0, 1), 10); + Forest forest = new Forest(10, 1, 2, 1); + forest.getBurrows().get(0).getLocation().setLocation(7, 2); + forest.getBurrows().get(1).getLocation().setLocation(9, 0); + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.getLocation().setLocation(4, 1); + rabbit.setRefuge(forest.getBurrows().get(0)); + + assertEquals("Second burrow was not visited by the rabbit", forest.getBurrows().get(1), forest.getNearestBurrow(hunter, rabbit)); + } + + @Test + public void testEscapeScared() { + Hunter hunter = new Hunter(new Point(3, 2), 10); + Forest forest = new Forest(10, 1, 1, 1); + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.getLocation().setLocation(6, 3); + rabbit.setScared(true); + + forest.escape(hunter, rabbit); + Burrow burrow = forest.getBurrows().get(0); + assertEquals("Right location X", burrow.getLocation().getX(), rabbit.getDestination().getX(), 0); + assertEquals("Right location Y", burrow.getLocation().getY(), rabbit.getDestination().getY(), 0); + } + + @Test + public void testEscapeNotScared() { + Hunter hunter = new Hunter(new Point(3, 2), 10); + Forest forest = new Forest(10, 1, 2, 1); + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.getLocation().setLocation(6, 3); + + forest.escape(hunter, rabbit); + + assertEquals("Right location X", 9, rabbit.getDestination().getX(), 0); + assertEquals("Right location Y", 4, rabbit.getDestination().getY(), 0); + } + + @Test + public void testEscapeNotScaredOutOfForest() { + Hunter hunter = new Hunter(new Point(5, 3), 10); + Forest forest = new Forest(10, 1, 2, 1); + Rabbit rabbit = forest.getRabbits().get(0); + rabbit.getLocation().setLocation(2, 2); + + forest.escape(hunter, rabbit); + + assertEquals("Right location X", 0, rabbit.getDestination().getX(), 0); + assertEquals("Right location Y", 1, rabbit.getDestination().getY(), 0); + } +} diff --git a/src/test/java/fr/pavnay/rabbits/model/HunterTest.java b/src/test/java/fr/pavnay/rabbits/model/HunterTest.java new file mode 100644 index 0000000..16ddb03 --- /dev/null +++ b/src/test/java/fr/pavnay/rabbits/model/HunterTest.java @@ -0,0 +1,108 @@ +package fr.pavnay.rabbits.model; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.awt.Point; + +import org.junit.Test; + +public class HunterTest { + + @Test + public void testInRangeSuccess() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Rabbit rabbit = new Rabbit(new Point(10, 0)); + assertTrue("Rabbit is in hunter range", hunter.isInRange(rabbit)); + } + + @Test + public void testInRangeFailure() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Rabbit rabbit = new Rabbit(new Point(11, 0)); + assertFalse("Rabbit is out of hunter range", hunter.isInRange(rabbit)); + } + + @Test + public void testShootSuccess() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Rabbit rabbit = new Rabbit(new Point(0, 0)); + assertTrue("Rabbit is close to the hunter. Shot successfully", hunter.shoot(rabbit)); + assertEquals("Hunter's ammos decreases", 9, hunter.getAmmos()); + } + + @Test + public void testShootNoMoreAmmosFailure() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Rabbit rabbit = new Rabbit(new Point(0, 0)); + hunter.shoot(rabbit); + hunter.shoot(rabbit); + hunter.shoot(rabbit); + hunter.shoot(rabbit); + hunter.shoot(rabbit); + hunter.shoot(rabbit); + hunter.shoot(rabbit); + hunter.shoot(rabbit); + hunter.shoot(rabbit); + hunter.shoot(rabbit); + assertFalse("No more ammos. Shot failure", hunter.shoot(rabbit)); + } + + @Test + public void testShootFailure() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Rabbit rabbit = new Rabbit(new Point(10, 0)); + assertFalse("Rabbit is out of hunter's range. Shot failure", hunter.shoot(rabbit)); + assertEquals("Hunter's ammos decreases", 9, hunter.getAmmos()); + } + + @Test + public void testGetAccuracyRate() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Rabbit rabbit = new Rabbit(new Point(5, 0)); + assertEquals("Hunter's half rate to shot", hunter.getAccuracyRate(rabbit), 0.5, 0); + } + + @Test + public void testGetAccuracyRateHalfAmmo() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Rabbit rabbit = new Rabbit(new Point(5, 0)); + hunter.setAmmos(5); + assertEquals("Hunter's half rate to shot", hunter.getAccuracyRate(rabbit), 0.475, 0); + } + + @Test + public void testGetAccuracyRateHalfHungryLevel() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Rabbit rabbit = new Rabbit(new Point(5, 0)); + hunter.setHungryLevel(5); + assertEquals("Hunter's half hungry level", hunter.getAccuracyRate(rabbit), 0.45, 0); + } + + @Test + public void testGetAccuracyRateFullHungryLevel() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Rabbit rabbit = new Rabbit(new Point(5, 0)); + hunter.setHungryLevel(10); + assertEquals("Hunter's half hungry level", hunter.getAccuracyRate(rabbit), 0.4, 0.001); + } + + @Test + public void testGetAccuracyRateHalfAmmosHalfHungryLevel() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Rabbit rabbit = new Rabbit(new Point(5, 0)); + hunter.setAmmos(5); + hunter.setHungryLevel(5); + assertEquals("Hunter's half hungry level", hunter.getAccuracyRate(rabbit), 0.425, 0.001); + } + + @Test + public void testGetAccuracyRateHalfAmmosFullHungryLevel() { + Hunter hunter = new Hunter(new Point(0, 0), 10); + Rabbit rabbit = new Rabbit(new Point(5, 0)); + hunter.setAmmos(5); + hunter.setHungryLevel(10); + assertEquals("Hunter's half hungry level", hunter.getAccuracyRate(rabbit), 0.375, 0); + } +} diff --git a/src/test/java/fr/pavnay/rabbits/model/RabbitTest.java b/src/test/java/fr/pavnay/rabbits/model/RabbitTest.java new file mode 100644 index 0000000..e71c278 --- /dev/null +++ b/src/test/java/fr/pavnay/rabbits/model/RabbitTest.java @@ -0,0 +1,29 @@ +package fr.pavnay.rabbits.model; + +import java.awt.Point; + +import org.junit.Test; + +import fr.pavnay.rabbits.model.enums.Speed; + +import static org.junit.Assert.assertEquals; + +public class RabbitTest { + + @Test + public void testSlowDown() { + Rabbit rabbit = new Rabbit(new Point(0,0)); + rabbit.setSpeed(Speed.MEDIUM.getSpeed()); + rabbit.slowDown(); + assertEquals("Rabbit slow down", Speed.MEDIUM.getSpeed() - 1, rabbit.getSpeed()); + } + + @Test + public void testSlowDownStopped() { + Rabbit rabbit = new Rabbit(new Point(0,0)); + rabbit.setSpeed(Speed.SLOW.getSpeed()); + rabbit.slowDown(); + rabbit.slowDown(); + assertEquals("Rabbit stopped", Speed.STOPPED.getSpeed(), rabbit.getSpeed()); + } +}