mirror of
https://gitlab.com/arnekeller/kit-programmieren-ws1920-final1.git
synced 2024-11-12 11:43:06 +00:00
Refactor train movement into train manager class
This commit is contained in:
parent
4da5eeebd3
commit
122193300d
@ -5,15 +5,10 @@ import edu.kit.informatik.Terminal;
|
||||
import edu.kit.informatik.ui.InvalidInputException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
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.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
@ -29,6 +24,10 @@ public class ModelRailwaySimulation {
|
||||
* Railway network used in this simulation.
|
||||
*/
|
||||
private final RailwayNetwork railNetwork = new RailwayNetwork();
|
||||
/**
|
||||
* Train manager used to simulate train placement and movement.
|
||||
*/
|
||||
private final TrainManager trainManager = new TrainManager(railNetwork);
|
||||
/**
|
||||
* List of engines used in the simulation.
|
||||
*/
|
||||
@ -41,10 +40,6 @@ public class ModelRailwaySimulation {
|
||||
* Map of coaches used in the simulation.
|
||||
*/
|
||||
private final Map<Integer, Coach> coaches = new HashMap<>();
|
||||
/**
|
||||
* Map of trains simulated.
|
||||
*/
|
||||
private final Map<Integer, Train> trains = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Add a new track to the rail network of this simulation.
|
||||
@ -76,7 +71,7 @@ public class ModelRailwaySimulation {
|
||||
*/
|
||||
public boolean removeRail(final int id) {
|
||||
// check whether any trains are on this rail
|
||||
return trains.values().stream().noneMatch(train -> train.isOnRail(id)) && railNetwork.removeRail(id);
|
||||
return !trainManager.anyTrainOnRail(id) && railNetwork.removeRail(id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,7 +91,7 @@ public class ModelRailwaySimulation {
|
||||
public boolean setSwitch(final int id, final Vector2D position) {
|
||||
boolean success = railNetwork.setSwitch(id, position);
|
||||
if (success) { // derail trains on switch, explicitly not (!) printing any removed trains (source: forum post)
|
||||
trains.values().stream().filter(train -> train.isOnRail(id)).forEach(Train::removeFromRails);
|
||||
trainManager.removeTrainsOnRail(id);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@ -123,9 +118,9 @@ public class ModelRailwaySimulation {
|
||||
} else {
|
||||
engines.sort(Comparator.comparing(Engine::getIdentifier));
|
||||
for (Engine engine : engines) {
|
||||
String trainId = trains.values().stream().filter(train -> train.contains(engine))
|
||||
String trainId = trainManager.getTrainContainingRollingStock(engine)
|
||||
.map(train -> Integer.toString(train.getIdentifier()))
|
||||
.findFirst().orElse("none");
|
||||
.orElse("none");
|
||||
Terminal.printLine(String.format("%s %s", trainId, engine));
|
||||
}
|
||||
}
|
||||
@ -180,9 +175,9 @@ public class ModelRailwaySimulation {
|
||||
} else {
|
||||
for (Integer identifier : coaches.keySet().stream().sorted().collect(Collectors.toList())) {
|
||||
Coach coach = coaches.get(identifier);
|
||||
String trainId = trains.values().stream().filter(train -> train.contains(coach))
|
||||
String trainId = trainManager.getTrainContainingRollingStock(coach)
|
||||
.map(train -> Integer.toString(train.getIdentifier()))
|
||||
.findFirst().orElse("none");
|
||||
.orElse("none");
|
||||
Terminal.printLine(String.format("%d %s %s %d %b %b",
|
||||
coach.getNumericalIdentifier(), trainId, coach.getType(),
|
||||
coach.getLength(), coach.hasCouplingFront(), coach.hasCouplingBack()));
|
||||
@ -213,9 +208,9 @@ public class ModelRailwaySimulation {
|
||||
} else {
|
||||
trainSets.sort(Comparator.comparing(TrainSet::getIdentifier));
|
||||
for (TrainSet trainSet : trainSets) {
|
||||
String trainId = trains.values().stream().filter(train -> train.contains(trainSet))
|
||||
String trainId = trainManager.getTrainContainingRollingStock(trainSet)
|
||||
.map(train -> Integer.toString(train.getIdentifier()))
|
||||
.findFirst().orElse("none");
|
||||
.orElse("none");
|
||||
Terminal.printLine(String.format("%s %s", trainId, trainSet));
|
||||
}
|
||||
}
|
||||
@ -228,7 +223,7 @@ public class ModelRailwaySimulation {
|
||||
*/
|
||||
public boolean deleteRollingStock(final String id) {
|
||||
RollingStock rollingStock = getRollingStock(id);
|
||||
if (trains.values().stream().anyMatch(train -> train.contains(rollingStock))) {
|
||||
if (trainManager.getTrainContainingRollingStock(rollingStock).isPresent()) {
|
||||
return false; // can not delete rolling stock in use
|
||||
}
|
||||
return engines.remove(rollingStock) || trainSets.remove(rollingStock) || coaches.values().remove(rollingStock);
|
||||
@ -261,32 +256,7 @@ public class ModelRailwaySimulation {
|
||||
if (rollingStock == null) {
|
||||
throw new InvalidInputException("rolling stock not found");
|
||||
}
|
||||
if (trains.values().stream().anyMatch(train -> train.contains(rollingStock))) {
|
||||
throw new InvalidInputException("rolling stock already used");
|
||||
}
|
||||
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 {
|
||||
int correctId = getNextTrainIdentifier();
|
||||
if (trainId != correctId) {
|
||||
throw new InvalidInputException("new train identifier must be next free identifier");
|
||||
}
|
||||
Train newTrain = new Train(trainId, rollingStock);
|
||||
trains.put(trainId, newTrain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next train identifier.
|
||||
* @return the next train identfier, or -1 if none available
|
||||
*/
|
||||
private int getNextTrainIdentifier() {
|
||||
return IntStream.rangeClosed(1, Integer.MAX_VALUE)
|
||||
.filter(id -> !trains.containsKey(id)).findFirst().orElse(-1);
|
||||
trainManager.addTrain(trainId, rollingStock);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -295,23 +265,14 @@ public class ModelRailwaySimulation {
|
||||
* @return whether the train could be deleted
|
||||
*/
|
||||
public boolean deleteTrain(int id) {
|
||||
Train train = trains.get(id);
|
||||
if (train != null) {
|
||||
trains.remove(train.getIdentifier());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return trainManager.deleteTrain(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a list of trains in the simulation.
|
||||
*/
|
||||
public void printTrains() {
|
||||
if (trains.isEmpty()) {
|
||||
Terminal.printLine("No train exists");
|
||||
return;
|
||||
}
|
||||
trains.values().forEach(Terminal::printLine);
|
||||
trainManager.printTrains();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -319,12 +280,7 @@ public class ModelRailwaySimulation {
|
||||
* @param id identifier of the train to show
|
||||
*/
|
||||
public void printTrain(int id) {
|
||||
Train train = trains.get(id);
|
||||
if (train != null) {
|
||||
train.print();
|
||||
} else {
|
||||
Terminal.printError("no such train");
|
||||
}
|
||||
trainManager.printTrain(id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -337,168 +293,7 @@ public class ModelRailwaySimulation {
|
||||
*/
|
||||
public boolean putTrain(final int trainId, final Vector2D position, final Vector2D direction)
|
||||
throws InvalidInputException {
|
||||
Train train = trains.get(trainId);
|
||||
if (train == null) {
|
||||
throw new InvalidInputException("train not found");
|
||||
} else if (!train.isProperTrain()) {
|
||||
throw new InvalidInputException("train is not valid");
|
||||
} else if (train.isPlaced()) {
|
||||
throw new InvalidInputException("train is already placed");
|
||||
} else if (direction.getX() != 0 && direction.getY() != 0) {
|
||||
throw new InvalidInputException("invalid train direction");
|
||||
} else if (!railNetwork.isReadyForTrains()) {
|
||||
throw new InvalidInputException("switches not set up");
|
||||
}
|
||||
// attempt to place train
|
||||
boolean placed = train.placeOn(railNetwork, position, direction);
|
||||
// check for collisions
|
||||
if (placed && !getStaticCollisions().isEmpty()) {
|
||||
train.removeFromRails();
|
||||
return false;
|
||||
} else {
|
||||
return placed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get collisions of trains currently placed.
|
||||
* @return list of collisions (never null, sometimes empty)
|
||||
*/
|
||||
private List<HashSet<Train>> getStaticCollisions() {
|
||||
List<HashSet<Train>> collisions = new ArrayList<>();
|
||||
int maxId = trains.keySet().stream().max(Integer::compareTo).orElse(0);
|
||||
for (int id1 = 1; id1 <= maxId; id1++) {
|
||||
Train train1 = trains.get(id1);
|
||||
if (train1 == null || !train1.isPlaced()) {
|
||||
continue;
|
||||
}
|
||||
HashSet<Train> collision = new HashSet<>();
|
||||
IntStream.rangeClosed(id1 + 1, maxId)
|
||||
.mapToObj(trains::get)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(Train::isPlaced)
|
||||
.filter(train1::touches)
|
||||
.forEach(collision::add);
|
||||
if (!collision.isEmpty()) {
|
||||
// check for existing collision
|
||||
Set<Train> otherCollision = collisions.stream()
|
||||
.filter(x -> x.stream().anyMatch(collision::contains))
|
||||
.findFirst().orElse(null);
|
||||
if (otherCollision != null) { // add to that collision
|
||||
otherCollision.add(train1);
|
||||
otherCollision.addAll(collision);
|
||||
} else { // create a new collision
|
||||
collision.add(train1);
|
||||
collisions.add(collision);
|
||||
}
|
||||
}
|
||||
}
|
||||
return collisions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get collisions of moving the trains one step forward.
|
||||
* @return list of collisions (never null, sometimes empty)
|
||||
*/
|
||||
private List<Set<Train>> getCollisionsOfOneStep() {
|
||||
List<Set<Train>> collisions = new ArrayList<>();
|
||||
Map<Train, Set<Rail>> occupiedRails = trains.values().stream().filter(Train::isPlaced)
|
||||
.collect(Collectors.toMap(Function.identity(), Train::getOccupiedRails));
|
||||
// perform step
|
||||
Map<Train, Set<Rail>> nextOccupiedRails = new HashMap<>();
|
||||
trains.values().stream().filter(Train::isPlaced).forEach(train -> {
|
||||
Vector2D position = train.getFrontPosition();
|
||||
Vector2D direction = train.getDirection();
|
||||
Vector2D nextPosition = railNetwork.move(position, direction);
|
||||
if (nextPosition == null
|
||||
|| train.isOnPosition(nextPosition) && !train.getRearPosition().equals(train.getFrontPosition())) {
|
||||
collisions.add(new HashSet<>(Arrays.asList(train)));
|
||||
train.removeFromRails();
|
||||
nextOccupiedRails.put(train, occupiedRails.get(train));
|
||||
} else {
|
||||
train.moveTo(railNetwork, nextPosition);
|
||||
train.setDirection(direction);
|
||||
nextOccupiedRails.put(train, train.getOccupiedRails());
|
||||
}
|
||||
});
|
||||
checkForBlockCollisions(collisions, occupiedRails, nextOccupiedRails);
|
||||
return collisions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get collisions of moving the trains one step backward.
|
||||
* @return list of collisions (never null, sometimes empty)
|
||||
*/
|
||||
private List<Set<Train>> getCollisionsOfOneReverseStep() {
|
||||
List<Set<Train>> collisions = new ArrayList<>();
|
||||
Map<Train, Set<Rail>> occupiedRails = new HashMap<>();
|
||||
for (Train train : trains.values()) {
|
||||
if (train.isPlaced()) {
|
||||
occupiedRails.put(train, train.getOccupiedRails());
|
||||
}
|
||||
}
|
||||
// perform step
|
||||
Map<Train, Set<Rail>> nextOccupiedRails = new HashMap<>();
|
||||
trains.values().stream().filter(Train::isPlaced).forEach(train -> {
|
||||
Vector2D front = train.getFrontPosition();
|
||||
Vector2D position = train.getRearPosition();
|
||||
Vector2D direction = train.getRearDirection();
|
||||
Vector2D nextPosition = railNetwork.move(position, direction);
|
||||
if (nextPosition == null
|
||||
|| train.isOnPosition(nextPosition) && !train.getRearPosition().equals(train.getFrontPosition())) {
|
||||
collisions.add(new HashSet<>(Arrays.asList(train)));
|
||||
train.removeFromRails();
|
||||
nextOccupiedRails.put(train, occupiedRails.get(train));
|
||||
} else {
|
||||
train.moveBackTo(railNetwork, nextPosition);
|
||||
train.setDirection(front.subtract(train.getFrontPosition()));
|
||||
nextOccupiedRails.put(train, train.getOccupiedRails());
|
||||
}
|
||||
});
|
||||
checkForBlockCollisions(collisions, occupiedRails, nextOccupiedRails);
|
||||
return collisions;
|
||||
}
|
||||
|
||||
private void checkForBlockCollisions(List<Set<Train>> collisions, Map<Train, Set<Rail>> occupiedRails,
|
||||
Map<Train, Set<Rail>> nextOccupiedRails) {
|
||||
trains.values().stream().filter(train -> train.isPlaced()
|
||||
&& collisions.stream().noneMatch(x -> x.contains(train))).forEach(train -> {
|
||||
Set<Rail> occupiedByThisTrain = nextOccupiedRails.get(train);
|
||||
trains.values().stream().filter(x -> x != train).forEach(otherTrain -> {
|
||||
Set<Rail> occupiedByOtherTrainPreviously = occupiedRails.get(otherTrain);
|
||||
Set<Rail> occupiedByOtherTrain = nextOccupiedRails.get(otherTrain);
|
||||
if (occupiedByOtherTrain == null) {
|
||||
return;
|
||||
}
|
||||
boolean anyIntersection = Stream.concat(
|
||||
occupiedByOtherTrain.stream(), occupiedByOtherTrainPreviously.stream())
|
||||
.anyMatch(occupiedByThisTrain::contains);
|
||||
if (anyIntersection || train.touches(otherTrain)) {
|
||||
train.removeFromRails();
|
||||
otherTrain.removeFromRails();
|
||||
// try to find/merge existing collisions
|
||||
Set<Train> existingCollision = null;
|
||||
for (Set<Train> collision : collisions) {
|
||||
if (collision.contains(otherTrain) || collision.contains(train)) {
|
||||
if (existingCollision == null) {
|
||||
existingCollision = collision;
|
||||
collision.add(train);
|
||||
collision.add(otherTrain);
|
||||
} else {
|
||||
existingCollision.addAll(collision);
|
||||
existingCollision.add(train);
|
||||
existingCollision.add(otherTrain);
|
||||
collisions.remove(collision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (existingCollision == null) {
|
||||
collisions.add(Stream.of(train, otherTrain).collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return trainManager.putTrain(trainId, position, direction);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -506,36 +301,6 @@ public class ModelRailwaySimulation {
|
||||
* @param speed amount of steps to move the trains
|
||||
*/
|
||||
public void step(final short speed) {
|
||||
if (!railNetwork.isReadyForTrains()) {
|
||||
Terminal.printError("rail tracks/switches not set up");
|
||||
return;
|
||||
}
|
||||
if (trains.values().stream().noneMatch(Train::isPlaced)) {
|
||||
Terminal.printLine("OK");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Set<Train>> collisions = IntStream.range(0, Math.abs(speed))
|
||||
.mapToObj(step -> speed >= 0 ? getCollisionsOfOneStep() : getCollisionsOfOneReverseStep())
|
||||
.flatMap(List::stream).collect(Collectors.toList());
|
||||
|
||||
for (int id : trains.keySet().stream().sorted().collect(Collectors.toList())) {
|
||||
Train train = trains.get(id);
|
||||
Set<Train> collisionSet = collisions.stream()
|
||||
.filter(collision -> collision.contains(train))
|
||||
.findFirst().orElse(null);
|
||||
if (collisionSet != null) { // print collision
|
||||
int first = collisionSet.stream().min(Comparator.comparing(Train::getIdentifier)).get().getIdentifier();
|
||||
if (train.getIdentifier() == first) { // only print each collision once
|
||||
List<Train> 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()));
|
||||
}
|
||||
}
|
||||
trainManager.step(speed);
|
||||
}
|
||||
}
|
359
src/edu/kit/informatik/model/TrainManager.java
Normal file
359
src/edu/kit/informatik/model/TrainManager.java
Normal file
@ -0,0 +1,359 @@
|
||||
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.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.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Train manager, used for processing train placements and movement on a rail network.
|
||||
*
|
||||
* @author Arne Keller
|
||||
* @version 1.0
|
||||
*/
|
||||
public final class TrainManager {
|
||||
/**
|
||||
* Railway network used in this simulation.
|
||||
*/
|
||||
private final RailwayNetwork railNetwork;
|
||||
/**
|
||||
* Map of trains simulated.
|
||||
*/
|
||||
private final Map<Integer, Train> trains = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Construct a new train manager that will operate on the provided rail network.
|
||||
*
|
||||
* @param railNetwork rail network to use
|
||||
*/
|
||||
public TrainManager(RailwayNetwork railNetwork) {
|
||||
this.railNetwork = railNetwork;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param id identfier of the rail to check
|
||||
* @return whether a train is on that rail
|
||||
*/
|
||||
public boolean anyTrainOnRail(int id) {
|
||||
return trains.values().stream().anyMatch(train -> train.isOnRail(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any trains on the rail with the specified identifier.
|
||||
* @param id identifier of the rail
|
||||
*/
|
||||
public void removeTrainsOnRail(int id) {
|
||||
trains.values().stream().filter(train -> train.isOnRail(id)).forEach(Train::removeFromRails);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the train containing the specified rolling stock.
|
||||
*
|
||||
* @param rollingStock rolling stock to search for
|
||||
* @return the train containing the rolling stock
|
||||
*/
|
||||
public Optional<Train> getTrainContainingRollingStock(RollingStock rollingStock) {
|
||||
return trains.values().stream().filter(train -> train.contains(rollingStock)).findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a rolling stock to an existing train or create a new one.
|
||||
*
|
||||
* @param trainId train identifier
|
||||
* @param rollingStock rolling stock to add
|
||||
* @throws InvalidInputException on invalid user input (e.g. rolling stock in use)
|
||||
*/
|
||||
public void addTrain(int trainId, RollingStock rollingStock) throws InvalidInputException {
|
||||
if (getTrainContainingRollingStock(rollingStock).isPresent()) {
|
||||
throw new InvalidInputException("rolling stock already used");
|
||||
}
|
||||
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 {
|
||||
int correctId = getNextTrainIdentifier();
|
||||
if (trainId != correctId) {
|
||||
throw new InvalidInputException("new train identifier must be next free identifier");
|
||||
}
|
||||
Train newTrain = new Train(trainId, rollingStock);
|
||||
trains.put(trainId, newTrain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a train.
|
||||
* @param trainId identifier of the train
|
||||
* @return whether the train could be deleted
|
||||
*/
|
||||
public boolean deleteTrain(int trainId) {
|
||||
Train train = trains.get(trainId);
|
||||
if (train != null) {
|
||||
trains.remove(trainId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a list of trains in the simulation.
|
||||
*/
|
||||
public void printTrains() {
|
||||
if (trains.isEmpty()) {
|
||||
Terminal.printLine("No train exists");
|
||||
return;
|
||||
}
|
||||
trains.values().forEach(Terminal::printLine);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a train as ASCII art.
|
||||
* @param trainId identifier of the train to show
|
||||
*/
|
||||
public void printTrain(int trainId) {
|
||||
Train train = trains.get(trainId);
|
||||
if (train != null) {
|
||||
train.print();
|
||||
} else {
|
||||
Terminal.printError("no such train");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Place a train on the rail network.
|
||||
* @param trainId identifier of the train to place
|
||||
* @param position where to place the train
|
||||
* @param direction direction in which the train should initially go
|
||||
* @return whether the train was successfully placed
|
||||
* @throws InvalidInputException when the train is too long
|
||||
*/
|
||||
public boolean putTrain(int trainId, Vector2D position, Vector2D direction) throws InvalidInputException {
|
||||
Train train = trains.get(trainId);
|
||||
if (train == null) {
|
||||
throw new InvalidInputException("train not found");
|
||||
} else if (!train.isProperTrain()) {
|
||||
throw new InvalidInputException("train is not valid");
|
||||
} else if (train.isPlaced()) {
|
||||
throw new InvalidInputException("train is already placed");
|
||||
} else if (direction.getX() != 0 && direction.getY() != 0) {
|
||||
throw new InvalidInputException("invalid train direction");
|
||||
} else if (!railNetwork.isReadyForTrains()) {
|
||||
throw new InvalidInputException("switches not set up");
|
||||
}
|
||||
// attempt to place train
|
||||
boolean placed = train.placeOn(railNetwork, position, direction);
|
||||
// check for collisions
|
||||
if (placed && !getStaticCollisions().isEmpty()) {
|
||||
train.removeFromRails();
|
||||
return false;
|
||||
} else {
|
||||
return placed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next train identifier.
|
||||
* @return the next train identfier, or -1 if none available
|
||||
*/
|
||||
private int getNextTrainIdentifier() {
|
||||
return IntStream.rangeClosed(1, Integer.MAX_VALUE)
|
||||
.filter(id -> !trains.containsKey(id)).findFirst().orElse(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get collisions of trains currently placed.
|
||||
* @return list of collisions (never null, sometimes empty)
|
||||
*/
|
||||
private List<HashSet<Train>> getStaticCollisions() {
|
||||
List<HashSet<Train>> collisions = new ArrayList<>();
|
||||
int maxId = trains.keySet().stream().max(Integer::compareTo).orElse(0);
|
||||
for (int id1 = 1; id1 <= maxId; id1++) {
|
||||
Train train1 = trains.get(id1);
|
||||
if (train1 == null || !train1.isPlaced()) {
|
||||
continue;
|
||||
}
|
||||
HashSet<Train> collision = new HashSet<>();
|
||||
IntStream.rangeClosed(id1 + 1, maxId)
|
||||
.mapToObj(trains::get)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(Train::isPlaced)
|
||||
.filter(train1::touches)
|
||||
.forEach(collision::add);
|
||||
if (!collision.isEmpty()) {
|
||||
// check for existing collision
|
||||
Set<Train> otherCollision = collisions.stream()
|
||||
.filter(x -> x.stream().anyMatch(collision::contains))
|
||||
.findFirst().orElse(null);
|
||||
if (otherCollision != null) { // add to that collision
|
||||
otherCollision.add(train1);
|
||||
otherCollision.addAll(collision);
|
||||
} else { // create a new collision
|
||||
collision.add(train1);
|
||||
collisions.add(collision);
|
||||
}
|
||||
}
|
||||
}
|
||||
return collisions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get collisions of moving the trains one step forward.
|
||||
* @return list of collisions (never null, sometimes empty)
|
||||
*/
|
||||
private List<Set<Train>> getCollisionsOfOneStep() {
|
||||
List<Set<Train>> collisions = new ArrayList<>();
|
||||
Map<Train, Set<Rail>> occupiedRails = trains.values().stream().filter(Train::isPlaced)
|
||||
.collect(Collectors.toMap(Function.identity(), Train::getOccupiedRails));
|
||||
// perform step
|
||||
Map<Train, Set<Rail>> nextOccupiedRails = new HashMap<>();
|
||||
trains.values().stream().filter(Train::isPlaced).forEach(train -> {
|
||||
Vector2D position = train.getFrontPosition();
|
||||
Vector2D direction = train.getDirection();
|
||||
Vector2D nextPosition = railNetwork.move(position, direction);
|
||||
if (nextPosition == null
|
||||
|| train.isOnPosition(nextPosition) && !train.getRearPosition().equals(train.getFrontPosition())) {
|
||||
collisions.add(new HashSet<>(Arrays.asList(train)));
|
||||
train.removeFromRails();
|
||||
nextOccupiedRails.put(train, occupiedRails.get(train));
|
||||
} else {
|
||||
train.moveTo(railNetwork, nextPosition);
|
||||
train.setDirection(direction);
|
||||
nextOccupiedRails.put(train, train.getOccupiedRails());
|
||||
}
|
||||
});
|
||||
checkForBlockCollisions(collisions, occupiedRails, nextOccupiedRails);
|
||||
return collisions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get collisions of moving the trains one step backward.
|
||||
* @return list of collisions (never null, sometimes empty)
|
||||
*/
|
||||
private List<Set<Train>> getCollisionsOfOneReverseStep() {
|
||||
List<Set<Train>> collisions = new ArrayList<>();
|
||||
Map<Train, Set<Rail>> occupiedRails = new HashMap<>();
|
||||
for (Train train : trains.values()) {
|
||||
if (train.isPlaced()) {
|
||||
occupiedRails.put(train, train.getOccupiedRails());
|
||||
}
|
||||
}
|
||||
// perform step
|
||||
Map<Train, Set<Rail>> nextOccupiedRails = new HashMap<>();
|
||||
trains.values().stream().filter(Train::isPlaced).forEach(train -> {
|
||||
Vector2D front = train.getFrontPosition();
|
||||
Vector2D position = train.getRearPosition();
|
||||
Vector2D direction = train.getRearDirection();
|
||||
Vector2D nextPosition = railNetwork.move(position, direction);
|
||||
if (nextPosition == null
|
||||
|| train.isOnPosition(nextPosition) && !train.getRearPosition().equals(train.getFrontPosition())) {
|
||||
collisions.add(new HashSet<>(Arrays.asList(train)));
|
||||
train.removeFromRails();
|
||||
nextOccupiedRails.put(train, occupiedRails.get(train));
|
||||
} else {
|
||||
train.moveBackTo(railNetwork, nextPosition);
|
||||
train.setDirection(front.subtract(train.getFrontPosition()));
|
||||
nextOccupiedRails.put(train, train.getOccupiedRails());
|
||||
}
|
||||
});
|
||||
checkForBlockCollisions(collisions, occupiedRails, nextOccupiedRails);
|
||||
return collisions;
|
||||
}
|
||||
|
||||
private void checkForBlockCollisions(List<Set<Train>> collisions, Map<Train, Set<Rail>> occupiedRails,
|
||||
Map<Train, Set<Rail>> nextOccupiedRails) {
|
||||
trains.values().stream().filter(train -> train.isPlaced()
|
||||
&& collisions.stream().noneMatch(x -> x.contains(train))).forEach(train -> {
|
||||
Set<Rail> occupiedByThisTrain = nextOccupiedRails.get(train);
|
||||
trains.values().stream().filter(x -> x != train).forEach(otherTrain -> {
|
||||
Set<Rail> occupiedByOtherTrainPreviously = occupiedRails.get(otherTrain);
|
||||
Set<Rail> occupiedByOtherTrain = nextOccupiedRails.get(otherTrain);
|
||||
if (occupiedByOtherTrain == null) {
|
||||
return;
|
||||
}
|
||||
boolean anyIntersection = Stream.concat(
|
||||
occupiedByOtherTrain.stream(), occupiedByOtherTrainPreviously.stream())
|
||||
.anyMatch(occupiedByThisTrain::contains);
|
||||
if (anyIntersection || train.touches(otherTrain)) {
|
||||
train.removeFromRails();
|
||||
otherTrain.removeFromRails();
|
||||
// try to find/merge existing collisions
|
||||
Set<Train> existingCollision = null;
|
||||
for (Set<Train> collision : collisions) {
|
||||
if (collision.contains(otherTrain) || collision.contains(train)) {
|
||||
if (existingCollision == null) {
|
||||
existingCollision = collision;
|
||||
collision.add(train);
|
||||
collision.add(otherTrain);
|
||||
} else {
|
||||
existingCollision.addAll(collision);
|
||||
existingCollision.add(train);
|
||||
existingCollision.add(otherTrain);
|
||||
collisions.remove(collision);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (existingCollision == null) {
|
||||
collisions.add(Stream.of(train, otherTrain).collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the trains in this simulation, printing their new positions and any collisions.
|
||||
* @param speed amount of steps to move the trains
|
||||
*/
|
||||
public void step(short speed) {
|
||||
if (!railNetwork.isReadyForTrains()) {
|
||||
Terminal.printError("rail tracks/switches not set up");
|
||||
return;
|
||||
}
|
||||
if (trains.values().stream().noneMatch(Train::isPlaced)) {
|
||||
Terminal.printLine("OK");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Set<Train>> collisions = IntStream.range(0, Math.abs(speed))
|
||||
.mapToObj(step -> speed >= 0 ? getCollisionsOfOneStep() : getCollisionsOfOneReverseStep())
|
||||
.flatMap(List::stream).collect(Collectors.toList());
|
||||
|
||||
for (int id : trains.keySet().stream().sorted().collect(Collectors.toList())) {
|
||||
Train train = trains.get(id);
|
||||
Set<Train> collisionSet = collisions.stream()
|
||||
.filter(collision -> collision.contains(train))
|
||||
.findFirst().orElse(null);
|
||||
if (collisionSet != null) { // print collision
|
||||
int first = collisionSet.stream().min(Comparator.comparing(Train::getIdentifier)).get().getIdentifier();
|
||||
if (train.getIdentifier() == first) { // only print each collision once
|
||||
List<Train> 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user