From d3504cea794f03102e5ad7e639217cc083a1aad6 Mon Sep 17 00:00:00 2001 From: Zabuzard Date: Mon, 1 Dec 2025 09:05:07 +0100 Subject: [PATCH] Submission --- .../org/togetherjava/event/elevator/Main.java | 15 +- .../event/elevator/elevators/Elevator.java | 200 ++++++++++++++++-- .../elevator/elevators/ElevatorPanel.java | 7 + .../elevator/elevators/ElevatorSystem.java | 70 +++++- .../elevator/elevators/TravelDirection.java | 1 + .../event/elevator/humans/Human.java | 75 +++++-- .../event/elevator/simulation/Simulation.java | 1 - 7 files changed, 305 insertions(+), 64 deletions(-) diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/Main.java b/Contest/Assignment/src/org/togetherjava/event/elevator/Main.java index e9e4159..842e801 100644 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/Main.java +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/Main.java @@ -1,7 +1,5 @@ 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; public final class Main { @@ -19,10 +17,13 @@ public static void main(final String[] args) { // 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 simulation = Simulation.createSingleElevatorSingleHumanSimulation(); + Simulation simulation = Simulation.createSimpleSimulation(); + //Simulation simulation = Simulation.createRandomSimulation(5, 50, 10); + //Simulation simulation = Simulation.createRandomSimulation(4637787693156730566L, 5, 5000, 100); + + //Simulation simulation = Simulation.createRandomSimulation(-806872529110342439L, 5, 50, 10); + //Simulation simulation = Simulation.createRandomSimulation(3, 100, 100_000, 100); simulation.printSummary(); @@ -35,7 +36,7 @@ public static void main(final String[] args) { simulation.step(); simulation.prettyPrint(); - if (simulation.getStepCount() >= 100_000) { + if (simulation.getStepCount() >= 200000) { throw new IllegalStateException("Simulation aborted. All humans should have arrived" + " by now, but they did not. There is likely a bug in your code."); } diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Elevator.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Elevator.java index 51333b2..554d4a7 100644 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Elevator.java +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/Elevator.java @@ -11,11 +11,16 @@ */ public final class Elevator implements ElevatorPanel { private static final AtomicInteger NEXT_ID = new AtomicInteger(0); + private static final String INVALID_FLOOR_ERROR = "Floor is outside the valid range for this elevator!"; private final int id; private final int minFloor; private final int floorsServed; + private final int maxFloor; + private final int[] requests; + private final Object lock = new Object(); private int currentFloor; + private TravelDirection currentDirection; /** * Creates a new elevator. @@ -37,6 +42,9 @@ public Elevator(int minFloor, int floorsServed, int currentFloor) { this.minFloor = minFloor; this.currentFloor = currentFloor; this.floorsServed = floorsServed; + this.maxFloor = minFloor + floorsServed - 1; + this.currentDirection = TravelDirection.NONE; + requests = new int[floorsServed]; } @Override @@ -54,37 +62,185 @@ public int getFloorsServed() { @Override public int getCurrentFloor() { - return currentFloor; + synchronized (lock) { + 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"); + synchronized (lock) { + addRequest(destinationFloor); + } } + @Override + public TravelDirection getAdvertisedDirection() { + synchronized (lock) { + if (!moreRequestsInCurrentDirection()) { + return computeBestDirection(); + } + return currentDirection; + } + } + + /** + * Moves the elevator one floor + * Elevator can either: move up one floor, move down one floor, or stand still + */ 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"); + synchronized (lock) { + clearRequests(currentFloor); + if (!moreRequestsInCurrentDirection()) { + currentDirection = computeBestDirection(); + } + if (currentDirection == TravelDirection.UP) { + goUp(); + } else if (currentDirection == TravelDirection.DOWN) { + goDown(); + } + } } @Override - public synchronized String toString() { - return new StringJoiner(", ", Elevator.class.getSimpleName() + "[", "]").add("id=" + id) - .add("minFloor=" + minFloor) - .add("floorsServed=" + floorsServed) - .add("currentFloor=" + currentFloor) - .toString(); + public String toString() { + synchronized (lock) { + return new StringJoiner(", ", Elevator.class.getSimpleName() + "[", "]").add("id=" + id) + .add("minFloor=" + minFloor) + .add("floorsServed=" + floorsServed) + .add("currentFloor=" + currentFloor) + .toString(); + } + } + + /** + * Determines if there are additional requests in currentDirection in requests array for this elevator + * + * @return a boolean (true means there are more requests in current direction, otherwise false) + */ + private boolean moreRequestsInCurrentDirection() { + synchronized (lock) { + if (currentDirection == TravelDirection.NONE) { + return getRequestCount(currentFloor) > 0; + } + if (currentDirection == TravelDirection.UP) { + return countRequestsAbove() > 0; + } + if (currentDirection == TravelDirection.DOWN) { + return countRequestsBelow() > 0; + } + throw new RuntimeException("currentDirection is in an invalid state!"); + } + } + + /** + * Determines the best direction to travel based on request count in each direction + * + * @return the desired TravelDirection to move in + */ + private TravelDirection computeBestDirection() { + synchronized (lock) { + int requestsAbove = countRequestsAbove(); + int requestsBelow = countRequestsBelow(); + if (requestsAbove == 0 && requestsBelow == 0) { + return TravelDirection.NONE; + } + return (requestsAbove >= requestsBelow) ? TravelDirection.UP : TravelDirection.DOWN; + } + } + + /** + * Counts the requests above the currentFloor for the current elevator instance + * + * @return an integer count of requests above the current floor + */ + private int countRequestsAbove() { + synchronized (lock) { + int requests = 0; + for (int i = currentFloor + 1; i <= maxFloor; i++) { + requests += getRequestCount(i); + } + return requests; + } + } + + /** + * Counts the requests below the currentFloor for the current elevator instance + * + * @return an integer count of requests below the current floor + */ + private int countRequestsBelow() { + synchronized (lock) { + int requests = 0; + for (int i = currentFloor - 1; i >= minFloor; i--) { + requests += getRequestCount(i); + } + return requests; + } + } + + /** + * Moves the elevator up one floor + */ + private void goUp() { + synchronized (lock) { + currentFloor += 1; + } + } + + /** + * Moves the elevator down one floor + */ + private void goDown() { + synchronized (lock) { + currentFloor -= 1; + } + } + + /** + * Adds a request to the current elevator for the desired floor. Should be used alongside + * {@link Elevator#getRequestCount(int)} to retrieve the counts to ensure offsets remain consistent. + * + * @param floor the floor the request is for + * @throws IllegalArgumentException if floor is outside valid range + */ + private void addRequest(int floor) { + if (floor < minFloor || floor > maxFloor) { + throw new IllegalArgumentException(INVALID_FLOOR_ERROR); + } + synchronized (lock) { + requests[floor - minFloor] += 1; + } + } + + /** + * Gets the number of requests from the current elevator for a given floor + * + * @param floor the floor to get the request count from + * @return an integer with the request count + * @throws IllegalArgumentException if floor is outside valid range + */ + private int getRequestCount(int floor) { + if (floor < minFloor || floor > maxFloor) { + throw new IllegalArgumentException(INVALID_FLOOR_ERROR); + } + synchronized (lock) { + return requests[floor - minFloor]; + } + } + + /** + * Removes requests for a given floor + * + * @param floor the floor to clear requests from + * @throws IllegalArgumentException if floor is outside valid range + */ + private void clearRequests(int floor) { + if (floor < minFloor || floor > maxFloor) { + throw new IllegalArgumentException(INVALID_FLOOR_ERROR); + } + synchronized (lock) { + requests[floor - minFloor] = 0; + } } } diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorPanel.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorPanel.java index 386ec77..c6a79fd 100644 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorPanel.java +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorPanel.java @@ -25,4 +25,11 @@ public interface ElevatorPanel { * @param destinationFloor the desired destination, must be within the range served by this elevator */ void requestDestinationFloor(int destinationFloor); + + /** + * Shows the "advertised" direction where the elevator is heading so passengers know whether to board. + * + * @return a TravelDirection that represents the direction this elevator is headed + */ + TravelDirection getAdvertisedDirection(); } diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorSystem.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorSystem.java index fadfe56..670ce79 100644 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorSystem.java +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/ElevatorSystem.java @@ -1,7 +1,6 @@ package org.togetherjava.event.elevator.elevators; import org.togetherjava.event.elevator.humans.ElevatorListener; - import java.util.ArrayList; import java.util.List; @@ -33,17 +32,66 @@ public void ready() { @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"); + findNearestInDesiredDirection(elevators, atFloor, desiredTravelDirection).requestDestinationFloor(atFloor); } public void moveOneFloor() { - elevators.forEach(Elevator::moveOneFloor); - elevators.forEach(elevator -> elevatorListeners.forEach(listener -> listener.onElevatorArrivedAtFloor(elevator))); + elevators.parallelStream().forEach(Elevator::moveOneFloor); + elevators.parallelStream().forEach(elevator -> elevatorListeners.parallelStream().forEach(listener -> listener.onElevatorArrivedAtFloor(elevator))); + } + + /** + * Helper method that finds the nearest elevator to a given floor in position to travel in the desired direction. + * + * @param elevators the list of elevators + * @param floor the desired floor (where the human resides) + * @param desiredDirection the desired TravelDirection + * @return elevator object representing the nearest in that direction OR a fallback to the nearest in general if + * none fit the direction criteria + */ + private static Elevator findNearestInDesiredDirection(List elevators, int floor, TravelDirection desiredDirection) { + List directionMatchedElevators = new ArrayList<>(); + for (Elevator e : elevators) { + if (desiredDirection == TravelDirection.DOWN) { + if (e.getCurrentFloor() > floor) { + directionMatchedElevators.add(e); + } + } else if (desiredDirection == TravelDirection.UP) { + if (e.getCurrentFloor() < floor){ + directionMatchedElevators.add(e); + } + } + } + Elevator result; + if (!directionMatchedElevators.isEmpty()) { + result = findNearestElevator(directionMatchedElevators, floor); + } else { + result = findNearestElevator(elevators, floor); + } + if (result == null) { + throw new RuntimeException("Found no elevators < Integer.MAX_VALUE distance away from complete list of elevators!"); + } + return result; + } + + /** + * A helper method that returns the nearest elevator to the given floor. + * NOTE: Returned elevator can be {@code null} (be careful if list can be empty) + * + * @param elevators the list of elevators + * @param floor the floor you want to find the nearest elevator to + * @return the closest Elevator or null if none found + */ + private static Elevator findNearestElevator(List elevators, int floor) { + Elevator nearest = null; + int nearestDistance = Integer.MAX_VALUE; + for (Elevator elevator : elevators) { + int distance = Math.abs(elevator.getCurrentFloor() - floor); + if (distance < nearestDistance) { + nearest = elevator; + nearestDistance = distance; + } + } + return nearest; } -} +} \ No newline at end of file diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/TravelDirection.java b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/TravelDirection.java index b1c01c0..dded296 100644 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/TravelDirection.java +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/elevators/TravelDirection.java @@ -3,4 +3,5 @@ public enum TravelDirection { UP, DOWN, + NONE } diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/humans/Human.java b/Contest/Assignment/src/org/togetherjava/event/elevator/humans/Human.java index 0af2511..076b01c 100644 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/humans/Human.java +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/humans/Human.java @@ -2,6 +2,7 @@ import org.togetherjava.event.elevator.elevators.ElevatorPanel; import org.togetherjava.event.elevator.elevators.FloorPanelSystem; +import org.togetherjava.event.elevator.elevators.TravelDirection; import java.util.OptionalInt; import java.util.StringJoiner; @@ -17,6 +18,8 @@ public final class Human implements ElevatorListener { private State currentState; private final int startingFloor; private final int destinationFloor; + private final TravelDirection desiredDirection; + private final Object lock = new Object(); /** * 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. @@ -36,19 +39,24 @@ 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; + if (startingFloor == destinationFloor) { + this.desiredDirection = TravelDirection.NONE; + } else { + this.desiredDirection = (destinationFloor > startingFloor) ? TravelDirection.UP : TravelDirection.DOWN; + } } public State getCurrentState() { - return currentState; + synchronized (lock) { + return currentState; + } } public int getStartingFloor() { - return startingFloor; + return startingFloor; } public int getDestinationFloor() { @@ -57,36 +65,57 @@ public int getDestinationFloor() { @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"); + synchronized (lock) { + currentState = State.WAITING_FOR_ELEVATOR; + floorPanelSystem.requestElevator(startingFloor, desiredDirection); + } } @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"); + synchronized (lock) { + if (currentState == State.WAITING_FOR_ELEVATOR && desiredDirection == TravelDirection.NONE) { + currentEnteredElevatorId = null; + currentState = State.ARRIVED; + return; + } + if (currentState.equals(State.WAITING_FOR_ELEVATOR) + && elevatorPanel.getCurrentFloor() == startingFloor + /* or advertised direction equals TravelDirection.NONE part is needed here as advertised direction + can be none if this was the last stop of the existing queue but more people need to board still */ + && ((elevatorPanel.getAdvertisedDirection() == desiredDirection) + || elevatorPanel.getAdvertisedDirection() == TravelDirection.NONE)) { + currentEnteredElevatorId = elevatorPanel.getId(); + elevatorPanel.requestDestinationFloor(destinationFloor); + currentState = State.TRAVELING_WITH_ELEVATOR; + } + if (currentState.equals(State.TRAVELING_WITH_ELEVATOR) + && elevatorPanel.getId() == currentEnteredElevatorId + && elevatorPanel.getCurrentFloor() == destinationFloor) { + currentEnteredElevatorId = null; + currentState = State.ARRIVED; + } + } } public OptionalInt getCurrentEnteredElevatorId() { - return currentEnteredElevatorId == null - ? OptionalInt.empty() - : OptionalInt.of(currentEnteredElevatorId); + synchronized (lock) { + 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(); + synchronized (lock) { + return new StringJoiner(", ", Human.class.getSimpleName() + "[", "]") + .add("currentState=" + currentState) + .add("startingFloor=" + startingFloor) + .add("destinationFloor=" + destinationFloor) + .add("currentEnteredElevatorId=" + currentEnteredElevatorId) + .toString(); + } } public enum State { diff --git a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/Simulation.java b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/Simulation.java index 6f462c6..701ce3b 100644 --- a/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/Simulation.java +++ b/Contest/Assignment/src/org/togetherjava/event/elevator/simulation/Simulation.java @@ -82,7 +82,6 @@ public void startAndExecuteUntilDone(int stepLimit) { 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.");