diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala
index ddb5e0f969ebb4ea52825b4250b37920fc0f919d..e3d7bf4bafbfa7c06643abccce14831e34291a0c 100644
--- a/src/main/scala/Main.scala
+++ b/src/main/scala/Main.scala
@@ -1,13 +1,17 @@
 // Copyright (C) Maxime MORGE 2024
 import org.scata.core.SingleAssignmentProblem
 import org.scata.algorithm.CBAA
+import org.scata.patrol.Environment
 
 object Main {
   def main(args: Array[String]): Unit = {
 
-    val nbWorkers = 4
-    val nbTasks = 3
-    val pb = SingleAssignmentProblem.randomProblem(nbWorkers, nbTasks)
+    val nbRobots= 4
+    val nbTargets = 3
+    val env = Environment.randomEnvironment(nbRobots, nbTargets)
+    println(env)
+    val pb = env.toSAP()
+    println(pb)
     val cbaa = new CBAA(pb)
     println(pb)
     val solution = cbaa.solve()
diff --git a/src/main/scala/org/scata/algorithm/CBAA.scala b/src/main/scala/org/scata/algorithm/CBAA.scala
index cd2b02b43596c45e6c172ee91ca69cf63db7db28..55cd064688704df8e07e3420a8ccfc1817e21e24 100644
--- a/src/main/scala/org/scata/algorithm/CBAA.scala
+++ b/src/main/scala/org/scata/algorithm/CBAA.scala
@@ -54,7 +54,7 @@ class CBAA(pb : SingleAssignmentProblem) {
       }
       // If there are valid tasks, select the one with the minimum cost
       if (pb.tasks.exists(task => validTasks(task))) {
-        val bestTask = validTasks.filter(_._2).keys.minBy(task => pb.cost(worker, task))
+        val bestTask = validTasks.filter(_._2).keys.minBy(task => (pb.cost(worker, task), task.name))
         if (debug) println(s"Worker ${worker.name} selects task ${bestTask.name} with cost ${pb.cost(worker, bestTask)}")
         workerTaskList = workerTaskList.updated(worker, workerTaskList(worker).updated(bestTask, true))
         winningBid = winningBid.updated(worker, winningBid(worker).updated(bestTask, (worker, pb.cost(worker, bestTask))))
@@ -77,7 +77,7 @@ class CBAA(pb : SingleAssignmentProblem) {
       // Find the minimum winning bid among the worker's neighbors (including itself)
       val minWinningBid = neighbours(worker)
         .map(neighbor => winningBid(neighbor)(task))
-        .minBy(_._2)
+        .minBy { case (winningWorker, cost) => (cost, winningWorker.name) }
       println(s"Worker ${worker.name} minimum winning bid for task ${task.name} is ${minWinningBid}")
       // Update the worker's winning bid for the task if a lower bid is found
       if (minWinningBid._2 < winningBid(worker)(task)._2) {
diff --git a/src/main/scala/org/scata/patrol/Entity.scala b/src/main/scala/org/scata/patrol/Entity.scala
new file mode 100644
index 0000000000000000000000000000000000000000..e1a55b988036fb321f2413213ffbee8e1e9a7f50
--- /dev/null
+++ b/src/main/scala/org/scata/patrol/Entity.scala
@@ -0,0 +1,22 @@
+// Copyright (C) Maxime MORGE, 2024
+package org.scata.patrol
+
+abstract class Entity(val name : String, val x : Int, val y: Int) extends Ordered[Entity]{
+  
+  override def equals(that: Any): Boolean =
+    that match {
+      case that: Entity => that.canEqual(this) && this.name == that.name
+      case _ => false
+    }
+  private def canEqual(a: Any) : Boolean = a.isInstanceOf[Entity]
+
+  /**
+   * Returns 0 if this an that are the same, negative if this < that, and positive otherwise
+   * Entitys are sorted with their name
+   */
+  def compare(that: Entity) : Int = {
+    if (this.name == that.name) return 0
+    else if (this.name > that.name) return 1
+    -1
+  }
+}
diff --git a/src/main/scala/org/scata/patrol/Environment.scala b/src/main/scala/org/scata/patrol/Environment.scala
new file mode 100644
index 0000000000000000000000000000000000000000..2efab9f5ba19cc4aa5ecbf5f9c737795fcfdc49d
--- /dev/null
+++ b/src/main/scala/org/scata/patrol/Environment.scala
@@ -0,0 +1,102 @@
+// Copyright (C) Maxime MORGE, 2024
+package org.scata.patrol
+
+import org.scata.core.{SingleAssignmentProblem, Task, Worker}
+import org.scata.core.SingleAssignmentProblem.MAX_COST
+
+import scala.collection.SortedSet
+import org.scata.utils.RandomUtils
+
+/**
+ * Class representing an environment
+ * @param robots are the robots
+ * @param targets art the targets
+ */
+class Environment(val robots: SortedSet[Robot],
+                              val targets: SortedSet[Target]) {
+
+  /**
+   * Returns a string describing the MASTAPlus problem
+   */
+  override def toString: String = {
+    val robotsStr = robots.map(_.toString).mkString(", ")
+    val targetStr = targets.map(_.toString).mkString(", ")
+
+    s"Robots (${robots.size}): $robotsStr\n" +
+      s"Tasks (${targets.size}): $targetStr"
+  }
+
+  /**
+   * Returns the number of robots
+   */
+  def m: Int = robots.size
+
+  /**
+   * Returns the number of targets
+   */
+  def n: Int = targets.size
+
+  /**
+   * Generate a single assignment problem
+   */
+  def toSAP(): SingleAssignmentProblem = {
+    if (n > m)
+      throw new RuntimeException("Cannot generate a single assignment problem since there are more targets than robots")
+
+    // Convert robots and targets into Workers and Tasks respectively
+    val workers: SortedSet[Worker] = robots.map(r => new Worker(r.name))
+    val tasks: SortedSet[Task] = targets.map(t => new Task(t.name))
+
+    // Create mapping from names to positions for cost calculation
+    val robotMap = robots.map(r => r.name -> (r.x, r.y)).toMap
+    val targetMap = targets.map(t => t.name -> (t.x, t.y)).toMap
+
+    // Compute cost using Euclidean distance
+    val cost: Map[(Worker, Task), Int] = (for {
+      worker <- workers
+      task <- tasks
+    } yield {
+      val (rx, ry) = robotMap(worker.name)
+      val (tx, ty) = targetMap(task.name)
+      val distance = math.abs(rx - tx) + math.abs(ry - ty)
+      ((worker, task), distance.toInt)
+    }).toMap
+
+    new SingleAssignmentProblem(workers, tasks, cost)
+  }
+}
+
+/**
+ * Factory for SingleAssignmentProblem
+ */
+object Environment{
+
+  implicit val order  : Ordering[Double] = Ordering.Double.TotalOrdering
+  // eventually Ordering.Double.IeeeOrdering
+
+  private val MAX_X : Int = 10 // Maximum width
+  private val MAX_Y : Int = 10 // Maximum height
+
+  /**
+   * Returns a random single-assignment problem  instance with
+   * @param m robots
+   * @param n tasks
+   */
+  def randomEnvironment(m: Int, n: Int): Environment = {
+    // Robots
+    val robots: SortedSet[Robot] = collection.immutable.SortedSet[Robot]() ++
+      (for (i <- 1 to m) yield new Robot(name = "r%02d".format(i),
+        RandomUtils.random(1, MAX_X),
+        RandomUtils.random(1, MAX_Y)
+      ))
+
+    // Targets
+    val targets: SortedSet[Target] = collection.immutable.SortedSet[Target]() ++
+      (for (i <- 1 to n) yield new Target(name = "t%02d".format(i),
+        RandomUtils.random(0, MAX_X-1),
+        RandomUtils.random(0, MAX_Y-1)
+      ))
+
+    new Environment(robots, targets)
+  }
+}
diff --git a/src/main/scala/org/scata/patrol/Robot.scala b/src/main/scala/org/scata/patrol/Robot.scala
new file mode 100644
index 0000000000000000000000000000000000000000..fb9d88795007c42651650e4bb819e3938b9f1a78
--- /dev/null
+++ b/src/main/scala/org/scata/patrol/Robot.scala
@@ -0,0 +1,6 @@
+// Copyright (C) Maxime MORGE, 2024
+package org.scata.patrol
+
+class Robot(name: String, x: Int, y: Int) extends Entity(name, x, y) {
+  override def toString: String = s"Robot($name, $x, $y)"
+}
\ No newline at end of file
diff --git a/src/main/scala/org/scata/patrol/Target.scala b/src/main/scala/org/scata/patrol/Target.scala
new file mode 100644
index 0000000000000000000000000000000000000000..55f3e6d74674d5d690634446b22fee795c88b044
--- /dev/null
+++ b/src/main/scala/org/scata/patrol/Target.scala
@@ -0,0 +1,6 @@
+// Copyright (C) Maxime MORGE, 2024
+package org.scata.patrol
+
+class Target(name: String, x: Int, y: Int) extends Entity(name, x, y) {
+  override def toString: String = s"Target($name, $x, $y)"
+}