#include "Polyomino.h" #include "Position.h" #include #include #include #include #include #include Polyomino::Polyomino(const std::set& positions) { int minX = INT_MAX; int maxX = INT_MIN; int minY = INT_MAX; int maxY = INT_MIN; for (Position position : positions) { if (position.x < minX) minX = position.x; if (position.x > maxX) maxX = position.x; if (position.y < minY) minY = position.y; if (position.y > maxY) maxY = position.y; } this->length = std::max(maxX - minX + 1, maxY - minY + 1); // we normalize here instead of calling this->normalize() to reduce the number of calculations for the generation algorithm std::set newPositions; for (Position position : positions) { newPositions.insert(Position{position.x - minX, position.y - minY}); } this->positions = std::move(newPositions); } Polyomino::Polyomino(const std::set& positions, int length) : positions(positions), length(length) { } void Polyomino::normalize() { int minX = INT_MAX; int minY = INT_MAX; for (Position position : this->positions) { if (position.x < minX) minX = position.x; if (position.y < minY) minY = position.y; } std::set newPositions; for (Position position : this->positions) { newPositions.insert(Position{position.x - minX, position.y - minY}); } this->positions = std::move(newPositions); } void Polyomino::rotateCW() { std::set newPositions; for (Position position : this->positions) { newPositions.insert(Position{position.y, (length - 1) - (position.x)}); } this->positions = std::move(newPositions); } void Polyomino::rotate180() { std::set newPositions; for (Position position : this->positions) { newPositions.insert(Position{(length - 1) - (position.x), (length - 1) - (position.y)}); } this->positions = std::move(newPositions); } void Polyomino::rotateCCW() { std::set newPositions; for (Position position : this->positions) { newPositions.insert(Position{(length - 1) - (position.y), position.x}); } this->positions = std::move(newPositions); } void Polyomino::goToSpawnPosition() { // initialize array std::vector> linesCompleteness; linesCompleteness.reserve(4); std::vector empty; for (int j = 0; j < this->length; j++) { empty.push_back(0); } for (int i = 0; i < 4; i++) { linesCompleteness.push_back(empty); } // calculates amount of squares per rows and columns for (Position position : this->positions) { linesCompleteness.at(0).at(position.y) += 1; // 0 = bottom to top = no rotation linesCompleteness.at(1).at((length - 1) - position.x) += 1; // 1 = right to left = CW linesCompleteness.at(2).at((length - 1) - position.y) += 1; // 2 = top to bottom = 180 linesCompleteness.at(3).at(position.x) += 1; // 3 = left to right = CCW } // count empty lines and push the non-empty lines to the start of each vector int horizontalEmptyLines = 0; int verticalEmptyLines = 0; for (int i = 0; i < 4; i++) { for (int j = this->length - 1; j >= 0; j--) { if (linesCompleteness.at(i).at(j) == 0) { linesCompleteness.at(i).erase(linesCompleteness.at(i).begin() + j); linesCompleteness.at(i).push_back(0); if (i == 0) horizontalEmptyLines++; if (i == 1) verticalEmptyLines++; } } } // we ignore the sides which are not the widest bool currentFlattestSides[4] = {true, true, true, true}; if (horizontalEmptyLines < verticalEmptyLines) { currentFlattestSides[0] = false; currentFlattestSides[2] = false; } if (verticalEmptyLines < horizontalEmptyLines) { currentFlattestSides[1] = false; currentFlattestSides[3] = false; } // checks for the flattest side int sideToBeOn = -1; this->checkForFlattestSide(linesCompleteness, currentFlattestSides, sideToBeOn, false); // if ther's no winner, checks for the side which has the flattest side to its left if (sideToBeOn == -1) { this->checkForFlattestSide(linesCompleteness, currentFlattestSides, sideToBeOn, true); } // if there's still no winner we take the side (amongst the remaining ones) that's the smallest once rotated if (sideToBeOn == -1) { std::vector candidateRotations; std::vector candidateSides; for (int i = 0; i < 4; i++) { this->normalize(); if (currentFlattestSides[i]) { candidateRotations.push_back(*this); candidateSides.push_back(i); } this->rotateCW(); } sideToBeOn = candidateSides.at(std::distance(candidateRotations.begin(), std::min_element(candidateRotations.begin(), candidateRotations.end()))); } switch (sideToBeOn) { case 1 : {this->rotateCW(); break;} case 2 : {this->rotate180(); break;} case 3 : {this->rotateCCW(); break;} default: break; } if (sideToBeOn % 2 == 1) { std::swap(verticalEmptyLines, horizontalEmptyLines); } int minX = INT_MAX; int minY = INT_MAX; for (Position position : this->positions) { if (position.x < minX) minX = position.x; if (position.y < minY) minY = position.y; } // center the piece with an up bias std::set newPositions; for (Position position : positions) { newPositions.insert(Position{(position.x - minX) + (verticalEmptyLines / 2), (position.y - minY) + ((horizontalEmptyLines + 1) / 2)}); } this->positions = std::move(newPositions); } void Polyomino::checkForFlattestSide(const std::vector>& linesCompleteness, bool currentFlattestSides[4], int& sideToBeOn, bool checkLeftSide) const { for (int j = 0; j < this->length; j++) { // we check which sides are the flattest on this line int max = 0; std::set maxOwners; for (int i = 0; i < 4; i++) { if (!currentFlattestSides[i]) continue; int sideToCheck = i; if (checkLeftSide) { sideToCheck = (i + 3) % 4; } if (linesCompleteness.at(sideToCheck).at(j) > max) { max = linesCompleteness.at(sideToCheck).at(j); maxOwners.clear(); maxOwners.insert(i); } else if (linesCompleteness.at(sideToCheck).at(j) == max) { maxOwners.insert(i); } } // if there's no tie we choose the only side if (maxOwners.size() == 1) { sideToBeOn = *maxOwners.begin(); return; } // else we only keep the flattest on this line and ignore the others else { for (int i = 0; i < 4; i++) { currentFlattestSides[i] = currentFlattestSides[i] && maxOwners.contains(i); } } } } bool Polyomino::isConvex() const { for (int j = 0; j < this->length; j++) { bool startedLine = false; bool completedLine = false; bool startedColumn = false; bool completedColumn = false; for (int i = 0; i < this->length; i++) { if (this->positions.contains(Position{i, j})) { if (completedLine) return false; else startedLine = true; } else { if (startedLine) completedLine = true; } if (this->positions.contains(Position{j, i})) { if (completedColumn) return false; else startedColumn = true; } else { if (startedColumn) completedColumn = true; } } } return true; } bool Polyomino::hasHole() const { // add every empty square on the outer of the box containing the polyomino std::set emptyPositions; for (int i = 0; i < this->length - 1; i++) { this->tryToInsertPosition(emptyPositions, Position{i, 0}); // up row this->tryToInsertPosition(emptyPositions, Position{this->length - 1, i}); // rigth column this->tryToInsertPosition(emptyPositions, Position{this->length - 1 - i, this->length - 1}); // bottom row this->tryToInsertPosition(emptyPositions, Position{0, this->length - 1 - i}); // left column } // if we didn't reached all empty squares in the box then there was some contained within the polyomino, i.e. there was a hole return (emptyPositions.size() < (this->length * this->length) - this->positions.size()); } void Polyomino::tryToInsertPosition(std::set& emptyPositions, const Position& candidate) const { if (candidate.x >= this->length || candidate.x < 0 || candidate.y >= this->length || candidate.y < 0) return; if (this->positions.contains(candidate) || emptyPositions.contains(candidate)) return; // if it's a new empty square, try its neighbors emptyPositions.insert(candidate); tryToInsertPosition(emptyPositions, Position{candidate.x, candidate.y + 1}); tryToInsertPosition(emptyPositions, Position{candidate.x + 1, candidate.y}); tryToInsertPosition(emptyPositions, Position{candidate.x, candidate.y - 1}); tryToInsertPosition(emptyPositions, Position{candidate.x - 1, candidate.y}); } const std::set& Polyomino::getPositions() const { return this->positions; } int Polyomino::getLength() const { return this->length; } int Polyomino::getPolyominoSize() const { return this->positions.size(); } bool Polyomino::operator<(const Polyomino& other) const { if (this->length != other.length) return this->length < other.length; for (int y = this->length - 1; y >= 0; y--) { for (int x = 0; x < this->length; x++) { bool hasThisPosition = this->positions.contains(Position{x, y}); bool hasOtherPosition = other.positions.contains(Position{x, y}); if (hasThisPosition != hasOtherPosition) return hasThisPosition; } } return false; } bool Polyomino::operator==(const Polyomino& other) const { return this->positions == other.positions; } std::ostream& operator<<(std::ostream& os, const Polyomino& polyomino) { for (int y = polyomino.length - 1; y >= 0; y--) { for (int x = 0; x < polyomino.length; x++) { if (polyomino.positions.contains(Position{x, y})) { os << "*"; } else { os << "-"; } } os << std::endl; } return os; }