diff --git a/src/HPModel.cpp b/src/HPModel.cpp index ac100b11220f835fd8b30b6e8fb7c03164efd3ca..a5191f736ce18030dbe87289bdb2f266dffab1dd 100644 --- a/src/HPModel.cpp +++ b/src/HPModel.cpp @@ -11,10 +11,10 @@ Point::Point(const int x, const int y, const int z, const bool hydrophobic) void Point::print() const { std::cout << "Point (" << x << ", " << y << ", " << z << ")\n"; - std::cout << (hydrophobic ? " - Hydrophobic (H)" : " - Polar (P)") << "\n"; + //std::cout << (hydrophobic ? " - Hydrophobic (H)" : " - Polar (P)") << "\n"; } -bool Point::coords_match(Point other) { +bool Point::coords_match(Point other) const { return other.x == x && other.y == y && other.z == z; } @@ -23,6 +23,7 @@ bool Point::coords_match(Point other) { HPModel::HPModel(const std::string &input_sequence) { currentDirection = NORTH; previousDirection = NORTH; + rotation = NO_ROTATION; if (input_sequence.size() < 2) { throw std::invalid_argument("Sequence length must be at least 2"); } @@ -68,6 +69,37 @@ bool HPModel::isInsertValid(Point toInsert) { return true; } +/** + * Updates the rotation in 90 degree steps + * POSITIVE = CLOCKWISE + * NEGATIVE = COUNTER CLOCKWISE + * @param amount amount of steps to rotate, for each step rotates 90 degrees, negative amount will rotate opposite direction + */ +void HPModel::rotate(int amount) { + // Since a rotation step is 90 degrees, every 4 rotations will be 360 degrees + int modAmount = amount % 4; + int rotationTimes = modAmount; + if (modAmount < 0) { + rotationTimes = 4 - (-modAmount); + } + for (int i = 0; i < rotationTimes; i++) { + switch (rotation) { + case NO_ROTATION: + rotation = ROTATE_90; + break; + case ROTATE_90: + rotation = ROTATE_180; + break; + case ROTATE_180: + rotation = ROTATE_270; + break; + case ROTATE_270: + rotation = NO_ROTATION; + break; + } + } +} + ABSOLUTE_DIRECTION HPModel::getPlaneDirectionChange(ABSOLUTE_DIRECTION absolute_direction, DIRECTION relative_direction) { if (absolute_direction == ABS_UP || absolute_direction == ABS_DOWN || relative_direction == UP || @@ -108,16 +140,84 @@ HPModel::getPlaneDirectionChange(ABSOLUTE_DIRECTION absolute_direction, DIRECTIO } } -void HPModel::updateCurrentDirection(DIRECTION direction) { +/* + * Has the side effect of updating previousDirection, because this update is non-trivial + * Similarly updates the rotation, because up/down are the only folds that will do so + */ +ABSOLUTE_DIRECTION HPModel::getUpDownDirectionAndUpdatePrevious(DIRECTION relative_direction) { + if (relative_direction != UP && relative_direction != DOWN) { + throw std::invalid_argument("Invalid direction passed, must be either UP/DOWN\n"); + } ABSOLUTE_DIRECTION new_direction; - switch (direction) { - case FORWARD: - if (currentDirection != ABS_UP && currentDirection != ABS_DOWN) { - new_direction = currentDirection; + switch (currentDirection) { + case NORTH: + case EAST: + case SOUTH: + case WEST: + if (relative_direction == UP) { + // NORTH/EAST/SOUTH/WEST -> UP + new_direction = ABS_UP; } else { - new_direction = previousDirection; + // NORTH/EAST/SOUTH/WEST -> DOWN + new_direction = ABS_DOWN; + } + // Note: in this case we DO NOT want to update the previous direction + break; + case ABS_UP: + switch (previousDirection) { + case NORTH: + case EAST: + case SOUTH: + case WEST: + if (relative_direction == UP) { + // UP -> UP + new_direction = invertDirection(previousDirection); + // After UP -> UP we will be "upside-down" and will need to rotate 180 degrees + rotate(2); + } else { + // UP -> DOWN + new_direction = previousDirection; + // No rotation required because this is equivalent to simply FORWARD + } + break; + case ABS_UP: + case ABS_DOWN: + throw std::invalid_argument("Both current and previous direction is UP/DOWN, illegal state\n"); } break; + case ABS_DOWN: + switch (previousDirection) { + case NORTH: + case EAST: + case SOUTH: + case WEST: + if (relative_direction == UP) { + // DOWN -> UP + new_direction = previousDirection; + // No rotation required + } else { + // DOWN -> DOWN + new_direction = invertDirection(previousDirection); + // DOWN -> DOWN results in "upside-down" state just as UP -> UP does + rotate(-2); + } + break; + case ABS_UP: + case ABS_DOWN: + throw std::invalid_argument("Both current and previous direction is UP/DOWN, illegal state\n"); + } + break; + } + return new_direction; +} + +void HPModel::updateCurrentDirection(DIRECTION direction) { + ABSOLUTE_DIRECTION new_direction; + DIRECTION rotated_direction = getRotatedRelativeDirection(direction); + switch (rotated_direction) { + case FORWARD: + new_direction = currentDirection; + break; case RIGHT: case LEFT: switch (currentDirection) { @@ -125,26 +225,58 @@ void HPModel::updateCurrentDirection(DIRECTION direction) { case SOUTH: case EAST: case WEST: - new_direction = getPlaneDirectionChange(currentDirection, direction); + new_direction = getPlaneDirectionChange(currentDirection, rotated_direction); break; case ABS_UP: case ABS_DOWN: - new_direction = getPlaneDirectionChange(previousDirection, direction); + // In this case we need to handle rotation changes, because moving "out" of an UP/DOWN forces a rotation + if (currentDirection == ABS_UP) { + // UP + if (rotated_direction == RIGHT) { + // UP -> RIGHT + rotate(1); + } else { + // UP -> LEFT + rotate(-1); + } + } else { + // DOWN + if (rotated_direction == RIGHT) { + // DOWN -> RIGHT + rotate(-1); + } else { + // DOWN -> LEFT + rotate(1); + } + } + new_direction = getPlaneDirectionChange(previousDirection, rotated_direction); break; default: - throw std::invalid_argument("Enum value: " + directionToString(direction) + " is invalid\n"); + throw std::invalid_argument("Enum value: " + directionToString(rotated_direction) + " is invalid\n"); } break; case UP: - new_direction = ABS_UP; + //new_direction = ABS_UP; + new_direction = getUpDownDirectionAndUpdatePrevious(rotated_direction); break; case DOWN: - new_direction = ABS_DOWN; + //new_direction = ABS_DOWN; + new_direction = getUpDownDirectionAndUpdatePrevious(rotated_direction); break; default: - throw std::invalid_argument("Enum value: " + directionToString(direction) + " is invalid\n"); + throw std::invalid_argument("Enum value: " + directionToString(rotated_direction) + " is invalid\n"); } + // ABS_UP/ABS_DOWN are handled directly in getUpDownDirectionAndUpdatePrevious + if ((new_direction == ABS_UP || new_direction == ABS_DOWN) && (currentDirection == ABS_UP || currentDirection == ABS_DOWN)) { + currentDirection = new_direction; + return; + } + if ((new_direction == ABS_UP || new_direction == ABS_DOWN) && (previousDirection == ABS_UP || previousDirection == ABS_DOWN)) { + previousDirection = currentDirection; + currentDirection = new_direction; + return; + } if (new_direction != previousDirection) { previousDirection = currentDirection; } @@ -169,7 +301,8 @@ void HPModel::fold(DIRECTION direction) { // At this point, there exists k folds, and no_of_points = k + 1 => k = no_of_points - 1 // We want k <= n - 1, thus we need, no_of_points - 1 < n - 1 => no_of_points < n if (no_of_points >= sequence.size()) { - throw std::invalid_argument("Too many folds when trying to insert fold number " + std::to_string(no_of_points - 1)); + throw std::invalid_argument( + "Too many folds when trying to insert fold number " + std::to_string(no_of_points - 1)); } addPoint(direction, isCurrentAcidHydrophobic()); } @@ -222,3 +355,65 @@ void HPModel::print() const { std::vector<Point> HPModel::getPoints() { return acids; } + +ABSOLUTE_DIRECTION HPModel::invertDirection(ABSOLUTE_DIRECTION direction) { + switch (direction) { + + case NORTH: + return SOUTH; + case EAST: + return WEST; + case SOUTH: + return NORTH; + case WEST: + return EAST; + case ABS_UP: + case ABS_DOWN: + throw std::invalid_argument("Unable to invert direction of ABS_UP/ABS_DOWN directions\n"); + break; + } +} + +DIRECTION rotateRelativeDirection90Degrees(DIRECTION relativeDirection) { + switch (relativeDirection) { + case FORWARD: + return FORWARD; + case UP: + return RIGHT; + case DOWN: + return LEFT; + case LEFT: + return UP; + case RIGHT: + return DOWN; + } +} + +/** + * Rotates a relative direction based on the current rotation + */ +DIRECTION HPModel::getRotatedRelativeDirection(DIRECTION relative_direction) { + int rotateAmount; + + switch (rotation) { + case NO_ROTATION: + rotateAmount = 0; + break; + case ROTATE_90: + rotateAmount = 1; + break; + case ROTATE_180: + rotateAmount = 2; + break; + case ROTATE_270: + rotateAmount = 3; + break; + } + + DIRECTION new_relative_direction = relative_direction; + for (int i = 0; i < rotateAmount; i++) { + new_relative_direction = rotateRelativeDirection90Degrees(new_relative_direction); + } + + return new_relative_direction; +} \ No newline at end of file diff --git a/src/HPModel.h b/src/HPModel.h index c17a736aa9eaa6ee84af2290d43ba28f6c9020c4..4409c929e39299aa20900c76d7927ba653b96a52 100644 --- a/src/HPModel.h +++ b/src/HPModel.h @@ -9,6 +9,7 @@ enum DIRECTION {FORWARD, UP, DOWN, LEFT, RIGHT}; enum ABSOLUTE_DIRECTION {NORTH, EAST, SOUTH, WEST, ABS_UP, ABS_DOWN}; +enum ROTATION {NO_ROTATION, ROTATE_90, ROTATE_180, ROTATE_270}; class Point { public: @@ -17,7 +18,7 @@ public: Point(const int x, const int y, const int z, const bool hydrophobic); void print() const; - bool coords_match(Point other); + bool coords_match(Point other) const; }; class HPModel { @@ -26,13 +27,19 @@ private: std::vector<Point> acids; ABSOLUTE_DIRECTION currentDirection; ABSOLUTE_DIRECTION previousDirection; + ROTATION rotation; Point getHead(); static std::string directionToString(DIRECTION direction); bool isInsertValid(Point toInsert); bool isCurrentAcidHydrophobic(); - ABSOLUTE_DIRECTION getPlaneDirectionChange(ABSOLUTE_DIRECTION absolute_direction, DIRECTION relative_direction); + static ABSOLUTE_DIRECTION getPlaneDirectionChange(ABSOLUTE_DIRECTION absolute_direction, DIRECTION relative_direction); + ABSOLUTE_DIRECTION getUpDownDirectionAndUpdatePrevious(DIRECTION relative_direction); void updateCurrentDirection(DIRECTION direction); void addPoint(DIRECTION direction, bool isHydrophobic); + void rotate(int amount); + DIRECTION getRotatedRelativeDirection(DIRECTION relative_direction); + static ABSOLUTE_DIRECTION invertDirection(ABSOLUTE_DIRECTION direction); + public: std::vector<Point> getPoints(); void fold(DIRECTION direction); diff --git a/test/basic_tests/basic_check.cpp b/test/basic_tests/basic_check.cpp index cde246646060eb5d807e1cd28d93bceff0ddac60..68302fbc7483b16a9bb88a805916cb5a1e6c9fef 100644 --- a/test/basic_tests/basic_check.cpp +++ b/test/basic_tests/basic_check.cpp @@ -68,6 +68,86 @@ TEST(basic_check, test_planar) { EXPECT_TRUE(p4.coords_match(p4Match)); } +TEST(basic_check, test_up_loop_valid) { + HPModel model = HPModel("pppp"); + model.fold(UP); + model.fold(UP); + model.fold(UP); + + auto points = model.getPoints(); + + // Initial Point + Point p0 = points[0]; + Point p0Match = Point(0, 0, 0, false); + + // Up + Point p1 = points[1]; + Point p1Match = Point(0, 1, 0, false); + + // Up + Point p2 = points[2]; + Point p2Match = Point(0, 1, -1, false); + + // Up + Point p3 = points[3]; + Point p3Match = Point(0, 0, -1, false); + + model.print(); + + EXPECT_TRUE(p0.coords_match(p0Match)); + EXPECT_TRUE(p1.coords_match(p1Match)); + EXPECT_TRUE(p2.coords_match(p2Match)); + EXPECT_TRUE(p3.coords_match(p3Match)); +} + +TEST(basic_check, test_up_relative_valid) { + HPModel model = HPModel("ppppp"); + model.fold(UP); + model.fold(RIGHT); + model.fold(LEFT); + model.fold(LEFT); + + auto points = model.getPoints(); + + // Initial Point + Point p0 = points[0]; + Point p0Match = Point(0, 0, 0, false); + + // Up + Point p1 = points[1]; + Point p1Match = Point(0, 1, 0, false); + + // Right + Point p2 = points[2]; + Point p2Match = Point(1, 1, 0, false); + + // Left + Point p3 = points[3]; + Point p3Match = Point(1, 2, 0, false); + + // Left + Point p4 = points[4]; + Point p4Match = Point(0, 2, 0, false); + + model.print(); + + EXPECT_TRUE(p0.coords_match(p0Match)); + EXPECT_TRUE(p1.coords_match(p1Match)); + EXPECT_TRUE(p2.coords_match(p2Match)); + EXPECT_TRUE(p3.coords_match(p3Match)); + EXPECT_TRUE(p4.coords_match(p4Match)); +} + +TEST(basic_check, test_up_loop_invalid) { + HPModel model = HPModel("ppppp"); + model.fold(UP); + model.fold(UP); + model.fold(UP); + EXPECT_THROW(model.fold(UP), std::invalid_argument); + model.print(); +} + + TEST(basic_check, test_up_down) { HPModel model = HPModel("pppppp"); model.fold(RIGHT); @@ -91,15 +171,69 @@ TEST(basic_check, test_up_down) { // Forward Point p3 = points[3]; - Point p3Match = Point(2, 1, 0, false); + Point p3Match = Point(1, 2, 0, false); // Down Point p4 = points[4]; - Point p4Match = Point(2, 0, 0, false); + Point p4Match = Point(2, 2, 0, false); // Left Point p5 = points[5]; - Point p5Match = Point(2, 0, 1, false); + Point p5Match = Point(2, 2, 1, false); + + EXPECT_TRUE(p0.coords_match(p0Match)); + EXPECT_TRUE(p1.coords_match(p1Match)); + EXPECT_TRUE(p2.coords_match(p2Match)); + EXPECT_TRUE(p3.coords_match(p3Match)); + EXPECT_TRUE(p4.coords_match(p4Match)); + EXPECT_TRUE(p5.coords_match(p5Match)); +} + +TEST(basic_check, big_test) { + HPModel model = HPModel("pppppppp"); + model.fold(DOWN); + model.fold(DOWN); + model.fold(RIGHT); + model.fold(DOWN); + model.fold(RIGHT); + model.fold(UP); + model.fold(LEFT); + + auto points = model.getPoints(); + + model.print(); + + // Initial point + Point p0 = points[0]; + Point p0Match = Point(0, 0, 0, false); + + // Down + Point p1 = points[1]; + Point p1Match = Point(0, -1, 0, false); + + // Down + Point p2 = points[2]; + Point p2Match = Point(0, -1, -1, false); + + // Right + Point p3 = points[3]; + Point p3Match = Point(1, -1, -1, false); + + // Down + Point p4 = points[4]; + Point p4Match = Point(1, 0, -1, false); + + // Right + Point p5 = points[5]; + Point p5Match = Point(1, 0, 0, false); + + // Up + Point p6 = points[6]; + Point p6Match = Point(2, 0, 0, false); + + // Left + Point p7 = points[7]; + Point p7Match = Point(2, 1, 0, false); EXPECT_TRUE(p0.coords_match(p0Match)); EXPECT_TRUE(p1.coords_match(p1Match)); @@ -107,4 +241,45 @@ TEST(basic_check, test_up_down) { EXPECT_TRUE(p3.coords_match(p3Match)); EXPECT_TRUE(p4.coords_match(p4Match)); EXPECT_TRUE(p5.coords_match(p5Match)); + EXPECT_TRUE(p6.coords_match(p6Match)); + EXPECT_TRUE(p7.coords_match(p7Match)); +} + +TEST(basic_check, right_up) { + HPModel model = HPModel("ppppp"); + model.fold(RIGHT); + model.fold(UP); + model.fold(UP); + model.fold(RIGHT); + + auto points = model.getPoints(); + + // Initial + Point p0 = points[0]; + Point p0Match = Point(0, 0, 0, false); + + // Right + Point p1 = points[1]; + Point p1Match = Point(1, 0, 0, false); + + // Up + Point p2 = points[2]; + Point p2Match = Point(1, 1, 0, false); + + // Up + Point p3 = points[3]; + Point p3Match = Point(0, 1, 0, false); + + + // Right + Point p4 = points[4]; + Point p4Match = Point(0, 1, -1, false); + + model.print(); + + EXPECT_TRUE(p0.coords_match(p0Match)); + EXPECT_TRUE(p1.coords_match(p1Match)); + EXPECT_TRUE(p2.coords_match(p2Match)); + EXPECT_TRUE(p3.coords_match(p3Match)); + EXPECT_TRUE(p4.coords_match(p4Match)); } \ No newline at end of file