Sfoglia il codice sorgente

Feature: initial commit

Febbweiss 8 anni fa
commit
9065ef6af5
27 ha cambiato i file con 2243 aggiunte e 0 eliminazioni
  1. 23 0
      README.md
  2. 63 0
      pom.xml
  3. 17 0
      src/main/java/fr/pavnay/rabbits/Main.java
  4. 202 0
      src/main/java/fr/pavnay/rabbits/engine/HuntEngine.java
  5. 36 0
      src/main/java/fr/pavnay/rabbits/model/Burrow.java
  6. 18 0
      src/main/java/fr/pavnay/rabbits/model/Character.java
  7. 295 0
      src/main/java/fr/pavnay/rabbits/model/Forest.java
  8. 129 0
      src/main/java/fr/pavnay/rabbits/model/Hunter.java
  9. 91 0
      src/main/java/fr/pavnay/rabbits/model/Rabbit.java
  10. 22 0
      src/main/java/fr/pavnay/rabbits/model/Tree.java
  11. 22 0
      src/main/java/fr/pavnay/rabbits/model/enums/Color.java
  12. 25 0
      src/main/java/fr/pavnay/rabbits/model/enums/Speed.java
  13. 18 0
      src/main/java/fr/pavnay/rabbits/model/enums/Status.java
  14. 25 0
      src/main/java/fr/pavnay/rabbits/model/predicate/RangePredicate.java
  15. 115 0
      src/main/java/fr/pavnay/rabbits/ui/FormFrame.java
  16. 98 0
      src/main/java/fr/pavnay/rabbits/ui/HuntWorker.java
  17. 114 0
      src/main/java/fr/pavnay/rabbits/ui/MainFrame.java
  18. 40 0
      src/main/java/fr/pavnay/rabbits/ui/UiUtils.java
  19. 71 0
      src/main/java/fr/pavnay/rabbits/ui/panel/Canvas.java
  20. 54 0
      src/main/java/fr/pavnay/rabbits/ui/panel/CanvasLegend.java
  21. 98 0
      src/main/java/fr/pavnay/rabbits/ui/panel/CharacterPanel.java
  22. 28 0
      src/main/java/fr/pavnay/rabbits/ui/panel/LogPanel.java
  23. 8 0
      src/main/resources/log4j.properties
  24. 248 0
      src/test/java/fr/pavnay/rabbits/engine/HuntEngineTest.java
  25. 246 0
      src/test/java/fr/pavnay/rabbits/model/ForestTest.java
  26. 108 0
      src/test/java/fr/pavnay/rabbits/model/HunterTest.java
  27. 29 0
      src/test/java/fr/pavnay/rabbits/model/RabbitTest.java

+ 23 - 0
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
+```

+ 63 - 0
pom.xml

@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>fr.pavnay</groupId>
+	<artifactId>rabbits-vs-hunter</artifactId>
+	<version>1.0-SNAPSHOT</version>
+	<packaging>jar</packaging>
+	<dependencies>
+		<!-- Tools -->
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-collections4</artifactId>
+			<version>4.0</version>
+		</dependency>
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>log4j</artifactId>
+			<version>1.2.17</version>
+		</dependency>
+
+		<!-- Testing -->
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>4.12</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+	<properties>
+		<maven.compiler.source>1.7</maven.compiler.source>
+		<maven.compiler.target>1.7</maven.compiler.target>
+	</properties>
+	<build>
+		<plugins>
+			<plugin>
+				<artifactId>maven-assembly-plugin</artifactId>
+				<version>3.1.0</version>
+				<configuration>
+					<descriptorRefs>
+						<descriptorRef>jar-with-dependencies</descriptorRef>
+					</descriptorRefs>
+					<archive>
+						<manifest>
+							<mainClass>fr.pavnay.rabbits.Main</mainClass>
+						</manifest>
+					</archive>
+				</configuration>
+				<executions>
+					<execution>
+						<id>make-assembly</id>
+						<!-- this is used for inheritance merges -->
+						<phase>package</phase>
+						<!-- bind to the packaging phase -->
+						<goals>
+							<goal>single</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>

+ 17 - 0
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();
+		
+	}
+}

+ 202 - 0
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<Burrow> 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;
+	}
+	
+}

+ 36 - 0
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 + "]";
+	}
+    
+}

+ 18 - 0
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);
+}

+ 295 - 0
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<Tree> trees = new ArrayList<Tree>();
+    private List<Rabbit> rabbits = new CopyOnWriteArrayList<Rabbit>();
+    private List<Rabbit> savedRabbits = new CopyOnWriteArrayList<Rabbit>();
+    private List<Rabbit> deadRabbits = new CopyOnWriteArrayList<Rabbit>();
+    private List<Burrow> burrows = new ArrayList<Burrow>();
+    
+    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<Rabbit> getRabbits() {
+		return rabbits;
+	}
+    
+    /**
+     * 
+     * @param hunter
+     * @return Rabbits in hunter range
+     */
+    public List<Rabbit> getRabbitsInRange(Hunter hunter) {
+    	return (List<Rabbit>) CollectionUtils.select(rabbits, new RangePredicate(hunter));
+    }
+    
+    /**
+     * 
+     * @return Dead rabbits
+     */
+    public List<Rabbit> getDeadRabbits() {
+		return deadRabbits;
+	}
+    
+    /**
+     * 
+     * @return Rabbits in burrows
+     */
+    public List<Rabbit> 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<Burrow> 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<Tree> 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<Rabbit> rabbits) {
+    	for( Rabbit rabbit : rabbits ) {
+    		logger.debug(rabbit);
+    	}
+    	
+    }
+	@Override
+	public String toString() {
+		return "Forest [trees=" + trees + ", rabbits=" + rabbits + ", burrows=" + burrows + ", edgeArea=" + edgeArea
+				+ "]";
+	}
+    
+}

+ 129 - 0
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 + "]";
+	}
+    
+}

+ 91 - 0
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<Burrow> refuges = new ArrayList<Burrow>();
+    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<Burrow> 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
+				+ "]";
+	}
+	
+}

+ 22 - 0
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 + "]";
+	}
+    
+}

+ 22 - 0
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;
+    }
+}

+ 25 - 0
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;
+	}
+	
+}

+ 18 - 0
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('_', ' ');
+	}
+}

+ 25 - 0
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<Rabbit> {
+
+	private Hunter hunter;
+	
+	public RangePredicate(Hunter hunter) {
+		this.hunter = hunter;
+	}
+	
+	public boolean evaluate(Rabbit rabbit) {
+		return hunter.isInRange(rabbit);
+	}
+
+}

+ 115 - 0
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);
+	}
+
+}

+ 98 - 0
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<Status, String> {
+
+	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<String> 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;
+	    }
+	}
+	
+}

+ 114 - 0
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);
+	}
+
+}

+ 40 - 0
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);
+	}
+}

+ 71 - 0
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);
+	}
+
+}

+ 54 - 0
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);
+	}
+}

+ 98 - 0
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()));
+	}
+	
+}

+ 28 - 0
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);
+	}
+}

+ 8 - 0
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

+ 248 - 0
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());
+	}
+}

+ 246 - 0
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<Rabbit> 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);
+	}
+}

+ 108 - 0
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);
+	}
+}

+ 29 - 0
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());
+	}
+}