From ce226f261aedb1b81335408adc130c49eb2161df Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Tue, 21 Oct 2025 11:11:56 +0100 Subject: [PATCH] added intersect, vehicle and light logic + random poisson dist --- .gitignore | 50 +++++ main/src/main/java/sd/model/Intersection.java | 132 +++++++++++++ main/src/main/java/sd/model/TrafficLight.java | 180 ++++++++++++++++++ main/src/main/java/sd/model/Vehicle.java | 117 ++++++++++++ .../main/java/sd/util/RandomGenerator.java | 68 +++++++ main/src/main/resources/simulation.properties | 50 +++-- main/target/classes/sd/Entry.class | Bin 526 -> 0 bytes 7 files changed, 577 insertions(+), 20 deletions(-) create mode 100644 .gitignore create mode 100644 main/src/main/java/sd/model/Intersection.java create mode 100644 main/src/main/java/sd/model/TrafficLight.java create mode 100644 main/src/main/java/sd/model/Vehicle.java create mode 100644 main/src/main/java/sd/util/RandomGenerator.java delete mode 100644 main/target/classes/sd/Entry.class diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6fa7db9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Compiled class files +*.class + +# Log files +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# VS Code settings +.vscode/ + +# Eclipse files +*.pydevproject +.project +.classpath +.cproject +.settings/ +bin/ +tmp/ + +# IntelliJ IDEA files +*.iml +.idea/ +out/ + +# Mac system files +.DS_Store + +# Windows system files +Thumbs.db + +# Maven +target/ + +# Gradle +.gradle/ +build/ + +# Other +*.swp +*.pdf diff --git a/main/src/main/java/sd/model/Intersection.java b/main/src/main/java/sd/model/Intersection.java new file mode 100644 index 0000000..3e0df5e --- /dev/null +++ b/main/src/main/java/sd/model/Intersection.java @@ -0,0 +1,132 @@ +package sd.model; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Represents an intersection in the traffic simulation. + * + * Each intersection coordinates multiple traffic lights - one for each direction - + * and handles routing vehicles based on their next destination. + */ +public class Intersection { + + // Identity and configuration + private final String id; // ex. "Cr1", "Cr2" + private final Map trafficLights; // direction -> light + private final Map routing; // destination -> direction + + // Stats + private int totalVehiclesReceived; + private int totalVehiclesSent; + private double averageWaitingTime; + + public Intersection(String id) { + this.id = id; + this.trafficLights = new HashMap<>(); + this.routing = new HashMap<>(); + this.totalVehiclesReceived = 0; + this.totalVehiclesSent = 0; + this.averageWaitingTime = 0.0; + } + + /** + * Registers a traffic light under this intersection. + * The light is identified by its direction (ex., "North", "East"). + */ + public void addTrafficLight(TrafficLight trafficLight) { + trafficLights.put(trafficLight.getDirection(), trafficLight); + } + + /** + * Defines how vehicles should be routed through this intersection. + * + * @param nextDestination The next intersection or exit on the vehicle's route + * @param direction The direction (traffic light) vehicles should take + */ + public void configureRoute(String nextDestination, String direction) { + routing.put(nextDestination, direction); + } + + /** + * Accepts an incoming vehicle and places it in the correct queue. + * If the route or traffic light can't be found, logs an error. + */ + public void receiveVehicle(Vehicle vehicle) { + totalVehiclesReceived++; + + String nextDestination = vehicle.getCurrentDestination(); + String direction = routing.get(nextDestination); + + if (direction != null && trafficLights.containsKey(direction)) { + trafficLights.get(direction).addVehicle(vehicle); + } else { + System.err.printf( + "Routing error: could not place vehicle %s (destination: %s)%n", + vehicle.getId(), nextDestination + ); + } + } + + /** Returns the traffic light controlling the given direction, if any. */ + public TrafficLight getTrafficLight(String direction) { + return trafficLights.get(direction); + } + + /** Returns all traffic lights belonging to this intersection. */ + public List getTrafficLights() { + return new ArrayList<>(trafficLights.values()); + } + + /** Returns the total number of vehicles currently queued across all directions. */ + public int getTotalQueueSize() { + return trafficLights.values().stream() + .mapToInt(TrafficLight::getQueueSize) + .sum(); + } + + // --- Stats and getters --- + + public String getId() { + return id; + } + + public int getTotalVehiclesReceived() { + return totalVehiclesReceived; + } + + public int getTotalVehiclesSent() { + return totalVehiclesSent; + } + + public void incrementVehiclesSent() { + totalVehiclesSent++; + } + + public double getAverageWaitingTime() { + return averageWaitingTime; + } + + /** + * Updates the running average waiting time with a new sample. + */ + public void updateAverageWaitingTime(double newTime) { + // Weighted incremental average (avoids recalculating from scratch) + averageWaitingTime = (averageWaitingTime * (totalVehiclesSent - 1) + newTime) + / totalVehiclesSent; + } + + @Override + public String toString() { + return String.format( + "Intersection{id='%s', lights=%d, queues=%d, received=%d, sent=%d}", + id, + trafficLights.size(), + getTotalQueueSize(), + totalVehiclesReceived, + totalVehiclesSent + ); + } +} diff --git a/main/src/main/java/sd/model/TrafficLight.java b/main/src/main/java/sd/model/TrafficLight.java new file mode 100644 index 0000000..b7f8516 --- /dev/null +++ b/main/src/main/java/sd/model/TrafficLight.java @@ -0,0 +1,180 @@ +package sd.model; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Represents a single traffic light controlling one direction at an intersection. + * + * Each light maintains its own queue of vehicles and alternates between + * green and red states. It's designed to be thread-safe (maybe...), so multiple + * threads (like vehicles or controllers) can safely interact with it. + */ +public class TrafficLight { + + // Identity and configuration + private final String id; // ex. "Cr1-N" + private final String direction; // ex. "North", "South", etc. + private TrafficLightState state; + + // Vehicle management + private final Queue queue; + + // Synchronization primitives + private final Lock lock; + private final Condition vehicleAdded; + private final Condition lightGreen; + + // Timing configuration (seconds) + private double greenTime; + private double redTime; + + // Basic stats + private int totalVehiclesProcessed; + + public TrafficLight(String id, String direction, double greenTime, double redTime) { + this.id = id; + this.direction = direction; + this.state = TrafficLightState.RED; + this.queue = new LinkedList<>(); + + this.lock = new ReentrantLock(); + this.vehicleAdded = lock.newCondition(); + this.lightGreen = lock.newCondition(); + + this.greenTime = greenTime; + this.redTime = redTime; + this.totalVehiclesProcessed = 0; + } + + /** + * Adds a vehicle to the waiting queue. + * Signals any waiting threads that a new vehicle has arrived. + */ + public void addVehicle(Vehicle vehicle) { + lock.lock(); + try { + queue.offer(vehicle); + vehicleAdded.signalAll(); + } finally { + lock.unlock(); + } + } + + /** + * Attempts to let one vehicle pass through. + * Only works if the light is green; otherwise returns null. + */ + public Vehicle removeVehicle() { + lock.lock(); + try { + if (state == TrafficLightState.GREEN && !queue.isEmpty()) { + Vehicle vehicle = queue.poll(); + totalVehiclesProcessed++; + return vehicle; + } + return null; + } finally { + lock.unlock(); + } + } + + /** + * Changes the light’s state (ex., RED -> GREEN). + * When the light turns green, waiting threads are notified. + * ¯\_(ツ)_/¯ + */ + public void changeState(TrafficLightState newState) { + lock.lock(); + try { + this.state = newState; + if (newState == TrafficLightState.GREEN) { + lightGreen.signalAll(); + } + } finally { + lock.unlock(); + } + } + + /** Returns how many vehicles are currently queued. */ + public int getQueueSize() { + lock.lock(); + try { + return queue.size(); + } finally { + lock.unlock(); + } + } + + /** Checks whether there are no vehicles waiting. */ + public boolean isQueueEmpty() { + lock.lock(); + try { + return queue.isEmpty(); + } finally { + lock.unlock(); + } + } + + // --- Getters & Setters --- + + public String getId() { + return id; + } + + public String getDirection() { + return direction; + } + + public TrafficLightState getState() { + lock.lock(); + try { + return state; + } finally { + lock.unlock(); + } + } + + public double getGreenTime() { + return greenTime; + } + + public void setGreenTime(double greenTime) { + this.greenTime = greenTime; + } + + public double getRedTime() { + return redTime; + } + + public void setRedTime(double redTime) { + this.redTime = redTime; + } + + public int getTotalVehiclesProcessed() { + return totalVehiclesProcessed; + } + + public Lock getLock() { + return lock; + } + + public Condition getVehicleAdded() { + return vehicleAdded; + } + + public Condition getLightGreen() { + return lightGreen; + } + + @Override + public String toString() { + return String.format( + "TrafficLight{id='%s', direction='%s', state=%s, queueSize=%d}", + id, direction, state, getQueueSize() + ); + } +} diff --git a/main/src/main/java/sd/model/Vehicle.java b/main/src/main/java/sd/model/Vehicle.java new file mode 100644 index 0000000..d8d4954 --- /dev/null +++ b/main/src/main/java/sd/model/Vehicle.java @@ -0,0 +1,117 @@ +package sd.model; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a single vehicle moving through the simulation. + * + * Each vehicle has a route - a sequence of intersections it will pass through - + * and keeps track of how long it has waited and traveled overall. + * + * Serializable so it can be sent between processes or nodes over sockets. type shit + */ +public class Vehicle implements Serializable { + private static final long serialVersionUID = 1L; + + // Identity and configuration + private final String id; + private final VehicleType type; + private final double entryTime; // When it entered the system + private final List route; // ex., ["Cr1", "Cr3", "S"] + private int currentRouteIndex; // Current position in the route + + // Metrics + private double totalWaitingTime; // Total time spent waiting at red lights + private double totalCrossingTime; // Time spent actually moving between intersections + + public Vehicle(String id, VehicleType type, double entryTime, List route) { + this.id = id; + this.type = type; + this.entryTime = entryTime; + this.route = new ArrayList<>(route); + this.currentRouteIndex = 0; + this.totalWaitingTime = 0.0; + this.totalCrossingTime = 0.0; + } + + /** + * Moves the vehicle to the next stop in its route. + * + * @return true if there are still destinations ahead, false if the route is finished + */ + public boolean advanceRoute() { + currentRouteIndex++; + return currentRouteIndex < route.size(); + } + + /** + * Gets the current destination (the next intersection or exit). + * Returns null if the route is already complete. + */ + public String getCurrentDestination() { + return (currentRouteIndex < route.size()) ? route.get(currentRouteIndex) : null; + } + + /** Returns true if the vehicle has completed its entire route. */ + public boolean hasReachedEnd() { + return currentRouteIndex >= route.size(); + } + + // --- Getters and metrics management --- + + public String getId() { + return id; + } + + public VehicleType getType() { + return type; + } + + public double getEntryTime() { + return entryTime; + } + + public List getRoute() { + return new ArrayList<>(route); + } + + public int getCurrentRouteIndex() { + return currentRouteIndex; + } + + public double getTotalWaitingTime() { + return totalWaitingTime; + } + + public void addWaitingTime(double time) { + totalWaitingTime += time; + } + + public double getTotalCrossingTime() { + return totalCrossingTime; + } + + public void addCrossingTime(double time) { + totalCrossingTime += time; + } + + /** + * Calculates how long the vehicle has been in the system so far. + * + * @param currentTime the current simulation time + * @return total elapsed time since the vehicle entered + */ + public double getTotalTravelTime(double currentTime) { + return currentTime - entryTime; + } + + @Override + public String toString() { + return String.format( + "Vehicle{id='%s', type=%s, next='%s', route=%s}", + id, type, getCurrentDestination(), route + ); + } +} diff --git a/main/src/main/java/sd/util/RandomGenerator.java b/main/src/main/java/sd/util/RandomGenerator.java new file mode 100644 index 0000000..3c44b0d --- /dev/null +++ b/main/src/main/java/sd/util/RandomGenerator.java @@ -0,0 +1,68 @@ +package sd.util; + +import java.util.Random; + +/** + * Utility class for generating random values used throughout the simulation. + * + * Includes helpers for exponential distributions (for vehicle arrivals), + * uniform randoms, and probability-based decisions. + */ +public class RandomGenerator { + + private static final Random random = new Random(); + + /** + * Returns a random time interval that follows an exponential distribution. + * + * Useful for modeling inter-arrival times in a Poisson process. + * + * @param lambda the arrival rate (λ) + * @return the time interval until the next arrival + */ + public static double generateExponentialInterval(double lambda) { + return Math.log(1 - random.nextDouble()) / -lambda; + } + + /** + * Returns a random integer between {@code min} and {@code max}, inclusive. + */ + public static int generateRandomInt(int min, int max) { + return random.nextInt(max - min + 1) + min; + } + + /** + * Returns a random double between {@code min} (inclusive) and {@code max} (exclusive). + */ + public static double generateRandomDouble(double min, double max) { + return min + (max - min) * random.nextDouble(); + } + + /** + * Returns {@code true} with the given probability. + * + * @param probability a value between 0.0 and 1.0 + */ + public static boolean occursWithProbability(double probability) { + return random.nextDouble() < probability; + } + + /** + * Picks a random element from the given array. + * + * @throws IllegalArgumentException if the array is empty + */ + public static T chooseRandom(T[] array) { + if (array.length == 0) { + throw new IllegalArgumentException("Array cannot be empty."); + } + return array[random.nextInt(array.length)]; + } + + /** + * Sets the random generator’s seed, allowing reproducible results. + */ + public static void setSeed(long seed) { + random.setSeed(seed); + } +} diff --git a/main/src/main/resources/simulation.properties b/main/src/main/resources/simulation.properties index 06ec791..6deec88 100644 --- a/main/src/main/resources/simulation.properties +++ b/main/src/main/resources/simulation.properties @@ -1,8 +1,13 @@ -# Traffic simulation configuration -# This file contains all the necessary configurations to run the simulation +# ========================================================= +# Traffic Simulation Configuration +# --------------------------------------------------------- +# All parameters controlling network layout, timing, +# and simulation behavior. +# ========================================================= -# === NETWORK CONFIGURATIONS === -# Intersections +# === NETWORK CONFIGURATION === + +# Intersections (each with its host and port) intersection.Cr1.host=localhost intersection.Cr1.port=8001 intersection.Cr2.host=localhost @@ -14,30 +19,32 @@ intersection.Cr4.port=8004 intersection.Cr5.host=localhost intersection.Cr5.port=8005 -# Exit Node +# Exit node exit.host=localhost exit.port=9001 -# Dashboard +# Dashboard server dashboard.host=localhost dashboard.port=9000 -# === SIMULATION CONFIGURATIONS === -# Simulation duration in seconds (3600 = 1 hour) + +# === SIMULATION CONFIGURATION === + +# Total duration in seconds (3600 = 1 hour) simulation.duration=3600.0 # Vehicle arrival model: FIXED or POISSON simulation.arrival.model=POISSON -# Arrival rate (λ) for Poisson model (vehicles per second) +# λ (lambda): average arrival rate (vehicles per second) simulation.arrival.rate=0.5 -# Fixed interval between arrivals (used if model = FIXED) +# Fixed interval between arrivals (only used if model=FIXED) simulation.arrival.fixed.interval=2.0 -# === TRAFFIC LIGHT CONFIGURATIONS === -# Times in seconds for each traffic light (green and red) -# Format: trafficlight... + +# === TRAFFIC LIGHT TIMINGS === +# Format: trafficlight...= # Intersection 1 trafficlight.Cr1.North.green=30.0 @@ -89,15 +96,18 @@ trafficlight.Cr5.East.red=30.0 trafficlight.Cr5.West.green=30.0 trafficlight.Cr5.West.red=30.0 -# === VEHICLE CONFIGURATIONS === -# Probability of generating a light vehicle (0.0 to 1.0) -# The rest will be heavy vehicles -vehicle.probability.light=0.7 +# === VEHICLE CONFIGURATION === +# Probability distribution for vehicle types (must sum to 1.0) +vehicle.probability.bike=0.2 +vehicle.probability.light=0.6 +vehicle.probability.heavy=0.2 -# Crossing time in seconds +# Average crossing times (in seconds) +vehicle.crossing.time.bike=1.5 vehicle.crossing.time.light=2.0 vehicle.crossing.time.heavy=4.0 -# === STATISTICS CONFIGURATIONS === -# Interval to send updates to the dashboard (in seconds) +# === STATISTICS === + +# Interval between dashboard updates (seconds) statistics.update.interval=10.0 diff --git a/main/target/classes/sd/Entry.class b/main/target/classes/sd/Entry.class deleted file mode 100644 index 72c0d912807819313e2300f0e339ffae72b597a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 526 zcmZutJ5K^Z5dN0eJv;pm+ak{fqNN5dTJoUP#p0W?kXM(-P_(s^dpAcsSIRv z&X8`lx(u0%a3E}?VL8Ym$58NOAZ{jpPw0EzQ-Y!7g?+BNT+6urYi6P+IcBiEf4w$w z`CJAJ)#jr&*X=}F2E%r%Yhev#1EmF5o#{9dzJqmC$Wb^UgOwRrhVC69Bf<;rw@qv? z