initial commit
This commit is contained in:
66
src/Pieces/Cell.h
Normal file
66
src/Pieces/Cell.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
||||
/**
|
||||
* A cell on a 2D grid
|
||||
*/
|
||||
struct Cell {
|
||||
int x; // x position
|
||||
int y; // y position
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Addition operator, returns the sums of the coordinates of both cells
|
||||
*/
|
||||
Cell operator+(const Cell& left, const Cell& right) {
|
||||
return Cell{left.x + right.x, left.y + right.y};
|
||||
}
|
||||
|
||||
/**
|
||||
* Additive assignation operator, adds the coordinates of the right cell to the left one
|
||||
*/
|
||||
Cell& operator+=(Cell& left, const Cell& right) {
|
||||
left = left + right;
|
||||
return left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Substraction operator, returns the difference of the coordinate between the left and right cell
|
||||
*/
|
||||
Cell operator-(const Cell& left, const Cell& right) {
|
||||
return Cell{left.x - right.x, left.y - right.y};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Substractive assignation operator, substract the coordinates of the right cell from the left one
|
||||
*/
|
||||
Cell& operator-=(Cell& left, const Cell& right) {
|
||||
left = left - right;
|
||||
return left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strict inferiority operator, a cell is inferior to another if it is lower or at the same height and more to the left
|
||||
*/
|
||||
bool operator<(const Cell& left, const Cell& right) {
|
||||
return (left.x == right.x) ? (left.y < right.y) : (left.x < right.x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality operator, two cells are equal if they have the same coordinates
|
||||
*/
|
||||
bool operator==(const Cell& left, const Cell& right) {
|
||||
return (left.x == right.x) && (left.y == right.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream output operator, adds the coordinates of the cell to the stream
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, const Cell& cell) {
|
||||
os << "x: " << cell.x << " y: " << cell.y;
|
||||
return os;
|
||||
}
|
||||
40
src/Pieces/Color.h
Normal file
40
src/Pieces/Color.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <String>
|
||||
|
||||
|
||||
/**
|
||||
* Every possible colors a block can take
|
||||
*/
|
||||
enum Color {
|
||||
NOTHING,
|
||||
OUT_OF_BOUND,
|
||||
GARBAGE,
|
||||
PURPLE,
|
||||
ORANGE,
|
||||
CYAN,
|
||||
PINK,
|
||||
YELLOW,
|
||||
RED,
|
||||
BLUE,
|
||||
GREEN
|
||||
};
|
||||
|
||||
|
||||
static const Color FIRST_PIECE_COLOR = PURPLE; // the first color a piece can be
|
||||
static const Color LAST_PIECE_COLOR = GREEN; // the last color a piece can be
|
||||
|
||||
static const std::string COLOR_RESET = "\033[38;2;255;255;255m"; // color code to reset the console color
|
||||
static const std::string COLOR_CODES[] = { // color codes to change the console color
|
||||
COLOR_RESET, // NOTHING
|
||||
COLOR_RESET, // OUT_OF_BOUND
|
||||
"\033[38;2;150;150;150m", // GARBAGE
|
||||
"\033[38;2;150;0;255m", // PURPLE
|
||||
"\033[38;2;255;150;0m", // ORANGE
|
||||
"\033[38;2;0;255;255m", // CYAN
|
||||
"\033[38;2;255;0;200m", // PINK
|
||||
"\033[38;2;255;255;0m", // YELLOW
|
||||
"\033[38;2;255;0;0m", // RED
|
||||
"\033[38;2;0;100;255m", // BLUE
|
||||
"\033[38;2;0;255;0m" // GREEN
|
||||
};
|
||||
85
src/Pieces/Generator.cpp
Normal file
85
src/Pieces/Generator.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
#include "Generator.h"
|
||||
|
||||
#include "Polyomino.h"
|
||||
|
||||
#include <Vector>
|
||||
#include <Set>
|
||||
#include <Map>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
Generator::Generator() {
|
||||
}
|
||||
|
||||
std::vector<Polyomino> Generator::generatePolyominos(unsigned int order) {
|
||||
// initialization
|
||||
this->validPolyominos.clear();
|
||||
this->currentTestedShape.clear();
|
||||
|
||||
// no polyomino with 0 cells
|
||||
if (order == 0) return this->validPolyominos;
|
||||
|
||||
// start generating from the monomino
|
||||
this->currentTestedShape.insert(Cell{0, 0});
|
||||
|
||||
// generate polyominos
|
||||
std::map<Cell, int> candidateCells;
|
||||
this->generate(order, 0, 1, candidateCells);
|
||||
return this->validPolyominos;
|
||||
}
|
||||
|
||||
void Generator::generate(unsigned int order, int lastAddedCellNumber, int nextAvaibleNumber, std::map<Cell, int> candidateCells) {
|
||||
// recursion stop
|
||||
if (order == this->currentTestedShape.size()) {
|
||||
// we test the polyomino formed by the current shape
|
||||
Polyomino candidate(this->currentTestedShape);
|
||||
|
||||
// we sort the rotations of the polyominos
|
||||
std::vector<Polyomino> candidateRotations;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
candidate.normalize();
|
||||
candidateRotations.push_back(candidate);
|
||||
if (!(i==3)) candidate.rotateCW();
|
||||
}
|
||||
std::sort(candidateRotations.begin(), candidateRotations.end());
|
||||
|
||||
// we keep the polyomino only if it was generated in its lowest rotation
|
||||
if (candidate == candidateRotations.at(0)) {
|
||||
this->validPolyominos.push_back(candidate);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// generate the list of candidate cells
|
||||
for (Cell cell : this->currentTestedShape) {
|
||||
this->tryToAddCandidateCell(Cell{cell.x, cell.y + 1}, nextAvaibleNumber, candidateCells);
|
||||
this->tryToAddCandidateCell(Cell{cell.x + 1, cell.y}, nextAvaibleNumber, candidateCells);
|
||||
this->tryToAddCandidateCell(Cell{cell.x, cell.y - 1}, nextAvaibleNumber, candidateCells);
|
||||
this->tryToAddCandidateCell(Cell{cell.x - 1, cell.y}, nextAvaibleNumber, candidateCells);
|
||||
}
|
||||
|
||||
// generate polyominos for all cells with a higher number than the last one
|
||||
for (auto [key, val] : candidateCells) {
|
||||
if (val > lastAddedCellNumber) {
|
||||
this->currentTestedShape.insert(key);
|
||||
this->generate(order, val, nextAvaibleNumber, (order == this->currentTestedShape.size()) ? std::map<Cell, int>() : candidateCells);
|
||||
this->currentTestedShape.erase(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Generator::tryToAddCandidateCell(const Cell& candidate, int& nextAvaibleNumber, std::map<Cell, int>& candidateCells) {
|
||||
// we declared the first cell as the lower-left square, since we always start with a monomino at (0,0) we can test with hard values
|
||||
if (candidate.y < 0 || (candidate.y == 0 && candidate.x < 0)) return;
|
||||
|
||||
// if the cell was already marked then we should not mark it again
|
||||
if (candidateCells.contains(candidate)) return;
|
||||
|
||||
// if the candidate overlaps with the shape there is no reason to add it
|
||||
if (this->currentTestedShape.contains(candidate)) return;
|
||||
|
||||
// once all tests passed we can add the cell
|
||||
candidateCells.insert({candidate, nextAvaibleNumber});
|
||||
nextAvaibleNumber++;
|
||||
}
|
||||
39
src/Pieces/Generator.h
Normal file
39
src/Pieces/Generator.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "Polyomino.h"
|
||||
|
||||
#include <Vector>
|
||||
#include <Set>
|
||||
#include <Map>
|
||||
|
||||
|
||||
/**
|
||||
* A generator of one-sided polyominos of any size
|
||||
*/
|
||||
class Generator {
|
||||
private:
|
||||
std::vector<Polyomino> validPolyominos; // the list of already generated polyominos
|
||||
std::set<Cell> currentTestedShape; // the polyomino being created
|
||||
|
||||
public:
|
||||
/**
|
||||
* Initializes generator
|
||||
*/
|
||||
Generator();
|
||||
|
||||
/**
|
||||
* Returns the list of all one-sided polyominos of the specified size
|
||||
*/
|
||||
std::vector<Polyomino> generatePolyominos(unsigned int order);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Generates all one-sided polyominos of the specified using the current tested shape
|
||||
*/
|
||||
void generate(unsigned int order, int lastAddedCellNumber, int nextAvaibleNumber, std::map<Cell, int> candidateCells);
|
||||
|
||||
/**
|
||||
* Check wheter a candidate cell can be added to the current tested shape
|
||||
*/
|
||||
void tryToAddCandidateCell(const Cell& candidate, int& nextAvaibleNumber, std::map<Cell, int>& candidateCells);
|
||||
};
|
||||
38
src/Pieces/Piece.cpp
Normal file
38
src/Pieces/Piece.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "Piece.h"
|
||||
|
||||
#include "Polyomino.h"
|
||||
#include "Rotation.h"
|
||||
#include "Color.h"
|
||||
|
||||
#include <Set>
|
||||
#include <String>
|
||||
|
||||
|
||||
Piece::Piece(const Polyomino& polyomino, Color color) : polyomino(polyomino), color(color) {
|
||||
}
|
||||
|
||||
void Piece::rotate(Rotation rotation) {
|
||||
if (rotation == CLOCKWISE)
|
||||
this->polyomino.rotateCW();
|
||||
if (rotation == DOUBLE)
|
||||
this->polyomino.rotate180();
|
||||
if (rotation == COUNTERCLOCKWISE)
|
||||
this->polyomino.rotateCCW();
|
||||
}
|
||||
|
||||
std::set<Cell> Piece::getPositions() const {
|
||||
return this->polyomino.getCells();
|
||||
}
|
||||
|
||||
int Piece::getLength() const {
|
||||
return this->polyomino.getLength();
|
||||
}
|
||||
|
||||
Color Piece::getColor() const {
|
||||
return this->color;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Piece& piece) {
|
||||
os << COLOR_CODES[piece.color] << piece.polyomino << COLOR_RESET;
|
||||
return os;
|
||||
}
|
||||
48
src/Pieces/Piece.h
Normal file
48
src/Pieces/Piece.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "Polyomino.h"
|
||||
#include "Rotation.h"
|
||||
#include "Color.h"
|
||||
|
||||
#include <Set>
|
||||
|
||||
|
||||
/**
|
||||
* An abstraction of a polyomino as a playable piece in a stacker game
|
||||
*/
|
||||
class Piece {
|
||||
private:
|
||||
Polyomino polyomino; // a polyomino representing the piece, (0, 0) is downleft
|
||||
Color color; // the color of the piece
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a piece with a specified shape and color
|
||||
*/
|
||||
Piece(const Polyomino& piece, Color color);
|
||||
|
||||
/**
|
||||
* Rotates the piece in the specified direction
|
||||
*/
|
||||
void rotate(Rotation rotation);
|
||||
|
||||
/**
|
||||
* Returns a copy of the list of cells of the piece
|
||||
*/
|
||||
std::set<Cell> getPositions() const;
|
||||
|
||||
/**
|
||||
* Returns the length of the piece
|
||||
*/
|
||||
int getLength() const;
|
||||
|
||||
/**
|
||||
* Returns the color of the piece
|
||||
*/
|
||||
Color getColor() const;
|
||||
|
||||
/**
|
||||
* Stream output operator, adds a 2D grid representing the piece
|
||||
*/
|
||||
friend std::ostream& operator<<(std::ostream& os, const Piece& piece);
|
||||
};
|
||||
141
src/Pieces/PiecesFiles.cpp
Normal file
141
src/Pieces/PiecesFiles.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include "PiecesFiles.h"
|
||||
|
||||
#include "Generator.h"
|
||||
#include "Piece.h"
|
||||
|
||||
#include <Vector>
|
||||
#include <String>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
PiecesFiles::PiecesFiles() {
|
||||
}
|
||||
|
||||
bool PiecesFiles::savePieces(int order) const {
|
||||
// open pieces file
|
||||
std::string filePath;
|
||||
if (!this->getFilePath(order, filePath)) {
|
||||
return false;
|
||||
}
|
||||
std::ofstream piecesFile(filePath, std::ios::trunc | std::ios::binary);
|
||||
if (!piecesFile.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// generates the polyominos
|
||||
Generator generator;
|
||||
std::vector<Polyomino> nMinos = generator.generatePolyominos(order);
|
||||
|
||||
// set the polyominos to their spawn position
|
||||
for (Polyomino& nMino : nMinos) {
|
||||
nMino.goToSpawnPosition();
|
||||
}
|
||||
|
||||
// sort the polyominos, is done after setting spawn position to ensure the order is always the same
|
||||
std::sort(nMinos.begin(), nMinos.end());
|
||||
|
||||
// write pieces
|
||||
Color pieceColor = Color(FIRST_PIECE_COLOR + (order % (LAST_PIECE_COLOR - FIRST_PIECE_COLOR + 1)));
|
||||
for (const Polyomino& nMino : nMinos) {
|
||||
// write polyomino length
|
||||
char lengthByte = nMino.getLength();
|
||||
piecesFile.write(&lengthByte, 1);
|
||||
|
||||
// write the type and color of the piece
|
||||
char infoByte = (nMino.isConvex() << 7) + (nMino.hasHole() << 6) + pieceColor;
|
||||
pieceColor = (pieceColor == LAST_PIECE_COLOR) ? FIRST_PIECE_COLOR : Color(pieceColor + 1);
|
||||
piecesFile.write(&infoByte, 1);
|
||||
|
||||
// write the cells of the piece
|
||||
char cellByte;
|
||||
for (Cell cell : nMino.getCells()) {
|
||||
cellByte = (cell.x << 4) + cell.y;
|
||||
piecesFile.write(&cellByte, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PiecesFiles::loadPieces(int order, std::vector<Piece>& pieces, std::vector<int>& convexPieces, std::vector<int>& holelessPieces, std::vector<int>& otherPieces) const {
|
||||
// open pieces file
|
||||
std::string filePath;
|
||||
if (!this->getFilePath(order, filePath)) {
|
||||
return false;
|
||||
}
|
||||
std::ifstream piecesFile(filePath, std::ios::binary);
|
||||
if (!piecesFile.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// get empty vectors
|
||||
pieces.clear();
|
||||
convexPieces.clear();
|
||||
holelessPieces.clear();
|
||||
otherPieces.clear();
|
||||
|
||||
// set up masks
|
||||
char convexMask = 0b1000'0000;
|
||||
char holeMask = 0b0100'0000;
|
||||
char colorMask = 0b0011'1111;
|
||||
char xMask = 0b1111'0000;
|
||||
char yMask = 0b0000'1111;
|
||||
|
||||
// read the pieces
|
||||
char lengthByte;
|
||||
int i = 0;
|
||||
while (piecesFile.get(lengthByte)) {
|
||||
if (piecesFile.eof()) break;
|
||||
|
||||
// read piece infos
|
||||
char infoByte;
|
||||
piecesFile.get(infoByte);
|
||||
bool isConvex = (infoByte & convexMask) >> 7;
|
||||
bool hasHole = (infoByte & holeMask) >> 6;
|
||||
Color color = Color(infoByte & colorMask);
|
||||
|
||||
// read cells
|
||||
std::set<Cell> pieceCells;
|
||||
char cellByte;
|
||||
for (int i = 0; i < order; i++) {
|
||||
piecesFile.get(cellByte);
|
||||
int x = (cellByte & xMask) >> 4;
|
||||
int y = cellByte & yMask;
|
||||
pieceCells.insert(Cell{x, y});
|
||||
}
|
||||
|
||||
// create piece
|
||||
Piece readPiece(Polyomino(pieceCells, lengthByte), color);
|
||||
pieces.push_back(readPiece);
|
||||
|
||||
// link it to its type
|
||||
if (isConvex) {
|
||||
convexPieces.push_back(i);
|
||||
}
|
||||
else if (hasHole) {
|
||||
otherPieces.push_back(i);
|
||||
}
|
||||
else {
|
||||
holelessPieces.push_back(i);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PiecesFiles::getFilePath(int order, std::string& filePath) const {
|
||||
// verify that the data folder exists
|
||||
std::string dataFolderPath = "data/pieces/";
|
||||
if (!std::filesystem::is_directory(dataFolderPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// return the file path
|
||||
filePath = dataFolderPath + std::to_string(order) + "minos.bin";
|
||||
return true;
|
||||
}
|
||||
37
src/Pieces/PiecesFiles.h
Normal file
37
src/Pieces/PiecesFiles.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "Piece.h"
|
||||
|
||||
#include <Vector>
|
||||
#include <String>
|
||||
|
||||
|
||||
/**
|
||||
* Manages creating pieces files and importing the data back from them
|
||||
*/
|
||||
class PiecesFiles {
|
||||
public:
|
||||
/**
|
||||
* Initializes file manager
|
||||
*/
|
||||
PiecesFiles();
|
||||
|
||||
/**
|
||||
* Generate a file containing all the pieces of the specified size,
|
||||
* returns false if the file couldn't be created
|
||||
*/
|
||||
bool savePieces(int order) const;
|
||||
|
||||
/**
|
||||
* Replace the content of the vectors by the pieces of the specified size, if the file wasn't found the vectors stays untouched,
|
||||
* returns false if the file wasn't found
|
||||
*/
|
||||
bool loadPieces(int order, std::vector<Piece>& pieces, std::vector<int>& convexPieces, std::vector<int>& holelessPieces, std::vector<int>& otherPieces) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Puts the path to the piece file of the specified size in order, if the data folder wasn't found the string stays untouched,
|
||||
* returns false if the data folder wasn't found
|
||||
*/
|
||||
bool getFilePath(int order, std::string& filePath) const;
|
||||
};
|
||||
322
src/Pieces/Polyomino.cpp
Normal file
322
src/Pieces/Polyomino.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
#include "Polyomino.h"
|
||||
|
||||
#include "Cell.h"
|
||||
|
||||
#include <Vector>
|
||||
#include <Set>
|
||||
#include <iostream>
|
||||
#include <climits>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
Polyomino::Polyomino(const std::set<Cell>& cells) {
|
||||
// find min/max
|
||||
int minX = INT_MAX;
|
||||
int maxX = INT_MIN;
|
||||
int minY = INT_MAX;
|
||||
int maxY = INT_MIN;
|
||||
for (Cell cell : cells) {
|
||||
if (cell.x < minX) minX = cell.x;
|
||||
if (cell.x > maxX) maxX = cell.x;
|
||||
if (cell.y < minY) minY = cell.y;
|
||||
if (cell.y > maxY) maxY = cell.y;
|
||||
}
|
||||
|
||||
// normalize
|
||||
std::set<Cell> newCells;
|
||||
for (Cell cell : cells) {
|
||||
newCells.insert(Cell{cell.x - minX, cell.y - minY});
|
||||
}
|
||||
this->cells = newCells;
|
||||
|
||||
// set polyomino length
|
||||
this->length = std::max(maxX - minX + 1, maxY - minY + 1);
|
||||
}
|
||||
|
||||
Polyomino::Polyomino(const std::set<Cell>& cells, int length) : cells(cells), length(length) {
|
||||
}
|
||||
|
||||
void Polyomino::normalize() {
|
||||
// find min values
|
||||
int minX = INT_MAX;
|
||||
int minY = INT_MAX;
|
||||
for (Cell cell : this->cells) {
|
||||
if (cell.x < minX) minX = cell.x;
|
||||
if (cell.y < minY) minY = cell.y;
|
||||
}
|
||||
|
||||
// translate the polyomino to the lowest unsigned values
|
||||
std::set<Cell> newCells;
|
||||
for (Cell cell : this->cells) {
|
||||
newCells.insert(Cell{cell.x - minX, cell.y - minY});
|
||||
}
|
||||
this->cells = newCells;
|
||||
}
|
||||
|
||||
void Polyomino::rotateCW() {
|
||||
// rotate 90° clockwise
|
||||
std::set<Cell> newCells;
|
||||
for (Cell cell : this->cells) {
|
||||
newCells.insert(Cell{cell.y, (length - 1) - (cell.x)});
|
||||
}
|
||||
this->cells = newCells;
|
||||
}
|
||||
|
||||
void Polyomino::rotate180() {
|
||||
// rotate 180°
|
||||
std::set<Cell> newCells;
|
||||
for (Cell cell : this->cells) {
|
||||
newCells.insert(Cell{(length - 1) - (cell.x), (length - 1) - (cell.y)});
|
||||
}
|
||||
this->cells = newCells;
|
||||
}
|
||||
|
||||
void Polyomino::rotateCCW() {
|
||||
// rotate 90° counter-clockwise
|
||||
std::set<Cell> newCells;
|
||||
for (Cell cell : this->cells) {
|
||||
newCells.insert(Cell{(length - 1) - (cell.y), cell.x});
|
||||
}
|
||||
this->cells = newCells;
|
||||
}
|
||||
|
||||
void Polyomino::goToSpawnPosition() {
|
||||
// initialize array
|
||||
std::vector<std::vector<int>> linesCompleteness;
|
||||
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 cells per rows and columns
|
||||
for (Cell cell : this->cells) {
|
||||
linesCompleteness.at(0).at(cell.y) += 1; // 0 = bottom to top = no rotation
|
||||
linesCompleteness.at(1).at((length - 1) - cell.x) += 1; // 1 = right to left = CW
|
||||
linesCompleteness.at(2).at((length - 1) - cell.y) += 1; // 2 = top to bottom = 180
|
||||
linesCompleteness.at(3).at(cell.x) += 1; // 3 = left to right = CCW
|
||||
}
|
||||
|
||||
// checks for empty lines
|
||||
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) {
|
||||
// push the non-empty lines to the start of each vector
|
||||
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 sides
|
||||
int sideToBeOn = -1;
|
||||
this->checkForFlattestSide(linesCompleteness, currentFlattestSides, sideToBeOn, false);
|
||||
|
||||
// if all sides are as flat, checks for the most left-biased side
|
||||
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())));
|
||||
}
|
||||
|
||||
// do the correct rotation
|
||||
if (sideToBeOn == 1) this->rotateCW();
|
||||
if (sideToBeOn == 2) this->rotate180();
|
||||
if (sideToBeOn == 3) this->rotateCCW();
|
||||
|
||||
// find min
|
||||
int minX = INT_MAX;
|
||||
int minY = INT_MAX;
|
||||
for (Cell cell : this->cells) {
|
||||
if (cell.x < minX) minX = cell.x;
|
||||
if (cell.y < minY) minY = cell.y;
|
||||
}
|
||||
|
||||
// center the piece with an up bias if it is assymetric
|
||||
if (sideToBeOn % 2 == 1) {
|
||||
std::swap(verticalEmptyLines, horizontalEmptyLines);
|
||||
}
|
||||
std::set<Cell> newCells;
|
||||
for (Cell cell : cells) {
|
||||
newCells.insert(Cell{(cell.x - minX) + (verticalEmptyLines / 2), (cell.y - minY) + ((horizontalEmptyLines + 1) / 2)});
|
||||
}
|
||||
this->cells = newCells;
|
||||
}
|
||||
|
||||
void Polyomino::checkForFlattestSide(const std::vector<std::vector<int>>& linesCompleteness, bool currentFlattestSides[4], int& sideToBeOn, bool checkLeftSide) const {
|
||||
// for each line
|
||||
for (int j = 0; j < this->length; j++) {
|
||||
// we check which sides are the flattest
|
||||
int max = 0;
|
||||
std::set<int> maxOwners;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
// only the candidate sides are compared
|
||||
if (!currentFlattestSides[i]) continue;
|
||||
|
||||
// if we need to check the flatness of the side to the left
|
||||
int sideToCheck = i;
|
||||
if (checkLeftSide) {
|
||||
sideToCheck = (i + 3) % 4;
|
||||
}
|
||||
|
||||
// we check which sides are the flattest
|
||||
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 this side
|
||||
if (maxOwners.size() == 1) {
|
||||
sideToBeOn = *maxOwners.begin();
|
||||
return;
|
||||
}
|
||||
|
||||
// else we only keep the flattest from this round and ignore the others
|
||||
else {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
currentFlattestSides[i] = currentFlattestSides[i] && maxOwners.contains(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Polyomino::isConvex() const {
|
||||
// for each line and column we check if every cells are adjacent to each others
|
||||
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++) {
|
||||
// line check
|
||||
if (this->cells.contains(Cell{i, j})) {
|
||||
if (completedLine) return false;
|
||||
else startedLine = true;
|
||||
}
|
||||
else {
|
||||
if (startedLine) completedLine = true;
|
||||
}
|
||||
// column check
|
||||
if (this->cells.contains(Cell{j, i})) {
|
||||
if (completedColumn) return false;
|
||||
else startedColumn = true;
|
||||
}
|
||||
else {
|
||||
if (startedColumn) completedColumn = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Polyomino::hasHole() const {
|
||||
// add every outer cells of the square containing the polyomino
|
||||
std::set<Cell> emptyCells;
|
||||
for (int i = 0; i < this->length - 1; i++) {
|
||||
this->tryToInsertCell(emptyCells, Cell{i, 0}); // up row
|
||||
this->tryToInsertCell(emptyCells, Cell{this->length - 1, i}); // rigth column
|
||||
this->tryToInsertCell(emptyCells, Cell{this->length - 1 - i, this->length - 1}); // bottom row
|
||||
this->tryToInsertCell(emptyCells, Cell{0, this->length - 1 - i}); // left column
|
||||
}
|
||||
|
||||
// if we didn't reached all empty cells in the square then there was some contained within the polyomino, i.e. there was a hole
|
||||
return (emptyCells.size() < (this->length * this->length) - this->cells.size());
|
||||
}
|
||||
|
||||
void Polyomino::tryToInsertCell(std::set<Cell>& emptyCells, const Cell& candidate) const {
|
||||
// check if the cell is in the square containing the polyomino
|
||||
if (candidate.x >= this->length || candidate.x < 0 || candidate.y >= this->length || candidate.y < 0) return;
|
||||
|
||||
// check if the cell is empty and hasn't already been tested
|
||||
if (this->cells.contains(candidate) || emptyCells.contains(candidate)) return;
|
||||
|
||||
// adds the cell to the list of empty cells and try its neighbors
|
||||
emptyCells.insert(candidate);
|
||||
tryToInsertCell(emptyCells, Cell{candidate.x, candidate.y + 1});
|
||||
tryToInsertCell(emptyCells, Cell{candidate.x + 1, candidate.y});
|
||||
tryToInsertCell(emptyCells, Cell{candidate.x, candidate.y - 1});
|
||||
tryToInsertCell(emptyCells, Cell{candidate.x - 1, candidate.y});
|
||||
}
|
||||
|
||||
std::set<Cell> Polyomino::getCells() const {
|
||||
return this->cells;
|
||||
}
|
||||
|
||||
int Polyomino::getLength() const {
|
||||
return this->length;
|
||||
}
|
||||
|
||||
int Polyomino::getPolyominoOrder() const {
|
||||
return this->cells.size();
|
||||
}
|
||||
|
||||
bool Polyomino::operator<(const Polyomino& other) const {
|
||||
// if one has an inferior length then it is deemed inferior
|
||||
if (this->length != other.length) return this->length < other.length;
|
||||
|
||||
// else we check for all cells from left to right and top to bottom, until one has a cell that the other doesn't
|
||||
for (int y = this->length - 1; y >= 0; y--) {
|
||||
for (int x = 0; x < this->length; x++) {
|
||||
bool hasThisCell = this->cells.contains(Cell{x, y});
|
||||
bool hasOtherCell = other.cells.contains(Cell{x, y});
|
||||
if (hasThisCell != hasOtherCell) return hasThisCell;
|
||||
}
|
||||
}
|
||||
|
||||
// if they are equal
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Polyomino::operator ==(const Polyomino& other) const {
|
||||
return this->cells == other.cells;
|
||||
}
|
||||
|
||||
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.cells.contains(Cell{x, y})) {
|
||||
os << "*";
|
||||
}
|
||||
else {
|
||||
os << "-";
|
||||
}
|
||||
}
|
||||
os << std::endl;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
108
src/Pieces/Polyomino.h
Normal file
108
src/Pieces/Polyomino.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cell.h"
|
||||
|
||||
#include <Vector>
|
||||
#include <Set>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
/**
|
||||
* A mathematical object consisting of touching squares on a 2D grid
|
||||
*/
|
||||
class Polyomino {
|
||||
private:
|
||||
std::set<Cell> cells; // the squares composing the polyomino, (0,0) is downleft
|
||||
int length; // the size of the smallest square in which the polyomino can fit on any rotation
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a polyomino with the specified cells and normalizes it, wheter it is actually a polyonimo is not checked
|
||||
*/
|
||||
Polyomino(const std::set<Cell>& cells);
|
||||
|
||||
/**
|
||||
* Creates a polyomino with the specified cells and length, wheter it is actually a polyonimo of this length is not checked
|
||||
*/
|
||||
Polyomino(const std::set<Cell>& cells, int length);
|
||||
|
||||
/**
|
||||
* Translates the polyomino to the lowest unsigned values (lower row on y = 0, and left-most column on x = 0)
|
||||
*/
|
||||
void normalize();
|
||||
|
||||
/**
|
||||
* Rotates the polyomino 90° clockwise, the center of rotation being the middle of the square going from (0,0) to (length-1, length-1)
|
||||
*/
|
||||
void rotateCW();
|
||||
|
||||
/**
|
||||
* Rotates the polyomino 180°, the center of rotation being the middle of the square going from (0,0) to (length-1, length-1)
|
||||
*/
|
||||
void rotate180();
|
||||
|
||||
/**
|
||||
* Rotates the polyomino 90° counter-clockwise, the center of rotation being the middle of the square going from (0,0) to (length-1, length-1)
|
||||
*/
|
||||
void rotateCCW();
|
||||
|
||||
/**
|
||||
* Set the polyomino to a normalized default spawn position
|
||||
*/
|
||||
void goToSpawnPosition();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Auxiliary method of goToSpawnPosition()
|
||||
*/
|
||||
void checkForFlattestSide(const std::vector<std::vector<int>>& linesCompleteness, bool currentFlattestSides[4], int& sideToBeOn, bool checkLeftSide) const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Returns wheter the polyomino is convex, that is if every line and column has at most one continuous line of cells
|
||||
*/
|
||||
bool isConvex() const;
|
||||
|
||||
/**
|
||||
* Returns wheter the polyomino has at least one hole
|
||||
*/
|
||||
bool hasHole() const;
|
||||
|
||||
private :
|
||||
/**
|
||||
* Auxiliary method of hasHole()
|
||||
*/
|
||||
void tryToInsertCell(std::set<Cell>& emptyCells, const Cell& candidate) const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Returns a copy of the cells of the polyomino
|
||||
*/
|
||||
std::set<Cell> getCells() const;
|
||||
|
||||
/**
|
||||
* Returns the length of the polyomino
|
||||
*/
|
||||
int getLength() const;
|
||||
|
||||
/**
|
||||
* Returns the number of squares in the polyomino
|
||||
*/
|
||||
int getPolyominoOrder() const;
|
||||
|
||||
/**
|
||||
* Strict inferiority operator, a polyomino is inferior than another if it has a smaller length, or if they are the same length,
|
||||
* while checking from left to right and top to bottom, is the first which has a cell while the other doesn't
|
||||
*/
|
||||
bool operator<(const Polyomino& other) const;
|
||||
|
||||
/**
|
||||
* Equality operator, two polyominos are equal if they overlap, that means two polyominos of the same shape but different positions will not be equal
|
||||
*/
|
||||
bool operator ==(const Polyomino& other) const;
|
||||
|
||||
/**
|
||||
* Stream output operator, adds a 2D grid representing the polyomino
|
||||
*/
|
||||
friend std::ostream& operator<<(std::ostream& os, const Polyomino& polyomino);
|
||||
};
|
||||
28
src/Pieces/Rotation.h
Normal file
28
src/Pieces/Rotation.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/**
|
||||
* Every type of rotation that can be applied to an object in a 2D grid
|
||||
*/
|
||||
enum Rotation {
|
||||
NONE,
|
||||
CLOCKWISE,
|
||||
DOUBLE,
|
||||
COUNTERCLOCKWISE
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Addition operator, returns a rotation corresponding to doing both rotations
|
||||
*/
|
||||
Rotation operator+(const Rotation& left, const Rotation& right) {
|
||||
return Rotation((left + right) % 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Additive assignation operator, rotate the left rotation by the right rotation
|
||||
*/
|
||||
Rotation& operator+=(Rotation& left, const Rotation& right) {
|
||||
left = left + right;
|
||||
return left;
|
||||
}
|
||||
Reference in New Issue
Block a user