From d92362edb061b6ab8595b8c02c6d83c350f35f05 Mon Sep 17 00:00:00 2001 From: Zabuzard Date: Mon, 1 Dec 2025 09:04:01 +0100 Subject: [PATCH] Submission --- .../Assignment/src/main/resources/logback.xml | 17 + .../org/togetherjava/event/elevator/Main.java | 71 ++-- .../event/elevator/SimulationException.java | 11 + .../event/elevator/SimulationService.java | 101 +++++ .../event/elevator/SimulationUtils.java | 70 ++++ .../event/elevator/api/ElevatorListener.java | 25 ++ .../event/elevator/api/ElevatorPanel.java | 29 ++ .../event/elevator/api/FloorPanelSystem.java | 17 + .../event/elevator/elevators/Elevator.java | 90 ----- .../elevator/elevators/ElevatorPanel.java | 28 -- .../elevator/elevators/ElevatorSystem.java | 49 --- .../elevator/elevators/FloorPanelSystem.java | 17 - .../elevator/elevators/TravelDirection.java | 6 - .../event/elevator/enums/HumanState.java | 8 + .../event/elevator/enums/TravelDirection.java | 6 + .../elevator/humans/ElevatorListener.java | 28 -- .../event/elevator/humans/Human.java | 98 ----- .../event/elevator/models/Elevator.java | 190 ++++++++++ .../event/elevator/models/ElevatorSystem.java | 129 +++++++ .../event/elevator/models/Human.java | 190 ++++++++++ .../elevator/models/HumanStatistics.java | 24 ++ .../event/elevator/models/Simulation.java | 43 +++ .../event/elevator/models/View.java | 136 +++++++ .../elevator/simulation/HumanStatistics.java | 24 -- .../event/elevator/simulation/Simulation.java | 155 -------- .../event/elevator/simulation/View.java | 113 ------ .../test/PreviousElevatorSystemTest.java | 92 ++--- .../Assignment/test/PreviousElevatorTest.java | 106 +++--- .../Assignment/test/PreviousHumanTest.java | 172 +++++---- Contest/Assignment/test/SanityTest.java | 349 ++++++++++-------- Contest/Assignment/test/SimulationTest.java | 129 ++++--- 31 files changed, 1492 insertions(+), 1031 deletions(-) create mode 100644 Contest/Assignment/src/main/resources/logback.xml create mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/SimulationException.java create mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/SimulationService.java create mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/SimulationUtils.java create mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/api/ElevatorListener.java create mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/api/ElevatorPanel.java create mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/api/FloorPanelSystem.java delete mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Elevator.java delete mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorPanel.java delete mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorSystem.java delete mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/elevators/FloorPanelSystem.java delete mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/elevators/TravelDirection.java create mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/enums/HumanState.java create mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/enums/TravelDirection.java delete mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/humans/ElevatorListener.java delete mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/humans/Human.java create mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/models/Elevator.java create mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/models/ElevatorSystem.java create mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/models/Human.java create mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/models/HumanStatistics.java create mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/models/Simulation.java create mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/models/View.java delete mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/simulation/HumanStatistics.java delete mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/simulation/Simulation.java delete mode 100644 Contest/Assignment/src/org/togetherjava/event/elevator/simulation/View.java diff --git a/Contest/Assignment/src/main/resources/logback.xml b/Contest/Assignment/src/main/resources/logback.xml new file mode 100644 index 0000000..5e004a6 --- /dev/null +++ b/Contest/Assignment/src/main/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + + + + ${PATTERN} + + + + + + + + + diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/Main.java b/Contest/Assignment/src/org/togetherjava/event/elevator/Main.java index e9e4159..2d06db7 100644 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/Main.java +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/Main.java @@ -1,47 +1,40 @@ package org.togetherjava.event.elevator; -import org.togetherjava.event.elevator.elevators.Elevator; -import org.togetherjava.event.elevator.humans.Human; -import org.togetherjava.event.elevator.simulation.Simulation; +import org.togetherjava.event.elevator.models.Simulation; +import lombok.extern.slf4j.Slf4j; + +@Slf4j public final class Main { - /** - * Starts the application. - *

- * Will create an elevator-system simulation, execute it until it is done - * and pretty print the state to console. - * - * @param args Not supported - */ - public static void main(final String[] args) { - // Select a desired simulation for trying out your code. - // Start with the simple simulations first, try out the bigger systems once you got it working. - // Eventually try out the randomly generated systems. If you want to debug a problem you encountered - // with one of them, note down the seed that it prints at the beginning and then use the variant that takes this seed. - // That way, it will generate the same system again, and you can repeat the test. - Simulation simulation = Simulation.createSingleElevatorSingleHumanSimulation(); - // Simulation simulation = Simulation.createSimpleSimulation(); - // Simulation simulation = Simulation.createRandomSimulation(5, 50, 10); - // Simulation simulation = Simulation.createRandomSimulation(putDesiredSeedHere, 5, 50, 10); - - simulation.printSummary(); - - System.out.println("Starting simulation..."); - simulation.start(); - simulation.prettyPrint(); - - while (!simulation.isDone()) { - System.out.println("\tSimulation step " + simulation.getStepCount()); - simulation.step(); - simulation.prettyPrint(); - - if (simulation.getStepCount() >= 100_000) { - throw new IllegalStateException("Simulation aborted. All humans should have arrived" - + " by now, but they did not. There is likely a bug in your code."); - } + + static void main() { + try { + Simulation simulation = SimulationService.createRandomSimulation(2, 4, 4); + + SimulationUtils.printSummary(simulation); + + SimulationService.start(simulation); + + SimulationUtils.prettyPrint(simulation); + + final int stepLimit = 100_000; + + while (!SimulationService.isDone(simulation)) { + log.info("\tSimulation step {}", simulation.getStepCount()); + SimulationService.step(simulation); + SimulationUtils.prettyPrint(simulation); + + if (simulation.getStepCount() >= stepLimit) { + throw new SimulationException( + "Simulation aborted. All humans should have arrived by now, but they did not. There is likely a bug in your code."); } - System.out.println("Simulation is done."); + } + log.info("Elevator Simulation is done."); + + SimulationUtils.printResult(simulation); - simulation.printResult(); + } catch (SimulationException ex) { + log.error("Elevator Simulation error: {}", ex.getMessage()); } + } } diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/SimulationException.java b/Contest/Assignment/src/org/togetherjava/event/elevator/SimulationException.java new file mode 100644 index 0000000..c0a6606 --- /dev/null +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/SimulationException.java @@ -0,0 +1,11 @@ +package org.togetherjava.event.elevator; + +import java.io.Serial; + +public final class SimulationException extends RuntimeException { + @Serial private static final long serialVersionUID = 1L; + + public SimulationException(String message) { + super(message); + } +} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/SimulationService.java b/Contest/Assignment/src/org/togetherjava/event/elevator/SimulationService.java new file mode 100644 index 0000000..de06e99 --- /dev/null +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/SimulationService.java @@ -0,0 +1,101 @@ +package org.togetherjava.event.elevator; + +import java.util.List; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Stream; + +import org.togetherjava.event.elevator.enums.HumanState; +import org.togetherjava.event.elevator.models.Elevator; +import org.togetherjava.event.elevator.models.Human; +import org.togetherjava.event.elevator.models.HumanStatistics; +import org.togetherjava.event.elevator.models.Simulation; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public final class SimulationService { + private SimulationService() { + throw new UnsupportedOperationException(SimulationUtils.CANNOT_INSTANTIATE_UTILITY_CLASS); + } + + public static Simulation createSingleElevatorSingleHumanSimulation() { + return new Simulation(List.of(new Elevator(1, 10, 5)), List.of(new Human(1, 10))); + } + + public static Simulation createSimpleSimulation() { + int minFloor = 1; + int floorsServed = 10; + + return new Simulation( + List.of(new Elevator(minFloor, floorsServed, 1), new Elevator(minFloor, floorsServed, 6)), + List.of( + new Human(1, 2), new Human(1, 5), new Human(8, 10), new Human(9, 3), new Human(10, 1))); + } + + public static Simulation createRandomSimulation( + int amountOfElevators, int amountOfHumans, int floorsServed) { + return createRandomSimulation( + ThreadLocalRandom.current().nextLong(), amountOfElevators, amountOfHumans, floorsServed); + } + + public static Simulation createRandomSimulation( + long seed, int amountOfElevators, int amountOfHumans, int floorsServed) { + + log.debug("Seed for random simulation is: {}", seed); + + // log.info( + // "creating random simulation: elevatorsCount {}, humansCount {}, floorsServed {}", + // amountOfElevators, + // amountOfHumans, + // floorsServed); + + Random random = new Random(seed); + + final int minFloor = 1; + + List elevators = + Stream.generate( + () -> { + int currentFloor = minFloor + random.nextInt(floorsServed); + return new Elevator(minFloor, floorsServed, currentFloor); + }) + .limit(amountOfElevators) + .toList(); + + List humans = + Stream.generate( + () -> { + int startingFloor = minFloor + random.nextInt(floorsServed); + int destinationFloor = minFloor + random.nextInt(floorsServed); + return new Human(startingFloor, destinationFloor); + }) + .limit(amountOfHumans) + .toList(); + + return new Simulation(elevators, humans); + } + + public static void start(Simulation simulation) { + // log.info("starting new simulation..."); + simulation.getElevatorSystem().ready(); + // log.info("Simulation is ready"); + } + + public static void step(Simulation simulation) { + // log.info("step started"); + + simulation.getElevatorSystem().moveOneFloor(); + + simulation.getHumanStatistics().forEach(HumanStatistics::step); + + simulation.increaseStepCount(); + + // log.info("step finished!"); + } + + public static boolean isDone(Simulation simulation) { + List humans = simulation.getHumans(); + return humans.stream().map(Human::getCurrentState).allMatch(HumanState.ARRIVED::equals); + } +} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/SimulationUtils.java b/Contest/Assignment/src/org/togetherjava/event/elevator/SimulationUtils.java new file mode 100644 index 0000000..81ea5e0 --- /dev/null +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/SimulationUtils.java @@ -0,0 +1,70 @@ +package org.togetherjava.event.elevator; + +import java.util.List; +import java.util.stream.LongStream; + +import org.togetherjava.event.elevator.enums.HumanState; +import org.togetherjava.event.elevator.models.HumanStatistics; +import org.togetherjava.event.elevator.models.Simulation; +import org.togetherjava.event.elevator.models.View; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public final class SimulationUtils { + + public static final String CANNOT_INSTANTIATE_UTILITY_CLASS = "Cannot instantiate utility class"; + + // public static final String LOG_ELEVATOR_DOESNT_MOVE_NO_PENDING_FLOORS = + // "elevator {} doesn't move, no pending floors"; + + public static final String LOG_ELEVATOR_MOVING_FROM_TO = + "elevator {} moving {} from {} to {} (target: {})"; + + private SimulationUtils() { + throw new UnsupportedOperationException(CANNOT_INSTANTIATE_UTILITY_CLASS); + } + + public static void printSummary(Simulation simulation) { + View view = simulation.getView(); + view.printSummary(); + } + + public static void prettyPrint(Simulation simulation) { + View view = simulation.getView(); + view.prettyPrint(); + } + + public static void printResult(Simulation simulation) { + log.info("Steps: {}", simulation.getStepCount()); + log.info("Median time spend per state:"); + + for (HumanState state : HumanState.values()) { + int averagePercentage = getAverageTimePercentageSpendForState(simulation, state); + log.info("\t{}: {}%", state, averagePercentage); + } + } + + public static int getAverageTimePercentageSpendForState(Simulation simulation, HumanState state) { + + List humanStatistics = simulation.getHumanStatistics(); + + long stepCount = simulation.getStepCount(); + + if (stepCount == 0) { + return 0; + } + + LongStream sortedSteps = + humanStatistics.stream().mapToLong(stats -> stats.stepsForState(state)).sorted(); + + long medianSteps = + humanStatistics.size() % 2 == 0 + ? (long) + sortedSteps.skip(humanStatistics.size() / 2 - 1).limit(2).average().orElseThrow() + : sortedSteps.skip(humanStatistics.size() / 2).findFirst().orElseThrow(); + + long medianPercentage = 100 * medianSteps / stepCount; + return (int) medianPercentage; + } +} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/api/ElevatorListener.java b/Contest/Assignment/src/org/togetherjava/event/elevator/api/ElevatorListener.java new file mode 100644 index 0000000..2985f7a --- /dev/null +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/api/ElevatorListener.java @@ -0,0 +1,25 @@ +package org.togetherjava.event.elevator.api; + +/** + * Listeners to elevator events. This is mostly interesting for humans who can then request + * elevators to move to desired floors. + */ +public interface ElevatorListener { + /** + * Fired when the elevator system is ready to receive requests. Elevators can now move. + * + * @param floorPanelSystem the system in the corridor that allows requesting elevators to the + * current floor + */ + void onElevatorSystemReady(FloorPanelSystem floorPanelSystem); + + /** + * Fired when an elevator arrived at a floor. Humans can now enter or exit if desired. + * + * @param elevatorPanel the system inside the elevator which provides information about the + * elevator and can be used to request a destination floor. + * @implNote The default implementation fires this event from all elevators to all humans, not + * only to humans that are relevant (i.e. humans that can enter the elevator). + */ + void onElevatorArrivedAtFloor(ElevatorPanel elevatorPanel); +} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/api/ElevatorPanel.java b/Contest/Assignment/src/org/togetherjava/event/elevator/api/ElevatorPanel.java new file mode 100644 index 0000000..98d914a --- /dev/null +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/api/ElevatorPanel.java @@ -0,0 +1,29 @@ +package org.togetherjava.event.elevator.api; + +/** + * The system inside an elevator which provides information about the elevator and can be used to + * request a destination floor. + */ +public interface ElevatorPanel { + /** + * The unique ID of the elevator. + * + * @return the unique ID of the elevator + */ + int getId(); + + /** + * The floor the elevator is currently at. + * + * @return the current floor + */ + int getCurrentFloor(); + + /** + * Requesting the elevator to eventually move to the given destination floor, for humans to exit. + * + * @param destinationFloor the desired destination, must be within the range served by this + * elevator + */ + void requestDestinationFloor(int destinationFloor); +} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/api/FloorPanelSystem.java b/Contest/Assignment/src/org/togetherjava/event/elevator/api/FloorPanelSystem.java new file mode 100644 index 0000000..4975253 --- /dev/null +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/api/FloorPanelSystem.java @@ -0,0 +1,17 @@ +package org.togetherjava.event.elevator.api; + +import org.togetherjava.event.elevator.enums.TravelDirection; + +/** The system in corridors that allows requesting elevators to the current floor. */ +public interface FloorPanelSystem { + /** + * Requests an elevator to move to the given floor to pick up a human. + * + * @param atFloor the floor to pick up the human at, must be within the range served by the system + * @param desiredTravelDirection the direction the human wants to travel into, can be used for + * determination of the best elevator + * @apiNote This represents a human standing in the corridor, pressing a button on the wall, + * requesting that an elevator comes to pick them up for travel into the given direction. + */ + void requestElevator(int atFloor, TravelDirection desiredTravelDirection); +} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Elevator.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Elevator.java deleted file mode 100644 index 51333b2..0000000 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Elevator.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.togetherjava.event.elevator.elevators; - -import java.util.StringJoiner; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * A single elevator that can serve a given amount of floors. - *

- * An elevator can take floor requests from either humans or the elevator system itself. - * The elevator will eventually move towards the requested floor and transport humans to their destinations. - */ -public final class Elevator implements ElevatorPanel { - private static final AtomicInteger NEXT_ID = new AtomicInteger(0); - - private final int id; - private final int minFloor; - private final int floorsServed; - private int currentFloor; - - /** - * Creates a new elevator. - * - * @param minFloor the minimum floor that the elevator can serve, must be greater than or equal to 1. - * @param floorsServed the amount of floors served in total by this elevator, must be greater than or equal to 2. - * Together with the minFloor this forms a consecutive range of floors with no gaps in between. - * @param currentFloor the floor the elevator starts at, must be within the defined range of floors served by the elevator - */ - public Elevator(int minFloor, int floorsServed, int currentFloor) { - if (minFloor <= 0 || floorsServed < 2) { - throw new IllegalArgumentException("Min floor must at least 1, floors served at least 2."); - } - if (currentFloor < minFloor || currentFloor >= minFloor + floorsServed) { - throw new IllegalArgumentException("The current floor must be between the floors served by the elevator."); - } - - this.id = NEXT_ID.getAndIncrement(); - this.minFloor = minFloor; - this.currentFloor = currentFloor; - this.floorsServed = floorsServed; - } - - @Override - public int getId() { - return id; - } - - public int getMinFloor() { - return minFloor; - } - - public int getFloorsServed() { - return floorsServed; - } - - @Override - public int getCurrentFloor() { - return currentFloor; - } - - @Override - public void requestDestinationFloor(int destinationFloor) { - // TODO Implement. This represents a human or the elevator system - // itself requesting this elevator to eventually move to the given floor. - // The elevator is supposed to memorize the destination in a way that - // it can ensure to eventually reach it. - System.out.println("Request for destination floor received"); - } - - public void moveOneFloor() { - // TODO Implement. Essentially there are three possibilities: - // - move up one floor - // - move down one floor - // - stand still - // The elevator is supposed to move in a way that it will eventually reach - // the floors requested by Humans via requestDestinationFloor(), ideally "fast" but also "fair", - // meaning that the average time waiting (either in corridor or inside the elevator) - // is minimized across all humans. - // It is essential that this method updates the currentFloor field accordingly. - System.out.println("Request to move a floor received"); - } - - @Override - public synchronized String toString() { - return new StringJoiner(", ", Elevator.class.getSimpleName() + "[", "]").add("id=" + id) - .add("minFloor=" + minFloor) - .add("floorsServed=" + floorsServed) - .add("currentFloor=" + currentFloor) - .toString(); - } -} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorPanel.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorPanel.java deleted file mode 100644 index 386ec77..0000000 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorPanel.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.togetherjava.event.elevator.elevators; - -/** - * The system inside an elevator which provides information about the elevator and can be - * used to request a destination floor. - */ -public interface ElevatorPanel { - /** - * The unique ID of the elevator. - * - * @return the unique ID of the elevator - */ - int getId(); - - /** - * The floor the elevator is currently at. - * - * @return the current floor - */ - int getCurrentFloor(); - - /** - * Requesting the elevator to eventually move to the given destination floor, for humans to exit. - * - * @param destinationFloor the desired destination, must be within the range served by this elevator - */ - void requestDestinationFloor(int destinationFloor); -} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorSystem.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorSystem.java deleted file mode 100644 index fadfe56..0000000 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorSystem.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.togetherjava.event.elevator.elevators; - -import org.togetherjava.event.elevator.humans.ElevatorListener; - -import java.util.ArrayList; -import java.util.List; - -/** - * System controlling all elevators of a building. - *

- * Once all elevators and humans have been registered via {@link #registerElevator(Elevator)} - * and {@link #registerElevatorListener(ElevatorListener)} respectively, - * the system can be made ready using {@link #ready()}. - */ -public final class ElevatorSystem implements FloorPanelSystem { - private final List elevators = new ArrayList<>(); - private final List elevatorListeners = new ArrayList<>(); - - public void registerElevator(Elevator elevator) { - elevators.add(elevator); - } - - public void registerElevatorListener(ElevatorListener listener) { - elevatorListeners.add(listener); - } - - /** - * Upon calling this, the system is ready to receive elevator requests. Elevators may now start moving. - */ - public void ready() { - elevatorListeners.forEach(listener -> listener.onElevatorSystemReady(this)); - } - - @Override - public void requestElevator(int atFloor, TravelDirection desiredTravelDirection) { - // TODO Implement. This represents a human standing in the corridor, - // requesting that an elevator comes to pick them up for travel into the given direction. - // The system is supposed to make sure that an elevator will eventually reach this floor to pick up the human. - // The human can then enter the elevator and request their actual destination within the elevator. - // Ideally this has to select the best elevator among all which can reduce the time - // for the human spending waiting (either in corridor or in the elevator itself). - System.out.println("Request for elevator received"); - } - - public void moveOneFloor() { - elevators.forEach(Elevator::moveOneFloor); - elevators.forEach(elevator -> elevatorListeners.forEach(listener -> listener.onElevatorArrivedAtFloor(elevator))); - } -} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/FloorPanelSystem.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/FloorPanelSystem.java deleted file mode 100644 index 8043228..0000000 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/FloorPanelSystem.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.togetherjava.event.elevator.elevators; - -/** - * The system in corridors that allows requesting elevators to the current floor. - */ -public interface FloorPanelSystem { - /** - * Requests an elevator to move to the given floor to pick up a human. - * - * @param atFloor the floor to pick up the human at, must be within the range served by the system - * @param desiredTravelDirection the direction the human wants to travel into, - * can be used for determination of the best elevator - * @apiNote This represents a human standing in the corridor, pressing a button on the wall, - * requesting that an elevator comes to pick them up for travel into the given direction. - */ - void requestElevator(int atFloor, TravelDirection desiredTravelDirection); -} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/TravelDirection.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/TravelDirection.java deleted file mode 100644 index b1c01c0..0000000 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/TravelDirection.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.togetherjava.event.elevator.elevators; - -public enum TravelDirection { - UP, - DOWN, -} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/enums/HumanState.java b/Contest/Assignment/src/org/togetherjava/event/elevator/enums/HumanState.java new file mode 100644 index 0000000..c716552 --- /dev/null +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/enums/HumanState.java @@ -0,0 +1,8 @@ +package org.togetherjava.event.elevator.enums; + +public enum HumanState { + IDLE, + WAITING_FOR_ELEVATOR, + TRAVELING_WITH_ELEVATOR, + ARRIVED +} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/enums/TravelDirection.java b/Contest/Assignment/src/org/togetherjava/event/elevator/enums/TravelDirection.java new file mode 100644 index 0000000..2967eaf --- /dev/null +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/enums/TravelDirection.java @@ -0,0 +1,6 @@ +package org.togetherjava.event.elevator.enums; + +public enum TravelDirection { + UP, + DOWN, +} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/humans/ElevatorListener.java b/Contest/Assignment/src/org/togetherjava/event/elevator/humans/ElevatorListener.java deleted file mode 100644 index a124b1b..0000000 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/humans/ElevatorListener.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.togetherjava.event.elevator.humans; - -import org.togetherjava.event.elevator.elevators.ElevatorPanel; -import org.togetherjava.event.elevator.elevators.FloorPanelSystem; - -/** - * Listeners to elevator events. This is mostly interesting for - * humans who can then request elevators to move to desired floors. - */ -public interface ElevatorListener { - /** - * Fired when the elevator system is ready to receive requests. Elevators can now move. - * - * @param floorPanelSystem the system in the corridor that allows - * requesting elevators to the current floor - */ - void onElevatorSystemReady(FloorPanelSystem floorPanelSystem); - - /** - * Fired when an elevator arrived at a floor. Humans can now enter or exit if desired. - * - * @param elevatorPanel the system inside the elevator which provides information - * about the elevator and can be used to request a destination floor. - * @implNote The default implementation fires this event from all elevators to all humans, not only to humans that are - * relevant (i.e. humans that can enter the elevator). - */ - void onElevatorArrivedAtFloor(ElevatorPanel elevatorPanel); -} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/humans/Human.java b/Contest/Assignment/src/org/togetherjava/event/elevator/humans/Human.java deleted file mode 100644 index 0af2511..0000000 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/humans/Human.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.togetherjava.event.elevator.humans; - -import org.togetherjava.event.elevator.elevators.ElevatorPanel; -import org.togetherjava.event.elevator.elevators.FloorPanelSystem; - -import java.util.OptionalInt; -import java.util.StringJoiner; - -/** - * A single human that starts at a given floor and wants to - * reach a destination floor via an elevator. - *

- * The class mainly acts upon given elevator events it listens to, - * for example requesting an elevator, eventually entering and exiting them. - */ -public final class Human implements ElevatorListener { - private State currentState; - private final int startingFloor; - private final int destinationFloor; - /** - * If the human is currently inside an elevator, this is its unique ID. - * Otherwise, this is {@code null} to indicate that the human is currently on the corridor. - */ - private Integer currentEnteredElevatorId; - - /** - * Creates a new human. - *

- * It is supported that starting and destination floors are equal. - * The human will then not travel with an elevator at all. - * - * @param startingFloor the floor the human currently stands at, must be greater than or equal to 1 - * @param destinationFloor the floor the human eventually wants to reach, must be greater than or equal to 1 - */ - public Human(int startingFloor, int destinationFloor) { - if (startingFloor <= 0 || destinationFloor <= 0) { - throw new IllegalArgumentException("Floors must be at least 1"); - } - - this.startingFloor = startingFloor; - this.destinationFloor = destinationFloor; - - currentState = State.IDLE; - } - - public State getCurrentState() { - return currentState; - } - - public int getStartingFloor() { - return startingFloor; - } - - public int getDestinationFloor() { - return destinationFloor; - } - - @Override - public void onElevatorSystemReady(FloorPanelSystem floorPanelSystem) { - // TODO Implement. The system is now ready and the human should leave - // their initial IDLE state, requesting an elevator by clicking on the buttons of - // the floor panel system. The human will now enter the WAITING_FOR_ELEVATOR state. - System.out.println("Ready-event received"); - } - - @Override - public void onElevatorArrivedAtFloor(ElevatorPanel elevatorPanel) { - // TODO Implement. If the human is currently waiting for an elevator and - // this event represents arrival at the humans current floor, the human can now enter the - // elevator and request their actual destination floor. The state has to change to TRAVELING_WITH_ELEVATOR. - // If the human is currently traveling with this elevator and the event represents - // arrival at the human's destination floor, the human can now exit the elevator. - System.out.println("Arrived-event received"); - } - - public OptionalInt getCurrentEnteredElevatorId() { - return currentEnteredElevatorId == null - ? OptionalInt.empty() - : OptionalInt.of(currentEnteredElevatorId); - } - - @Override - public String toString() { - return new StringJoiner(", ", Human.class.getSimpleName() + "[", "]") - .add("currentState=" + currentState) - .add("startingFloor=" + startingFloor) - .add("destinationFloor=" + destinationFloor) - .add("currentEnteredElevatorId=" + currentEnteredElevatorId) - .toString(); - } - - public enum State { - IDLE, - WAITING_FOR_ELEVATOR, - TRAVELING_WITH_ELEVATOR, - ARRIVED - } -} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/models/Elevator.java b/Contest/Assignment/src/org/togetherjava/event/elevator/models/Elevator.java new file mode 100644 index 0000000..86db8e2 --- /dev/null +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/models/Elevator.java @@ -0,0 +1,190 @@ +package org.togetherjava.event.elevator.models; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicInteger; + +import org.togetherjava.event.elevator.SimulationException; +import org.togetherjava.event.elevator.SimulationUtils; +import org.togetherjava.event.elevator.api.ElevatorPanel; +import org.togetherjava.event.elevator.enums.TravelDirection; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * A single elevator that can serve a given amount of floors. + * + *

An elevator can take floor requests from either humans or the elevator system itself. The + * elevator will eventually move towards the requested floor and transport humans to their + * destinations. + */ +@Slf4j +public final class Elevator implements ElevatorPanel { + private static final AtomicInteger NEXT_ID = new AtomicInteger(0); + private final int id; + @Getter private final int minFloor; + @Getter private final int floorsServed; + private int currentFloor; + private final Set pendingFloors = new LinkedHashSet<>(); + private Integer activeTarget; + + /** + * Creates a new elevator. + * + * @param minFloor the minimum floor that the elevator can serve, must be greater than or equal to + * 1. + * @param floorsServed the amount of floors served in total by this elevator, must be greater than + * or equal to 2. Together with the minFloor this forms a consecutive range of floors with no + * gaps in between. + * @param currentFloor the floor the elevator starts at, must be within the defined range of + * floors served by the elevator + */ + public Elevator(int minFloor, int floorsServed, int currentFloor) { + if (minFloor <= 0 || floorsServed < 2) { + throw new IllegalArgumentException("Min floor must at least 1, floors served at least 2."); + } + if (currentFloor < minFloor || currentFloor >= minFloor + floorsServed) { + throw new IllegalArgumentException( + "The current floor must be between the floors served by the elevator."); + } + + this.id = NEXT_ID.getAndIncrement(); + + this.minFloor = minFloor; + this.currentFloor = currentFloor; + this.floorsServed = floorsServed; + } + + @Override + public int getId() { + return this.id; + } + + @Override + public int getCurrentFloor() { + return this.currentFloor; + } + + /** + * Requests this elevator to eventually move to the given floor. + * + *

This represents a human or the elevator system itself requesting this elevator to move to + * the given floor. The elevator memorizes the destination to ensure it will eventually reach it. + * + * @param destinationFloor the floor to move to, must be within the range of floors served + * @throws IllegalArgumentException if the destination floor is out of range + */ + @Override + public void requestDestinationFloor(int destinationFloor) { + + // log.info("Requesting elevator {} to reach destination floor {}", this.id, + // destinationFloor); + + if (destinationFloor < this.minFloor || destinationFloor >= this.minFloor + this.floorsServed) { + throw new SimulationException("destination outof range for elevator " + this.id); + } + + if (!this.pendingFloors.contains(destinationFloor)) { + + this.pendingFloors.add(destinationFloor); + + if (this.activeTarget == null) { + this.activeTarget = destinationFloor; + } + } + } + + /** + * Moves the elevator one floor towards its target destination, or stands still. + * + *

The elevator has three possibilities: + * + *

+ * + * The elevator moves in a way that eventually reaches all floors requested via {@link + * #requestDestinationFloor(int)}, ideally fast and fair, meaning that the average time waiting + * (either in corridor or inside the elevator) is minimized across all humans. + * + *

This method updates the {@code currentFloor} field accordingly. + */ + public void moveOneFloor() { + + // log.debug("mooving elevator {} one floor", this.id); + + if (this.activeTarget == null) { + if (!this.pendingFloors.isEmpty()) { + + this.activeTarget = this.pendingFloors.iterator().next(); + + // log.debug( + // "Elevator {}, has no active target, selecting next pending floor: {}", + // this.id, + // this.activeTarget); + + } else { + // log.debug("elevator {} is idle (no pending floors)", this.id); + return; + } + } + + if (this.currentFloor == this.activeTarget) { + // log.debug( + // "elevator {} reached target floor {}, marking as complete", this.id, + // this.activeTarget); + + this.pendingFloors.remove(this.activeTarget); + + this.activeTarget = null; + + if (!this.pendingFloors.isEmpty()) { + this.activeTarget = this.pendingFloors.iterator().next(); + log.debug("elvator {} selecting next target floor: {}", this.id, this.activeTarget); + } else { + log.debug("Elevator {} has no more pending floors", this.id); + return; + } + } + + if (this.currentFloor < this.activeTarget) { + this.currentFloor++; + + log.debug( + SimulationUtils.LOG_ELEVATOR_MOVING_FROM_TO, + this.id, + TravelDirection.UP, + this.currentFloor - 1, + this.currentFloor, + this.activeTarget); + + } else if (this.currentFloor > this.activeTarget) { + + this.currentFloor--; + + log.debug( + SimulationUtils.LOG_ELEVATOR_MOVING_FROM_TO, + this.id, + TravelDirection.DOWN, + this.currentFloor + 1, + this.currentFloor, + this.activeTarget); + } + } + + @Override + public String toString() { + return new StringJoiner(", ", Elevator.class.getSimpleName() + "[", "]") + .add("id=" + this.id) + .add("minFloor=" + this.minFloor) + .add("floorsServed=" + this.floorsServed) + .add("currentFloor=" + this.currentFloor) + .add("pendingFloors=" + this.pendingFloors) + .add("activeTarget=" + this.activeTarget) + .toString(); + } +} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/models/ElevatorSystem.java b/Contest/Assignment/src/org/togetherjava/event/elevator/models/ElevatorSystem.java new file mode 100644 index 0000000..ee6314f --- /dev/null +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/models/ElevatorSystem.java @@ -0,0 +1,129 @@ +package org.togetherjava.event.elevator.models; + +import java.util.ArrayList; +import java.util.List; + +import org.togetherjava.event.elevator.SimulationException; +import org.togetherjava.event.elevator.api.ElevatorListener; +import org.togetherjava.event.elevator.api.FloorPanelSystem; +import org.togetherjava.event.elevator.enums.TravelDirection; + +import lombok.extern.slf4j.Slf4j; + +/** + * System controlling all elevators of a building. + * + *

Once all elevators and humans have been registered via {@link #registerElevator(Elevator)} and + * {@link #registerElevatorListener(ElevatorListener)} respectively, the system can be made ready + * using {@link #ready()}. + */ +@Slf4j +public final class ElevatorSystem implements FloorPanelSystem { + private final List elevators = new ArrayList<>(); + private final List elevatorListeners = new ArrayList<>(); + + public ElevatorSystem() { + log.info("new ElevatorSystem created"); + } + + public void registerElevator(Elevator elevator) { + log.debug("registering new elevator {}", elevator); + this.elevators.add(elevator); + } + + public void registerElevatorListener(ElevatorListener listener) { + log.debug("registering new listener (human) {}", listener); + this.elevatorListeners.add(listener); + } + + /** + * Upon calling this, the system is ready to receive elevator requests. Elevators may now start + * moving. + */ + public void ready() { + log.info("Starting new Elevator System"); + this.elevatorListeners.forEach(listener -> listener.onElevatorSystemReady(this)); + log.info("Elevator System is ready!"); + } + + /** + * Requests an elevator for a human standing in the corridor. + * + *

This represents a human requesting that an elevator comes to pick them up for travel into + * the given direction. The system makes sure that an elevator will eventually reach this floor to + * pick up the human. The human can then enter the elevator and request their actual destination + * within the elevator. + * + *

The implementation selects the best elevator among all candidates to reduce the time spent + * waiting (either in the corridor or in the elevator itself). + * + * @param atFloor the floor where the human is waiting + * @param desiredTravelDirection the direction the human wants to travel + */ + @Override + public void requestElevator(int atFloor, TravelDirection desiredTravelDirection) { + // log.info( + // "requesting any close elevator to respond to human at floor {}, going {}", + // atFloor, + // desiredTravelDirection); + + List staging = + this.elevators.stream() + .filter( + elevator -> { + int min = elevator.getMinFloor(); + return atFloor >= min && atFloor < min + elevator.getFloorsServed(); + }) + .toList(); + + if (staging.isEmpty()) { + throw new SimulationException("No elevators can serve floor " + atFloor); + } + + Elevator target = staging.getFirst(); + + int distanceFlag = Math.abs(atFloor - target.getCurrentFloor()); + + for (int i = 1; i < staging.size(); i++) { + + Elevator elevator = staging.get(i); + + final int distance = Math.abs(atFloor - elevator.getCurrentFloor()); + + if (distance < distanceFlag + || (distance == distanceFlag && elevator.getId() < target.getId())) { + + target = elevator; + distanceFlag = distance; + } + } + + // log.info("selected elevator {}", target.getId()); + + target.requestDestinationFloor(atFloor); + + // log.info( + // "Selected elevator {} at floor {} for pickup at floor {} (distance {}) direction {}", + // target.getId(), + // target.getCurrentFloor(), + // atFloor, + // distanceFlag, + // desiredTravelDirection); + } + + public void moveOneFloor() { + + // log.info("movement order started"); + + this.elevators.forEach(Elevator::moveOneFloor); + + // log.info("dispatching arrived at floor events to all listeners per elevator"); + + this.elevators.forEach( + elevator -> + this.elevatorListeners.forEach( + listener -> listener.onElevatorArrivedAtFloor(elevator))); + + // log.info("movement order finished"); + } +} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/models/Human.java b/Contest/Assignment/src/org/togetherjava/event/elevator/models/Human.java new file mode 100644 index 0000000..ffa8623 --- /dev/null +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/models/Human.java @@ -0,0 +1,190 @@ +package org.togetherjava.event.elevator.models; + +import java.util.OptionalInt; +import java.util.StringJoiner; + +import org.togetherjava.event.elevator.api.ElevatorListener; +import org.togetherjava.event.elevator.api.ElevatorPanel; +import org.togetherjava.event.elevator.api.FloorPanelSystem; +import org.togetherjava.event.elevator.enums.HumanState; +import org.togetherjava.event.elevator.enums.TravelDirection; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * A single human that starts at a given floor and wants to reach a destination floor via an + * elevator. + * + *

The class mainly acts upon given elevator events it listens to, for example requesting an + * elevator, eventually entering and exiting them. + */ +@Slf4j +public final class Human implements ElevatorListener { + @Getter private HumanState currentState; + @Getter private final int startingFloor; + @Getter private final int destinationFloor; + + /** + * If the human is currently inside an elevator, this is its unique ID. Otherwise, this is {@code + * null} to indicate that the human is currently on the corridor. + */ + private Integer currentEnteredElevatorId; + + /** + * Creates a new human. + * + *

It is supported that starting and destination floors are equal. The human will then not + * travel with an elevator at all. + * + * @param startingFloor the floor the human currently stands at, must be greater than or equal to + * 1 + * @param destinationFloor the floor the human eventually wants to reach, must be greater than or + * equal to 1 + */ + public Human(int startingFloor, int destinationFloor) { + if (startingFloor <= 0 || destinationFloor <= 0) { + throw new IllegalArgumentException("Floors must be at least 1"); + } + this.startingFloor = startingFloor; + this.destinationFloor = destinationFloor; + this.currentState = HumanState.IDLE; + } + + /** + * Invoked once the elevator system is ready to accept requests. Transitions the human out of the + * IDLE state. If the starting and destination floors are identical, the human is marked as + * arrived immediately. Otherwise, the correct travel direction is determined, an elevator is + * requested at the starting floor, and the state changes to WAITING_FOR_ELEVATOR. + * + * @param floorPanelSystem panel system used to request an elevator + */ + @Override + public void onElevatorSystemReady(FloorPanelSystem floorPanelSystem) { + + final int start = this.startingFloor; + + log.info("checking human at floor {}, state", start); + + final int destination = this.destinationFloor; + + if (start == destination) { + + final HumanState state = HumanState.ARRIVED; + + log.info("starting floor is the same as destination floor => marking state as {}", state); + + this.currentState = state; + + return; + } + + TravelDirection direction = start < destination ? TravelDirection.UP : TravelDirection.DOWN; + + log.info("computed direction for human at floor {} is {}", start, direction); + + floorPanelSystem.requestElevator(start, direction); + + HumanState newState = HumanState.WAITING_FOR_ELEVATOR; + + log.info("updating human at floor {} state from {} to {}", start, this.currentState, newState); + + this.currentState = newState; + + log.info(" human at floor {} state updated", start); + + log.info( + "human startin from floor {}, requesting elevator towards floor {}, direction {}", + start, + destination, + direction); + } + + /** + * Handles the event when an elevator arrives at a floor. + * + *

If the human is currently waiting for an elevator and this event represents arrival at the + * human's current floor, the human can now enter the elevator and request their actual + * destination floor. The state has to change to TRAVELING_WITH_ELEVATOR. + * + *

If the human is currently traveling with this elevator and the event represents arrival at + * the human's destination floor, the human can now exit the elevator. + * + * @param elevatorPanel the panel of the elevator that arrived at a floor + */ + @Override + public void onElevatorArrivedAtFloor(ElevatorPanel elevatorPanel) { + final int elevatorId = elevatorPanel.getId(); + final int elevatorCurrentFloor = elevatorPanel.getCurrentFloor(); + + // log.debug( + // "human at floor {}, (state: {}) received elevator {} arrival at floor {}", + // this.startingFloor, + // this.currentState, + // elevatorId, + // elevatorCurrentFloor); + + if (this.currentState == HumanState.ARRIVED) { + // log.debug("human already arrived! ignoring elevator arrival"); + return; + } + + // humanDestinationFloor = this.destinationFloor; + + if (this.currentState == HumanState.WAITING_FOR_ELEVATOR + && elevatorCurrentFloor == this.startingFloor) { + + // log.debug( + // "uman at floor {}, enters elevator {}, to travel to floor {}", + // this.startingFloor, + // elevatorId, + // this.destinationFloor); + + this.currentEnteredElevatorId = elevatorId; + + this.currentState = HumanState.TRAVELING_WITH_ELEVATOR; + + elevatorPanel.requestDestinationFloor(this.destinationFloor); + + // log.debug("human entered elevator {}, state changed to {}", elevatorId, + // this.currentState); + + return; + } + + if (this.currentState == HumanState.TRAVELING_WITH_ELEVATOR + && this.currentEnteredElevatorId != null + && this.currentEnteredElevatorId == elevatorId + && elevatorCurrentFloor == this.destinationFloor) { + + // log.debug( + // "human is in elevator {}, has reached destination floor {}, exiting", + // elevatorId, + // this.destinationFloor); + + this.currentEnteredElevatorId = null; + + this.currentState = HumanState.ARRIVED; + + // log.debug( + // "human exited elevator {}, the state now becomes {}", elevatorId, + // this.currentState); + } + } + + public OptionalInt getCurrentEnteredElevatorId() { + return this.currentEnteredElevatorId == null + ? OptionalInt.empty() + : OptionalInt.of(this.currentEnteredElevatorId); + } + + @Override + public String toString() { + return new StringJoiner(", ", Human.class.getSimpleName() + "[", "]") + .add("currentState=" + this.currentState) + .add("startingFloor=" + this.startingFloor) + .add("destinationFloor=" + this.destinationFloor) + .add("currentEnteredElevatorId=" + this.currentEnteredElevatorId) + .toString(); + } +} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/models/HumanStatistics.java b/Contest/Assignment/src/org/togetherjava/event/elevator/models/HumanStatistics.java new file mode 100644 index 0000000..a99941a --- /dev/null +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/models/HumanStatistics.java @@ -0,0 +1,24 @@ +package org.togetherjava.event.elevator.models; + +import java.util.EnumMap; +import java.util.Map; + +import org.togetherjava.event.elevator.enums.HumanState; + +public final class HumanStatistics { + private final Human human; + private final Map stateToStepCount = new EnumMap<>(HumanState.class); + + HumanStatistics(Human human) { + this.human = human; + } + + public void step() { + HumanState state = this.human.getCurrentState(); + this.stateToStepCount.put(state, this.stateToStepCount.getOrDefault(state, 0L) + 1); + } + + public long stepsForState(HumanState state) { + return this.stateToStepCount.getOrDefault(state, 0L); + } +} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/models/Simulation.java b/Contest/Assignment/src/org/togetherjava/event/elevator/models/Simulation.java new file mode 100644 index 0000000..20fd16f --- /dev/null +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/models/Simulation.java @@ -0,0 +1,43 @@ +package org.togetherjava.event.elevator.models; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public final class Simulation { + private final List humans; + private final List elevators; + @Getter private final ElevatorSystem elevatorSystem; + @Getter private final View view; + @Getter private long stepCount; + @Getter private final List humanStatistics; + + public Simulation(List elevators, List humans) { + log.info("Creating new Simulation"); + this.elevators = new ArrayList<>(elevators); + this.humans = new ArrayList<>(humans); + + this.elevatorSystem = new ElevatorSystem(); + this.elevators.forEach(this.elevatorSystem::registerElevator); + this.humans.forEach(this.elevatorSystem::registerElevatorListener); + + this.humanStatistics = this.humans.stream().map(HumanStatistics::new).toList(); + this.view = new View(this); + } + + public List getHumans() { + return Collections.unmodifiableList(this.humans); + } + + public List getElevators() { + return Collections.unmodifiableList(this.elevators); + } + + public void increaseStepCount() { + this.stepCount++; + } +} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/models/View.java b/Contest/Assignment/src/org/togetherjava/event/elevator/models/View.java new file mode 100644 index 0000000..305acfb --- /dev/null +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/models/View.java @@ -0,0 +1,136 @@ +package org.togetherjava.event.elevator.models; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.OptionalInt; +import java.util.function.IntFunction; +import java.util.stream.Collectors; + +import org.togetherjava.event.elevator.enums.HumanState; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public final class View { + private final Simulation simulation; + private static final int ELEVATOR_WIDTH = 7; + private static final int CORRIDOR_WIDTH = 9; + private static final int FLOOR_LABEL_WIDTH = 4; + + public View(Simulation simulation) { + this.simulation = simulation; + } + + public void printSummary() { + log.info( + "Simulation with {} elevators and {} humans.", + this.simulation.getElevators().size(), + this.simulation.getHumans().size()); + log.debug("\tElevators: {}", this.simulation.getElevators()); + log.debug("\tHumans: {}", this.simulation.getHumans()); + } + + public void prettyPrint() { + int totalFloors = + this.simulation.getElevators().stream() + .mapToInt(elevator -> elevator.getMinFloor() + elevator.getFloorsServed() - 1) + .max() + .orElseThrow(); + Map elevatorIdToHumansCount = + this.simulation.getHumans().stream() + .collect( + Collectors.groupingBy(Human::getCurrentEnteredElevatorId, Collectors.counting())); + + log.info( + "{}", + " ".repeat(FLOOR_LABEL_WIDTH) + + "_" + .repeat( + 1 + CORRIDOR_WIDTH + ELEVATOR_WIDTH * this.simulation.getElevators().size() + 1) + + " "); + for (int floor = totalFloors; floor >= 1; floor--) { + this.printFloor(floor, elevatorIdToHumansCount); + } + log.info( + " ".repeat(FLOOR_LABEL_WIDTH - 2) + + "^" + .repeat( + 2 + + 2 + + CORRIDOR_WIDTH + + ELEVATOR_WIDTH * this.simulation.getElevators().size() + + 2 + + 2)); + } + + private void printFloor(int floor, Map elevatorIdToHumansCount) { + String emptyLeftLine = " ".repeat(FLOOR_LABEL_WIDTH) + "| "; + List leftLines = List.of(emptyLeftLine, "%3s | ".formatted(floor), emptyLeftLine); + + String emptyRightLine = " |"; + List rightLines = List.of(emptyRightLine, emptyRightLine, emptyRightLine); + + List corridorLines = this.corridorForFloorToLines(floor); + + List> allElevatorLines = + this.simulation.getElevators().stream() + .map( + elevator -> + this.elevatorForFloorToLines( + floor, + elevator, + elevatorIdToHumansCount.getOrDefault(OptionalInt.of(elevator.getId()), 0L))) + .toList(); + + List> allLines = new ArrayList<>(); + allLines.add(leftLines); + allLines.add(corridorLines); + allLines.addAll(allElevatorLines); + allLines.add(rightLines); + + IntFunction mergeRow = + row -> allLines.stream().map(lines -> lines.get(row)).collect(Collectors.joining()); + + log.info("{}", mergeRow.apply(0)); + log.info("{}", mergeRow.apply(1)); + log.info("{}", mergeRow.apply(2)); + } + + private List elevatorForFloorToLines( + int floor, Elevator elevator, long humansInElevator) { + if (floor != elevator.getCurrentFloor()) { + String emptyLine = " ".repeat(ELEVATOR_WIDTH / 2) + "." + " ".repeat(ELEVATOR_WIDTH / 2); + return List.of(emptyLine, emptyLine, emptyLine); + } + + String humansInElevatorText = humansInElevator == 0 ? "" : Long.toString(humansInElevator); + + return List.of( + " " + "_".repeat(ELEVATOR_WIDTH - 2) + " ", + "| %3s |".formatted(humansInElevatorText), + " -" + "%3s".formatted(elevator.getId()).replace(' ', '-') + "- "); + } + + private List corridorForFloorToLines(int floor) { + long humansArrived = + this.simulation.getHumans().stream() + .filter(human -> human.getDestinationFloor() == floor) + .map(Human::getCurrentState) + .filter(HumanState.ARRIVED::equals) + .count(); + long humansWaiting = + this.simulation.getHumans().stream() + .filter(human -> human.getStartingFloor() == floor) + .map(Human::getCurrentState) + .filter( + state -> + state.equals(HumanState.IDLE) || state.equals(HumanState.WAITING_FOR_ELEVATOR)) + .count(); + + return List.of( + " %3s A | ".formatted(humansArrived), + " %3s W ".formatted(humansWaiting), + "~".repeat(CORRIDOR_WIDTH - 2) + "| "); + } +} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/HumanStatistics.java b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/HumanStatistics.java deleted file mode 100644 index 9c2c778..0000000 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/HumanStatistics.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.togetherjava.event.elevator.simulation; - -import org.togetherjava.event.elevator.humans.Human; - -import java.util.EnumMap; -import java.util.Map; - -final class HumanStatistics { - private final Human human; - private final Map stateToStepCount = new EnumMap<>(Human.State.class); - - HumanStatistics(Human human) { - this.human = human; - } - - void step() { - Human.State state = human.getCurrentState(); - stateToStepCount.put(state, stateToStepCount.getOrDefault(state, 0L) + 1); - } - - long stepsForState(Human.State state) { - return stateToStepCount.getOrDefault(state, 0L); - } -} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/Simulation.java b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/Simulation.java deleted file mode 100644 index 6f462c6..0000000 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/Simulation.java +++ /dev/null @@ -1,155 +0,0 @@ -package org.togetherjava.event.elevator.simulation; - -import org.togetherjava.event.elevator.elevators.Elevator; -import org.togetherjava.event.elevator.elevators.ElevatorSystem; -import org.togetherjava.event.elevator.humans.Human; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; -import java.util.stream.LongStream; -import java.util.stream.Stream; - -public final class Simulation { - private final List humans; - private final List elevators; - private final ElevatorSystem elevatorSystem; - private final View view; - private long stepCount; - private final List humanStatistics; - - public static Simulation createSingleElevatorSingleHumanSimulation() { - return new Simulation(List.of(new Elevator(1, 10, 5)), - List.of(new Human(1, 10))); - } - - public static Simulation createSimpleSimulation() { - int minFloor = 1; - int floorsServed = 10; - - return new Simulation( - List.of( - new Elevator(minFloor, floorsServed, 1), - new Elevator(minFloor, floorsServed, 6)), - List.of( - new Human(1, 2), - new Human(1, 5), - new Human(8, 10), - new Human(9, 3), - new Human(10, 1))); - } - - public static Simulation createRandomSimulation(int amountOfElevators, int amountOfHumans, int floorsServed) { - return createRandomSimulation(ThreadLocalRandom.current().nextLong(), amountOfElevators, amountOfHumans, floorsServed); - } - - public static Simulation createRandomSimulation(long seed, int amountOfElevators, int amountOfHumans, int floorsServed) { - System.out.println("Seed for random simulation is: " + seed); - Random random = new Random(seed); - - int minFloor = 1; - - List elevators = Stream.generate(() -> { - int currentFloor = minFloor + random.nextInt(floorsServed); - return new Elevator(minFloor, floorsServed, currentFloor); - }).limit(amountOfElevators).toList(); - - List humans = Stream.generate(() -> { - int startingFloor = minFloor + random.nextInt(floorsServed); - int destinationFloor = minFloor + random.nextInt(floorsServed); - return new Human(startingFloor, destinationFloor); - }).limit(amountOfHumans).toList(); - - return new Simulation(elevators, humans); - } - - public Simulation(List elevators, List humans) { - this.elevators = new ArrayList<>(elevators); - this.humans = new ArrayList<>(humans); - - elevatorSystem = new ElevatorSystem(); - this.elevators.forEach(elevatorSystem::registerElevator); - this.humans.forEach(elevatorSystem::registerElevatorListener); - - humanStatistics = this.humans.stream().map(HumanStatistics::new).toList(); - view = new View(this); - } - - public void startAndExecuteUntilDone(int stepLimit) { - start(); - - while (!isDone()) { - step(); - - if (stepCount >= stepLimit) { - throw new IllegalStateException("Simulation aborted. All humans should have arrived" - + " by now, but they did not. There is likely a bug in your code."); - } - } - } - - public void start() { - elevatorSystem.ready(); - } - - public void step() { - elevatorSystem.moveOneFloor(); - - humanStatistics.forEach(HumanStatistics::step); - stepCount++; - } - - public boolean isDone() { - return humans.stream() - .map(Human::getCurrentState) - .allMatch(Human.State.ARRIVED::equals); - } - - public long getStepCount() { - return stepCount; - } - - public List getHumans() { - return Collections.unmodifiableList(humans); - } - - public List getElevators() { - return Collections.unmodifiableList(elevators); - } - - public ElevatorSystem getElevatorSystem() { - return elevatorSystem; - } - - public void printSummary() { - view.printSummary(); - } - - public void prettyPrint() { - view.prettyPrint(); - } - - public void printResult() { - System.out.println("Steps: " + stepCount); - - System.out.println("Median time spend per state:"); - for (Human.State state : Human.State.values()) { - int averagePercentage = getAverageTimePercentageSpendForState(state); - System.out.printf("\t%s: %d%%%n", state, averagePercentage); - } - } - - public int getAverageTimePercentageSpendForState(Human.State state) { - LongStream sortedSteps = humanStatistics.stream() - .mapToLong(stats -> stats.stepsForState(state)) - .sorted(); - long medianSteps = humanStatistics.size() % 2 == 0 - ? (long) sortedSteps.skip(humanStatistics.size() / 2 - 1).limit(2).average().orElseThrow() - : sortedSteps.skip(humanStatistics.size() / 2).findFirst().orElseThrow(); - - long medianPercentage = 100 * medianSteps / stepCount; - return (int) medianPercentage; - } -} diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/View.java b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/View.java deleted file mode 100644 index e61eb63..0000000 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/View.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.togetherjava.event.elevator.simulation; - -import org.togetherjava.event.elevator.elevators.Elevator; -import org.togetherjava.event.elevator.humans.Human; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.OptionalInt; -import java.util.function.IntFunction; -import java.util.stream.Collectors; - -public final class View { - private final Simulation simulation; - private static final int ELEVATOR_WIDTH = 7; - private static final int CORRIDOR_WIDTH = 9; - private static final int FLOOR_LABEL_WIDTH = 4; - - public View(Simulation simulation) { - this.simulation = simulation; - } - - public void printSummary() { - System.out.printf("Simulation with %d elevators and %d humans.%n", simulation.getElevators().size(), simulation.getHumans().size()); - System.out.println("\tElevators: " + simulation.getElevators()); - System.out.println("\tHumans: " + simulation.getHumans()); - } - - public void prettyPrint() { - int totalFloors = simulation.getElevators().stream() - .mapToInt(elevator -> elevator.getMinFloor() + elevator.getFloorsServed() - 1) - .max() - .orElseThrow(); - Map elevatorIdToHumansCount = simulation.getHumans().stream() - .collect(Collectors.groupingBy(Human::getCurrentEnteredElevatorId, - Collectors.counting())); - - printRoof(); - for (int floor = totalFloors; floor >= 1; floor--) { - printFloor(floor, elevatorIdToHumansCount); - } - printBasement(); - } - - private void printRoof() { - System.out.println(" ".repeat(FLOOR_LABEL_WIDTH) - + " " + "_".repeat(1 + CORRIDOR_WIDTH + ELEVATOR_WIDTH * simulation.getElevators().size() + 1) + " "); - } - - private void printBasement() { - System.out.println(" ".repeat(FLOOR_LABEL_WIDTH - 2) - + "^".repeat(2 + 2 + CORRIDOR_WIDTH + ELEVATOR_WIDTH * simulation.getElevators().size() + 2 + 2)); - } - - private void printFloor(int floor, Map elevatorIdToHumansCount) { - String emptyLeftLine = " ".repeat(FLOOR_LABEL_WIDTH) + "| "; - List leftLines = List.of(emptyLeftLine, - "%3s | ".formatted(floor), - emptyLeftLine); - - String emptyRightLine = " |"; - List rightLines = List.of(emptyRightLine, emptyRightLine, emptyRightLine); - - List corridorLines = corridorForFloorToLines(floor); - - List> allElevatorLines = simulation.getElevators().stream() - .map(elevator -> elevatorForFloorToLines(floor, elevator, - elevatorIdToHumansCount.getOrDefault(OptionalInt.of(elevator.getId()), 0L))) - .toList(); - - List> allLines = new ArrayList<>(); - allLines.add(leftLines); - allLines.add(corridorLines); - allLines.addAll(allElevatorLines); - allLines.add(rightLines); - - IntFunction mergeRow = row -> allLines.stream().map(lines -> lines.get(row)).collect(Collectors.joining()); - - System.out.println(mergeRow.apply(0)); - System.out.println(mergeRow.apply(1)); - System.out.println(mergeRow.apply(2)); - } - - private List elevatorForFloorToLines(int floor, Elevator elevator, long humansInElevator) { - if (floor != elevator.getCurrentFloor()) { - String emptyLine = " ".repeat(ELEVATOR_WIDTH / 2) + "." + " ".repeat(ELEVATOR_WIDTH / 2); - return List.of(emptyLine, emptyLine, emptyLine); - } - - String humansInElevatorText = humansInElevator == 0 ? "" : Long.toString(humansInElevator); - - return List.of(" " + "_".repeat(ELEVATOR_WIDTH - 2) + " ", - "| %3s |".formatted(humansInElevatorText), - " -" + "%3s".formatted(elevator.getId()).replace(' ', '-') + "- "); - } - - private List corridorForFloorToLines(int floor) { - long humansArrived = simulation.getHumans().stream() - .filter(human -> human.getDestinationFloor() == floor) - .map(Human::getCurrentState) - .filter(Human.State.ARRIVED::equals) - .count(); - long humansWaiting = simulation.getHumans().stream() - .filter(human -> human.getStartingFloor() == floor) - .map(Human::getCurrentState) - .filter(state -> state.equals(Human.State.IDLE) || state.equals(Human.State.WAITING_FOR_ELEVATOR)) - .count(); - - return List.of(" %3s A | ".formatted(humansArrived), - " %3s W ".formatted(humansWaiting), - "~".repeat(CORRIDOR_WIDTH - 2) + "| "); - } -} diff --git a/Contest/Assignment/test/PreviousElevatorSystemTest.java b/Contest/Assignment/test/PreviousElevatorSystemTest.java index 1d060e6..5d60b78 100644 --- a/Contest/Assignment/test/PreviousElevatorSystemTest.java +++ b/Contest/Assignment/test/PreviousElevatorSystemTest.java @@ -1,63 +1,69 @@ -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.togetherjava.event.elevator.elevators.Elevator; -import org.togetherjava.event.elevator.elevators.ElevatorSystem; -import org.togetherjava.event.elevator.elevators.FloorPanelSystem; -import org.togetherjava.event.elevator.humans.ElevatorListener; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.description; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import java.util.List; import java.util.function.Supplier; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.*; +import org.junit.jupiter.api.Test; +import org.togetherjava.event.elevator.api.ElevatorListener; +import org.togetherjava.event.elevator.api.FloorPanelSystem; +import org.togetherjava.event.elevator.models.Elevator; +import org.togetherjava.event.elevator.models.ElevatorSystem; final class PreviousElevatorSystemTest { - @Test - void testImplementsFloorPanelSystem() { - assertTrue(FloorPanelSystem.class.isAssignableFrom(ElevatorSystem.class), - "The ElevatorSystem class is supposed to implement the FloorPanelSystem interface."); - } + @Test + void testImplementsFloorPanelSystem() { + assertTrue( + FloorPanelSystem.class.isAssignableFrom(ElevatorSystem.class), + "The ElevatorSystem class is supposed to implement the FloorPanelSystem interface."); + } - @Test - void testReady() { - ElevatorSystem system = new ElevatorSystem(); + @Test + void testReady() { + ElevatorSystem system = new ElevatorSystem(); - List listeners = - Stream.generate(() -> mock(ElevatorListener.class)).limit(5).toList(); + List listeners = + Stream.generate(() -> mock(ElevatorListener.class)).limit(5).toList(); - listeners.forEach(system::registerElevatorListener); + listeners.forEach(system::registerElevatorListener); - system.ready(); + system.ready(); - for (ElevatorListener listener : listeners) { - verify(listener, description( - "The 'ready' method of ElevatorSystem is supposed to invoke 'onElevatorSystemReady' on all registered elevator listeners.")).onElevatorSystemReady( - system); - } + for (ElevatorListener listener : listeners) { + verify( + listener, + description( + "The 'ready' method of ElevatorSystem is supposed to invoke 'onElevatorSystemReady' on all registered elevator listeners.")) + .onElevatorSystemReady(system); } + } - @Test - void testMoveOneFloor() { - ElevatorSystem system = new ElevatorSystem(); + @Test + void testMoveOneFloor() { + ElevatorSystem system = new ElevatorSystem(); - Supplier createAnyElevator = () -> new Elevator(1, 5, 2); - List elevators = Stream.generate(createAnyElevator).limit(3).toList(); + Supplier createAnyElevator = () -> new Elevator(1, 5, 2); + List elevators = Stream.generate(createAnyElevator).limit(3).toList(); - List listeners = - Stream.generate(() -> mock(ElevatorListener.class)).limit(5).toList(); + List listeners = + Stream.generate(() -> mock(ElevatorListener.class)).limit(5).toList(); - elevators.forEach(system::registerElevator); - listeners.forEach(system::registerElevatorListener); - system.ready(); + elevators.forEach(system::registerElevator); + listeners.forEach(system::registerElevatorListener); + system.ready(); - system.moveOneFloor(); - for (Elevator elevator : elevators) { - for (ElevatorListener listener : listeners) { - verify(listener, description( - "The 'moveOneFloor' method of ElevatorSystem is supposed to invoke 'onElevatorArrivedAtFloor' on all registered elevator listeners for each registered elevator.")).onElevatorArrivedAtFloor( - elevator); - } - } + system.moveOneFloor(); + for (Elevator elevator : elevators) { + for (ElevatorListener listener : listeners) { + verify( + listener, + description( + "The 'moveOneFloor' method of ElevatorSystem is supposed to invoke 'onElevatorArrivedAtFloor' on all registered elevator listeners for each registered elevator.")) + .onElevatorArrivedAtFloor(elevator); + } } + } } diff --git a/Contest/Assignment/test/PreviousElevatorTest.java b/Contest/Assignment/test/PreviousElevatorTest.java index 28a8b1e..2185a64 100644 --- a/Contest/Assignment/test/PreviousElevatorTest.java +++ b/Contest/Assignment/test/PreviousElevatorTest.java @@ -1,69 +1,77 @@ -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.togetherjava.event.elevator.elevators.Elevator; -import org.togetherjava.event.elevator.elevators.ElevatorPanel; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashSet; import java.util.Set; -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.togetherjava.event.elevator.api.ElevatorPanel; +import org.togetherjava.event.elevator.models.Elevator; final class PreviousElevatorTest { - @Test - void testGetFloors() { - int expectedMinFloor = 3; - int expectedFloorsServed = 4; - int expectedCurrentFloor = 5; - Elevator elevator = - new Elevator(expectedMinFloor, expectedFloorsServed, expectedCurrentFloor); + @Test + void testGetFloors() { + int expectedMinFloor = 3; + int expectedFloorsServed = 4; + int expectedCurrentFloor = 5; + Elevator elevator = new Elevator(expectedMinFloor, expectedFloorsServed, expectedCurrentFloor); - int actualMinFloor = elevator.getMinFloor(); - int actualFloorsServed = elevator.getFloorsServed(); - int actualCurrentFloor = elevator.getCurrentFloor(); + int actualMinFloor = elevator.getMinFloor(); + int actualFloorsServed = elevator.getFloorsServed(); + int actualCurrentFloor = elevator.getCurrentFloor(); - assertEquals(expectedMinFloor, actualMinFloor, - "The getMinFloor() method of Elevator needs to return the min floor given in the constructor."); + assertEquals( + expectedMinFloor, + actualMinFloor, + "The getMinFloor() method of Elevator needs to return the min floor given in the constructor."); - assertEquals(expectedFloorsServed, actualFloorsServed, - "The getFloorsServed() method of Elevator needs to return the served floors given in the constructor."); + assertEquals( + expectedFloorsServed, + actualFloorsServed, + "The getFloorsServed() method of Elevator needs to return the served floors given in the constructor."); - assertEquals(expectedCurrentFloor, actualCurrentFloor, - "The getCurrentFloor() method of Elevator needs to return the current floor given in the constructor."); - } + assertEquals( + expectedCurrentFloor, + actualCurrentFloor, + "The getCurrentFloor() method of Elevator needs to return the current floor given in the constructor."); + } - @Test - void testGetId() { - int anyMinFloor = 3; - int anyFloorsServed = 4; - int anyCurrentFloor = 5; + @Test + void testGetId() { + int anyMinFloor = 3; + int anyFloorsServed = 4; + int anyCurrentFloor = 5; - Set elevatorIds = new HashSet<>(); - for (int i = 0; i < 500; i++) { - Elevator elevator = new Elevator(anyMinFloor, anyFloorsServed, anyCurrentFloor); - int id = elevator.getId(); + Set elevatorIds = new HashSet<>(); + for (int i = 0; i < 500; i++) { + Elevator elevator = new Elevator(anyMinFloor, anyFloorsServed, anyCurrentFloor); + int id = elevator.getId(); - assertFalse(elevatorIds.contains(id), - "The IDs of elevators must be unique. Found a collision with ID %d when generating a few of them.".formatted( - id)); - elevatorIds.add(id); - } + assertFalse( + elevatorIds.contains(id), + "The IDs of elevators must be unique. Found a collision with ID %d when generating a few of them." + .formatted(id)); + elevatorIds.add(id); } + } - @Test - void testImplementsElevatorPanel() { - assertTrue(ElevatorPanel.class.isAssignableFrom(Elevator.class), - "The Elevator class is supposed to implement the ElevatorPanel interface."); - } + @Test + void testImplementsElevatorPanel() { + assertTrue( + ElevatorPanel.class.isAssignableFrom(Elevator.class), + "The Elevator class is supposed to implement the ElevatorPanel interface."); + } - @Test - void testMoveOneFloor() { - boolean hasMethod = Arrays.stream(Elevator.class.getMethods()) - .map(Method::getName) - .anyMatch("moveOneFloor"::equals); + @Test + void testMoveOneFloor() { + boolean hasMethod = + Arrays.stream(Elevator.class.getMethods()) + .map(Method::getName) + .anyMatch("moveOneFloor"::equals); - assertTrue(hasMethod, "The Elevator class is supposed to have a 'void moveOneFloor()' method."); - } + assertTrue(hasMethod, "The Elevator class is supposed to have a 'void moveOneFloor()' method."); + } } diff --git a/Contest/Assignment/test/PreviousHumanTest.java b/Contest/Assignment/test/PreviousHumanTest.java index 4cfbf72..e6bab1e 100644 --- a/Contest/Assignment/test/PreviousHumanTest.java +++ b/Contest/Assignment/test/PreviousHumanTest.java @@ -1,6 +1,5 @@ -import org.junit.jupiter.api.Test; -import org.togetherjava.event.elevator.humans.ElevatorListener; -import org.togetherjava.event.elevator.humans.Human; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; import java.util.Objects; @@ -8,82 +7,97 @@ import java.util.Set; import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.togetherjava.event.elevator.api.ElevatorListener; +import org.togetherjava.event.elevator.enums.HumanState; +import org.togetherjava.event.elevator.models.Human; final class PreviousHumanTest { - @Test - void testStateEnum() { - Set actualNames = Arrays.stream(Human.State.values()) - .map(Objects::toString) - .collect(Collectors.toSet()); - - Set expectedNames = - Set.of("IDLE", "WAITING_FOR_ELEVATOR", "TRAVELING_WITH_ELEVATOR", "ARRIVED"); - - assertEquals(expectedNames, actualNames, - "The names of the enum values for 'Human.State' do not match. Please check that the spelling is exactly the same."); - } - - @Test - void testGetCurrentState() { - int anyStartingFloor = 1; - int anyDestinationFloor = 3; - Human human = new Human(anyStartingFloor, anyDestinationFloor); - - Human.State currentState = human.getCurrentState(); - - assertEquals("IDLE", currentState.name(), - "The getCurrentState() method of Human needs to return the current state, which is supposed to be IDLE for now."); - } - - @Test - void testGetFloors() { - int expectedStartingFloor = 1; - int expectedDestinationFloor = 3; - Human human = new Human(expectedStartingFloor, expectedDestinationFloor); - - int actualStartingFloor = human.getStartingFloor(); - int actualDestinationFloor = human.getDestinationFloor(); - - assertEquals(expectedStartingFloor, actualStartingFloor, - "The getStartingFloor() method of Human needs to return the starting floor given in the constructor."); - - assertEquals(expectedDestinationFloor, actualDestinationFloor, - "The getDestinationFloor() method of Human needs to return the destination floor given in the constructor."); - } - - @Test - void testSameFloorAllowed() { - int expectedStartingAndDestinationFloor = 2; - Human human = - new Human(expectedStartingAndDestinationFloor, expectedStartingAndDestinationFloor); - - int actualStartingFloor = human.getStartingFloor(); - int actualDestinationFloor = human.getDestinationFloor(); - - assertEquals(expectedStartingAndDestinationFloor, actualStartingFloor, - "The human needs to support that both, starting and destination floor, have the same value."); - - assertEquals(expectedStartingAndDestinationFloor, actualDestinationFloor, - "The human needs to support that both, starting and destination floor, have the same value."); - } - - @Test - void testCurrentEnteredElevatorId() { - int anyStartingFloor = 1; - int anyDestinationFloor = 3; - Human human = new Human(anyStartingFloor, anyDestinationFloor); - - OptionalInt currentEnteredElevatorId = human.getCurrentEnteredElevatorId(); - - assertTrue(currentEnteredElevatorId.isEmpty(), - "The getCurrentEnteredElevatorId() method of Human needs to return the current entered elevator ID, which is supposed to be empty for now."); - } - - @Test - void testImplementsElevatorListener() { - assertTrue(ElevatorListener.class.isAssignableFrom(Human.class), - "The Human class is supposed to implement the ElevatorListener interface."); - } + @Test + void testStateEnum() { + Set actualNames = + Arrays.stream(HumanState.values()).map(Objects::toString).collect(Collectors.toSet()); + + Set expectedNames = + Set.of("IDLE", "WAITING_FOR_ELEVATOR", "TRAVELING_WITH_ELEVATOR", "ARRIVED"); + + assertEquals( + expectedNames, + actualNames, + "The names of the enum values for 'Human.State' do not match. Please check that the spelling is exactly the same."); + } + + @Test + void testGetCurrentState() { + int anyStartingFloor = 1; + int anyDestinationFloor = 3; + Human human = new Human(anyStartingFloor, anyDestinationFloor); + + HumanState currentState = human.getCurrentState(); + + assertEquals( + "IDLE", + currentState.name(), + "The getCurrentState() method of Human needs to return the current state, which is supposed to be IDLE for now."); + } + + @Test + void testGetFloors() { + int expectedStartingFloor = 1; + int expectedDestinationFloor = 3; + Human human = new Human(expectedStartingFloor, expectedDestinationFloor); + + int actualStartingFloor = human.getStartingFloor(); + int actualDestinationFloor = human.getDestinationFloor(); + + assertEquals( + expectedStartingFloor, + actualStartingFloor, + "The getStartingFloor() method of Human needs to return the starting floor given in the constructor."); + + assertEquals( + expectedDestinationFloor, + actualDestinationFloor, + "The getDestinationFloor() method of Human needs to return the destination floor given in the constructor."); + } + + @Test + void testSameFloorAllowed() { + int expectedStartingAndDestinationFloor = 2; + Human human = + new Human(expectedStartingAndDestinationFloor, expectedStartingAndDestinationFloor); + + int actualStartingFloor = human.getStartingFloor(); + int actualDestinationFloor = human.getDestinationFloor(); + + assertEquals( + expectedStartingAndDestinationFloor, + actualStartingFloor, + "The human needs to support that both, starting and destination floor, have the same value."); + + assertEquals( + expectedStartingAndDestinationFloor, + actualDestinationFloor, + "The human needs to support that both, starting and destination floor, have the same value."); + } + + @Test + void testCurrentEnteredElevatorId() { + int anyStartingFloor = 1; + int anyDestinationFloor = 3; + Human human = new Human(anyStartingFloor, anyDestinationFloor); + + OptionalInt currentEnteredElevatorId = human.getCurrentEnteredElevatorId(); + + assertTrue( + currentEnteredElevatorId.isEmpty(), + "The getCurrentEnteredElevatorId() method of Human needs to return the current entered elevator ID, which is supposed to be empty for now."); + } + + @Test + void testImplementsElevatorListener() { + assertTrue( + ElevatorListener.class.isAssignableFrom(Human.class), + "The Human class is supposed to implement the ElevatorListener interface."); + } } diff --git a/Contest/Assignment/test/SanityTest.java b/Contest/Assignment/test/SanityTest.java index 7f79e27..8e5a5db 100644 --- a/Contest/Assignment/test/SanityTest.java +++ b/Contest/Assignment/test/SanityTest.java @@ -1,191 +1,224 @@ -import org.junit.jupiter.api.Test; -import org.togetherjava.event.elevator.elevators.Elevator; -import org.togetherjava.event.elevator.humans.Human; -import org.togetherjava.event.elevator.simulation.Simulation; +import static org.junit.jupiter.api.Assertions.*; import java.util.Map; import java.util.OptionalInt; import java.util.function.Function; import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; +import org.togetherjava.event.elevator.SimulationService; +import org.togetherjava.event.elevator.enums.HumanState; +import org.togetherjava.event.elevator.models.Elevator; +import org.togetherjava.event.elevator.models.Human; +import org.togetherjava.event.elevator.models.Simulation; final class SanityTest { - private static final String ERROR_MESSAGE_PRE = - "Sanity checks for 'Simulation.createRandomSimulation(5, 5, 50, 10)' failed. "; + private static final String ERROR_MESSAGE_PRE = + "Sanity checks for 'SimulationService.createRandomSimulation(5, 5, 50, 10)' failed. "; - @Test - void testSimulationSanity() { - Simulation simulation = Simulation.createRandomSimulation(5, 5, 50, 10); - int stepLimit = 1_000; + @Test + void testSimulationSanity() { + Simulation simulation = SimulationService.createRandomSimulation(5, 5, 50, 10); + int stepLimit = 1_000; - requiredHumansToBeIdle(simulation); + requiredHumansToBeIdle(simulation); - var previousSnapshot = new SimulationSnapshot(simulation); + var previousSnapshot = new SimulationSnapshot(simulation); - simulation.start(); + SimulationService.start(simulation); - var currentSnapshot = new SimulationSnapshot(simulation); - verifySnapshotSanity(previousSnapshot, currentSnapshot, simulation); - previousSnapshot = currentSnapshot; + var currentSnapshot = new SimulationSnapshot(simulation); + verifySnapshotSanity(previousSnapshot, currentSnapshot, simulation); + previousSnapshot = currentSnapshot; - requiredHumansNotToBeIdle(simulation); + requiredHumansNotToBeIdle(simulation); - while (!simulation.isDone()) { - simulation.step(); + while (!SimulationService.isDone(simulation)) { + SimulationService.step(simulation); - currentSnapshot = new SimulationSnapshot(simulation); - verifySnapshotSanity(previousSnapshot, currentSnapshot, simulation); - previousSnapshot = currentSnapshot; + currentSnapshot = new SimulationSnapshot(simulation); + verifySnapshotSanity(previousSnapshot, currentSnapshot, simulation); + previousSnapshot = currentSnapshot; - if (simulation.getStepCount() >= stepLimit) { - fail(ERROR_MESSAGE_PRE + "All humans should have arrived by now, but they did not." - + " There is likely a bug in your code."); - } - } + if (simulation.getStepCount() >= stepLimit) { + fail( + ERROR_MESSAGE_PRE + + "All humans should have arrived by now, but they did not." + + " There is likely a bug in your code."); + } } - - private static void requiredHumansToBeIdle(Simulation simulation) { - for (Human human : simulation.getHumans()) { - assertEquals(Human.State.IDLE, human.getCurrentState(), ERROR_MESSAGE_PRE - + "Before the start of the simulation, all humans should be in the IDLE state. But '%s' was not.".formatted( - human)); - } + } + + private static void requiredHumansToBeIdle(Simulation simulation) { + for (Human human : simulation.getHumans()) { + assertEquals( + HumanState.IDLE, + human.getCurrentState(), + ERROR_MESSAGE_PRE + + "Before the start of the simulation, all humans should be in the IDLE state. But '%s' was not." + .formatted(human)); } - - private static void requiredHumansNotToBeIdle(Simulation simulation) { - for (Human human : simulation.getHumans()) { - assertNotEquals(Human.State.IDLE, human.getCurrentState(), ERROR_MESSAGE_PRE - + "Right after the start of the simulation, all humans should not be in the IDLE state anymore. But '%s' was.".formatted( - human)); - } + } + + private static void requiredHumansNotToBeIdle(Simulation simulation) { + for (Human human : simulation.getHumans()) { + assertNotEquals( + HumanState.IDLE, + human.getCurrentState(), + ERROR_MESSAGE_PRE + + "Right after the start of the simulation, all humans should not be in the IDLE state anymore. But '%s' was." + .formatted(human)); } - - private static void verifySnapshotSanity(SimulationSnapshot previousSnapshot, - SimulationSnapshot currentSnapshot, Simulation simulation) { - for (Human human : simulation.getHumans()) { - HumanSnapshot previousHumanSnapshot = previousSnapshot.getHumanSnapshot(human); - HumanSnapshot currentHumanSnapshot = currentSnapshot.getHumanSnapshot(human); - - Human.State previousState = previousHumanSnapshot.state(); - Human.State currentState = currentHumanSnapshot.state(); - - boolean changedBackToIdle = - previousState != Human.State.IDLE && currentState == Human.State.IDLE; - assertFalse(changedBackToIdle, ERROR_MESSAGE_PRE - + "Humans must never change their state back to IDLE. But '%s' did.".formatted( - human)); - - boolean changedOutOfArrived = - previousState == Human.State.ARRIVED && currentState != Human.State.ARRIVED; - assertFalse(changedOutOfArrived, ERROR_MESSAGE_PRE - + "Once a human arrived, they must never change their state again. But '%s' did.".formatted( - human)); - - boolean enteredElevator = previousState == Human.State.WAITING_FOR_ELEVATOR - && currentState == Human.State.TRAVELING_WITH_ELEVATOR; - if (enteredElevator) { - OptionalInt maybeElevatorId = currentHumanSnapshot.currentElevatorId(); - assertTrue(maybeElevatorId.isPresent(), ERROR_MESSAGE_PRE - + "When a human enters an elevator, they need a current elevator id. But '%s' does not.".formatted( - human)); - assertTrue(previousHumanSnapshot.currentElevatorId().isEmpty(), ERROR_MESSAGE_PRE - + "When a human enters an elevator, they must not have been in an elevator previously. But '%s' was.".formatted( - human)); - - ElevatorSnapshot currentElevatorSnapshot = - currentSnapshot.getElevatorSnapshot(maybeElevatorId.orElseThrow()); - assertEquals(human.getStartingFloor(), currentElevatorSnapshot.currentFloor(), - ERROR_MESSAGE_PRE - + "When a human enters an elevator, the elevator must be at the humans starting floor. But '%s' entered elevator with ID '%d' at a different floor.".formatted( - human, maybeElevatorId.orElseThrow())); - } - - boolean exitedElevator = - previousState != Human.State.ARRIVED && currentState == Human.State.ARRIVED; - if (exitedElevator) { - assertTrue(currentHumanSnapshot.currentElevatorId().isEmpty(), ERROR_MESSAGE_PRE - + "When a human exits an elevator, they must not have a current elevator id anymore. But '%s' has.".formatted( - human)); - - // Only if the human actually travelled around - if (human.getStartingFloor() != human.getDestinationFloor()) { - OptionalInt maybeElevatorId = previousHumanSnapshot.currentElevatorId(); - assertTrue(maybeElevatorId.isPresent(), ERROR_MESSAGE_PRE - + "When a human exits an elevator, they must have had a current elevator id previously. But '%s' does not.".formatted( - human)); - - assertEquals(Human.State.TRAVELING_WITH_ELEVATOR, previousState, - "When a human exits an elevator, their previous state must be traveling. But '%s' was in state %s.".formatted( - human, previousState)); - - ElevatorSnapshot currentElevatorSnapshot = - currentSnapshot.getElevatorSnapshot(maybeElevatorId.orElseThrow()); - assertEquals(human.getDestinationFloor(), - currentElevatorSnapshot.currentFloor(), ERROR_MESSAGE_PRE - + "When a human exits an elevator, the elevator must be at the humans destination floor. But '%s' exited elevator with ID '%d' at a different floor.".formatted( - human, maybeElevatorId.orElseThrow())); - } - } - - for (Elevator elevator : simulation.getElevators()) { - int previousFloor = - previousSnapshot.getElevatorSnapshot(elevator.getId()).currentFloor(); - int currentFloor = - currentSnapshot.getElevatorSnapshot(elevator.getId()).currentFloor(); - - int travelDistance = Math.abs(previousFloor - currentFloor); - assertTrue(travelDistance <= 1, ERROR_MESSAGE_PRE - + "Elevators must either travel 0 or 1 floor each step, but '%s' travelled %d floors.".formatted( - elevator, travelDistance)); - - int minFloor = elevator.getMinFloor(); - int maxFloor = minFloor + elevator.getFloorsServed() - 1; - - assertTrue(currentFloor >= minFloor, ERROR_MESSAGE_PRE - + "Elevators must never travel beyond their min floor, but '%s' reached floor '%d'.".formatted( - elevator, currentFloor)); - assertTrue(currentFloor <= maxFloor, ERROR_MESSAGE_PRE - + "Elevators must never travel beyond their max floor, but '%s' reached floor '%d'.".formatted( - elevator, currentFloor)); - } + } + + private static void verifySnapshotSanity( + SimulationSnapshot previousSnapshot, + SimulationSnapshot currentSnapshot, + Simulation simulation) { + for (Human human : simulation.getHumans()) { + HumanSnapshot previousHumanSnapshot = previousSnapshot.getHumanSnapshot(human); + HumanSnapshot currentHumanSnapshot = currentSnapshot.getHumanSnapshot(human); + + HumanState previousState = previousHumanSnapshot.state(); + HumanState currentState = currentHumanSnapshot.state(); + + boolean changedBackToIdle = + previousState != HumanState.IDLE && currentState == HumanState.IDLE; + assertFalse( + changedBackToIdle, + ERROR_MESSAGE_PRE + + "Humans must never change their state back to IDLE. But '%s' did." + .formatted(human)); + + boolean changedOutOfArrived = + previousState == HumanState.ARRIVED && currentState != HumanState.ARRIVED; + assertFalse( + changedOutOfArrived, + ERROR_MESSAGE_PRE + + "Once a human arrived, they must never change their state again. But '%s' did." + .formatted(human)); + + boolean enteredElevator = + previousState == HumanState.WAITING_FOR_ELEVATOR + && currentState == HumanState.TRAVELING_WITH_ELEVATOR; + if (enteredElevator) { + OptionalInt maybeElevatorId = currentHumanSnapshot.currentElevatorId(); + assertTrue( + maybeElevatorId.isPresent(), + ERROR_MESSAGE_PRE + + "When a human enters an elevator, they need a current elevator id. But '%s' does not." + .formatted(human)); + assertTrue( + previousHumanSnapshot.currentElevatorId().isEmpty(), + ERROR_MESSAGE_PRE + + "When a human enters an elevator, they must not have been in an elevator previously. But '%s' was." + .formatted(human)); + + ElevatorSnapshot currentElevatorSnapshot = + currentSnapshot.getElevatorSnapshot(maybeElevatorId.orElseThrow()); + assertEquals( + human.getStartingFloor(), + currentElevatorSnapshot.currentFloor(), + ERROR_MESSAGE_PRE + + "When a human enters an elevator, the elevator must be at the humans starting floor. But '%s' entered elevator with ID '%d' at a different floor." + .formatted(human, maybeElevatorId.orElseThrow())); + } + + boolean exitedElevator = + previousState != HumanState.ARRIVED && currentState == HumanState.ARRIVED; + if (exitedElevator) { + assertTrue( + currentHumanSnapshot.currentElevatorId().isEmpty(), + ERROR_MESSAGE_PRE + + "When a human exits an elevator, they must not have a current elevator id anymore. But '%s' has." + .formatted(human)); + + // Only if the human actually travelled around + if (human.getStartingFloor() != human.getDestinationFloor()) { + OptionalInt maybeElevatorId = previousHumanSnapshot.currentElevatorId(); + assertTrue( + maybeElevatorId.isPresent(), + ERROR_MESSAGE_PRE + + "When a human exits an elevator, they must have had a current elevator id previously. But '%s' does not." + .formatted(human)); + + assertEquals( + HumanState.TRAVELING_WITH_ELEVATOR, + previousState, + "When a human exits an elevator, their previous state must be traveling. But '%s' was in state %s." + .formatted(human, previousState)); + + ElevatorSnapshot currentElevatorSnapshot = + currentSnapshot.getElevatorSnapshot(maybeElevatorId.orElseThrow()); + assertEquals( + human.getDestinationFloor(), + currentElevatorSnapshot.currentFloor(), + ERROR_MESSAGE_PRE + + "When a human exits an elevator, the elevator must be at the humans destination floor. But '%s' exited elevator with ID '%d' at a different floor." + .formatted(human, maybeElevatorId.orElseThrow())); } + } + + for (Elevator elevator : simulation.getElevators()) { + int previousFloor = previousSnapshot.getElevatorSnapshot(elevator.getId()).currentFloor(); + int currentFloor = currentSnapshot.getElevatorSnapshot(elevator.getId()).currentFloor(); + + int travelDistance = Math.abs(previousFloor - currentFloor); + assertTrue( + travelDistance <= 1, + ERROR_MESSAGE_PRE + + "Elevators must either travel 0 or 1 floor each step, but '%s' travelled %d floors." + .formatted(elevator, travelDistance)); + + int minFloor = elevator.getMinFloor(); + int maxFloor = minFloor + elevator.getFloorsServed() - 1; + + assertTrue( + currentFloor >= minFloor, + ERROR_MESSAGE_PRE + + "Elevators must never travel beyond their min floor, but '%s' reached floor '%d'." + .formatted(elevator, currentFloor)); + assertTrue( + currentFloor <= maxFloor, + ERROR_MESSAGE_PRE + + "Elevators must never travel beyond their max floor, but '%s' reached floor '%d'." + .formatted(elevator, currentFloor)); + } } + } - private record HumanSnapshot(Human.State state, OptionalInt currentElevatorId) { - static HumanSnapshot of(Human human) { - return new HumanSnapshot(human.getCurrentState(), human.getCurrentEnteredElevatorId()); - } + private record HumanSnapshot(HumanState state, OptionalInt currentElevatorId) { + static HumanSnapshot of(Human human) { + return new HumanSnapshot(human.getCurrentState(), human.getCurrentEnteredElevatorId()); } + } - - private record ElevatorSnapshot(int currentFloor) { - static ElevatorSnapshot of(Elevator elevator) { - return new ElevatorSnapshot(elevator.getCurrentFloor()); - } + private record ElevatorSnapshot(int currentFloor) { + static ElevatorSnapshot of(Elevator elevator) { + return new ElevatorSnapshot(elevator.getCurrentFloor()); } + } + private static class SimulationSnapshot { + private final Map humanToSnapshots; + private final Map elevatorIdToSnapshots; - private static class SimulationSnapshot { - private final Map humanToSnapshots; - private final Map elevatorIdToSnapshots; + SimulationSnapshot(Simulation simulation) { + this.humanToSnapshots = + simulation.getHumans().stream() + .collect(Collectors.toMap(Function.identity(), HumanSnapshot::of)); - SimulationSnapshot(Simulation simulation) { - humanToSnapshots = simulation.getHumans() - .stream() - .collect(Collectors.toMap(Function.identity(), HumanSnapshot::of)); - - elevatorIdToSnapshots = simulation.getElevators() - .stream() - .collect(Collectors.toMap(Elevator::getId, ElevatorSnapshot::of)); - } + this.elevatorIdToSnapshots = + simulation.getElevators().stream() + .collect(Collectors.toMap(Elevator::getId, ElevatorSnapshot::of)); + } - HumanSnapshot getHumanSnapshot(Human human) { - return humanToSnapshots.get(human); - } + HumanSnapshot getHumanSnapshot(Human human) { + return this.humanToSnapshots.get(human); + } - ElevatorSnapshot getElevatorSnapshot(int elevatorId) { - return elevatorIdToSnapshots.get(elevatorId); - } + ElevatorSnapshot getElevatorSnapshot(int elevatorId) { + return this.elevatorIdToSnapshots.get(elevatorId); } + } } diff --git a/Contest/Assignment/test/SimulationTest.java b/Contest/Assignment/test/SimulationTest.java index 440f444..6da53d7 100644 --- a/Contest/Assignment/test/SimulationTest.java +++ b/Contest/Assignment/test/SimulationTest.java @@ -1,80 +1,99 @@ -import org.junit.jupiter.api.*; -import org.togetherjava.event.elevator.simulation.Simulation; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assumptions.assumeFalse; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.togetherjava.event.elevator.SimulationService; +import org.togetherjava.event.elevator.models.Simulation; + final class SimulationTest { - private boolean simulationFailed; - private static boolean stopSimulations = false; + private boolean simulationFailed; + private static boolean stopSimulations = false; - @BeforeEach - void setUp() { - assumeFalse(stopSimulations, - "A previous simulation failed already, skipping the remaining."); + @BeforeEach + void setUp() { + assumeFalse(stopSimulations, "A previous simulation failed already, skipping the remaining."); - simulationFailed = true; - } + this.simulationFailed = true; + } - @AfterEach - void tearDown() { - if (simulationFailed) { - stopSimulations = true; - } + @AfterEach + void tearDown() { + if (this.simulationFailed) { + stopSimulations = true; } + } - @Test - void testSingleElevatorSingleHumanSimulation() { - Simulation simulation = Simulation.createSingleElevatorSingleHumanSimulation(); - int stepLimit = 200; + @Test + void testSingleElevatorSingleHumanSimulation() { + Simulation simulation = SimulationService.createSingleElevatorSingleHumanSimulation(); + int stepLimit = 200; - assertDoesNotThrow(() -> simulation.startAndExecuteUntilDone(stepLimit), - "Simulation obtained by 'Simulation.createSingleElevatorSingleHumanSimulation()' was aborted because it could not finish in time."); + assertDoesNotThrow( + () -> startAndExecuteUntilDone(simulation, stepLimit), + "Simulation obtained by 'SimulationService.createSingleElevatorSingleHumanSimulation()' was aborted because it could not finish in time."); - simulationFailed = false; - } + this.simulationFailed = false; + } - @Test - void testSimpleSimulation() { - Simulation simulation = Simulation.createSimpleSimulation(); - int stepLimit = 500; + @Test + void testSimpleSimulation() { + Simulation simulation = SimulationService.createSimpleSimulation(); + int stepLimit = 500; - assertDoesNotThrow(() -> simulation.startAndExecuteUntilDone(stepLimit), - "Simulation obtained by 'Simulation.createSimpleSimulation()' was aborted because it could not finish in time."); + assertDoesNotThrow( + () -> startAndExecuteUntilDone(simulation, stepLimit), + "Simulation obtained by 'SimulationService.createSimpleSimulation()' was aborted because it could not finish in time."); - simulationFailed = false; - } + this.simulationFailed = false; + } - @Test - void testRandomSimulationSmall() { - Simulation simulation = Simulation.createRandomSimulation(1, 5, 50, 10); - int stepLimit = 1_000; + @Test + void testRandomSimulationSmall() { + Simulation simulation = SimulationService.createRandomSimulation(1, 5, 50, 10); + int stepLimit = 1_000; - assertDoesNotThrow(() -> simulation.startAndExecuteUntilDone(stepLimit), - "Simulation obtained by 'Simulation.createRandomSimulation(1, 5, 50, 10)' was aborted because it could not finish in time."); + assertDoesNotThrow( + () -> startAndExecuteUntilDone(simulation, stepLimit), + "Simulation obtained by 'SimulationService.createRandomSimulation(1, 5, 50, 10)' was aborted because it could not finish in time."); - simulationFailed = false; - } + this.simulationFailed = false; + } - @Test - void testRandomSimulationMedium() { - Simulation simulation = Simulation.createRandomSimulation(2, 20, 1_000, 50); - int stepLimit = 10_000; + @Test + void testRandomSimulationMedium() { + Simulation simulation = SimulationService.createRandomSimulation(2, 20, 1_000, 50); + int stepLimit = 10_000; - assertDoesNotThrow(() -> simulation.startAndExecuteUntilDone(stepLimit), - "Simulation obtained by 'Simulation.createRandomSimulation(2, 20, 1_000, 50)' was aborted because it could not finish in time."); + assertDoesNotThrow( + () -> startAndExecuteUntilDone(simulation, stepLimit), + "Simulation obtained by 'SimulationService.createRandomSimulation(2, 20, 1_000, 50)' was aborted because it could not finish in time."); - simulationFailed = false; - } + this.simulationFailed = false; + } + + @Test + void testRandomSimulationBig() { + Simulation simulation = SimulationService.createRandomSimulation(3, 100, 100_000, 100); + int stepLimit = 100_000; + + assertDoesNotThrow( + () -> startAndExecuteUntilDone(simulation, stepLimit), + "Simulation obtained by 'SimulationService.createRandomSimulation(3, 100, 100_000, 100)' was aborted because it could not finish in time."); - @Test - void testRandomSimulationBig() { - Simulation simulation = Simulation.createRandomSimulation(3, 100, 100_000, 100); - int stepLimit = 100_000; + this.simulationFailed = false; + } - assertDoesNotThrow(() -> simulation.startAndExecuteUntilDone(stepLimit), - "Simulation obtained by 'Simulation.createRandomSimulation(3, 100, 100_000, 100)' was aborted because it could not finish in time."); + private static void startAndExecuteUntilDone(Simulation simulation, int stepLimit) { + SimulationService.start(simulation); - simulationFailed = false; + while (!SimulationService.isDone(simulation)) { + SimulationService.step(simulation); + if (simulation.getStepCount() >= stepLimit) { + throw new IllegalStateException( + "Simulation aborted. All humans should have arrived by now, but they did not. There is likely a bug in your code."); + } } + } }