From 0e298492a36a5236811a4e7a6775f30a0ceff006 Mon Sep 17 00:00:00 2001 From: Arne Keller Date: Fri, 6 Mar 2020 14:27:03 +0100 Subject: [PATCH] Move step output in UI --- .../model/ModelRailwaySimulation.java | 29 +++- src/edu/kit/informatik/model/Train.java | 14 +- .../kit/informatik/model/TrainManager.java | 144 +++++++++--------- .../kit/informatik/ui/command/ListTrains.java | 6 +- src/edu/kit/informatik/ui/command/Step.java | 26 +++- 5 files changed, 131 insertions(+), 88 deletions(-) diff --git a/src/edu/kit/informatik/model/ModelRailwaySimulation.java b/src/edu/kit/informatik/model/ModelRailwaySimulation.java index 736fc78..5fc5e2b 100644 --- a/src/edu/kit/informatik/model/ModelRailwaySimulation.java +++ b/src/edu/kit/informatik/model/ModelRailwaySimulation.java @@ -9,6 +9,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.SortedMap; +import java.util.SortedSet; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -66,6 +68,7 @@ public class ModelRailwaySimulation { /** * Remove the specified rail from the rail network. + * * @param id identifier of the rail to remove * @return whether the rail could be successfully removed */ @@ -275,12 +278,22 @@ public class ModelRailwaySimulation { } /** - * Get a list of trains (their text representation) in the simulation. + * Get the trains (their text representation) in the simulation. * - * @return list of trains + * @return sorted collection of trains */ - public List listTrains() { - return trainManager.listTrains(); + public SortedMap getTrains() { + return trainManager.getTrains(); + } + + /** + * Get the front position of a train. + * + * @param trainId train identifier + * @return position of the train, or null if not found or placed + */ + public Vector2D getTrainPosition(int trainId) { + return trainManager.getPosition(trainId); } /** @@ -307,11 +320,13 @@ public class ModelRailwaySimulation { } /** - * Move the trains in this simulation, printing their new positions and any collisions. + * Move the trains in this simulation. + * * @param speed amount of steps to move the trains + * @return train collisions, null if no trains are placed * @throws InvalidInputException if the simulation is not yet ready */ - public void step(short speed) throws InvalidInputException { - trainManager.step(speed); + public List> step(short speed) throws InvalidInputException { + return trainManager.step(speed); } } \ No newline at end of file diff --git a/src/edu/kit/informatik/model/Train.java b/src/edu/kit/informatik/model/Train.java index 9d286a1..fecbe56 100644 --- a/src/edu/kit/informatik/model/Train.java +++ b/src/edu/kit/informatik/model/Train.java @@ -16,7 +16,7 @@ import java.util.stream.Collectors; * @author Arne Keller * @version 1.1 */ -public final class Train { +public final class Train implements Comparable { /** * Separator between rolling stocks in {@link #show()}. */ @@ -85,13 +85,14 @@ public final class Train { } /** - * Check whether this train is on a particular rail. Note that at least one rolling stock has to be on the rail, - * just touching the rail is not enough. + * Check whether this train is on a particular rail. Just touching the rail is enough. + * * @param rail the rail to check * @return whether this train is on that rail */ public boolean isOnRail(Rail rail) { - return isPlaced() && (occupiedRails.stream().anyMatch(blockedRail -> blockedRail == rail) + return isPlaced() + && (occupiedRails.stream().anyMatch(blockedRail -> blockedRail == rail) || positions.stream().anyMatch(rail::canConnectTo)); } @@ -367,4 +368,9 @@ public final class Train { } return stringBuilder.toString(); } + + @Override + public int compareTo(Object o) { + return Integer.compare(this.identifier, ((Train) o).identifier); + } } diff --git a/src/edu/kit/informatik/model/TrainManager.java b/src/edu/kit/informatik/model/TrainManager.java index 9417140..0076d8b 100644 --- a/src/edu/kit/informatik/model/TrainManager.java +++ b/src/edu/kit/informatik/model/TrainManager.java @@ -1,25 +1,24 @@ package edu.kit.informatik.model; -import edu.kit.informatik.Terminal; import edu.kit.informatik.ui.InvalidInputException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; -import static edu.kit.informatik.ui.CommandLine.OK; - /** * Train manager, used for processing train placements and movement on a rail network. * @@ -32,7 +31,7 @@ public final class TrainManager { */ private final RailwayNetwork railNetwork; /** - * Map of trains simulated. + * Map of trains simulated. The train identifier is used as key. */ private final Map trains = new HashMap<>(); @@ -46,8 +45,8 @@ public final class TrainManager { } /** - * Check whether a train is on the rail with the specified identifier. Note that a train must be partially on that - * rail, simply touching one of the end points is not enough. + * Check whether a train is on the rail with the specified identifier. Trains only touching the end of the rail + * will also be considered. * * @param rail the rail to check * @return whether a train is on that rail @@ -87,21 +86,31 @@ public final class TrainManager { throw new InvalidInputException("rolling stock already used"); } final Train train = trains.get(trainId); - if (train != null && train.isPlaced()) { - throw new InvalidInputException("can not add rolling stock to placed train"); - } - if (train != null) { - train.add(rollingStock); - } else { + if (train == null) { // create new train final int correctId = getNextTrainIdentifier(); if (trainId != correctId) { throw new InvalidInputException("new train identifier must be next free identifier"); } final Train newTrain = new Train(trainId, rollingStock); trains.put(trainId, newTrain); + } else { + if (train.isPlaced()) { + throw new InvalidInputException("can not add rolling stock to placed train"); + } + train.add(rollingStock); } } + /** + * Calculate the next available train identifier. Will fill 'holes' in the identifier list first. + * + * @return the next train identifier, or -1 if none available + */ + private int getNextTrainIdentifier() { + return IntStream.rangeClosed(1, Integer.MAX_VALUE) + .filter(id -> !trains.containsKey(id)).findFirst().orElse(-1); + } + /** * Delete a train. Will not delete rolling stock contained in that train. * @@ -118,20 +127,20 @@ public final class TrainManager { } /** - * Get a sorted list of trains (their text representation) in the simulation. + * Get the trains (their text representation) in the simulation. * - * @return list of trains + * @return sorted collection of trains */ - public List listTrains() { + public SortedMap getTrains() { return trains.keySet().stream() .sorted() .map(trains::get) - .map(Object::toString) - .collect(Collectors.toList()); + .collect(Collectors.toMap(Train::getIdentifier, Object::toString, (id1, id2) -> id1, TreeMap::new)); } /** * Get the ASCII art representation of a train. + * * @param trainId identifier of the train to show * @return ASCII art representation of said train * @throws InvalidInputException if train not found @@ -179,19 +188,12 @@ public final class TrainManager { } /** - * Calculate the next available train identifier. - * @return the next train identifier, or -1 if none available + * Get collisions of trains currently placed. This implements the first collision checking algorithm (1A): + * trains collide if they partially occupy the same rail or touch each other. + * + * @param collisions list of collisions to expand */ - private int getNextTrainIdentifier() { - return IntStream.rangeClosed(1, Integer.MAX_VALUE) - .filter(id -> !trains.containsKey(id)).findFirst().orElse(-1); - } - - /** - * Get collisions of trains currently placed. - * @param collisions list of collisions (never null, sometimes empty) - */ - private void getStaticCollisions(List> collisions) { + private void getStaticCollisions(List> collisions) { final int maxId = trains.keySet().stream().max(Integer::compareTo).orElse(0); for (int id = 1; id <= maxId; id++) { final Train train1 = trains.get(id); @@ -200,13 +202,13 @@ public final class TrainManager { } final Set occupiedRails = train1.getOccupiedRails(); // check for same position, and rail collisions - final Set collision = IntStream.rangeClosed(id + 1, maxId) + final SortedSet collision = IntStream.rangeClosed(id + 1, maxId) .mapToObj(trains::get) .filter(Objects::nonNull) .filter(Train::isPlaced) .filter(train -> train1.touches(train) || train.getOccupiedRails().stream().anyMatch(occupiedRails::contains)) - .collect(Collectors.toSet()); + .collect(Collectors.toCollection(TreeSet::new)); if (!collision.isEmpty()) { collision.add(train1); addToSetOrAddNew(collisions, collision); @@ -215,15 +217,15 @@ public final class TrainManager { } /** - * Implementation of the silly *second* collision checking algorithm. - * Will put two trains in a collision even if they only touch the same rail. + * Implementation of the silly second collision checking algorithm (2B): + * two trains collide if they touch the same rail or position. * * @return list of collisions */ - private List> getPlacementCollisions() { - final List> collisions = new ArrayList<>(); + private List> getPlacementCollisions() { + final List> collisions = new ArrayList<>(); trains.values().stream().filter(Train::isPlaced).forEach(train1 -> - trains.values().stream().filter(Train::isPlaced).forEach(train2 -> { + trains.values().stream().filter(train -> train != train1).filter(Train::isPlaced).forEach(train2 -> { final Set occupiedByTrain1 = train1.getOccupiedRails(); Collections.addAll(occupiedByTrain1, railNetwork.findTouchingRails(train1.getFrontPosition())); Collections.addAll(occupiedByTrain1, railNetwork.findTouchingRails(train1.getRearPosition())); @@ -233,7 +235,8 @@ public final class TrainManager { // calculate intersection occupiedByTrain2.retainAll(occupiedByTrain1); if (!occupiedByTrain2.isEmpty()) { - final Set collision = Stream.of(train1, train2).collect(Collectors.toSet()); + final SortedSet collision + = Stream.of(train1, train2).collect(Collectors.toCollection(TreeSet::new)); addToSetOrAddNew(collisions, collision); } })); @@ -243,7 +246,7 @@ public final class TrainManager { /** * Add the set to an existing set (and merge sets), or add it to the list. */ - private void addToSetOrAddNew(List> setList, Set newSet) { + private void addToSetOrAddNew(List> setList, SortedSet newSet) { Set existing = null; int i = 0; while (i < setList.size()) { @@ -265,13 +268,23 @@ public final class TrainManager { } } + /** + * Get the front position of the specified train. + * + * @param trainId train identifier + * @return front position of that train + */ + public Vector2D getPosition(int trainId) { + return trains.get(trainId).getFrontPosition(); + } + /** * Get collisions of moving the trains one step forward, removing crashing trains from the rails in the process. * * @return list of collisions (never null, sometimes empty) */ - private List> getCollisionsOfOneStep() { - final List> collisions = new ArrayList<>(); + private List> getCollisionsOfOneStep() { + final List> collisions = new ArrayList<>(); trains.values().stream().filter(Train::isPlaced).forEach(train -> { final Vector2D position = train.getFrontPosition(); final Vector2D direction = train.getDirection(); @@ -279,7 +292,7 @@ public final class TrainManager { if (nextPosition == null || nextPosition.equals(position)) { // train is derailing train.moveTo(railNetwork, null); - collisions.add(new HashSet<>(Arrays.asList(train))); + collisions.add(Stream.of(train).collect(Collectors.toCollection(TreeSet::new))); } else { // train is moving successfully train.moveTo(railNetwork, nextPosition); @@ -295,8 +308,8 @@ public final class TrainManager { * * @return list of collisions (never null, sometimes empty) */ - private List> getCollisionsOfOneReverseStep() { - final List> collisions = new ArrayList<>(); + private List> getCollisionsOfOneReverseStep() { + final List> collisions = new ArrayList<>(); // perform step trains.values().stream().filter(Train::isPlaced).forEach(train -> { final Vector2D position = train.getRearPosition(); @@ -305,7 +318,7 @@ public final class TrainManager { if (nextPosition == null || nextPosition.equals(position)) { // derailing train.moveBackTo(railNetwork, nextPosition); - collisions.add(new HashSet<>(Arrays.asList(train))); + collisions.add(Stream.of(train).collect(Collectors.toCollection(TreeSet::new))); } else { // train moving successfully train.moveBackTo(railNetwork, nextPosition); @@ -317,42 +330,27 @@ public final class TrainManager { } /** - * Move the trains in this simulation, printing their new positions and any collisions. + * Move the trains in this simulation. + * * @param speed amount of steps to move the trains + * @return train collisions, null if no trains are placed * @throws InvalidInputException if simulation is not yet ready */ - public void step(short speed) throws InvalidInputException { + public List> step(short speed) throws InvalidInputException { if (!railNetwork.isReadyForTrains()) { throw new InvalidInputException("rail tracks/switches not set up"); } if (trains.values().stream().noneMatch(Train::isPlaced)) { - Terminal.printLine(OK); - return; + return null; } - final List> collisions = IntStream.range(0, Math.abs(speed)) + return IntStream.range(0, Math.abs(speed)) .mapToObj(step -> speed >= 0 ? getCollisionsOfOneStep() : getCollisionsOfOneReverseStep()) - .flatMap(List::stream).collect(Collectors.toList()); - - for (final int id : trains.keySet().stream().sorted().collect(Collectors.toList())) { - final Train train = trains.get(id); - final Set collisionSet = collisions.stream() - .filter(collision -> collision.contains(train)) - .findFirst().orElse(null); - if (collisionSet != null) { // print collision - final int first = collisionSet.stream() - .min(Comparator.comparing(Train::getIdentifier)).get().getIdentifier(); - if (train.getIdentifier() == first) { // only print each collision once - final List collision = collisionSet.stream() - .sorted(Comparator.comparing(Train::getIdentifier)) - .collect(Collectors.toList()); - Terminal.printLine("Crash of train " + String.join(",", collision.stream() - .map(crashedTrain -> Integer.toString(crashedTrain.getIdentifier())) - .toArray(String[]::new))); - } - } else if (train.isPlaced()) { // or position, if not in collision - Terminal.printLine(String.format("Train %d at %s", train.getIdentifier(), train.getFrontPosition())); - } - } + // replace train references with identifiers to prevent modification by caller + .flatMap(collisions -> collisions.stream() + .map(collision -> collision.stream().map(Train::getIdentifier) + .collect(Collectors.toCollection(TreeSet::new)))) + .sorted(Comparator.comparing(TreeSet::first)) + .collect(Collectors.toList()); } } diff --git a/src/edu/kit/informatik/ui/command/ListTrains.java b/src/edu/kit/informatik/ui/command/ListTrains.java index 4dbb856..5309078 100644 --- a/src/edu/kit/informatik/ui/command/ListTrains.java +++ b/src/edu/kit/informatik/ui/command/ListTrains.java @@ -4,7 +4,7 @@ import edu.kit.informatik.Terminal; import edu.kit.informatik.model.ModelRailwaySimulation; import edu.kit.informatik.ui.InvalidInputException; -import java.util.List; +import java.util.SortedMap; import static edu.kit.informatik.ui.command.CommandFactory.LIST_TRAINS; @@ -17,11 +17,11 @@ import static edu.kit.informatik.ui.command.CommandFactory.LIST_TRAINS; public class ListTrains extends Command { @Override public void apply(ModelRailwaySimulation simulation) { - final List trains = simulation.listTrains(); + final SortedMap trains = simulation.getTrains(); if (trains.isEmpty()) { Terminal.printLine("No train exists"); } else { - trains.forEach(Terminal::printLine); + trains.values().forEach(Terminal::printLine); } } diff --git a/src/edu/kit/informatik/ui/command/Step.java b/src/edu/kit/informatik/ui/command/Step.java index 126ea5f..1f5673d 100644 --- a/src/edu/kit/informatik/ui/command/Step.java +++ b/src/edu/kit/informatik/ui/command/Step.java @@ -1,8 +1,14 @@ package edu.kit.informatik.ui.command; +import edu.kit.informatik.Terminal; import edu.kit.informatik.model.ModelRailwaySimulation; +import edu.kit.informatik.model.Vector2D; import edu.kit.informatik.ui.InvalidInputException; +import java.util.List; +import java.util.SortedSet; + +import static edu.kit.informatik.ui.CommandLine.OK; import static edu.kit.informatik.ui.command.CommandFactory.NUMBER; import static edu.kit.informatik.ui.command.CommandFactory.STEP; @@ -20,7 +26,25 @@ public class Step extends Command { @Override public void apply(ModelRailwaySimulation simulation) throws InvalidInputException { - simulation.step(speed); + final List> collisions = simulation.step(speed); + if (collisions == null) { + Terminal.printLine(OK); + } else { + for (final int id : simulation.getTrains().keySet()) { + final SortedSet collisionSet = collisions.stream() + .filter(collision -> collision.first() == id) + .findFirst().orElse(null); + if (collisionSet != null) { // print collision + Terminal.printLine("Crash of train " + String.join(",", + collisionSet.stream().map(Object::toString).toArray(String[]::new))); + } else { // or position, if not in collision + final Vector2D position = simulation.getTrainPosition(id); + if (position != null) { // only print placed trains + Terminal.printLine(String.format("Train %d at %s", id, position)); + } + } + } + } } @Override