First of all, please ensure you have runned build.sbt. This file contains the configuration of the project.
To model our traffic project we choose 4 different classes: Position, Velocity, Acceleration, Vehicle.
The Position class is designed to represent the coordinates of a point in a 2D space, allowing for manipulation of the position and calculation of distances between points. This is particularly useful for modeling the movement of objects, such as cars, in a 2D plane
case class Position(var x: Float, var y: Float) {
def move(dx: Float, dy: Float): Position = Position(x + dx, y + dy)
def distance(position1: Position, position2: Position): Double = {
Math.sqrt(
Math.pow(position1.x - position2.x, 2) +
Math.pow(position1.y - position2.y, 2)
)
}
}
The Velocity class represents the velocity of an object in a 2D space, with components along the x-axis (dx) and y-axis (dy). It ensures that the velocity components are non-negative and calculates the total speed (vitesseTotal).
case class Velocity(var dx: Float, var dy: Float) {
require(dx >= 0, "dx must be non-negative")
require(dy >= 0, "dy must be non-negative")
var vitesseTotal: Float = Math.sqrt(dx * dx + dy * dy).toFloat
}
This class is useful for representing and manipulating the acceleration of an object moving in a 2D plane.
case class Acceleration(var ax: Float, var ay: Float){}
The Vehicule class models a vehicle in a 2D space, managing its position, velocity, and acceleration.
case class Vehicule(var currentvitesse: Velocity,
var maxVitesse: Float,
var position: Position)
This function will:
- sort cars by their x-coordinate
- returns the index of the first element that satisfies the condition,
- return the Car in Front or no car
def findCarInFront(cars: ArrayBuffer[Vehicule]): Option[Vehicule] = {
val sortedCars = cars.sortBy(_.position.x)
val index = sortedCars.indexWhere(_.position.x > position.x)
if (index != -1) Some(sortedCars(index)) else None }
Unfortunately, this create some inconsistencies each time a car is in the right most position, not detecting that there is a car but at the beginning of the left most position.
The updateVelocity method in the Vehicule class is responsible for updating the velocity of the vehicle based on its surroundings and the presence of other vehicles.
If no cars are detected within the threshold, the vehicle accelerates by increasing its velocity (currentvitesse.dx) based on its acceleration (acceleration.ax) and the time elapsed (deltaTime).
If a car is detected within the threshold, the method tries to find the car directly in front using the findCarInFront method.
If a car in front is found:
- It checks the distance to the car in front.
- If the distance is greater than
minDistance, the vehicle accelerates. - If the distance is less than
minDistance, the vehicle decelerates to match the speed of the car in front. - If no car is found in front (which theoretically shouldn't happen if
detectCarreturned true), the vehicle accelerates.
To simulate our traffic, we selected JFreeChart, which allows for real-time simulation.

To compare different state of our simulation, we had to fix all the parameters aside one and modify the last one. Some parameters we found relevant to increase or decrease are the following.
//params to modify
val carWidth = 10f
val numCars = 40
val density = numCars*carWidth / roadLength
val minDistance = 30f
For example, reducing the number of cars will result in less stopped car overall.
To meet the physics course requirements, we had to make some compromise to the functional programming world. The same project, can be found in the branch called lemniscate, written in functional programming. Unfortunately, some implementations didn't make it. Here are some of the functional programming paradigm that we respected:
We return new instances of Vehicule with the updated state instead of modifying the existing instance.
They take inputs and return outputs without modifying any state outside their scope.
def accelerate(v: Vehicule): Vehicule = {
if (v.currentvitesse.dx < v.maxVitesse) {
v.copy(currentvitesse = v.currentvitesse.update(v.currentvitesse.dx + 0.01f))
} else v
}
It encapsulates specific functionalities. This promotes code reusability and makes the code easier to reason about.
def updateVelocity(v: Vehicule, deltaTime: Float, cars: ArrayBuffer[Vehicule], threshold: Float, g: GdxGraphics, roadLength: Float): Vehicule = {
val updatingCondition = detectCar(v, cars, threshold, g)
val updatedSpeed = if (!updatingCondition) {
val newDx = math.min(v.currentvitesse.dx + 0.01f * deltaTime, v.maxVitesse)
v.copy(currentvitesse = v.currentvitesse.update(newDx))
} else {
findCarInFront(v, cars, roadLength) match {
case Some(car) =>
val speedOfCarInFront = car.currentvitesse.dx
if (distanceToCar(v, car) >= minDistance) {
accelerate(v)
} else {
decelerate(v, speedOfCarInFront)
}
case None =>
accelerate(v)
}
}
updatedSpeed
}
Here, we establish a condition. Then we calculate a new state and we return a new Vehicle object.

