Correctly handle a few edge cases in put train

This commit is contained in:
Arne Keller 2020-03-04 11:35:32 +01:00
parent 1653db3c3b
commit afb5e62d24
10 changed files with 269 additions and 70 deletions

View File

@ -1,6 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="FindBugsConfigurable">
<option name="make" value="true" />
<option name="effort" value="default" />
<option name="priority" value="Medium" />
<option name="excludeFilter" value="" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="SuppressionsComponent">
<option name="suppComments" value="[]" />
</component>
</project>

View File

@ -98,6 +98,16 @@ class MainTest {
cmpInOut("train_one_length_input.txt", "train_one_length_output.txt");
}
@Test
void switchInconsistency() throws IOException {
cmpInOut("switch_inconsistency_input.txt", "switch_inconsistency_output.txt");
}
@Test
void largeGenericTest() throws IOException {
cmpInOut("lgt_input.txt", "lgt_output.txt");
}
private void cmpInOut(String in, String out) throws IOException {
System.setIn(new ByteArrayInputStream(readFile(in)));
ByteArrayOutputStream output = new ByteArrayOutputStream();

View File

@ -97,7 +97,11 @@ public final class Terminal {
*/
public static String readLine() {
try {
return IN.readLine();
String line = IN.readLine();
if ("breakpoint".equals(line)) {
return readLine();
}
return line;
} catch (final IOException e) {
/*
* The IOException will not occur during tests executed by the praktomat, therefore the

View File

@ -65,10 +65,33 @@ public abstract class Rail {
*/
public abstract boolean contains(Vector2D position);
/**
* @param position the point to check
* @return whether the point can be inside this rail (not on the edge)
*/
public abstract boolean canContain(Vector2D position);
/**
* Get the direction trains can move on this rail starting at the specified position
* @param position the position to check
* @return the direction trains can move starting at that position
*/
public abstract Vector2D getDirectionFrom(Vector2D position);
/**
* Check whether this rail extends in the specified direction.
* @param position start position
* @param direction the (normalized) direction to check
* @return whether this rail can extend from that position in that direction
*/
public abstract boolean allowsDirectionFrom(Vector2D position, Vector2D direction);
/**
*
* @param position
* @param direction
* @param steps
* @return
*/
public abstract Vector2D move(Vector2D position, Vector2D direction, long steps);
}

View File

@ -1,5 +1,6 @@
package edu.kit.informatik.model;
import edu.kit.informatik.Terminal;
import edu.kit.informatik.ui.InvalidInputException;
import java.util.Collection;
@ -201,24 +202,57 @@ public class RailwayNetwork {
* @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) {
public Vector2D move(Vector2D position, Vector2D direction) {
Rail containingRail = findContainingRail(position);
if (containingRail != null) {
return position.add(direction);
}
Rail[] touchingRails = findTouchingRails(position);
Vector2D nextPossiblePosition = position.add(direction);
Rail possibleContainingRail = findRailPotentiallyContaining(nextPossiblePosition);
if (possibleContainingRail != null && !possibleContainingRail.contains(position.subtract(direction))) {
Vector2D positionOnThatRail = possibleContainingRail.move(position, direction, 1);
/*
if ((positionOnThatRail == null || positionOnThatRail.equals(position)) && touchingRails.length == 2) {
Vector2D onRailOne = touchingRails[0].move(position, new Vector2D(direction), 1);
Vector2D onRailTwo = touchingRails[1].move(position, new Vector2D(direction), 1);
if ((position.equals(onRailOne) || onRailOne == null) && !position.equals(onRailTwo)) {
// we are moving on rail two
Vector2D newDirection = touchingRails[1].getDirectionFrom(position);
direction.copyFrom(newDirection);
return onRailTwo;
} else if ((position.equals(onRailTwo) || onRailTwo == null) && !position.equals(onRailOne)) {
Vector2D newDirection = touchingRails[0].getDirectionFrom(position);
direction.copyFrom(newDirection);
return onRailOne;
} else {
throw new RuntimeException("wtf");
}
}
*/
return positionOnThatRail;
}
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) && !touchingRails[0].connectsTo(nextPosition)) {
return null;
return touchingRails[0].move(position, direction, 1);
}
Vector2D onRailOne = touchingRails[0].move(position, new Vector2D(direction), 1);
Vector2D onRailTwo = touchingRails[1].move(position, new Vector2D(direction), 1);
if (position.equals(onRailOne) || onRailOne == null) {
// we are moving on rail two
Vector2D newDirection = touchingRails[1].getDirectionFrom(position);
direction.copyFrom(newDirection);
return onRailTwo;
} else if (position.equals(onRailTwo) || onRailTwo == null) {
Vector2D newDirection = touchingRails[0].getDirectionFrom(position);
direction.copyFrom(newDirection);
return onRailOne;
} else {
return nextPosition;
}
throw new RuntimeException("wtf");
}
/*
Vector2D nextPosition = position.add(direction);
if (touchingRails[0].contains(nextPosition) || touchingRails[0].connectsTo(nextPosition)) {
return nextPosition;
@ -239,6 +273,7 @@ public class RailwayNetwork {
} else {
return null;
}
*/
}
/**
@ -250,6 +285,10 @@ public class RailwayNetwork {
return rails.values().stream().filter(rail -> rail.contains(position)).findFirst().orElse(null);
}
private Rail findRailPotentiallyContaining(Vector2D position) {
return rails.values().stream().filter(rail -> rail.canContain(position)).findFirst().orElse(null);
}
/**
* Find all rails that *touch* (not contain) this position.
* @param position the position to check

View File

@ -2,6 +2,7 @@ package edu.kit.informatik.model;
import edu.kit.informatik.ui.InvalidInputException;
import java.util.Arrays;
import java.util.Objects;
/**
@ -12,21 +13,17 @@ import java.util.Objects;
*/
public final class Switch extends Rail {
/**
* Start position.
* First leg of the switch.
*/
private final Vector2D start;
private final Track positionOne;
/**
* End position one, has to be in a straight line from start.
* Second leg of the switch.
*/
private final Vector2D end1;
/**
* End position two, has to be in a straight line from start.
*/
private final Vector2D end2;
private final Track positionTwo;
/**
* Currently selected position (either one or two).
*/
private Vector2D selection;
private Track selection;
/**
* Construct a new switch.
@ -44,30 +41,33 @@ public final class Switch extends Rail {
throw new InvalidInputException("start has to be connected in straight lines to end positions!");
}
// make sure caller can not modify internal state after calling
this.start = new Vector2D(start);
this.end1 = new Vector2D(end1);
this.end2 = new Vector2D(end2);
this.positionOne = new Track(start, end1, -1);
this.positionTwo = new Track(start, end2, -1);
}
@Override
public boolean canConnectTo(Vector2D point) {
return point.equals(start) || point.equals(end1) || point.equals(end2);
return positionOne.canConnectTo(point) || positionTwo.canConnectTo(point);
}
@Override
public boolean connectsTo(Vector2D point) {
return point.equals(start) || point.equals(selection);
return (positionOne.canConnectTo(point) && positionTwo.canConnectTo(point))
|| (selection != null && selection.canConnectTo(point));
}
@Override
public boolean canConnectToRail(Rail rail) {
return rail.canConnectTo(start) || rail.canConnectTo(end1) || rail.canConnectTo(end2);
return rail.canConnectToRail(positionOne) || rail.canConnectToRail(positionTwo);
}
@Override
public boolean switchTo(Vector2D position) {
if (position.equals(end1) || position.equals(end2)) {
selection = new Vector2D(position.getX(), position.getY());
if (positionOne.canConnectTo(position)) {
selection = positionOne;
return true;
} else if (positionTwo.canConnectTo(position)) {
selection = positionTwo;
return true;
}
return false;
@ -79,46 +79,65 @@ public final class Switch extends Rail {
}
@Override
public boolean contains(final Vector2D position) {
public boolean contains(Vector2D position) {
if (selection == null) {
return false;
}
if (start.getX() == selection.getX() && position.getX() == start.getX()) {
if (start.getY() < selection.getY()) {
return start.getY() < position.getY() && position.getY() < selection.getY();
return selection.contains(position);
}
@Override
public boolean canContain(Vector2D position) {
return positionOne.contains(position) || positionTwo.contains(position);
}
@Override
public Vector2D move(Vector2D position, Vector2D direction, long steps) {
if (contains(position) || connectsTo(position)) {
return selection.move(position, direction, steps);
} else {
return start.getY() > position.getY() && position.getY() > selection.getY();
return null;
}
} else if (start.getY() == selection.getY() && position.getY() == start.getY()) {
if (start.getX() < selection.getX()) {
return start.getX() < position.getX() && position.getX() < selection.getX();
} else {
return start.getX() > position.getX() && position.getX() > selection.getX();
}
}
return false;
}
@Override
public Vector2D getDirectionFrom(Vector2D position) {
if (selection == null) {
return null;
} else if (start.equals(position)) {
return new Vector2D(selection.getX() - start.getX(), selection.getY() - start.getY()).normalized();
} else if (selection.equals(position)) {
return new Vector2D(start.getX() - selection.getX(), start.getY() - selection.getY()).normalized();
} else {
return null;
}
return selection.getDirectionFrom(position);
}
private Vector2D[] getPossibleDirectionsFrom(Vector2D position) {
if (positionOne.canConnectTo(position) && positionTwo.canConnectTo(position)) {
return new Vector2D[] {
positionOne.getDirectionFrom(position), positionOne.getDirectionFrom(position).negated(),
positionTwo.getDirectionFrom(position), positionTwo.getDirectionFrom(position).negated()};
} else if (positionOne.canConnectTo(position)) {
return new Vector2D[] {positionOne.getDirectionFrom(position)};
} else if (positionTwo.canConnectTo(position)) {
return new Vector2D[] {positionTwo.getDirectionFrom(position)};
} else {
return new Vector2D[0];
}
}
@Override
public boolean allowsDirectionFrom(Vector2D position, Vector2D direction) {
return (selection != null && selection.contains(position))
|| (positionOne.connectsTo(position) && positionOne.allowsDirectionFrom(position, direction))
|| (positionTwo.connectsTo(position) && positionTwo.allowsDirectionFrom(position, direction));
}
@Override
public String toString() {
if (selection == null) {
return String.format("s %d %s -> %s,%s", getIdentifier(), start, end1, end2);
return String.format("s %d %s -> %s,%s", getIdentifier(), positionOne.getStart(),
positionOne.getEnd(), positionTwo.getEnd());
} else {
return String.format("s %d %s -> %s,%s %d", getIdentifier(),
start, end1, end2, start.distanceTo(selection));
positionOne.getStart(), positionOne.getEnd(), positionTwo.getEnd(),
selection.getLength());
}
}
@ -127,7 +146,8 @@ public final class Switch extends Rail {
if (obj != null && getClass().equals(obj.getClass())) {
final Switch otherSwitch = (Switch) obj;
return start.equals(otherSwitch.start) && end1.equals(otherSwitch.end1) && end2.equals(otherSwitch.end2);
return positionOne.equals(otherSwitch.positionOne) && positionTwo.equals(otherSwitch.positionTwo)
&& Objects.equals(selection, otherSwitch.selection);
}
return false;
@ -135,6 +155,6 @@ public final class Switch extends Rail {
@Override
public int hashCode() {
return Objects.hash(start, end1, end2);
return Objects.hash(positionOne, positionTwo, selection);
}
}

View File

@ -72,6 +72,30 @@ public final class Track extends Rail {
return false;
}
@Override
public boolean canContain(Vector2D position) {
return contains(position);
}
@Override
public Vector2D move(Vector2D position, Vector2D direction, long steps) {
Vector2D nextPosition = position.add(direction.scale(steps));
if (contains(nextPosition) || connectsTo(nextPosition)) {
return nextPosition;
} else if (direction.equals(getDirectionFrom(start))) {
return new Vector2D(end);
} else if (direction.equals(getDirectionFrom(end))) {
return new Vector2D(start);
} else if (position.equals(end)) {
direction.copyFrom(getDirectionFrom(end));
return move(position, getDirectionFrom(end), steps);
} else if (position.equals(start)) {
direction.copyFrom(getDirectionFrom(start));
return move(position, getDirectionFrom(start), steps);
}
return null;
}
@Override
public Vector2D getDirectionFrom(Vector2D position) {
// have to use long arithmetic here to avoid overflows
@ -82,13 +106,37 @@ public final class Track extends Rail {
return new Vector2D(Long.signum((long) start.getX() - (long) end.getX()),
Long.signum((long) start.getY() - (long) end.getY()));
} else {
throw new IllegalArgumentException("can only compute direction from track ends");
// in the middle of track, simply return direction from start
return getDirectionFrom(start);
}
}
@Override
public boolean allowsDirectionFrom(Vector2D position, Vector2D direction) {
if (contains(position)) {
return true;
} else if (connectsTo(position)) {
return getDirectionFrom(position).equals(direction)
|| getDirectionFrom(position).negated().equals(direction);
}
return false;
}
public Vector2D getStart() {
return new Vector2D(start);
}
public Vector2D getEnd() {
return new Vector2D(end);
}
public long getLength() {
return start.distanceTo(end);
}
@Override
public String toString() {
return String.format("t %d %s -> %s %d", getIdentifier(), start, end, start.distanceTo(end));
return String.format("t %d %s -> %s %d", getIdentifier(), start, end, getLength());
}
@Override

View File

@ -142,9 +142,10 @@ public final class Train {
* @return whether the train was successfully placed
* @throws InvalidInputException when the train is too long to place
*/
public boolean placeOn(final RailwayNetwork railNetwork, final Vector2D position, final Vector2D rawDirection)
public boolean placeOn(RailwayNetwork railNetwork, Vector2D position, Vector2D rawDirection)
throws InvalidInputException {
Vector2D positioningDirection = rawDirection.normalized().negated();
Vector2D direction = rawDirection.normalized();
Vector2D positioningDirection = direction.negated();
long length = getLength();
if (length > Integer.MAX_VALUE) {
// TODO: implement this case
@ -157,10 +158,26 @@ public final class Train {
if (length > 10000) {
return false; // TODO: remove!
}
// TODO check that the requested direction is equal to the direction of one the tracks
// check that the requested direction is equal to the direction of one the tracks
if ((railNetwork.findContainingRail(position) != null
&& !railNetwork.findContainingRail(position).allowsDirectionFrom(position, positioningDirection))
|| (railNetwork.findTouchingRails(position).length > 0
&& Arrays.stream(railNetwork.findTouchingRails(position))
.noneMatch(rail -> rail.allowsDirectionFrom(position, direction)))) {
return false;
}
if (railNetwork.findTouchingRails(position).length > 0
&& (Arrays.stream(railNetwork.findTouchingRails(position))
.noneMatch(rail -> rail.connectsTo(position)))) {
return false;
}
// consider fuzz 9 (0,-1)
for (int i = 1; i <= length; i++) {
rollingStockPosition = railNetwork.move(rollingStockPosition, positioningDirection);
if (positions.getLast().distanceTo(rollingStockPosition) == 0) {
// stuck
return false;
}
if (rollingStockPosition == null
|| positions.contains(rollingStockPosition) && !positions.get(0).equals(rollingStockPosition)) {
return false;

View File

@ -274,9 +274,9 @@ public final class TrainManager {
Vector2D position = train.getFrontPosition();
Vector2D direction = train.getDirection();
Vector2D nextPosition = railNetwork.move(position, direction);
if (nextPosition == null
if (nextPosition == null || nextPosition.equals(position)
|| train.isOnPosition(nextPosition) && !train.getRearPosition().equals(train.getFrontPosition())) {
train.moveTo(railNetwork, nextPosition);
train.moveTo(railNetwork, null);
collisions.add(new HashSet<>(Arrays.asList(train)));
} else {
train.moveTo(railNetwork, nextPosition);

View File

@ -12,18 +12,18 @@ public class Vector2D {
/**
* First coordinate of the vector, usually referred to as 'x'.
*/
private int x;
private long x;
/**
* Second coordinate of the vector, usally referred to as 'y'.
* Second coordinate of the vector, usually referred to as 'y'.
*/
private int y;
private long y;
/**
* Construct a new vector.
* @param x first component
* @param y second component
*/
public Vector2D(final int x, final int y) {
public Vector2D(long x, long y) {
this.x = x;
this.y = y;
}
@ -32,13 +32,13 @@ public class Vector2D {
* Copy a vector, creating a new object.
* @param other a vector
*/
public Vector2D(final Vector2D other) {
public Vector2D(Vector2D other) {
this.x = other.x;
this.y = other.y;
}
/**
* @param input two numbers separated by a comma
* @param input two 32-bit numbers separated by a comma
* @return a vector containing the two numbers, null otherwise
*/
public static Vector2D parse(final String input) {
@ -58,8 +58,8 @@ public class Vector2D {
* @param other the point to measure distance to
* @return the manhattan distance
*/
public long distanceTo(final Vector2D other) {
return Math.abs((long) this.x - (long) other.x) + Math.abs((long) this.y - (long) other.y);
public long distanceTo(Vector2D other) {
return other != null ? Math.abs(this.x - other.x) + Math.abs(this.y - other.y) : 0;
}
/**
@ -89,7 +89,7 @@ public class Vector2D {
* @param other another vector
* @return the difference of this vector and the other vector
*/
public Vector2D subtract(final Vector2D other) {
public Vector2D subtract(Vector2D other) {
return new Vector2D(x - other.x, y - other.y);
}
@ -97,21 +97,41 @@ public class Vector2D {
* @param movement vector to add
* @return component-wise sum of this vector and the other vector
*/
public Vector2D add(final Vector2D movement) {
public Vector2D add(Vector2D movement) {
return new Vector2D(x + movement.x, y + movement.y);
}
public Vector2D scale(long multiplier) {
return new Vector2D(x * multiplier, y * multiplier);
}
/**
* @return the first component of this vector
* @return the first component of this vector, casted to an int
*/
public int getX() {
return (int) x;
}
/**
*
* @return
*/
public long getLongX() {
return x;
}
/**
* @return the second component of this vector
* @return the second component of this vector, casted to an int
*/
public int getY() {
return (int) y;
}
/**
*
* @return
*/
public long getLongY() {
return y;
}
@ -131,6 +151,15 @@ public class Vector2D {
this.y = y;
}
/**
*
* @param other
*/
public void copyFrom(Vector2D other) {
this.x = other.x;
this.y = other.y;
}
@Override
public boolean equals(final Object obj) {
if (obj != null && getClass().equals(obj.getClass())) {