Skip to content
Snippets Groups Projects
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;
}