308 lines
11 KiB
C++
308 lines
11 KiB
C++
#include "Polyomino.h"
|
|
|
|
#include "Position.h"
|
|
|
|
#include <vector>
|
|
#include <set>
|
|
#include <iostream>
|
|
#include <climits>
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
|
|
Polyomino::Polyomino(const std::set<Position>& 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<Position> newPositions;
|
|
for (Position position : positions) {
|
|
newPositions.insert(Position{position.x - minX, position.y - minY});
|
|
}
|
|
this->positions = std::move(newPositions);
|
|
}
|
|
|
|
Polyomino::Polyomino(const std::set<Position>& 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<Position> 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<Position> 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<Position> 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<Position> 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<std::vector<int>> linesCompleteness;
|
|
linesCompleteness.reserve(4);
|
|
std::vector<int> 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<Polyomino> candidateRotations;
|
|
std::vector<int> 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<Position> 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<std::vector<int>>& 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<int> 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<Position> 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<Position>& 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<Position>& 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;
|
|
}
|