From 4ffb3a56651a7834bd1753af461a20b9161d8f39 Mon Sep 17 00:00:00 2001 From: Arne Keller Date: Sun, 16 Feb 2020 16:38:25 +0100 Subject: [PATCH] Refactor rail network in own class --- src/edu/kit/informatik/Main.java | 2 + .../model/ModelRailwaySimulation.java | 335 ++++-------------- .../kit/informatik/model/RailwayNetwork.java | 281 +++++++++++++++ src/edu/kit/informatik/model/Train.java | 206 ++++++++++- 4 files changed, 552 insertions(+), 272 deletions(-) create mode 100644 src/edu/kit/informatik/model/RailwayNetwork.java diff --git a/src/edu/kit/informatik/Main.java b/src/edu/kit/informatik/Main.java index b7ed84a..d2f2098 100644 --- a/src/edu/kit/informatik/Main.java +++ b/src/edu/kit/informatik/Main.java @@ -1,5 +1,7 @@ package edu.kit.informatik; +import edu.kit.informatik.ui.CommandLine; + public class Main { public static void main(String[] args) { CommandLine.startInteractive(); diff --git a/src/edu/kit/informatik/model/ModelRailwaySimulation.java b/src/edu/kit/informatik/model/ModelRailwaySimulation.java index ced6dd0..0a3b3b8 100644 --- a/src/edu/kit/informatik/model/ModelRailwaySimulation.java +++ b/src/edu/kit/informatik/model/ModelRailwaySimulation.java @@ -7,11 +7,11 @@ import java.util.*; import java.util.stream.Collectors; public class ModelRailwaySimulation { - private List rails = new ArrayList<>(); - private List engines = new ArrayList<>(); - private List trainSets = new ArrayList<>(); - private List coaches = new ArrayList<>(); - private List trains = new ArrayList<>(); + private final RailwayNetwork railNetwork = new RailwayNetwork(); + private final List engines = new ArrayList<>(); + private final List trainSets = new ArrayList<>(); + private final List coaches = new ArrayList<>(); + private final List trains = new ArrayList<>(); /** * Add a new track to the rail network of this simulation. @@ -20,32 +20,7 @@ public class ModelRailwaySimulation { * @return the positive identifier of the new track if successful, -1 otherwise */ public int addTrack(final Vector2D start, final Vector2D end) { - if (start.distanceTo(end) == 0) { - return -1; - } - long startPossibleConnections = rails.stream().filter((rail) -> rail.canConnectTo(start)).count(); - long endPossibleConnections = rails.stream().filter((rail) -> rail.canConnectTo(end)).count(); - if (startPossibleConnections == 2 || endPossibleConnections == 2) { - return -1; // TODO better error msg? - } - if (rails.isEmpty()) { - Track newTrack = new Track(start, end, 1); - rails.add(newTrack); - return newTrack.id; - } else { - for (Rail rail : rails) { - if (rail.canConnectTo(start) || rail.canConnectTo(end)) { - int id = getNextRailIdentifier(); - if (id < 0) { - return -1; - } - Track newTrack = new Track(start, end, id); - rails.add(newTrack.id - 1, newTrack); - return newTrack.id; - } - } - return -1; - } + return railNetwork.addTrack(start, end); } /** @@ -56,118 +31,28 @@ public class ModelRailwaySimulation { * @return the positive identifier of the switch if successful, -1 otherwise */ public int addSwitch(final Vector2D start, final Vector2D end1, final Vector2D end2) { - if (start.distanceTo(end1) == 0 || start.distanceTo(end2) == 0 || end1.distanceTo(end2) == 0) { - return -1; - } - if (rails.isEmpty()) { - Switch newSwitch = new Switch(start, end1, end2, 1); - rails.add(newSwitch); - return newSwitch.id; - } else { - long startPossibleConnections = rails.stream().filter((rail) -> rail.canConnectTo(start)).count(); - long end1PossibleConnections = rails.stream().filter((rail) -> rail.canConnectTo(end1)).count(); - long end2PossibleConnections = rails.stream().filter((rail) -> rail.canConnectTo(end2)).count(); - if (startPossibleConnections == 2 || end1PossibleConnections == 2 || end2PossibleConnections == 2) { - return -1; // TODO better error msg? - } - for (Rail rail : rails) { - if (rail.canConnectTo(start) || rail.canConnectTo(end1) || rail.canConnectTo(end2)) { - int id = getNextRailIdentifier(); - if (id < 0) { - return -1; - } - Switch newSwitch = new Switch(start, end1, end2, id); - rails.add(newSwitch.id - 1, newSwitch); - return newSwitch.id; - } - } - return -1; - } - } - - /** - * positive if success, negative if fail - * @return - */ - private int getNextRailIdentifier() { - int id = 1; - for (Rail rail : rails) { - if (rail.id == id) { - id++; - } - } - return id; + return railNetwork.addSwitch(start, end1, end2); } /** * Remove the specified rail from the rail network. * @param id identifier of the rail to remove - * @return true if rail could be successfully removed, false otherwise + * @return whether the rail could be successfully removed */ public boolean removeRail(final int id) { - if (rails.size() == 0) { + // check whether any trains are on this rail + if (trains.stream().anyMatch(train -> train.isOnRail(id))) { return false; - } else if (rails.size() == 1) { - if (rails.get(0).id == id) { - rails.clear(); - return true; - } else { - return false; - } } - for (int i = 0; i < rails.size(); i++) { - Rail rail = rails.get(i); - if (rail.getIdentifier() == id) { - if (trains.stream().anyMatch((train) -> (train.isPlaced() && train.isPartiallyOn(rail)))) { - return false; - } - Set connectedRails = new HashSet<>(); - - Rail otherRail = null; - for (int j = 0; j < rails.size(); j++) { - Rail anotherRail = rails.get(j); - if (anotherRail.getIdentifier() != id && rail.canConnectToRail(anotherRail)) { - otherRail = anotherRail; - break; - } - } - - connectedRails.add(otherRail); - Queue toProcess = new LinkedList<>(); - toProcess.add(otherRail); - - while (!toProcess.isEmpty()) { - Rail connected = toProcess.poll(); - - for (Rail other : rails) { - if (other != rail && !connectedRails.contains(other) && other.canConnectToRail(connected)) { - connectedRails.add(other); - toProcess.offer(other); - } - } - } - - if (connectedRails.size() == rails.size() - 1) { - rails.remove(i); - return true; - } else { - return false; - } - } else if (rail.getIdentifier() > id) { - return false; - } - } - return false; + return railNetwork.removeRail(id); } + /** + * Print a list of all rails in the network, specifying their identifier, start and end points, their length and + * their current configuration. + */ public void printTracks() { - if (rails.isEmpty()) { - Terminal.printLine("No track exists"); - } else { - for (Rail rail : rails) { - Terminal.printLine(rail.toString()); - } - } + railNetwork.printTracks(); } /** @@ -176,14 +61,9 @@ public class ModelRailwaySimulation { * @param position position to set the switch to * @return whether the switch could be set */ - public boolean setSwitch(int id, Vector2D position) { + public boolean setSwitch(final int id, final Vector2D position) { // TODO Falls Gleiseweichen auf denen ein Zug bereitssteht, geschaltet werden, entgleist der daraufstehende Zug - for (Rail rail : rails) { - if (rail.getIdentifier() == id) { - return rail.switchTo(position); - } - } - return false; + return railNetwork.setSwitch(id, position); } /** @@ -418,6 +298,10 @@ public class ModelRailwaySimulation { } } + /** + * Display a train as ASCII art. + * @param id identifier of the train to show + */ public void printTrain(int id) { Train train = getTrain(id); if (train != null) { @@ -431,86 +315,69 @@ public class ModelRailwaySimulation { * Put a train on the rail network. * @param trainId identifier of the train to place * @param position where to place the train - * @param rawDirection direction in which the train should initially go + * @param direction direction in which the train should initially go * @return whether the train was successfully placed */ - public boolean putTrain(final int trainId, final Vector2D position, final Vector2D rawDirection) { - final Vector2D direction = rawDirection.normalized(); + public boolean putTrain(final int trainId, final Vector2D position, final Vector2D direction) { Train train = getTrain(trainId); - if (train == null) { + if (train == null || !train.isProperTrain()) { return false; } - if (!train.isProperTrain()) { - return false; - } - int length = train.getLength(); - List positions = new ArrayList<>(); - positions.add(position); - Vector2D positioningDirection = direction.negated(); - Vector2D rollingStockPosition = position; - if (length > 10000) { - return false; // TODO: remove! - } - for (int i = 1; i <= length; i++) { - rollingStockPosition = move(rollingStockPosition, positioningDirection); - if (rollingStockPosition == null - || (positions.contains(rollingStockPosition) && !positions.get(0).equals(rollingStockPosition))) { + boolean placed = train.placeOn(railNetwork, position, direction); + + if (placed) { + List> collisions = getStaticCollisions(false); + if (!collisions.isEmpty()) { + train.storePositionDirectionBlocks(null, null, null); return false; + } else { + return true; } - positions.add(rollingStockPosition); - } - train.storePositionAndDirection(positions, direction); - - List> collisions = getStaticCollisions(false); - if (!collisions.isEmpty()) { - train.storePositionAndDirection(null, null); + } else { return false; } - - return true; } - private ArrayList> getStaticCollisions(boolean removeTrains) { - ArrayList> collisions = new ArrayList<>(); - for (Train train1 : trains) { - if (!train1.isPlaced()) { + private ArrayList> getStaticCollisions(boolean removeTrains) { + if (trains.isEmpty()) { + return new ArrayList<>(); + } + ArrayList> collisions = new ArrayList<>(); + int maxId = trains.get(trains.size() - 1).getIdentifier(); + train: for (int id1 = 1; id1 <= maxId; id1++) { + Train train1 = getTrain(id1); + if (train1 == null || !train1.isPlaced()) { continue; } - ArrayList collision = new ArrayList<>(); - for (Train train2 : trains) { - if (!train2.isPlaced()) { + HashSet collision = new HashSet<>(); + for (int id2 = id1 + 1; id2 <= maxId; id2++) { + Train train2 = getTrain(id2); + if (train2 == null || !train2.isPlaced()) { continue; } - if (train2.touches(train1)) { + + if (train1.touches(train2)) { collision.add(train2); } } - if (collision.size() > 1) { - if (removeTrains) { - for (Train crashed : collision) { - crashed.removeFromRails(); + if (collision.size() >= 1) { + // check for existing collision + for (HashSet otherCollision : collisions) { + if (otherCollision.stream().anyMatch(collision::contains)) { + otherCollision.addAll(collision); + continue train; } } + // else, create a new collision + collision.add(train1); collisions.add(collision); } } - for (Rail rail : rails) { - ArrayList containedTrains = new ArrayList<>(); - for (Train train : trains) { - if (train.isPlaced() && train.isPartiallyOn(rail)) { - containedTrains.add(train); - } - } - if (containedTrains.size() >= 2) { - containedTrains.sort(Comparator.comparingInt(Train::getIdentifier)); - if (!collisions.contains(containedTrains)) { - if (removeTrains) { - for (Train crashed : containedTrains) { - crashed.removeFromRails(); - } - } - collisions.add(containedTrains); + if (removeTrains) { + for (HashSet collision : collisions) { + for (Train t : collision) { + t.removeFromRails(); } } } @@ -522,8 +389,7 @@ public class ModelRailwaySimulation { Map> occupiedRails = new HashMap<>(); for (Train train : trains) { if (train.isPlaced()) { - Set occupied = rails.stream().filter(train::isPartiallyOn).collect(Collectors.toSet()); - occupiedRails.put(train, occupied); + occupiedRails.put(train, train.getOccupiedRails()); } } // perform step @@ -534,7 +400,7 @@ public class ModelRailwaySimulation { } Vector2D position = train.getFrontPosition(); Vector2D direction = train.getDirection(); - Vector2D nextPosition = move(position, direction); + Vector2D nextPosition = railNetwork.move(position, direction); if (nextPosition == null || (train.isOnPosition(nextPosition) && !train.getBackPosition().equals(train.getFrontPosition()))) { @@ -543,9 +409,9 @@ public class ModelRailwaySimulation { nextOccupiedRails.put(train, occupiedRails.get(train)); continue; } - train.moveTo(nextPosition); + train.moveTo(railNetwork, nextPosition); train.setDirection(direction); - nextOccupiedRails.put(train, rails.stream().filter(train::isPartiallyOn).collect(Collectors.toSet())); + nextOccupiedRails.put(train, train.getOccupiedRails()); } // check for block collisions for (Train train : trains) { @@ -589,8 +455,7 @@ public class ModelRailwaySimulation { Map> occupiedRails = new HashMap<>(); for (Train train : trains) { if (train.isPlaced()) { - Set occupied = rails.stream().filter(train::isPartiallyOn).collect(Collectors.toSet()); - occupiedRails.put(train, occupied); + occupiedRails.put(train, train.getOccupiedRails()); } } // perform step @@ -602,7 +467,7 @@ public class ModelRailwaySimulation { Vector2D front = train.getFrontPosition(); Vector2D position = train.getBackPosition(); Vector2D direction = train.getBackDirection(); - Vector2D nextPosition = move(position, direction); + Vector2D nextPosition = railNetwork.move(position, direction); if (nextPosition == null || (train.isOnPosition(nextPosition) && !train.getFrontPosition().equals(nextPosition))) { collisions.add(new HashSet<>(Arrays.asList(train))); @@ -610,9 +475,9 @@ public class ModelRailwaySimulation { nextOccupiedRails.put(train, occupiedRails.get(train)); continue; } - train.moveBackTo(nextPosition); + train.moveBackTo(railNetwork, nextPosition); train.setDirection(front.subtract(train.getFrontPosition())); - nextOccupiedRails.put(train, rails.stream().filter(train::isPartiallyOn).collect(Collectors.toSet())); + nextOccupiedRails.put(train, train.getOccupiedRails()); } // check for block collisions for (Train train : trains) { @@ -648,68 +513,8 @@ public class ModelRailwaySimulation { return collisions; } - /** - * - * @param position - * @param direction has to be (0,1) (0,-1) (1,0) (-1,0) will be modified if necessary - * @return - */ - private Vector2D move(final Vector2D position, final Vector2D direction) { - Rail containingRail = findContainingRail(position); - if (containingRail != null) { - return position.add(direction); - } - Rail[] touchingRails = findTouchingRails(position); - if (touchingRails.length == 0) { - return null; - } - if (touchingRails.length == 1) { - // at the end of a rail - // either derail or move backwards - Vector2D nextPosition = position.add(direction); - if (!touchingRails[0].contains(nextPosition)) { - return null; - } else { - return nextPosition; - } - } - Vector2D nextPosition = position.add(direction); - if (touchingRails[0].contains(nextPosition) || touchingRails[0].connectsTo(nextPosition)) { - return nextPosition; - } else if (touchingRails[1].contains(nextPosition)) { - Vector2D nextDirection = touchingRails[1].getDirectionFrom(position); - direction.setX(nextDirection.getX()); - direction.setY(nextDirection.getY()); - return nextPosition; - } - Vector2D previousPosition = position.add(direction.negated()); - Rail nextRail = (touchingRails[0].contains(previousPosition) - || touchingRails[0].canConnectTo(previousPosition)) ? touchingRails[1] : touchingRails[0]; - Vector2D nextDirection = nextRail.getDirectionFrom(position); - if (nextDirection != null) { - direction.setX(nextDirection.getX()); - direction.setY(nextDirection.getY()); - return position.add(direction); - } else { - return null; - } - } - - private Rail findContainingRail(final Vector2D position) { - for (Rail rail : rails) { - if (rail.contains(position)) { - return rail; - } - } - return null; - } - - private Rail[] findTouchingRails(final Vector2D position) { - return rails.stream().filter((rail) -> rail.canConnectTo(position)).toArray(Rail[]::new); - } - public void step(final short speed) { - if (!rails.stream().allMatch(Rail::isReadyForTrains)) { + if (!railNetwork.isReadyForTrains()) { Terminal.printError("rail tracks/switches not set up"); return; } diff --git a/src/edu/kit/informatik/model/RailwayNetwork.java b/src/edu/kit/informatik/model/RailwayNetwork.java new file mode 100644 index 0000000..b20b1b2 --- /dev/null +++ b/src/edu/kit/informatik/model/RailwayNetwork.java @@ -0,0 +1,281 @@ +package edu.kit.informatik.model; + +import edu.kit.informatik.Terminal; + +import java.util.*; + +/** + * A rail network consisting of tracks and switches. Is only concerned with rails, does not process trains. + * + * @author Arne Keller + * @version 1.0 + */ +public class RailwayNetwork { + private List rails = new ArrayList<>(); + + /** + * Add a new track to the rail network. + * @param start start position of the new track + * @param end end position of the new track + * @return the positive identifier of the new track if successful, -1 otherwise + */ + public int addTrack(final Vector2D start, final Vector2D end) { + if (start.distanceTo(end) == 0) { + return -1; + } + long startPossibleConnections = rails.stream().filter((rail) -> rail.canConnectTo(start)).count(); + long endPossibleConnections = rails.stream().filter((rail) -> rail.canConnectTo(end)).count(); + if (startPossibleConnections == 2 || endPossibleConnections == 2) { + return -1; // TODO better error msg? + } + if (rails.isEmpty()) { + Track newTrack = new Track(start, end, 1); + rails.add(newTrack); + return newTrack.id; + } else { + for (Rail rail : rails) { + if (rail.canConnectTo(start) || rail.canConnectTo(end)) { + int id = getNextRailIdentifier(); + if (id < 0) { + return -1; + } + Track newTrack = new Track(start, end, id); + rails.add(newTrack.id - 1, newTrack); + return newTrack.id; + } + } + return -1; + } + } + + /** + * Add a switch to the rail network. + * @param start start point of the switch + * @param end1 end position 1 of the switch + * @param end2 end position 2 of the switch + * @return the positive identifier of the switch if successful, -1 otherwise + */ + public int addSwitch(final Vector2D start, final Vector2D end1, final Vector2D end2) { + if (start.distanceTo(end1) == 0 || start.distanceTo(end2) == 0 || end1.distanceTo(end2) == 0) { + return -1; + } + if (rails.isEmpty()) { + Switch newSwitch = new Switch(start, end1, end2, 1); + rails.add(newSwitch); + return newSwitch.id; + } else { + long startPossibleConnections = rails.stream().filter((rail) -> rail.canConnectTo(start)).count(); + long end1PossibleConnections = rails.stream().filter((rail) -> rail.canConnectTo(end1)).count(); + long end2PossibleConnections = rails.stream().filter((rail) -> rail.canConnectTo(end2)).count(); + if (startPossibleConnections == 2 || end1PossibleConnections == 2 || end2PossibleConnections == 2) { + return -1; // TODO better error msg? + } + for (Rail rail : rails) { + if (rail.canConnectTo(start) || rail.canConnectTo(end1) || rail.canConnectTo(end2)) { + int id = getNextRailIdentifier(); + if (id < 0) { + return -1; + } + Switch newSwitch = new Switch(start, end1, end2, id); + rails.add(newSwitch.id - 1, newSwitch); + return newSwitch.id; + } + } + return -1; + } + } + + /** + * Remove the specified rail from the rail network. + * @param id identifier of the rail to remove + * @return whether the rail could be successfully removed + */ + public boolean removeRail(final int id) { + if (rails.size() == 0) { + return false; + } else if (rails.size() == 1) { + if (rails.get(0).id == id) { + rails.clear(); + return true; + } else { + return false; + } + } + Rail toRemove = getRail(id); + if (toRemove == null) { + return false; + } + Set connectedRails = new HashSet<>(); + + // locate one other rail: TODO use rail.connectedrails + Rail otherRail = null; + for (Rail anotherRail : rails) { + if (anotherRail.getIdentifier() != id && toRemove.canConnectToRail(anotherRail)) { + otherRail = anotherRail; + break; + } + } + + if (otherRail == null) { + rails.clear(); + return true; + } + + connectedRails.add(otherRail); + Queue toProcess = new LinkedList<>(); + toProcess.add(otherRail); + + while (!toProcess.isEmpty()) { + Rail connected = toProcess.poll(); + + for (Rail other : rails) { + if (other != toRemove && !connectedRails.contains(other) && other.canConnectToRail(connected)) { + connectedRails.add(other); + toProcess.offer(other); + } + } + } + + if (connectedRails.size() == rails.size() - 1) { + rails.remove(toRemove); + return true; + } else { + return false; + } + } + + /** + * Change the setting of a switch in the rail network. + * @param id identifier of the switch + * @param position position to set the switch to + * @return whether the switch could be set + */ + public boolean setSwitch(final int id, final Vector2D position) { + Rail toSwitch = getRail(id); + if (toSwitch != null) { + return toSwitch.switchTo(position); + } + return false; + } + + /** + * Print a list of all rails in the network, specifying their identifier, start and end points, their length and + * their current configuration. + */ + public void printTracks() { + if (rails.isEmpty()) { + Terminal.printLine("No track exists"); + } else { + for (Rail rail : rails) { + Terminal.printLine(rail.toString()); + } + } + } + + /** + * @return the next positive rail identifier, or -1 if none available + */ + private int getNextRailIdentifier() { + int id = 1; + for (Rail rail : rails) { + if (rail.id == id) { + id++; + } + } + if (id > 0) { + return id; + } else { + return -1; + } + } + + /** + * @param id identifier of the rail to find + * @return the specified rail, or null if not found + */ + private Rail getRail(final int id) { + for (Rail rail : rails) { + if (rail.getIdentifier() == id) { + return rail; + } + } + return null; + } + + /** + * Move a position along the rails of this rail network in the specified direction. + * @param position the position to move + * @param direction has to be (0,1) (0,-1) (1,0) (-1,0), will be modified if turn is necessary + * @return next position, null if off the rails + */ + public Vector2D move(final Vector2D position, final Vector2D direction) { + Rail containingRail = findContainingRail(position); + if (containingRail != null) { + return position.add(direction); + } + Rail[] touchingRails = findTouchingRails(position); + if (touchingRails.length == 0) { + return null; + } else if (touchingRails.length == 1) { + // at the end of a rail + // either derail or move backwards + Vector2D nextPosition = position.add(direction); + if (!touchingRails[0].contains(nextPosition)) { + return null; + } else { + return nextPosition; + } + } + Vector2D nextPosition = position.add(direction); + if (touchingRails[0].contains(nextPosition) || touchingRails[0].connectsTo(nextPosition)) { + return nextPosition; + } else if (touchingRails[1].contains(nextPosition)) { + Vector2D nextDirection = touchingRails[1].getDirectionFrom(position); + direction.setX(nextDirection.getX()); + direction.setY(nextDirection.getY()); + return nextPosition; + } + Vector2D previousPosition = position.add(direction.negated()); + Rail nextRail = (touchingRails[0].contains(previousPosition) + || touchingRails[0].canConnectTo(previousPosition)) ? touchingRails[1] : touchingRails[0]; + Vector2D nextDirection = nextRail.getDirectionFrom(position); + if (nextDirection != null) { + direction.setX(nextDirection.getX()); + direction.setY(nextDirection.getY()); + return position.add(direction); + } else { + return null; + } + } + + /** + * Find a rail that *contains* (not touch) this position. + * @param position the position to check + * @return the rail that contains the position, null if none found + */ + public Rail findContainingRail(final Vector2D position) { + for (Rail rail : rails) { + if (rail.contains(position)) { + return rail; + } + } + return null; + } + + /** + * Find all rails that *touch* (not contain) this position. + * @param position the position to check + * @return the rail(s) that touch this position + */ + public Rail[] findTouchingRails(final Vector2D position) { + return rails.stream().filter((rail) -> rail.canConnectTo(position)).toArray(Rail[]::new); + } + + /** + * Check whether the rail network is ready for trains (all switches set, etc.). + * @return whether the rail network is ready + */ + public boolean isReadyForTrains() { + return rails.stream().allMatch(Rail::isReadyForTrains); + } +} diff --git a/src/edu/kit/informatik/model/Train.java b/src/edu/kit/informatik/model/Train.java index 0eed916..c00d13b 100644 --- a/src/edu/kit/informatik/model/Train.java +++ b/src/edu/kit/informatik/model/Train.java @@ -2,8 +2,7 @@ package edu.kit.informatik.model; import edu.kit.informatik.Terminal; -import java.util.ArrayList; -import java.util.List; +import java.util.*; /** * A train consisting of one or more rolling stock. Can be placed on a rail network. @@ -16,6 +15,7 @@ public final class Train { private final List rollingStocks = new ArrayList<>(); private List position; private Vector2D direction; + private Set occupiedRails; /** * Construct a new train. @@ -27,10 +27,17 @@ public final class Train { this.rollingStocks.add(rollingStock); } + /** + * @return the unique identifier of this train + */ public int getIdentifier() { return identifier; } + /** + * @param rollingStock rolling stock to check + * @return whether the specified rolling stock is in this train + */ public boolean contains(RollingStock rollingStock) { return rollingStocks.contains(rollingStock); } @@ -58,6 +65,9 @@ public final class Train { return stringBuilder.toString(); } + /** + * Print this train as ASCII art. + */ public void print() { List lines = new ArrayList<>(); for (RollingStock rollingStock : rollingStocks) { @@ -91,15 +101,88 @@ public final class Train { } } + /** + * + * @param id + * @return + */ + public boolean isOnRail(final int id) { + return occupiedRails.stream().anyMatch(rail -> rail.getIdentifier() == id); + } + + /** + * Put a train on the rail network. + * @param railNetwork rail network to place the train on + * @param position where to place the train + * @param rawDirection direction in which the train should initially go + * @return whether the train was successfully placed + */ + public boolean placeOn(final RailwayNetwork railNetwork, final Vector2D position, final Vector2D rawDirection) { + Vector2D direction = rawDirection.normalized(); + int length = getLength(); + + List positions = new ArrayList<>(); + positions.add(position); + Vector2D positioningDirection = direction.negated(); + Vector2D rollingStockPosition = position; + if (length > 10000) { + return false; // TODO: remove! + } + for (int i = 1; i <= length; i++) { + rollingStockPosition = railNetwork.move(rollingStockPosition, positioningDirection); + if (rollingStockPosition == null + || (positions.contains(rollingStockPosition) && !positions.get(0).equals(rollingStockPosition))) { + return false; + } + positions.add(rollingStockPosition); + } + Set occupiedRails = new HashSet<>(); + for (int i = 0; i < positions.size(); i++) { + Vector2D point = positions.get(i); + Rail containingRail = railNetwork.findContainingRail(point); + if (containingRail != null) { + occupiedRails.add(containingRail); + } else if (i > 0) { + Rail[] touchingRails = railNetwork.findTouchingRails(point); + for (Rail rail : touchingRails) { + if (positions.stream().anyMatch(rail::connectsTo)) { + // ONLY add this rail if we actually occupy it fully + // note that this edge case only happens with rails of length one: + // otherwise at least one position will be contained in the rail (see above) + occupiedRails.add(rail); + } + } + } + } + storePositionDirectionBlocks(positions, direction, occupiedRails); + + return true; + } + + /** + * @return whether this train is placed on a rail network + */ public boolean isPlaced() { return position != null && direction != null; } - public void storePositionAndDirection(final List positions, final Vector2D direction) { + /** + * + * @param positions + * @param direction + * @param occupiedRails + */ + public void storePositionDirectionBlocks(final List positions, final Vector2D direction, + final Set occupiedRails) { this.position = positions; this.direction = direction; + this.occupiedRails = occupiedRails; } + /** + * + * @return + */ public boolean isProperTrain() { RollingStock first = rollingStocks.get(0); RollingStock last = rollingStocks.get(rollingStocks.size() - 1); @@ -109,11 +192,20 @@ public final class Train { || (last instanceof Engine || last instanceof TrainSet); } + /** + * + * @param rail + * @return + */ public boolean isPartiallyOn(Rail rail) { return position.stream().anyMatch(rail::contains) || position.stream().filter(rail::connectsTo).count() == 2; } + /** + * + * @return + */ public Vector2D getFrontPosition() { if (position != null) { return position.get(0); @@ -122,6 +214,10 @@ public final class Train { } } + /** + * + * @return + */ public Vector2D getBackPosition() { if (position != null) { return position.get(position.size() - 1); @@ -130,10 +226,18 @@ public final class Train { } } + /** + * + * @return + */ public Vector2D getDirection() { return direction; } + /** + * + * @return + */ public Vector2D getBackDirection() { if (position.size() == 1) { return direction.negated(); @@ -142,37 +246,125 @@ public final class Train { } } - public void moveTo(Vector2D nextPosition) { + /** + * + * @return + */ + public Set getOccupiedRails() { + // make sure caller can not modify internal state + return new HashSet<>(occupiedRails); + } + + /** + * + * @param railNetwork + * @param nextPosition + */ + public void moveTo(final RailwayNetwork railNetwork, final Vector2D nextPosition) { position.add(0, nextPosition); - position.remove(position.size() - 1); + Vector2D last = position.remove(position.size() - 1); + updateOccupiedRails(railNetwork, position.get(0), last, position.get(position.size() - 1)); } - public void moveBackTo(Vector2D backPosition) { - position.remove(0); + /** + * + * @param backPosition + */ + public void moveBackTo(final RailwayNetwork railNetwork, final Vector2D backPosition) { + Vector2D last = position.remove(0); position.add(backPosition); + updateOccupiedRails(railNetwork, backPosition, last, position.get(0)); } + private void updateOccupiedRails(final RailwayNetwork railNetwork, final Vector2D nextPosition, + final Vector2D lastPosition, final Vector2D secondToLastPosition) { + if (nextPosition.equals(secondToLastPosition)) { + // we are moving in a loop, no need to update + return; + } + // update occupied blocks, first removing the rail block left behind + Rail leavingRail = railNetwork.findContainingRail(lastPosition); + if (leavingRail != null) { + // check whether we really leave rail + if (!leavingRail.contains(secondToLastPosition)) { + // we are really leaving this rail + occupiedRails.remove(leavingRail); + } // else: we are still on the rail + } else if (getLength() == 1) { + // we evidently just moved into another rail + occupiedRails.clear(); + } // else: only touched rail + // update occupied rails, adding next rail + Rail nextRail = railNetwork.findContainingRail(nextPosition); + if (nextRail != null) { + occupiedRails.add(nextRail); + } else if (getLength() == 1) { + // we evidently just moved into another rail + Rail[] nextTouchingRails = railNetwork.findTouchingRails(nextPosition); + Rail[] alreadyTouchingRails = railNetwork.findTouchingRails(position.get(position.size() - 1)); + if (nextTouchingRails[0] == alreadyTouchingRails[0]) { + occupiedRails.add(nextTouchingRails[0]); + } else if (nextTouchingRails.length == 2) { + if (nextTouchingRails[1] == alreadyTouchingRails[0]) { + occupiedRails.add(nextTouchingRails[1]); + } else if (alreadyTouchingRails.length == 2) { + if (alreadyTouchingRails[1] == nextTouchingRails[0]) { + occupiedRails.add(nextTouchingRails[0]); + } else if (alreadyTouchingRails[1] == nextTouchingRails[1]) { + occupiedRails.add(nextTouchingRails[1]); + } + } + } + } // else: only touching rail + } + + /** + * + * @param newDirection + */ public void setDirection(Vector2D newDirection) { direction = newDirection; } + /** + * + */ public void removeFromRails() { position = null; direction = null; } + /** + * + * @param other + * @return + */ public boolean touches(Train other) { return other.isOnAnyPosition(position); } + /** + * + * @param point + * @return + */ public boolean isOnPosition(Vector2D point) { return position.contains(point); } + /** + * + * @param positions + * @return + */ public boolean isOnAnyPosition(List positions) { return position.stream().anyMatch(positions::contains); } + /** + * + * @return + */ public int getLength() { return rollingStocks.stream().mapToInt((RollingStock::getLength)).sum(); }