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"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <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"> <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" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
<component name="SuppressionsComponent">
<option name="suppComments" value="[]" />
</component>
</project> </project>

View File

@ -98,6 +98,16 @@ class MainTest {
cmpInOut("train_one_length_input.txt", "train_one_length_output.txt"); 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 { private void cmpInOut(String in, String out) throws IOException {
System.setIn(new ByteArrayInputStream(readFile(in))); System.setIn(new ByteArrayInputStream(readFile(in)));
ByteArrayOutputStream output = new ByteArrayOutputStream(); ByteArrayOutputStream output = new ByteArrayOutputStream();

View File

@ -97,7 +97,11 @@ public final class Terminal {
*/ */
public static String readLine() { public static String readLine() {
try { try {
return IN.readLine(); String line = IN.readLine();
if ("breakpoint".equals(line)) {
return readLine();
}
return line;
} catch (final IOException e) { } catch (final IOException e) {
/* /*
* The IOException will not occur during tests executed by the praktomat, therefore the * 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); 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 * Get the direction trains can move on this rail starting at the specified position
* @param position the position to check * @param position the position to check
* @return the direction trains can move starting at that position * @return the direction trains can move starting at that position
*/ */
public abstract Vector2D getDirectionFrom(Vector2D 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; package edu.kit.informatik.model;
import edu.kit.informatik.Terminal;
import edu.kit.informatik.ui.InvalidInputException; import edu.kit.informatik.ui.InvalidInputException;
import java.util.Collection; 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 * @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 * @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); Rail containingRail = findContainingRail(position);
if (containingRail != null) { if (containingRail != null) {
return position.add(direction); return position.add(direction);
} }
Rail[] touchingRails = findTouchingRails(position); 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) { if (touchingRails.length == 0) {
return null; return null;
} else if (touchingRails.length == 1) { } else if (touchingRails.length == 1) {
// at the end of a rail return touchingRails[0].move(position, direction, 1);
// either derail or move backwards
Vector2D nextPosition = position.add(direction);
if (!touchingRails[0].contains(nextPosition) && !touchingRails[0].connectsTo(nextPosition)) {
return null;
} else {
return nextPosition;
}
} }
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 {
throw new RuntimeException("wtf");
}
/*
Vector2D nextPosition = position.add(direction); Vector2D nextPosition = position.add(direction);
if (touchingRails[0].contains(nextPosition) || touchingRails[0].connectsTo(nextPosition)) { if (touchingRails[0].contains(nextPosition) || touchingRails[0].connectsTo(nextPosition)) {
return nextPosition; return nextPosition;
@ -239,6 +273,7 @@ public class RailwayNetwork {
} else { } else {
return null; return null;
} }
*/
} }
/** /**
@ -250,6 +285,10 @@ public class RailwayNetwork {
return rails.values().stream().filter(rail -> rail.contains(position)).findFirst().orElse(null); 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. * Find all rails that *touch* (not contain) this position.
* @param position the position to check * @param position the position to check

View File

@ -2,6 +2,7 @@ package edu.kit.informatik.model;
import edu.kit.informatik.ui.InvalidInputException; import edu.kit.informatik.ui.InvalidInputException;
import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
/** /**
@ -12,21 +13,17 @@ import java.util.Objects;
*/ */
public final class Switch extends Rail { 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; private final Track positionTwo;
/**
* End position two, has to be in a straight line from start.
*/
private final Vector2D end2;
/** /**
* Currently selected position (either one or two). * Currently selected position (either one or two).
*/ */
private Vector2D selection; private Track selection;
/** /**
* Construct a new switch. * 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!"); throw new InvalidInputException("start has to be connected in straight lines to end positions!");
} }
// make sure caller can not modify internal state after calling // make sure caller can not modify internal state after calling
this.start = new Vector2D(start); this.positionOne = new Track(start, end1, -1);
this.end1 = new Vector2D(end1); this.positionTwo = new Track(start, end2, -1);
this.end2 = new Vector2D(end2);
} }
@Override @Override
public boolean canConnectTo(Vector2D point) { public boolean canConnectTo(Vector2D point) {
return point.equals(start) || point.equals(end1) || point.equals(end2); return positionOne.canConnectTo(point) || positionTwo.canConnectTo(point);
} }
@Override @Override
public boolean connectsTo(Vector2D point) { 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 @Override
public boolean canConnectToRail(Rail rail) { public boolean canConnectToRail(Rail rail) {
return rail.canConnectTo(start) || rail.canConnectTo(end1) || rail.canConnectTo(end2); return rail.canConnectToRail(positionOne) || rail.canConnectToRail(positionTwo);
} }
@Override @Override
public boolean switchTo(Vector2D position) { public boolean switchTo(Vector2D position) {
if (position.equals(end1) || position.equals(end2)) { if (positionOne.canConnectTo(position)) {
selection = new Vector2D(position.getX(), position.getY()); selection = positionOne;
return true;
} else if (positionTwo.canConnectTo(position)) {
selection = positionTwo;
return true; return true;
} }
return false; return false;
@ -79,46 +79,65 @@ public final class Switch extends Rail {
} }
@Override @Override
public boolean contains(final Vector2D position) { public boolean contains(Vector2D position) {
if (selection == null) { if (selection == null) {
return false; return false;
} }
if (start.getX() == selection.getX() && position.getX() == start.getX()) { return selection.contains(position);
if (start.getY() < selection.getY()) { }
return start.getY() < position.getY() && position.getY() < selection.getY();
} else { @Override
return start.getY() > position.getY() && position.getY() > selection.getY(); public boolean canContain(Vector2D position) {
} return positionOne.contains(position) || positionTwo.contains(position);
} else if (start.getY() == selection.getY() && position.getY() == start.getY()) { }
if (start.getX() < selection.getX()) {
return start.getX() < position.getX() && position.getX() < selection.getX(); @Override
} else { public Vector2D move(Vector2D position, Vector2D direction, long steps) {
return start.getX() > position.getX() && position.getX() > selection.getX(); if (contains(position) || connectsTo(position)) {
} return selection.move(position, direction, steps);
} else {
return null;
} }
return false;
} }
@Override @Override
public Vector2D getDirectionFrom(Vector2D position) { public Vector2D getDirectionFrom(Vector2D position) {
if (selection == null) { if (selection == null) {
return 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 @Override
public String toString() { public String toString() {
if (selection == null) { 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 { } else {
return String.format("s %d %s -> %s,%s %d", getIdentifier(), 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())) { if (obj != null && getClass().equals(obj.getClass())) {
final Switch otherSwitch = (Switch) obj; 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; return false;
@ -135,6 +155,6 @@ public final class Switch extends Rail {
@Override @Override
public int hashCode() { 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; 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 @Override
public Vector2D getDirectionFrom(Vector2D position) { public Vector2D getDirectionFrom(Vector2D position) {
// have to use long arithmetic here to avoid overflows // 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()), return new Vector2D(Long.signum((long) start.getX() - (long) end.getX()),
Long.signum((long) start.getY() - (long) end.getY())); Long.signum((long) start.getY() - (long) end.getY()));
} else { } 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 @Override
public String toString() { 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 @Override

View File

@ -142,9 +142,10 @@ public final class Train {
* @return whether the train was successfully placed * @return whether the train was successfully placed
* @throws InvalidInputException when the train is too long to place * @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 { throws InvalidInputException {
Vector2D positioningDirection = rawDirection.normalized().negated(); Vector2D direction = rawDirection.normalized();
Vector2D positioningDirection = direction.negated();
long length = getLength(); long length = getLength();
if (length > Integer.MAX_VALUE) { if (length > Integer.MAX_VALUE) {
// TODO: implement this case // TODO: implement this case
@ -157,10 +158,26 @@ public final class Train {
if (length > 10000) { if (length > 10000) {
return false; // TODO: remove! 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) // consider fuzz 9 (0,-1)
for (int i = 1; i <= length; i++) { for (int i = 1; i <= length; i++) {
rollingStockPosition = railNetwork.move(rollingStockPosition, positioningDirection); rollingStockPosition = railNetwork.move(rollingStockPosition, positioningDirection);
if (positions.getLast().distanceTo(rollingStockPosition) == 0) {
// stuck
return false;
}
if (rollingStockPosition == null if (rollingStockPosition == null
|| positions.contains(rollingStockPosition) && !positions.get(0).equals(rollingStockPosition)) { || positions.contains(rollingStockPosition) && !positions.get(0).equals(rollingStockPosition)) {
return false; return false;

View File

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

View File

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