-
William Fledelius authoredWilliam Fledelius authored
HPModel.cpp 13.36 KiB
// HPModel.cpp
#include "HPModel.h"
#include <utility>
// Point method definitions
Point::Point(const int x, const int y, const int z, const bool hydrophobic)
: x(x), y(y), z(z), hydrophobic(hydrophobic) {}
void Point::print() const {
std::cout << "Point (" << x << ", " << y << ", " << z << ")\n";
//std::cout << (hydrophobic ? " - Hydrophobic (H)" : " - Polar (P)") << "\n";
}
bool Point::coords_match(Point other) const {
return other.x == x && other.y == y && other.z == z;
}
// HPModel method definitions
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");
}
for (char c: input_sequence) {
if (c != 'h' && c != 'p') {
throw std::invalid_argument("Invalid character found in sequence, '" + std::to_string(c) +
"' is not a valid char, only 'h' and 'p' allowed");
}
}
sequence = input_sequence;
}
Point HPModel::getHead() {
if (acids.size() == 0) {
throw std::out_of_range("Acid sequence empty, do not call getHead without elements\n");
}
return acids.back();
}
std::string HPModel::directionToString(DIRECTION direction) {
switch (direction) {
case FORWARD:
return "FORWARD";
case UP:
return "UP";
case DOWN:
return "DOWN";
case LEFT:
return "LEFT";
case RIGHT:
return "RIGHT";
default:
return "UNKNOWN";
}
}
bool HPModel::isInsertValid(Point toInsert) {
for (Point p: acids) {
if (toInsert.coords_match(p)) {
return false;
}
}
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 ||
relative_direction == DOWN) {
throw std::invalid_argument("Not a planar direction, only N/S/E/W allowed\n");
}
switch (relative_direction) {
case FORWARD:
return absolute_direction;
case RIGHT:
switch (absolute_direction) {
case NORTH:
return EAST;
case SOUTH:
return WEST;
case EAST:
return SOUTH;
case WEST:
return NORTH;
default:
throw std::invalid_argument("Invalid absolute_direction, case RIGHT \n");
}
case LEFT:
switch (absolute_direction) {
case NORTH:
return WEST;
case SOUTH:
return EAST;
case EAST:
return NORTH;
case WEST:
return SOUTH;
default:
throw std::invalid_argument("Invalid absolute_direction, case LEFT\n");
}
default:
throw std::invalid_argument("Invalid relative_direction in method getPlaneDirectionChange");
}
}
/*
* 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 (currentDirection) {
case NORTH:
case EAST:
case SOUTH:
case WEST:
if (relative_direction == UP) {
// NORTH/EAST/SOUTH/WEST -> UP
new_direction = ABS_UP;
} else {
// 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) {
case NORTH:
case SOUTH:
case EAST:
case WEST:
new_direction = getPlaneDirectionChange(currentDirection, rotated_direction);
break;
case ABS_UP:
case ABS_DOWN:
// 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(rotated_direction) + " is invalid\n");
}
break;
case UP:
//new_direction = ABS_UP;
new_direction = getUpDownDirectionAndUpdatePrevious(rotated_direction);
break;
case DOWN:
//new_direction = ABS_DOWN;
new_direction = getUpDownDirectionAndUpdatePrevious(rotated_direction);
break;
default:
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;
}
currentDirection = new_direction;
}
bool HPModel::isCurrentAcidHydrophobic() {
int size = acids.size();
char stringElement = sequence[size];
return stringElement == 'h';
}
void HPModel::fold(DIRECTION direction) {
// An initial fold will *ALWAYS* require an extra point inserted (the 0th element of the sequence),
// and this direction will always be 'forward'
if (acids.empty()) {
addPoint(FORWARD, isCurrentAcidHydrophobic());
}
int no_of_points = acids.size();
// We allow n-1 folds, where n is the size of the sequence, assume we are inserting fold k (for k > 0)
// 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));
}
addPoint(direction, isCurrentAcidHydrophobic());
}
void HPModel::addPoint(DIRECTION direction, bool isHydrophobic) {
if (acids.empty()) {
acids.emplace_back(Point(0, 0, 0, isHydrophobic));
updateCurrentDirection(direction);
return;
}
Point head = getHead();
int newX = head.x, newY = head.y, newZ = head.z;
updateCurrentDirection(direction);
switch (currentDirection) {
case NORTH:
newZ++;
break;
case SOUTH:
newZ--;
break;
case EAST:
newX++;
break;
case WEST:
newX--;
break;
case ABS_UP:
newY++;
break;
case ABS_DOWN:
newY--;
break;
}
Point toInsert = Point(newX, newY, newZ, isHydrophobic);
if (!isInsertValid(toInsert)) {
throw std::invalid_argument("Unable to insert point in sequence\n");
}
acids.emplace_back(toInsert);
}
void HPModel::print() const {
for (const Point &p: acids) {
p.print();
}
}
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;
}