12 Commits

Author SHA1 Message Date
021620acef enlever le debug 2025-03-10 09:49:18 +01:00
74797e935a fixed game logic 2025-03-05 19:02:51 +01:00
2fbe4a6052 meilleurs coms ig 2025-03-03 22:34:46 +01:00
d029589c21 smarter & pointier 2025-03-03 12:00:39 +01:00
47d3d929db typo 2025-03-03 00:26:05 +01:00
1033f3a64c ajouté interface textuelle 2025-03-02 23:36:20 +01:00
857f90d646 màj diagrammes 2025-03-01 23:15:58 +01:00
f525c00662 const everywhere + légèrement changer format fichiers 2025-03-01 22:21:43 +01:00
8088894073 ajouté classe Menu 2025-03-01 20:27:36 +01:00
d443b25de1 allow resetting games 2025-03-01 19:13:49 +01:00
03281a6d70 ajoute la possiblilité d'ajouter des lignes de garbage 2025-03-01 18:41:09 +01:00
66c2cf1013 fini PiecesList 2025-03-01 18:24:09 +01:00
35 changed files with 1389 additions and 445 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -1,17 +1,19 @@
# Game logic
This section will detail how the player's action are interpreted into the game.
We will only talk about pieces and not polyominos. In this project, pieces are an abstraction of a polyomino. Though if you want to know more the polyominos in this project, check [this other file](Pieces_representation.md).
We will only talk about pieces and not polyominoes. In this project, pieces are an abstraction of a polyomino. Though if you want to know more the polyominoes in this project, check [this other file](Pieces_representation.md).
## Order of operations
Each frame, the UI will translate the user's input into a series of action to apply to the game. The list of action is the following:
- Quit the game
- Pause
- Retry
- Hold
- Move left
- Move right
- Rotate 0°
- Rotate clockwise (CW)
- Rotate 180°
- Rotate counter-clockwise (CCW)
@@ -24,9 +26,34 @@ Then the rest of actions will be tested in the list's order.
Finally, gravity and lock delay are applied last.
Moving and soft dropping can be held but hard dropping, holding and rotating needs to be released and pressed again to happen.
## ARR and DAS
The sidewise movement of the piece is defined by two parameters: DAS and ARR.
**DAS** stands for Delayed Auto Shift and **ARR** for Auto Repeat Rate.
Theses can be changed by the player.
The parameters will be refered to as DAS and ARR, while the in-game values will be refered to as the piece's DAS ans ARR.
- Initially, both the piece DAS and piece ARR are at 0. When moving the piece in one direction,
the piece will move one position and then wait for the player to hold that direction as long as the DAS value.
_So for exemple if DAS is set to 20, on the first frame piece DAS will be set to 1 and the piece will move once.
The next movement will be delayed by 20 frames, so the piece will move again 20 frames later, on the 21th frame, or when the piece DAS = (DAS +1)._
- Then, if the player still holds the same direction, ARR takes on.
The piece will move every ARR frames.
_So in our exemple if ARR is set to 5, this means the piece will move every 5 frames after the 21th frame.
So on the 21th frame piece DAS = 21 and piece ARR = 0.
On the 26th frame, piece ARR = 5 so the piece moves and piece ARR is set back to 0._
Now DAS can be buffered, that is if a direction is held before the piece spawn, it will have an initial DAS value corresponding to the number of frames held.
_So in our example where DAS = 20 and ARR = 5, if we held a direction during 30f then hold without releasing this direction, the piece will move instantly on the frame it spawns.
Then, ARR will start counting on the next frame. So the piece will move on frames 1, 6, 11, etc._
When trying to hold two directions at the same time, only the direction held last will be applied.
So for example if you were holding left and you suddendly start holding the two directions at the same time, the piece will go right.
In the two directions are first pressed at the same frame, left will take priority.
## Leniency mechanics
In a general sense, we try to be kind to the players. Some of the following mechanics are just standards in a lot of stacker games, while other have been added because of this game using polyominos of high sizes and thus being way harder to play.
In a general sense, we try to be kind to the players. Some of the following mechanics are just standards in a lot of stacker games, while other have been added because of this game using polyominoes of high sizes and thus being way harder to play.
When a piece touches the ground, there is a short time period before it automatically locks. This period is called _lock delay_. Lock delay is reset everytime the piece move. To not allow for infinite stalling, there is another longer period called _forced lock delay_ that does not reset when moving the piece.
The player has a hold box in which they can temporarly store a piece. In this game, we allow the player to swap between the held piece and the active piece as much as they want. Once again to not allow for infinite stalling, forced lock delay does not reset when holding a piece.
@@ -38,17 +65,20 @@ IRS and IHS will fail if they actually loose the player the game when it would h
A common mechanic of stacker games is kicking. This happen when rotating a piece makes it collide with a wall. The game will try to move the piece in a few predetermined spot close to the current position until one of them allow the piece to not be in a wall again. If no positions allow for that, the rotation is simply cancelled.
This concept works very well for games with up to tetrominos or pentominos, but when using polyominos of any size we can't choose a few predetermined spots for each piece.
This concept works very well for games with up to tetrominos or pentominos, but when using polyominoes of any size we can't choose a few predetermined spots for each piece.
Since this game uses polyomino of high sizes which are very unplayable, we will try to be complaisant to the player and allow as much kicking spots as possible, while trying not to make him feel like the piece is going through walls. To solve this problem, this game introduce a new Rotation System called **AutoRS**, which does not have a predetermined list of spots to fit the piece but instead adapt to its shape. Its algorithm goes as follow:
1. Before rotating, mark every position containing the piece or touching the piece, we will call the set of all theses positions the ``safePositions``
2. Rotate the piece, if it fit stop the algorithm
3. Try fitting the piece, going from the center to the sides, that means we try to move the piece 1 position right, then 1 position left, then 2 position right, etc. until it fit (and then stop the algorithm), if at one point a position doesn't touch one of the ``safePositions`` we stop trying in this direction
3. Try fitting the piece, going from the center to the sides, that means we try to move the piece 1 position right, then 1 position left, then 2 position right, etc. until it fit (and then stop the algorithm), if at one point none of the squares of the pieces are in one of the ``safePositions`` we stop trying in this direction
4. Move the piece one line down, and repeat step 3 again, until we hit a line were the first position (shifted by 0 positions horizontally) touched none of the ``safePositions``
5. Do the same as step 4 but now we move the piece one line up every time
6. Cancel the rotation
Kicking is primarly designed for rotating, but it is also applied when a piece spawns into a wall.
0° rotations will first try to move the piece one position down, and if it can't try kicking the piece, only registering a kick if the piece couldn't move down.
## Detecting spins
Another common mechanic of stacker games that goes alongside kicking is spinning. A spin is a special move (a move is calculated once a piece has been locked to the board) which usually happen when the last move a piece did was a kick or a rotation and the piece is locked in place or is locked in certain corners, but the rules varies a lot from game to game.

View File

@@ -1,52 +1,52 @@
# Pieces representation
## What are polyominos ?
## What are polyominoes ?
In this game, pieces are represented as a polyomino and a block type.
Polyominos are mathematical objects consisting of multiple edge-touching squares.
Polyominoes are mathematical objects consisting of multiple edge-touching squares.
There must be a path from every square to every other square, going from square to square only through the sides and not the corners.
Polyominos can be classified in 3 ways:
Polyominoes can be classified in 3 ways:
- Fixed polyominos : only translation is allowed
- One-sided polyominos : only translation and rotation are allowed
- Fixed polyominoes : only translation is allowed
- One-sided polyominoes : only translation and rotation are allowed
- Free polyomins : translation, rotation, and reflection are allowed
For more detailed informations about polyominos, check the [Wikipedia page](https://en.wikipedia.org/wiki/Polyomino).
For more detailed informations about polyominoes, check the [Wikipedia page](https://en.wikipedia.org/wiki/Polyomino).
Most stacker game uses all one-sided polyominos of size 4 (called tetrominos), which results in 7 distinct polyominos.
In this game too, one-sided polyominos will be used since we will only allow to move and rotate the pieces.
Most stacker game uses all one-sided polyominoes of size 4 (called tetrominos), which results in 7 distinct polyominoes.
In this game too, one-sided polyominoes will be used since we will only allow to move and rotate the pieces.
Internally, polyominos are represented as a set of positions on a 2D grid.
This means that 2 polyominos of same shape but at different places will be interpreted as different polyominos.
To solve this, when doing equality checks, we normalize the polyominos so that their left-most column is at x=0 and their bottom-most row at y=0.
Internally, polyominoes are represented as a set of positions on a 2D grid.
This means that 2 polyominoes of same shape but at different places will be interpreted as different polyominoes.
To solve this, when doing equality checks, we normalize the polyominoes so that their left-most column is at x=0 and their bottom-most row at y=0.
Even if there is only 7 one-sided 4-minos, there is already more than 9,000 one-sided 10-minos.
Since the aim of this game is to be playable with polyominos of any size, listing all polyominos manually isn't viable.
Since the aim of this game is to be playable with polyominoes of any size, listing all polyominoes manually isn't viable.
We will need a way to automatically:
1. Generate all one-sided polyominos of size n
1. Generate all one-sided polyominoes of size n
2. Set their spawn positions and rotation centers
Aditionally, for this game we will also a want a way to separate the polyominos into multiple categories, specifically to allow removing those with holes who are harder to play with.
Aditionally, for this game we will also a want a way to separate the polyominoes into multiple categories, specifically to allow removing those with holes who are harder to play with.
## Ordering the polyominos
## Ordering the polyominoes
For practical reasons, we want to be able to sort all polyominos of the same size.
For practical reasons, we want to be able to sort all polyominoes of the same size.
But to sort objects we need a way to compare them.
When a polyomino is created, an attribute named its length is computed. This is simply the max between its width and its height. Thus the polyomino can be inscribed in a box of size ``length``.
We will now assume that our polyominos are always inscribed in a box of origin (0,0) and size ``length`` (which should always be the case under normal circumstances).
We will now assume that our polyominoes are always inscribed in a box of origin (0,0) and size ``length`` (which should always be the case under normal circumstances).
We can now compare polyominos using this method:
We can now compare polyominoes using this method:
- If one polyomino has an inferior length than another, it is deemed inferior
- If two polyomino have the same length, we check all the positions of their box, from left to right, and repeating from up to bottom, for the first position where a polyomino has a square and the another doesn't, the present polyomino which has a square is deemed inferior
A nice side-effect is that, once the polyomino are ordered, it is very simple to attribute them a block type, we can simply iterate through the list while looping over the block list.
## 1. Generating polyominos
## 1. Generating polyominoes
The method used to generate polyominos is similar to the [inductive method](https://en.wikipedia.org/wiki/Polyomino#Inductive_algorithms) described in the previously mentionned Wikipedia page.
The method used to generate polyominoes is similar to the [inductive method](https://en.wikipedia.org/wiki/Polyomino#Inductive_algorithms) described in the previously mentionned Wikipedia page.
The algorithm is the following:
@@ -64,15 +64,15 @@ The algorithm is the following:
1. We add this square to the polyomino
2. We call the generator function (recursive function!)
3. We remove this square from the polyomino
3. Return the list of polyominos
3. Return the list of polyominoes
The exact number of one-sided polyominos up to size 15 (and higher, but we only generated up to size 15) is known, and this method generated exactly theses numbers, without duplicates.
The exact number of one-sided polyominoes up to size 15 (and higher, but we only generated up to size 15) is known, and this method generated exactly theses numbers, without duplicates.
By marking squares and adding only ones that have a higher number than the last one everytime, we generate each fixed polyomino exactly n times. By ignoring the squares below y=0, or at exactly x=0 and y<0, we generate each fixed polyomino exactly 1 time.
An one-sided polyomino has 4 rotations, some of which can be the same if the polyomino has symmetries. 2 rotations that are the same corresponds to 1 fixed polyomino, we will refer to theses 2 rotation as 1 "unique" rotation.
Because we generate every fixed polyomino exactly 1 time, that means each "unique" rotation of an one-sided polyomino is generated exactly one-time, which includes its lowest rotation. That's how we can get away with only checking the rotation of the polyomino and not comparing it to the rest of the generated polyominos.
Because we generate every fixed polyomino exactly 1 time, that means each "unique" rotation of an one-sided polyomino is generated exactly one-time, which includes its lowest rotation. That's how we can get away with only checking the rotation of the polyomino and not comparing it to the rest of the generated polyominoes.
## 2. Setting the spawn position of polyominos
## 2. Setting the spawn position of polyominoes
Since we assume the polyomino is always inscribed in a box at origin (0,0), we can use formulae close to matrix rotations to rotate the piece:
@@ -91,19 +91,19 @@ The current algorithm for doing so is the following:
1. Check if the polyomino has more lines horizontally or vertically, this will determine either 2 or 4 sides which are wider than the others, and the others will be discarded
2. For each potential side check the number of square on the first line, if one has more than every others, then this side is choosed, else we only keep the sides that tied and repeat for the next line
3. If we still have at least 2 sides tied, we do the same again but check the flatness of the side to the left instead, this will make the pieces overall have more squares to their left at spawn
4. If there is still no winner, we sort the remaining sides (by simulating having them selectionned and then sorting the resulting polyominos) and keep the lowest
4. If there is still no winner, we sort the remaining sides (by simulating having them selectionned and then sorting the resulting polyominoes) and keep the lowest
5. We rotate the piece so that the chosen side ends up at the bottom of the polyomino
6. We center the polyomino inside its box, since we chose the widest side it will always fill the width of the square, but for the height it can be asymmetric, in that case we place it one line closer to the top than the bottom
_Note we could actually just skip straight to step 4 because it always give the same orientation, but we want the spawn rotations to follow somewhat of a logic. Step 1 to 3 actually already works for 99% of polyominos and they constrain step 4 too by preselecting certain sides._
_Note we could actually just skip straight to step 4 because it always give the same orientation, but we want the spawn rotations to follow somewhat of a logic. Step 1 to 3 actually already works for 99% of polyominoes and they constrain step 4 too by preselecting certain sides._
## 3. Separating the polyominos into categories
## 3. Separating the polyominoes into categories
For this game, we want the polyominos to be broken down into 3 categories:
For this game, we want the polyominoes to be broken down into 3 categories:
- Convex: this is said of a polyomino where every row and column is formed of at most one continous line of squares
- Holeless: the polyominos which are neither convex nor have a hole
- Others: the polyominos who have a hole (thoses are by definition not convex)
- Holeless: the polyominoes which are neither convex nor have a hole
- Others: the polyominoes who have a hole (thoses are by definition not convex)
To check for convexity, we simply iterate trough each row and column, and check if all the squares are contiguous.

View File

@@ -2,20 +2,19 @@
## What is stored
If you don't know what a polyomino is, check [this other file](Pieces_representation.md#what-are-polyominos).
If you don't know what a polyomino is, check [this other file](Pieces_representation.md#what-are-polyominoes).
Generating polyominos of size n is exponential in regard to n. Because of this, we will store the pieces beforehand and load them upon launching the game.
Generating polyominoes of size n is exponential in regard to n. Because of this, we will store the pieces beforehand and load them upon launching the game.
We want the pieces to be always sorted in the same order, always attributed the same block type, and always set at the same spawn position, no matter how they were generated. We also want them to be separated in 3 categories : convex, not convex but without a hole, and with a hole. Theses problematics are already resolved internally, but will be calculated before storage as to not need extra calculcations upon load.
We want the pieces to be always sorted in the same order, always attributed the same block type, and always set at the same spawn position, no matter how they were generated. We also want them to be separated in 3 categories : convex, not convex but without a hole, and with a hole. Theses problematics are already resolved internally, but will be calculated before storage as to not need extra calculcations upon load (except for the block type which is trivially computed).
## How is it stored
Pieces are stored in binary files. Each file simply contains every polyomino of one size, one after the other. Since each file contains all polyominos of the same size, we know how much stuff to read and don't need delimiters. We know we've read all pieces simply when we reach the end of file character.
Pieces are stored in binary files. Each file simply contains every polyomino of one size, one after the other. Since each file contains all polyominoes of the same size, we know how much stuff to read and don't need delimiters. We know we've read all pieces simply when we reach the end of file character.
Each piece is stored as follows:
- 1 byte for the length of the piece
- 1 byte for the other characteristics of the piece: ``ABCCCCCC`` where A indicates if the piece is convex, B indicates if it has a hole, and C is the block number of the piece
- 1 byte for the characteristics of the piece: ``ABCCCCCC`` where A indicates if the piece is convex, B indicates if it has a hole, and C is the length of the piece
- 1 byte for each position: ``XXXXYYYY`` where X is the x coordinate of the position and Y is the y coordinate of the position
The current implementation only allows to generate polyominos up to size 16, but can be upgraded by storing coordinates on 8 bits instead of 4.
The current implementation only allows to generate polyominoes up to size 16, but can be upgraded by storing coordinates on 8 bits instead of 4.
It has been currently choosen to use pieces only up to size 15 for this game.

View File

@@ -1,10 +1,13 @@
#pragma once
#include <iostream>
/**
* The list of actions that can be taken by the player
*/
enum Action {
QUIT,
PAUSE,
RETRY,
HOLD,
@@ -12,7 +15,33 @@ enum Action {
HARD_DROP,
MOVE_LEFT,
MOVE_RIGHT,
ROTATE_0,
ROTATE_CW,
ROTATE_180,
ROTATE_CCW
};
static const std::string ACTION_NAMES[] = { // name for each action
"Quit",
"Pause",
"Retry",
"Hold",
"Soft drop",
"Hard drop",
"Move left",
"Move right",
"Rotate 0°",
"Rotate CW",
"Rotate 180°",
"Rotate CCW"
};
/**
* Stream output operator, adds the name of the action
* @return A reference to the output stream
*/
inline std::ostream& operator<<(std::ostream& os, const Action action) {
os << ACTION_NAMES[action];
return os;
}

View File

@@ -8,43 +8,48 @@
#include <cstdlib>
Bag::Bag(std::shared_ptr<PiecesList> piecesList) :
Bag::Bag(const std::shared_ptr<PiecesList>& piecesList) :
piecesList(piecesList) {
// initialize bags
this->currentBag = this->piecesList->getSelectedPieces();
this->nextBag.clear();
// prepare first piece
this->prepareNext();
}
void Bag::jumpToNextBag() {
if (this->currentBag.size() < this->nextBag.size()) {
std::swap(this->currentBag, this->nextBag);
}
for (const std::pair<int, int>& pieceIndex : this->nextBag) {
this->currentBag.push_back(pieceIndex);
}
this->nextBag.clear();
this->prepareNext();
}
Piece Bag::lookNext() {
return *this->piecesList->getPiece(this->next.first, this->next.second);
return this->piecesList->getPiece(this->next);
}
Piece Bag::getNext() {
// get the piece to return
std::pair<int, int> nextIndex = this->next;
// prepare the piece even after the next
this->prepareNext();
// return the next piece
return *this->piecesList->getPiece(nextIndex.first, nextIndex.second);
return this->piecesList->getPiece(nextIndex);
}
void Bag::prepareNext() {
// if the bag is empty switch to the next bag
if (this->currentBag.empty()) {
std::swap(this->currentBag, this->nextBag);
}
// pick a random piece from the current bag
int indexIndex = std::rand() % this->currentBag.size();
this->next = this->currentBag.at(indexIndex);
// move the piece over to the next bag
this->nextBag.push_back(this->next);
this->currentBag.erase(this->currentBag.begin() + indexIndex);
}

View File

@@ -23,7 +23,12 @@ class Bag {
/**
* Creates a new bag with the pieces currently selected in the piece list
*/
Bag(std::shared_ptr<PiecesList> piecesList);
Bag(const std::shared_ptr<PiecesList>& piecesList);
/**
* Ignores the remaining pieces in the current bag and startd fresh from a new bag
*/
void jumpToNextBag();
/**
* Looks at what the next picked piece will be, without removing it from the bag

View File

@@ -16,15 +16,10 @@ Board::Board(int width, int height) :
this->emptyRow.push_back(NOTHING);
}
// initialize grid
this->grid.clear();
for (int j = 0; j < height; j++) {
this->grid.push_back(this->emptyRow);
}
this->clearBoard();
}
void Board::addBlock(const Position& position, Block block) {
// if the block is out of bounds we discard it
void Board::changeBlock(const Position& position, Block block) {
if (position.x < 0 || position.x >= this->width || position.y < 0) return;
// resize the grid if needed
@@ -37,6 +32,20 @@ void Board::addBlock(const Position& position, Block block) {
this->grid.at(position.y).at(position.x) = block;
}
void Board::insertRow(int height, int holePosition, Block blockType) {
std::vector<Block> insertedRow;
for (int i = 0; i < this->width; i++) {
if (i == holePosition) {
insertedRow.push_back(NOTHING);
}
else {
insertedRow.push_back(blockType);
}
}
this->grid.insert(this->grid.begin() + height, insertedRow);
}
int Board::clearRows() {
// check from top to bottom, so that erasing lines don't screw up the looping
int clearedLines = 0;
@@ -52,7 +61,7 @@ int Board::clearRows() {
if (lineIsFull) {
this->grid.erase(this->grid.begin() + j);
if(this->grid.size() < height) {
if(this->grid.size() < this->height) {
this->grid.push_back(this->emptyRow);
}
clearedLines++;
@@ -62,6 +71,13 @@ int Board::clearRows() {
return clearedLines;
}
void Board::clearBoard() {
this->grid.clear();
for (int j = 0; j < this->height; j++) {
this->grid.push_back(this->emptyRow);
}
}
Block Board::getBlock(const Position& position) const {
if (position.x < 0 || position.x >= this->width || position.y < 0) return OUT_OF_BOUNDS;
@@ -70,10 +86,14 @@ Block Board::getBlock(const Position& position) const {
return this->grid.at(position.y).at(position.x);
}
std::vector<std::vector<Block>> Board::getBlocks() const {
const std::vector<std::vector<Block>>& Board::getBlocks() const {
return this->grid;
}
int Board::getWidth() const {
return this->width;
}
int Board::getGridHeight() const {
return this->grid.size();
}
@@ -82,10 +102,6 @@ int Board::getBaseHeight() const {
return this->height;
}
int Board::getWidth() const {
return this->width;
}
std::ostream& operator<<(std::ostream& os, const Board& board) {
for (int y = board.grid.size() - 1; y >= 0; y--) {
for (int x = 0; x < board.width; x++) {

View File

@@ -14,7 +14,7 @@ class Board {
std::vector<std::vector<Block>> grid; // the grid, (0,0) is downleft
std::vector<Block> emptyRow; // an empty row of blocks
int width; // the width of the grid
int height; // the base height of the grid, which can extends indefinitely
int height; // the base height of the grid, which can extend indefinitely
public:
/**
@@ -23,9 +23,14 @@ class Board {
Board(int width, int height);
/**
* Change the block of the specified block, if the block is out of bounds it is simply ignored
* Changes the block at the specified position, if the block is out of bounds it is simply ignored
*/
void addBlock(const Position& position, Block block);
void changeBlock(const Position& position, Block block);
/**
* Inserts a row of the specified block type (unless on the specified column that has a hole), at the specified height
*/
void insertRow(int height, int holePosition, Block blockType);
/**
* Clears any complete row and moves down the rows on top
@@ -34,14 +39,24 @@ class Board {
int clearRows();
/**
* @return The block of the block at the specified position
* Deletes any block currently on the board
*/
void clearBoard();
/**
* @return The block at the specified position
*/
Block getBlock(const Position& position) const;
/**
* @return A copy of the grid
* @return The grid
*/
std::vector<std::vector<Block>> getBlocks() const;
const std::vector<std::vector<Block>>& getBlocks() const;
/**
* @return The width of the grid
*/
int getWidth() const;
/**
* @return The actual height of the grid
@@ -53,11 +68,6 @@ class Board {
*/
int getBaseHeight() const;
/**
* @return The width of the grid
*/
int getWidth() const;
/**
* Stream output operator, adds a 2D grid representing the board
* @return A reference to the output stream

View File

@@ -16,35 +16,42 @@ static const int B2B_SCORE_MULTIPLIER = 2; // by how much havaing B2B on mult
static const int B2B_MIN_LINE_NUMBER = 4; // the minimum number of lines needed to be cleared at once to gain B2B (without a spin)
Game::Game(Gamemode gamemode, const Player& controls, int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& bag) :
Game::Game(Gamemode gamemode, const Player& controls, int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& piecesList) :
parameters(gamemode, controls),
board(boardWidth, boardHeight, bag, parameters.getNextQueueLength()) {
board(boardWidth, boardHeight, piecesList, parameters.getNextQueueLength()) {
// the game has not yet started
this->initialize();
}
void Game::start() {
this->started = true;
this->leftARETime = 1;
}
void Game::reset() {
this->initialize();
this->parameters.reset();
this->board.reset();
}
void Game::initialize() {
this->started = false;
this->lost = false;
// initialize stats
this->score = 0;
this->framesPassed = 0;
this->B2BChain = 0;
// nothing happened yet
this->heldActions.clear();
this->initialActions.clear();
this->heldDAS = 0;
this->heldARR = 0;
this->subVerticalPosition = 0;
this->leftARETime = 0;
this->totalLockDelay = 0;
this->totalForcedLockDelay = 0;
}
void Game::start() {
this->started = true;
this->lost = this->board.spawnNextPiece();
}
void Game::nextFrame(const std::set<Action>& playerActions) {
if (this->lost || this->hasWon()) return;
@@ -56,57 +63,72 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
if (this->leftARETime == 0) {
if (AREJustEnded) {
this->board.spawnNextPiece();
this->lost = this->board.spawnNextPiece();
this->resetPiece(true);
}
/* IRS and IHS */
Rotation initialRotation = NONE
+ (this->initialActions.contains(ROTATE_CW)) ? CLOCKWISE : NONE
+ (this->initialActions.contains(ROTATE_180)) ? DOUBLE : NONE
+ (this->initialActions.contains(ROTATE_CCW)) ? COUNTERCLOCKWISE : NONE;
+ ((this->initialActions.contains(ROTATE_CW)) ? CLOCKWISE : NONE)
+ ((this->initialActions.contains(ROTATE_180)) ? DOUBLE : NONE)
+ ((this->initialActions.contains(ROTATE_CCW)) ? COUNTERCLOCKWISE : NONE);
if (this->initialActions.contains(HOLD)) {
if (this->board.hold(initialRotation)) {
this->subVerticalPosition = 0;
this->totalLockDelay = 0;
this->heldARR = 0;
}
this->lost = (!this->board.hold(initialRotation));
}
else {
if (initialRotation != NONE) {
this->board.rotate(initialRotation);
if ((initialRotation != NONE) || this->initialActions.contains(ROTATE_0)) {
this->lost = (!this->board.rotate(initialRotation));
}
}
if (this->lost) {
if (initialRotation == NONE) {
this->lost = (!this->board.rotate(initialRotation));
}
}
if (this->lost) {
this->framesPassed++;
return;
}
/* HOLD */
if (playerActions.contains(HOLD) && (!this->heldActions.contains(HOLD))) {
if (this->board.hold()) {
this->subVerticalPosition = 0;
this->totalLockDelay = 0;
this->heldARR = 0;
this->resetPiece(false);
}
}
/* MOVE LEFT/RIGHT */
if (playerActions.contains(MOVE_LEFT)) {
this->movePiece(-1, (this->heldDAS >= 0));
Position before = this->board.getActivePiecePosition();
if (this->heldDAS >= 0) {
if (playerActions.contains(MOVE_LEFT) && (!heldActions.contains(MOVE_LEFT))) this->movePiece(-1);
else if (playerActions.contains(MOVE_RIGHT)) this->movePiece(1);
else this->heldDAS = 0;
}
if (playerActions.contains(MOVE_RIGHT)) {
this->movePiece(1, (this->heldDAS <= 0));
else if (this->heldDAS < 0) {
if (playerActions.contains(MOVE_RIGHT) && (!heldActions.contains(MOVE_RIGHT))) this->movePiece(1);
else if (playerActions.contains(MOVE_LEFT)) this->movePiece(-1);
else this->heldDAS = 0;
}
else {
this->heldDAS = 0;
if (before != this->board.getActivePiecePosition()) {
this->totalLockDelay = 0;
}
/* ROTATIONS */
if (playerActions.contains(ROTATE_0) && (!this->heldActions.contains(ROTATE_0))) {
this->rotatePiece(NONE);
}
if (playerActions.contains(ROTATE_CW) && (!this->heldActions.contains(ROTATE_CW))) {
this->board.rotate(CLOCKWISE);
this->rotatePiece(CLOCKWISE);
}
if (playerActions.contains(ROTATE_180) && (!this->heldActions.contains(ROTATE_180))) {
this->board.rotate(DOUBLE);
this->rotatePiece(DOUBLE);
}
if (playerActions.contains(ROTATE_CCW) && (!this->heldActions.contains(ROTATE_CCW))) {
this->board.rotate(COUNTERCLOCKWISE);
this->rotatePiece(COUNTERCLOCKWISE);
}
/* SOFT DROP */
@@ -140,15 +162,20 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
// no need to apply gravity and lock delay if the piece was hard dropped
else {
/* GRAVITY */
// parameters.getGravity() gives the gravity for an assumed 20-line high board
int appliedGravity = this->parameters.getGravity() * (this->board.getBoard().getBaseHeight() / 20.0);
this->subVerticalPosition += appliedGravity;
while (this->subVerticalPosition >= SUBPX_PER_ROW) {
this->subVerticalPosition -= SUBPX_PER_ROW;
this->board.moveDown();
if (this->parameters.getLevel() >= 20) {
while (this->board.moveDown());
}
else {
// parameters.getGravity() gives the gravity for an assumed 20-line high board
int appliedGravity = this->parameters.getGravity() * std::max((double) this->board.getBoard().getBaseHeight() / 20.0, 1.0);
this->subVerticalPosition += appliedGravity;
while (this->subVerticalPosition >= SUBPX_PER_ROW) {
this->subVerticalPosition -= SUBPX_PER_ROW;
this->board.moveDown();
}
}
/* LOCK DELAY */
if (this->board.touchesGround()) {
this->totalLockDelay++;
@@ -163,56 +190,78 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
}
}
// remove initial actions only once they've been applied
if (AREJustEnded) {
this->initialActions.clear();
}
}
this->framesPassed++;
if (this->lost) {
return;
}
}
// update remembered actions
this->heldActions = playerActions;
if ((!this->started) || this->leftARETime > 0) {
for (Action action : playerActions) {
this->initialActions.insert(action);
}
}
this->heldActions = playerActions;
if (playerActions.contains(MOVE_LEFT)) {
this->heldDAS = std::min(-1, this->heldDAS - 1);
}
if (playerActions.contains(MOVE_RIGHT)) {
this->heldDAS = std::max(1, this->heldDAS + 1);
}
else {
this->heldDAS = 0;
if (this->heldDAS >= 0) {
if (playerActions.contains(MOVE_LEFT)) this->heldDAS = -1;
else if (playerActions.contains(MOVE_RIGHT)) this->heldDAS++;
else this->heldDAS = 0;
}
else if (this->heldDAS < 0) {
if (playerActions.contains(MOVE_RIGHT)) this->heldDAS = +1;
else if (playerActions.contains(MOVE_LEFT)) this->heldDAS--;
else this->heldDAS = 0;
}
}
}
void Game::movePiece(int movement, bool resetDirection) {
if (resetDirection) {
void Game::resetPiece(bool newPiece) {
int appliedDAS = this->parameters.getDAS();
this->subVerticalPosition = 0;
this->totalLockDelay = 0;
if (newPiece) {
this->totalForcedLockDelay = 0;
}
if (abs(this->heldDAS) > appliedDAS) {
this->heldDAS = (this->heldDAS > 0) ? (+appliedDAS) : (-appliedDAS);
}
this->heldARR = 0;
}
void Game::movePiece(int movement) {
int appliedDAS = this->parameters.getDAS();
int appliedARR = this->parameters.getARR();
if ((this->heldDAS * movement) <= 0) {
this->heldDAS = movement;
this->heldARR = 0;
if (movement == -1) this->board.moveLeft();
if (movement == 1) this->board.moveRight();
}
else {
this->heldDAS += movement;
}
if (abs(this->heldDAS) > this->parameters.getDAS()) {
int appliedARR = this->parameters.getARR();
if (abs(this->heldDAS) > appliedDAS) {
// ARR=0 -> instant movement
if (appliedARR == 0) {
if (movement == -1) while (this->board.moveLeft());
if (movement == 1) while (this->board.moveRight());
}
// ARR>1 -> move by specified amount
else {
this->heldARR++;
if (this->heldARR == appliedARR) {
if (abs(this->heldDAS) > appliedDAS + 1) {
this->heldARR++;
}
if ((this->heldARR == appliedARR) || (abs(this->heldDAS) == (appliedDAS + 1))) {
this->heldARR = 0;
if (movement == -1) this->board.moveLeft();
if (movement == 1) this->board.moveRight();
@@ -221,11 +270,21 @@ void Game::movePiece(int movement, bool resetDirection) {
}
}
void Game::rotatePiece(Rotation rotation) {
Position before = this->board.getActivePiecePosition();
if (this->board.rotate(rotation)) {
this->totalLockDelay = 0;
if (before != this->board.getActivePiecePosition()) {
this->subVerticalPosition = 0;
}
}
}
void Game::lockPiece() {
LineClear clear = this->board.lockPiece();
this->parameters.clearLines(clear.lines);
// update B2B and score
bool B2BConditionsAreMet = ((clear.lines > B2B_MIN_LINE_NUMBER) || clear.isSpin || clear.isMiniSpin);
if (clear.lines > 0) {
/* clearing one more line is worth 2x more
@@ -237,68 +296,68 @@ void Game::lockPiece() {
this->score += clearScore;
}
this->B2BChain = B2BConditionsAreMet;
// reset active piece
this->subVerticalPosition = 0;
this->totalLockDelay = 0;
this->totalForcedLockDelay = 0;
this->heldARR = 0;
// check for ARE
this->leftARETime = this->parameters.getARE();
if (this->leftARETime == 0) {
this->board.spawnNextPiece();
}
if (!this->hasWon()) {
this->leftARETime = this->parameters.getARE();
if (this->leftARETime == 0) {
this->lost = this->board.spawnNextPiece();
this->resetPiece(true);
}
}
}
bool Game::hasWon() {
bool Game::hasWon() const {
return this->parameters.hasWon(this->framesPassed);
}
bool Game::hasLost() {
bool Game::hasLost() const {
return this->lost;
}
int Game::getClearedLines() {
int Game::getClearedLines() const {
return this->parameters.getClearedLines();
}
int Game::getLevel() {
int Game::getLevel() const {
return this->parameters.getLevel();
}
int Game::getFramesPassed() {
int Game::getFramesPassed() const {
return this->framesPassed;
}
int Game::getScore() {
int Game::getScore() const {
return this->score;
}
bool Game::isOnB2BChain() {
bool Game::isOnB2BChain() const {
return this->B2BChain;
}
bool Game::areBlocksBones() {
bool Game::areBlocksBones() const {
return this->parameters.getBoneBlocks();
}
Board Game::getBoard() {
Position Game::ghostPiecePosition() const {
return this->board.lowestPosition();
}
const Board& Game::getBoard() const {
return this->board.getBoard();
}
Piece Game::getActivePiece() {
const std::shared_ptr<Piece>& Game::getActivePiece() const {
return this->board.getActivePiece();
}
Position Game::getActivePiecePosition() {
const Position& Game::getActivePiecePosition() const {
return this->board.getActivePiecePosition();
}
Piece Game::getHeldPiece() {
const std::shared_ptr<Piece>& Game::getHeldPiece() const {
return this->board.getHeldPiece();
}
std::vector<Piece> Game::getNextPieces() {
const std::vector<Piece>& Game::getNextPieces() const {
return this->board.getNextPieces();
}

View File

@@ -33,13 +33,26 @@ class Game {
/**
* Initialize the parameters and creates a new board
*/
Game(Gamemode gamemode, const Player& controls, int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& bag);
Game(Gamemode gamemode, const Player& controls, int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& piecesList);
/**
* Starts the game
*/
void start();
/**
* Resets the game
*/
void reset();
private:
/**
* Initializes the game
*/
void initialize();
public:
/**
* Advances to the next frame while excecuting the actions taken by the player,
* this is where the main game logic takes place
@@ -48,9 +61,19 @@ class Game {
private:
/**
* Movse the piece in the specified direction
* Resets the piece's parameter
*/
void movePiece(int movement, bool resetDirection);
void resetPiece(bool newPiece);
/**
* Moves the piece in the specified direction (1 for right and -1 for left)
*/
void movePiece(int movement);
/**
* Rotates the piece with the specified rotation
*/
void rotatePiece(Rotation rotation);
/**
* Locks the piece, updates level and score and spawns the next piece if necessary
@@ -61,65 +84,70 @@ class Game {
/**
* @return If the player has won
*/
bool hasWon();
bool hasWon() const;
/**
* @return If the player has lost
*/
bool hasLost();
bool hasLost() const;
/**
* @return The current level
*/
int getLevel();
int getLevel() const;
/**
* @return The current number of cleared lines
*/
int getClearedLines();
int getClearedLines() const;
/**
* @return The number of frames passed since the start of the game
*/
int getFramesPassed();
int getFramesPassed() const;
/**
* @return The current score
*/
int getScore();
int getScore() const;
/**
* @return If the player is currently on a B2B chain
*/
bool isOnB2BChain();
bool isOnB2BChain() const;
/**
* @return If all blocks are currently bone blocks
*/
bool areBlocksBones();
bool areBlocksBones() const;
/**
* @return A copy of the board
* @return The position of the ghost piece
*/
Board getBoard();
Position ghostPiecePosition() const;
/**
* @return A copy of the active piece
* @return The board
*/
Piece getActivePiece();
const Board& getBoard() const;
/**
* @return A copy of the active piece position
* @return A pointer to the active piece, can be null
*/
Position getActivePiecePosition();
const std::shared_ptr<Piece>& getActivePiece() const;
/**
* @return A copy of the held piece
* @return The position of the active piece
*/
Piece getHeldPiece();
const Position& getActivePiecePosition() const;
/**
* @return A copy of the next pieces queue
* @return A pointer to the held piece, can be null
*/
std::vector<Piece> getNextPieces();
const std::shared_ptr<Piece>& getHeldPiece() const;
/**
* @return The next piece queue, can be empty
*/
const std::vector<Piece>& getNextPieces() const;
};

View File

@@ -8,21 +8,38 @@
#include <vector>
#include <set>
#include <memory>
#include <utility>
#include <cstdlib>
GameBoard::GameBoard(int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& pieceList, int nextQueueLength) :
GameBoard::GameBoard(int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& piecesList, int nextQueueLength) :
board(boardWidth, boardHeight),
generator(pieceList),
generator(piecesList),
nextQueueLength(nextQueueLength) {
this->initialize();
}
void GameBoard::reset() {
this->board.clearBoard();
this->generator.jumpToNextBag();
this->initialize();
}
void GameBoard::initialize() {
this->nextQueue.clear();
for (int i = 0; i < nextQueueLength; i++) {
this->nextQueue.push_back(this->generator.getNext());
}
this->activePiece = nullptr;
this->heldPiece = nullptr;
this->isLastMoveKick = false;
}
bool GameBoard::moveLeft() {
if (this->isActivePieceInWall(Position{-1, 0})) {
if (this->activePieceInWall(Position{-1, 0})) {
return false;
}
else {
@@ -33,7 +50,7 @@ bool GameBoard::moveLeft() {
}
bool GameBoard::moveRight() {
if (this->isActivePieceInWall(Position{1, 0})) {
if (this->activePieceInWall(Position{1, 0})) {
return false;
}
else {
@@ -44,7 +61,7 @@ bool GameBoard::moveRight() {
}
bool GameBoard::moveDown() {
if (this->isActivePieceInWall(Position{0, -1})) {
if (this->activePieceInWall(Position{0, -1})) {
return false;
}
else {
@@ -56,15 +73,22 @@ bool GameBoard::moveDown() {
bool GameBoard::rotate(Rotation rotation) {
Piece stored = *this->activePiece;
this->rotate(rotation);
this->activePiece->rotate(rotation);
// before trying to kick, check if the piece can rotate without kicking
if (!this->isActivePieceInWall()) {
this->isLastMoveKick = false;
return true;
if (rotation == NONE) {
if (this->moveDown()) {
this->isLastMoveKick = false;
return true;
}
}
else {
if (!this->activePieceInWall()) {
this->isLastMoveKick = false;
return true;
}
}
// get the list of positions that touches the original piece
std::set<Position> safePositions;
for (Position position : stored.getPositions()) {
Position positionInGrid(position + this->activePiecePosition);
@@ -75,7 +99,10 @@ bool GameBoard::rotate(Rotation rotation) {
safePositions.insert(positionInGrid + Position{-1, 0});
}
// try kicking the piece down
// first try kicking the piece down
if (rotation == NONE) {
this->activePiecePosition.y -= 1;
}
bool suceeded = this->tryKicking(true, safePositions);
if (suceeded) {
this->isLastMoveKick = true;
@@ -83,6 +110,9 @@ bool GameBoard::rotate(Rotation rotation) {
}
// if it doesn't work try kicking the piece up
if (rotation == NONE) {
this->activePiecePosition.y += 1;
}
suceeded = this->tryKicking(false, safePositions);
if (suceeded) {
this->isLastMoveKick = true;
@@ -91,7 +121,10 @@ bool GameBoard::rotate(Rotation rotation) {
// if it still doesn't work, abort the rotation
this->activePiece = std::make_shared<Piece>(stored);
return false;
if (rotation == NONE) {
this->isLastMoveKick = false;
}
return (rotation == NONE);
}
bool GameBoard::tryKicking(bool testingBottom, const std::set<Position>& safePositions) {
@@ -102,30 +135,29 @@ bool GameBoard::tryKicking(bool testingBottom, const std::set<Position>& safePos
// we try from the center to the sides as long as the kicked piece touches the original
bool overlapsLeft = true;
bool overlapsRight = true;
int i = 0;
int i = (j == 0) ? 1 : 0;
do {
// check right before right arbitrarly, we don't decide this with rotations since it would still be arbitrary with 180° rotations
// check right before left arbitrarly, we don't decide this with rotations since it would still be arbitrary with 180° rotations
if (overlapsRight) {
Position shift{+i, j};
if (!this->activePieceOverlapsOnePosition(safePositions, shift)) {
if (!this->activePieceOverlaps(safePositions, shift)) {
overlapsLeft = false;
}
else {
if (!this->isActivePieceInWall(shift)) {
if (!this->activePieceInWall(shift)) {
this->activePiecePosition += shift;
return true;
}
}
}
// do the same on the left side
if (overlapsLeft) {
Position shift{-i, j};
if (!this->activePieceOverlapsOnePosition(safePositions, shift)) {
if (!this->activePieceOverlaps(safePositions, shift)) {
overlapsLeft = false;
}
else {
if (!this->isActivePieceInWall(shift)) {
if (!this->activePieceInWall(shift)) {
this->activePiecePosition += shift;
return true;
}
@@ -159,21 +191,23 @@ bool GameBoard::hold(Rotation initialRotation) {
}
}
this->goToSpawnPosition();
Piece stored = *this->activePiece;
Position storedPosition = this->activePiecePosition;
this->goToSpawnPosition();
this->rotate(initialRotation);
// if the piece can't spawn, abort initial rotation
if (this->isActivePieceInWall()) {
if (this->activePieceInWall()) {
this->activePiece = std::make_shared<Piece>(stored);
this->goToSpawnPosition();
// if the piece still can't spawn, abort holding
if (this->isActivePieceInWall()) {
if (this->activePieceInWall()) {
if (isFirstTimeHolding) {
this->activePiece = nullptr;
}
std::swap(this->activePiece, this->heldPiece);
this->activePiecePosition = storedPosition;
return false;
}
}
@@ -184,71 +218,87 @@ bool GameBoard::hold(Rotation initialRotation) {
this->nextQueue.erase(this->nextQueue.begin());
}
// this piece has done nothing yet
this->heldPiece->defaultRotation();
this->isLastMoveKick = false;
return true;
}
bool GameBoard::spawnNextPiece() {
// generate a new piece
this->nextQueue.push_back(this->generator.getNext());
// get next piece from queue
this->activePiece = std::make_shared<Piece>(this->nextQueue.front());
this->nextQueue.erase(this->nextQueue.begin());
this->goToSpawnPosition();
// this piece has done nothing yet
this->isLastMoveKick = false;
return !this->isActivePieceInWall();
return this->activePieceInWall();
}
bool GameBoard::touchesGround() {
return this->isActivePieceInWall(Position{0, -1});
bool GameBoard::touchesGround() const {
return this->activePieceInWall(Position{0, -1});
}
Position GameBoard::lowestPosition() const {
Position shift = Position{0, -1};
while (!activePieceInWall(shift)) {
shift.y -= 1;
}
shift.y += 1;
return (this->activePiecePosition + shift);
}
LineClear GameBoard::lockPiece() {
bool isLockedInPlace = (this->isActivePieceInWall(Position{0, 1}) && this->isActivePieceInWall(Position{1, 0})
&& this->isActivePieceInWall(Position{-1, 0}) && this->isActivePieceInWall(Position{0, -1}));
bool isLockedInPlace = (this->activePieceInWall(Position{0, 1}) && this->activePieceInWall(Position{1, 0})
&& this->activePieceInWall(Position{-1, 0}) && this->activePieceInWall(Position{0, -1}));
for (Position position : this->activePiece->getPositions()) {
this->board.addBlock(position + this->activePiecePosition, this->activePiece->getBlockType());
this->board.changeBlock(position + this->activePiecePosition, this->activePiece->getBlockType());
}
return LineClear{this->board.clearRows(), isLockedInPlace, (!isLockedInPlace) && this->isLastMoveKick};
}
Board GameBoard::getBoard() const {
void GameBoard::addGarbageRows(int number) {
int holePosition = std::rand() % this->board.getWidth();
for (int i = 0; i < number; i++) {
this->board.insertRow(0, holePosition, GARBAGE);
if (this->touchesGround()) {
this->activePiecePosition.y += 1;
}
}
}
const Board& GameBoard::getBoard() const {
return this->board;
}
Piece GameBoard::getActivePiece() const {
return *this->activePiece;
const std::shared_ptr<Piece>& GameBoard::getActivePiece() const {
return this->activePiece;
}
Position GameBoard::getActivePiecePosition() const {
const Position& GameBoard::getActivePiecePosition() const {
return this->activePiecePosition;
}
Piece GameBoard::getHeldPiece() const {
return *this->heldPiece;
const std::shared_ptr<Piece>& GameBoard::getHeldPiece() const {
return this->heldPiece;
}
std::vector<Piece> GameBoard::getNextPieces() const {
const std::vector<Piece>& GameBoard::getNextPieces() const {
return this->nextQueue;
}
bool GameBoard::isActivePieceInWall(const Position& shift) const {
bool GameBoard::activePieceInWall(const Position& shift) const {
for (Position position : this->activePiece->getPositions()) {
if (this->board.getBlock(position + this->activePiecePosition + shift) != NOTHING) return true;
}
return false;
}
bool GameBoard::activePieceOverlapsOnePosition(const std::set<Position>& safePositions, const Position& shift) const {
bool GameBoard::activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift) const {
for (Position position : this->activePiece->getPositions()) {
if (safePositions.contains(position + this->activePiecePosition + shift)) return true;
}
@@ -266,6 +316,8 @@ void GameBoard::goToSpawnPosition() {
// center the piece horizontally, biased towards left
this->activePiecePosition.x = (this->board.getWidth() - this->activePiece->getLength()) / 2;
this->activePiece->defaultRotation();
}
std::ostream& operator<<(std::ostream& os, const GameBoard& gameboard) {

View File

@@ -27,8 +27,20 @@ class GameBoard {
/**
* Creates a new board, generator, and next queue
*/
GameBoard(int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& bag, int nextQueueLength);
GameBoard(int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& piecesList, int nextQueueLength);
/**
* Resets the board as if it was newly created
*/
void reset();
private:
/**
* Initializes the board
*/
void initialize();
public:
/**
* Tries moving the piece one position to the left
* @return If it suceeded
@@ -48,7 +60,7 @@ class GameBoard {
bool moveDown();
/**
* Tries rotating the piece and kicking it if necessary
* Tries rotating the piece and kicking it if necessary, if it's a 0° rotation, it will forcefully try kicking
* @return If it suceeded
*/
bool rotate(Rotation rotation);
@@ -77,7 +89,13 @@ class GameBoard {
* Checks is the active piece as a wall directly below one of its position
* @return If it touches a ground
*/
bool touchesGround();
bool touchesGround() const;
/**
* Computes what the piece position would be if it were to be dropped down as much as possible
* @return The lowest position before hitting a wall
*/
Position lowestPosition() const;
/**
* Locks the active piece into the board and clears lines if needed
@@ -86,42 +104,47 @@ class GameBoard {
LineClear lockPiece();
/**
* @return A copy of the board
* Adds a specified number of garbage rows to the bottom of the board, the hole position being random but the same for all of them
*/
Board getBoard() const;
void addGarbageRows(int number);
/**
* @return A copy of the active piece
* @return The board
*/
Piece getActivePiece() const;
const Board& getBoard() const;
/**
* @return A copy of the position of the active piece
* @return A pointer to the active piece, can be null
*/
Position getActivePiecePosition() const;
const std::shared_ptr<Piece>& getActivePiece() const;
/**
* @return A copy of the held piece
* @return The position of the active piece
*/
Piece getHeldPiece() const;
const Position& getActivePiecePosition() const;
/**
* @return A copy of the next piece queue
* @return A pointer to the held piece, can be null
*/
std::vector<Piece> getNextPieces() const;
const std::shared_ptr<Piece>& getHeldPiece() const;
/**
* @return The next piece queue, can be empty
*/
const std::vector<Piece>& getNextPieces() const;
private:
/**
* Checks if one of the active piece's positions touches a wall in the board
* @return If the active piece is in a wall
* @return If the active piece spawned in a wall
*/
bool isActivePieceInWall(const Position& shift = Position{0, 0}) const;
bool activePieceInWall(const Position& shift = Position{0, 0}) const;
/**
* Check if one of the active piece's positions shifted by a specified position would overlap with a set of positions
* @return If the shifted active piece overlaps with one of the position
*/
bool activePieceOverlapsOnePosition(const std::set<Position>& safePositions, const Position& shift = Position{0, 0}) const;
bool activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift = Position{0, 0}) const;
/**
* Sets the active piece to its spawn position

View File

@@ -8,7 +8,10 @@ GameParameters::GameParameters(Gamemode gamemode, const Player& controls) :
gamemode(gamemode),
controls(controls) {
// initialize lines and level
this->reset();
}
void GameParameters::reset() {
this->clearedLines = 0;
switch (this->gamemode) {
// lowest gravity
@@ -22,17 +25,14 @@ GameParameters::GameParameters(Gamemode gamemode, const Player& controls) :
default : this->level = 1;
}
// initialize stats
this->updateStats();
}
void GameParameters::clearLines(int lineNumber) {
// update lines and level
switch (this->gamemode) {
// modes where level increases
case MARATHON :
case MASTER : {
// update cleared lines
int previousLines = this->clearedLines;
this->clearedLines += lineNumber;
@@ -48,7 +48,7 @@ void GameParameters::clearLines(int lineNumber) {
}
}
bool GameParameters::hasWon(int framesPassed) {
bool GameParameters::hasWon(int framesPassed) const {
switch (this->gamemode) {
// win once 40 lines have been cleared
case SPRINT : return this->clearedLines >= 40;
@@ -90,7 +90,7 @@ void GameParameters::updateStats() {
/* GRAVITY */
// get gravity for an assumed 20-rows board
static const int gravityPerLevel[] = {
0, // LVL0
0, // lvl0 = no gravity
1, // 60f/line, 20s total
2, // 30f/line, 10s total
3, // 20f/line, 6.66s total
@@ -109,12 +109,13 @@ void GameParameters::updateStats() {
40, // 1.5f/line, 30f total
1 * 60, // 1line/f, 20f total
2 * 60, // 2line/f, 10f total
4 * 60 // 4line/f, 5f total
4 * 60, // 4line/f, 5f total
20 * 60 // lvl20 = instant gravity
};
if (this->level < 0) {
this->gravity = gravityPerLevel[0];
}
else if (this->gravity > 20) {
else if (this->level > 20) {
this->gravity = gravityPerLevel[20];
}
else {
@@ -194,50 +195,50 @@ void GameParameters::updateStats() {
}
}
int GameParameters::getClearedLines() {
int GameParameters::getClearedLines() const {
return this->clearedLines;
}
int GameParameters::getLevel() {
int GameParameters::getLevel() const {
return this->level;
}
int GameParameters::getNextQueueLength() {
int GameParameters::getNextQueueLength() const {
return this->nextQueueLength;
}
bool GameParameters::getBoneBlocks() {
bool GameParameters::getBoneBlocks() const {
return this->boneBlocks;
}
int GameParameters::getGravity() {
int GameParameters::getGravity() const {
return this->gravity;
}
int GameParameters::getLockDelay() {
int GameParameters::getLockDelay() const {
return this->lockDelay;
}
int GameParameters::getForcedLockDelay() {
int GameParameters::getForcedLockDelay() const {
return this->forcedLockDelay;
}
int GameParameters::getARE() {
int GameParameters::getARE() const {
return this->ARE;
}
int GameParameters::getLineARE() {
int GameParameters::getLineARE() const {
return this->lineARE;
}
int GameParameters::getDAS() {
int GameParameters::getDAS() const {
return this->DAS;
}
int GameParameters::getARR() {
int GameParameters::getARR() const {
return this->ARR;
}
int GameParameters::getSDR() {
int GameParameters::getSDR() const {
return this->SDR;
}

View File

@@ -31,6 +31,11 @@ class GameParameters {
*/
GameParameters(Gamemode gamemode, const Player& controls);
/**
* Resets all stats and parameters
*/
void reset();
/**
* Counts the newly cleared lines and update level and stats if needed
*/
@@ -40,7 +45,7 @@ class GameParameters {
* Checks if the game ended based on the current states and time passed, accorind to the gamemode
* @return If the player has won
*/
bool hasWon(int framesPassed);
bool hasWon(int framesPassed) const;
private:
/**
@@ -52,60 +57,60 @@ class GameParameters {
/**
* @return The current number of cleared line
*/
int getClearedLines();
int getClearedLines() const;
/**
* @return The current level
*/
int getLevel();
int getLevel() const;
/**
* @return The length of the next queue
*/
int getNextQueueLength();
int getNextQueueLength() const;
/**
* Returns wheter the blocks are currently bone blocks
*/
bool getBoneBlocks();
bool getBoneBlocks() const;
/**
* @return The current gravity for a 20-line high board
*/
int getGravity();
int getGravity() const;
/**
* @return The current lock delay
*/
int getLockDelay();
int getLockDelay() const;
/**
* @return The current forced lock delay
*/
int getForcedLockDelay();
int getForcedLockDelay() const;
/**
* @return The current ARE
*/
int getARE();
int getARE() const;
/**
* @return The current line ARE
*/
int getLineARE();
int getLineARE() const;
/**
* @return The current DAS
*/
int getDAS();
int getDAS() const;
/**
* @return The current ARR
*/
int getARR();
int getARR() const;
/**
* @return The current SDR
*/
int getSDR();
int getSDR() const;
};

49
src/Core/Menu.cpp Normal file
View File

@@ -0,0 +1,49 @@
#include "Menu.h"
#include "PiecesList.h"
#include "Player.h"
#include "Game.h"
#include <memory>
Menu::Menu() {
this->piecesList = std::make_shared<PiecesList>(PiecesList());
this->boardWidth = DEFAULT_BOARD_WIDTH;
this->boardHeight = DEFAULT_BOARD_HEIGHT;
}
Game Menu::startGame(Gamemode gamemode) const {
return Game(gamemode, this->playerControls, this->boardWidth, this->boardHeight, this->piecesList);
}
bool Menu::setBoardWidth(int width) {
if (width < 1) return false;
this->boardWidth = width;
return true;
}
bool Menu::setBoardHeight(int height) {
if (height < 1) return false;
this->boardHeight = height;
return true;
}
int Menu::getBoardWidth() const {
return this->boardWidth;
}
int Menu::getBoardHeight() const {
return this->boardHeight;
}
Player& Menu::getPlayerControls() {
return this->playerControls;
}
PiecesList& Menu::getPiecesList() {
return *this->piecesList;
}

65
src/Core/Menu.h Normal file
View File

@@ -0,0 +1,65 @@
#pragma once
#include "PiecesList.h"
#include "Player.h"
#include "Game.h"
static const int FRAMES_PER_SECOND = 60; // the number of frames per second, all the values in the app were choosen with this number in mind
static const int DEFAULT_BOARD_WIDTH = 10; // the default width of the board when starting the menu
static const int DEFAULT_BOARD_HEIGHT = 20; // the default height of the board when starting the menu
/**
* The interface between an UI and the core of the app
*/
class Menu {
private:
std::shared_ptr<PiecesList> piecesList; // the list of pieces used by the app
Player playerControls; // the controls of the player
int boardWidth; // the width of the board for the next game
int boardHeight; // the height of the board for the next game
public:
/**
* Initializes the board size and player controls to their default values
*/
Menu();
/**
* Starts a new game with the current settings
* @return The game that has been created
*/
Game startGame(Gamemode gamemode) const;
/**
* Sets the width of the board, which must be greater than 0
* @return If the width has been changed
*/
bool setBoardWidth(int width);
/**
* Sets the height of the board, which must be greater than 0
* @return If the height has been changed
*/
bool setBoardHeight(int height);
/**
* @return The width of the board
*/
int getBoardWidth() const;
/**
* @return The height of the board
*/
int getBoardHeight() const;
/**
* @return A reference to the player's controls
*/
Player& getPlayerControls();
/**
* @return A reference to the pieces list
*/
PiecesList& getPiecesList();
};

View File

@@ -5,58 +5,109 @@
#include <vector>
#include <utility>
#include <memory>
PiecesList::PiecesList() {
this->maximumLoadedSize = 0;
this->loadedPieces.clear();
this->loadedPieces.push_back(std::vector<Piece>());
this->highestLoadedSize = 0;
this->selectedPieces.clear();
// we need to have something at index 0 even if there is no pieces of size 0
this->loadedPieces.clear();
this->convexPieces.clear();
this->holelessPieces.clear();
this->otherPieces.clear();
this->pushBackEmptyVectors();
// we always prepare vectors of the next size to be generated
this->pushBackEmptyVectors();
}
bool PiecesList::loadPieces(int size) {
if (size < 1) return false;
PiecesFiles piecesFiles;
std::vector<int> convexPieces;
std::vector<int> holelessPieces;
std::vector<int> otherPieces;
for (int i = this->highestLoadedSize + 1; i <= size; i++) {
for (int i = this->maximumLoadedSize + 1; i <= size; i++) {
std::vector<Piece> pieces;
this->loadedPieces.push_back(pieces);
if (!piecesFiles.loadPieces(i, this->loadedPieces.at(i), convexPieces, holelessPieces, otherPieces)) {
if (!piecesFiles.loadPieces(i, this->loadedPieces.at(i), this->convexPieces.at(i), this->holelessPieces.at(i), this->otherPieces.at(i))) {
return false;
}
else {
this->maximumLoadedSize++;
this->highestLoadedSize++;
this->pushBackEmptyVectors();
}
}
return true;
}
bool PiecesList::selectPieces(int size, int number) {
if (size < 1 || size > this->maximumLoadedSize || number >= this->loadedPieces.at(size).size()) return false;
bool PiecesList::selectPiece(int size, int number) {
if (size < 1 || size > this->highestLoadedSize || number >= this->loadedPieces.at(size).size()) return false;
this->selectedPieces.push_back(std::pair<int, int>(size, number));
return true;
}
std::vector<std::pair<int, int>> PiecesList::getSelectedPieces() {
return this->selectedPieces;
bool PiecesList::selectAllPieces(int size) {
if (size < 1 || size > this->highestLoadedSize) return false;
for (int i = 0; i < this->loadedPieces.at(size).size(); i++) {
this->selectedPieces.push_back(std::pair<int, int>(size, i));
}
return true;
}
int PiecesList::getNumberOfPiecesOfOneSize(int size) {
if (size < 1 || size > this->maximumLoadedSize) return 0;
bool PiecesList::selectConvexPieces(int size) {
if (size < 1 || size > this->highestLoadedSize) return false;
for (int index : this->convexPieces.at(size)) {
this->selectedPieces.push_back(std::pair<int, int>(size, index));
}
return true;
}
bool PiecesList::selectHolelessPieces(int size) {
if (size < 1 || size > this->highestLoadedSize) return false;
for (int index : this->holelessPieces.at(size)) {
this->selectedPieces.push_back(std::pair<int, int>(size, index));
}
return true;
}
bool PiecesList::selectOtherPieces(int size) {
if (size < 1 || size > this->highestLoadedSize) return false;
for (int index : this->otherPieces.at(size)) {
this->selectedPieces.push_back(std::pair<int, int>(size, index));
}
return true;
}
void PiecesList::unselectAll() {
this->selectedPieces.clear();
}
int PiecesList::getHighestLoadedSize() const {
return this->highestLoadedSize;
}
int PiecesList::getNumberOfPieces(int size) const {
if (size < 1 || size > this->highestLoadedSize) return 0;
return this->loadedPieces.at(size).size();
}
std::shared_ptr<Piece> PiecesList::getPiece(int size, int number) {
if (size < 1 || size > this->maximumLoadedSize || number >= this->loadedPieces.at(size).size()) return nullptr;
return std::make_shared<Piece>(this->loadedPieces.at(size).at(number));
std::vector<std::pair<int, int>> PiecesList::getSelectedPieces() const {
return this->selectedPieces;
}
Piece PiecesList::getPiece(const std::pair<int, int>& pieceIndex) const {
return this->loadedPieces.at(pieceIndex.first).at(pieceIndex.second);
}
void PiecesList::pushBackEmptyVectors() {
this->loadedPieces.push_back(std::vector<Piece>());
this->convexPieces.push_back(std::vector<int>());
this->holelessPieces.push_back(std::vector<int>());
this->otherPieces.push_back(std::vector<int>());
}

View File

@@ -4,25 +4,91 @@
#include <vector>
#include <utility>
#include <memory>
/**
* A container for all loaded pieces to prevent loading and copying them multiple times,
* also allows for the player to select a list of pieces to be used in a game
*/
class PiecesList {
private:
int maximumLoadedSize;
std::vector<std::vector<Piece>> loadedPieces;
std::vector<std::pair<int, int>> selectedPieces;
int highestLoadedSize; // the highest size of pieces currently loaded
std::vector<std::vector<Piece>> loadedPieces; // every loaded pieces by size
std::vector<std::vector<int>> convexPieces; // the list of convex loaded pieces by size
std::vector<std::vector<int>> holelessPieces; // the list of holeless loaded pieces by size
std::vector<std::vector<int>> otherPieces; // the list of other loaded pieces by size
std::vector<std::pair<int, int>> selectedPieces; // the list of all currently selected pieces
public:
/**
* Initializes a list of pieces up to size 0 (so no pieces)
*/
PiecesList();
/**
* Makes the list load all pieces of the specified size
* @return If it sucessfully loaded the pieces
*/
bool loadPieces(int size);
bool selectPieces(int size, int numbers);
/**
* Selects the specified piece
* @return If the piece could be selected
*/
bool selectPiece(int size, int number);
std::vector<std::pair<int, int>> getSelectedPieces();
/**
* Selects all pieces of the specified size
* @return If the pieces could be selected
*/
bool selectAllPieces(int size);
int getNumberOfPiecesOfOneSize(int size);
/**
* Selects all convex pieces of the specified size
* @return If the pieces could be selected
*/
bool selectConvexPieces(int size);
std::shared_ptr<Piece> getPiece(int size, int number);
/**
* Selects all holeless pieces of the specified size
* @return If the pieces could be selected
*/
bool selectHolelessPieces(int size);
/**
* Selects all other pieces of the specified size
* @return If the pieces could be selected
*/
bool selectOtherPieces(int size);
/**
* Unselects all previously selected pieces
*/
void unselectAll();
/**
* @return The highest loaded size of pieces
*/
int getHighestLoadedSize() const;
/**
* @return The number of pieces of the specified size
*/
int getNumberOfPieces(int size) const;
/**
* @return A copy of the indexes of all selected pieces
*/
std::vector<std::pair<int, int>> getSelectedPieces() const;
/**
* @return A copy of the piece corresponding to the specified index
*/
Piece getPiece(const std::pair<int, int>& pieceIndex) const;
private:
/**
* Adds empty vectors at the end of every pieces list
*/
void pushBackEmptyVectors();
};

View File

@@ -1,12 +1,5 @@
#include "Player.h"
static const int DAS_MIN_VALUE = 0; // 0ms
static const int DAS_MAX_VALUE = 30; // 500ms
static const int ARR_MIN_VALUE = 0; // 0ms
static const int ARR_MAX_VALUE = 30; // 500ms
static const int SDR_MIN_VALUE = 0; // 0ms
static const int SDR_MAX_VALUE = 6; // 100ms
Player::Player() {
// default settings
@@ -36,14 +29,14 @@ bool Player::setSDR(int SDR) {
return true;
}
int Player::getDAS() {
int Player::getDAS() const {
return this->DAS;
}
int Player::getARR() {
int Player::getARR() const {
return this->ARR;
}
int Player::getSDR() {
int Player::getSDR() const {
return this->SDR;
}

View File

@@ -1,5 +1,12 @@
#pragma once
static const int DAS_MIN_VALUE = 0; // the minimal selectable DAS value, equals to 0ms
static const int DAS_MAX_VALUE = 30; // the maximal selectable DAS value, equals to 500ms
static const int ARR_MIN_VALUE = 0; // the minimal selectable ARR value, equals to 0ms
static const int ARR_MAX_VALUE = 30; // the maximal selectable ARR value, equals to 500ms
static const int SDR_MIN_VALUE = 0; // the minimal selectable SDR value, equals to 0ms
static const int SDR_MAX_VALUE = 6; // the maximal selectable SDR value, equals to 100ms
/**
* The controls of a player
@@ -37,15 +44,15 @@ class Player {
/**
* @return DAS value
*/
int getDAS();
int getDAS() const;
/**
* @return ARR value
*/
int getARR();
int getARR() const;
/**
* @return SDR value
*/
int getSDR();
int getSDR() const;
};

View File

@@ -11,28 +11,29 @@
Generator::Generator() {
}
std::vector<Polyomino> Generator::generatePolyominos(unsigned int polyominoSize) {
this->validPolyominos.clear();
std::vector<Polyomino> Generator::generatePolyominoes(int polyominoSize) {
this->validPolyominoes.clear();
this->currentTestedShape.clear();
// no polyomino of size 0
if (polyominoSize == 0) return this->validPolyominos;
// a polyomino has at least 1 square
if (polyominoSize < 1) return this->validPolyominoes;
// start generating from the monomino
// always place the first cell at (0, 0)
this->currentTestedShape.insert(Position{0, 0});
std::map<Position, int> candidatePositions;
this->generate(polyominoSize, 0, 1, candidatePositions);
return this->validPolyominos;
return this->validPolyominoes;
}
void Generator::generate(unsigned int polyominoSize, int lastAddedPositionNumber, int nextAvaibleNumber, std::map<Position, int> candidatePositions) {
void Generator::generate(int polyominoSize, int lastAddedPositionNumber, int nextAvaibleNumber, std::map<Position, int> candidatePositions) {
// recursion stop
if (polyominoSize == this->currentTestedShape.size()) {
Polyomino candidate(this->currentTestedShape);
// we sort the rotations of the polyominos
// we sort the rotations of the polyominoes
std::vector<Polyomino> candidateRotations;
candidateRotations.reserve(4);
for (int i = 0; i < 4; i++) {
candidate.normalize();
candidateRotations.push_back(candidate);
@@ -42,7 +43,7 @@ void Generator::generate(unsigned int polyominoSize, int lastAddedPositionNumber
// we keep the polyomino only if it was generated in its lowest rotation
if (candidate == candidateRotations.at(0)) {
this->validPolyominos.push_back(candidate);
this->validPolyominoes.push_back(candidate);
}
return;
@@ -56,7 +57,7 @@ void Generator::generate(unsigned int polyominoSize, int lastAddedPositionNumber
this->tryToAddCandidatePosition(Position{position.x - 1, position.y}, nextAvaibleNumber, candidatePositions);
}
// generate polyominos for all positions with a higher number than the last one
// try adding a square only to positions with a higher number than the last one
for (auto [key, val] : candidatePositions) {
if (val > lastAddedPositionNumber) {
this->currentTestedShape.insert(key);

View File

@@ -8,11 +8,11 @@
/**
* A generator of one-sided polyominos of any size
* A generator of one-sided polyominoes of any size
*/
class Generator {
private:
std::vector<Polyomino> validPolyominos; // the list of already generated polyominos
std::vector<Polyomino> validPolyominoes; // the list of already generated polyominoes
std::set<Position> currentTestedShape; // the polyomino being created
public:
@@ -22,16 +22,16 @@ class Generator {
Generator();
/**
* Generates the list of all one-sided polyominos of the specified size
* @return The list of polyominos
* Generates the list of all one-sided polyominoes of the specified size
* @return The list of polyominoes
*/
std::vector<Polyomino> generatePolyominos(unsigned int polyominoSize);
std::vector<Polyomino> generatePolyominoes(int polyominoSize);
private:
/**
* Generates all one-sided polyominos of the specified size using the current tested shape
* Generates all one-sided polyominoes of the specified size using the current tested shape
*/
void generate(unsigned int polyominoSize, int lastAddedPositionNumber, int nextAvaibleNumber, std::map<Position, int> candidatePositions);
void generate(int polyominoSize, int lastAddedPositionNumber, int nextAvaibleNumber, std::map<Position, int> candidatePositions);
/**
* Checks wheter a candidate position can be added to the current tested shape

View File

@@ -12,6 +12,8 @@
Piece::Piece(const Polyomino& polyomino, Block blockType) :
polyomino(polyomino),
blockType(blockType) {
this->rotationState = NONE;
}
void Piece::rotate(Rotation rotation) {
@@ -21,9 +23,22 @@ void Piece::rotate(Rotation rotation) {
this->polyomino.rotate180();
if (rotation == COUNTERCLOCKWISE)
this->polyomino.rotateCCW();
this->rotationState += rotation;
}
std::set<Position> Piece::getPositions() const {
void Piece::defaultRotation() {
if (this->rotationState == CLOCKWISE)
this->polyomino.rotateCCW();
if (this->rotationState == DOUBLE)
this->polyomino.rotate180();
if (this->rotationState == COUNTERCLOCKWISE)
this->polyomino.rotateCW();
this->rotationState = NONE;
}
const std::set<Position>& Piece::getPositions() const {
return this->polyomino.getPositions();
}

View File

@@ -13,8 +13,9 @@
*/
class Piece {
private:
Polyomino polyomino; // a polyomino representing the piece, (0, 0) is downleft
Block blockType; // the block type of the piece
Polyomino polyomino; // a polyomino representing the piece, (0, 0) is downleft
Block blockType; // the block type of the piece
Rotation rotationState; // the current rotation of the piece
public:
/**
@@ -28,9 +29,14 @@ class Piece {
void rotate(Rotation rotation);
/**
* @return A copy of the list of positions of the piece
* Rotates the piece to its default rotation
*/
std::set<Position> getPositions() const;
void defaultRotation();
/**
* @return The list of positions of the piece
*/
const std::set<Position>& getPositions() const;
/**
* @return The length of the piece

View File

@@ -25,27 +25,17 @@ bool PiecesFiles::savePieces(int polyominoSize) const {
}
Generator generator;
std::vector<Polyomino> nMinos = generator.generatePolyominos(polyominoSize);
std::vector<Polyomino> nMinos = generator.generatePolyominoes(polyominoSize);
// sorting the polyominos is done after setting spawn position to ensure the order is always the same
// sorting the polyominoes is done after setting spawn position to ensure the order is always the same
for (Polyomino& nMino : nMinos) {
nMino.goToSpawnPosition();
}
std::sort(nMinos.begin(), nMinos.end());
Block pieceblock = firstPieceBlockType();
for (int i = 0; i < polyominoSize; i++) {
nextPieceBlockType(pieceblock);
}
for (const Polyomino& nMino : nMinos) {
// write polyomino length
char lengthByte = nMino.getLength();
piecesFile.write(&lengthByte, 1);
// write the type and block of the piece
char infoByte = (nMino.isConvex() << 7) + (nMino.hasHole() << 6) + pieceblock;
nextPieceBlockType(pieceblock);
// write the characteristics of the piece
char infoByte = (nMino.isConvex() << 7) + (nMino.hasHole() << 6) + nMino.getLength();
piecesFile.write(&infoByte, 1);
// write the positions of the piece
@@ -74,37 +64,42 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
holelessPieces.clear();
otherPieces.clear();
// we shift the first color of each size so that the small polyominoes (size 1-2-3) don't all have the same color
Block pieceBlock = firstPieceBlockType();
for (int i = 0; i < polyominoSize; i++) {
nextPieceBlockType(pieceBlock);
}
char convexMask = 0b1000'0000;
char holeMask = 0b0100'0000;
char blockMask = 0b0011'1111;
char lengthMask = 0b0011'1111;
char xMask = 0b1111'0000;
char yMask = 0b0000'1111;
char lengthByte;
char infoByte;
int i = 0;
// read piece length
while (piecesFile.get(lengthByte)) {
while (piecesFile.get(infoByte)) {
if (piecesFile.eof()) break;
// read piece infos
char infoByte;
piecesFile.get(infoByte);
bool isConvex = (infoByte & convexMask) >> 7;
bool hasHole = (infoByte & holeMask) >> 6;
Block block = Block(infoByte & blockMask);
int length = (infoByte & lengthMask);
// read positions
std::set<Position> piecepositions;
std::set<Position> piecePositions;
char positionByte;
for (int i = 0; i < polyominoSize; i++) {
piecesFile.get(positionByte);
int x = (positionByte & xMask) >> 4;
int y = positionByte & yMask;
piecepositions.insert(Position{x, y});
piecePositions.insert(Position{x, y});
}
// create piece
Piece readPiece(Polyomino(piecepositions, lengthByte), block);
Piece readPiece(Polyomino(piecePositions, length), pieceBlock);
nextPieceBlockType(pieceBlock);
pieces.push_back(readPiece);
if (isConvex) {
convexPieces.push_back(i);

View File

@@ -7,10 +7,10 @@
#include <iostream>
#include <climits>
#include <algorithm>
#include <utility>
Polyomino::Polyomino(const std::set<Position>& positions) {
// find min/max
int minX = INT_MAX;
int maxX = INT_MIN;
int minY = INT_MAX;
@@ -22,15 +22,14 @@ Polyomino::Polyomino(const std::set<Position>& positions) {
if (position.y > maxY) maxY = position.y;
}
// normalize
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 = newPositions;
// set polyomino length
this->length = std::max(maxX - minX + 1, maxY - minY + 1);
this->positions = std::move(newPositions);
}
Polyomino::Polyomino(const std::set<Position>& positions, int length) :
@@ -39,7 +38,6 @@ Polyomino::Polyomino(const std::set<Position>& positions, int length) :
}
void Polyomino::normalize() {
// find min values
int minX = INT_MAX;
int minY = INT_MAX;
for (Position position : this->positions) {
@@ -47,12 +45,11 @@ void Polyomino::normalize() {
if (position.y < minY) minY = position.y;
}
// translate the polyomino to the lowest unsigned values
std::set<Position> newPositions;
for (Position position : this->positions) {
newPositions.insert(Position{position.x - minX, position.y - minY});
}
this->positions = newPositions;
this->positions = std::move(newPositions);
}
void Polyomino::rotateCW() {
@@ -60,7 +57,7 @@ void Polyomino::rotateCW() {
for (Position position : this->positions) {
newPositions.insert(Position{position.y, (length - 1) - (position.x)});
}
this->positions = newPositions;
this->positions = std::move(newPositions);
}
void Polyomino::rotate180() {
@@ -68,7 +65,7 @@ void Polyomino::rotate180() {
for (Position position : this->positions) {
newPositions.insert(Position{(length - 1) - (position.x), (length - 1) - (position.y)});
}
this->positions = newPositions;
this->positions = std::move(newPositions);
}
void Polyomino::rotateCCW() {
@@ -76,12 +73,13 @@ void Polyomino::rotateCCW() {
for (Position position : this->positions) {
newPositions.insert(Position{(length - 1) - (position.y), position.x});
}
this->positions = newPositions;
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);
@@ -98,15 +96,15 @@ void Polyomino::goToSpawnPosition() {
linesCompleteness.at(3).at(position.x) += 1; // 3 = left to right = CCW
}
// count empty lines
// 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) {
// 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++;
}
@@ -124,11 +122,11 @@ void Polyomino::goToSpawnPosition() {
currentFlattestSides[3] = false;
}
// checks for the flattest sides
// checks for the flattest side
int sideToBeOn = -1;
this->checkForFlattestSide(linesCompleteness, currentFlattestSides, sideToBeOn, false);
// if all sides are as flat, checks for the most left-biased side
// 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);
}
@@ -148,12 +146,16 @@ void Polyomino::goToSpawnPosition() {
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();
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);
}
// find min
int minX = INT_MAX;
int minY = INT_MAX;
for (Position position : this->positions) {
@@ -161,32 +163,27 @@ void Polyomino::goToSpawnPosition() {
if (position.y < minY) minY = position.y;
}
// center the piece with an up bias if it is assymetric
if (sideToBeOn % 2 == 1) {
std::swap(verticalEmptyLines, horizontalEmptyLines);
}
// 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 = newPositions;
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++) {
// 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();
@@ -203,7 +200,7 @@ void Polyomino::checkForFlattestSide(const std::vector<std::vector<int>>& linesC
return;
}
// else we only keep the flattest from this round and ignore the others
// 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);
@@ -213,14 +210,12 @@ void Polyomino::checkForFlattestSide(const std::vector<std::vector<int>>& linesC
}
bool Polyomino::isConvex() const {
// for each line and column we check if every squares 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->positions.contains(Position{i, j})) {
if (completedLine) return false;
else startedLine = true;
@@ -228,7 +223,7 @@ bool Polyomino::isConvex() const {
else {
if (startedLine) completedLine = true;
}
// column check
if (this->positions.contains(Position{j, i})) {
if (completedColumn) return false;
else startedColumn = true;
@@ -267,7 +262,7 @@ void Polyomino::tryToInsertPosition(std::set<Position>& emptyPositions, const Po
tryToInsertPosition(emptyPositions, Position{candidate.x - 1, candidate.y});
}
std::set<Position> Polyomino::getPositions() const {
const std::set<Position>& Polyomino::getPositions() const {
return this->positions;
}
@@ -282,18 +277,17 @@ int Polyomino::getPolyominoSize() const {
bool Polyomino::operator<(const Polyomino& other) const {
if (this->length != other.length) return this->length < other.length;
// we check for all positions from left to right and top to bottom, until one has a square that the other doesn't
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;
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 {
bool Polyomino::operator==(const Polyomino& other) const {
return this->positions == other.positions;
}

View File

@@ -78,9 +78,9 @@ class Polyomino {
public:
/**
* @return A copy of the positions of the polyomino
* @return The positions of the polyomino
*/
std::set<Position> getPositions() const;
const std::set<Position>& getPositions() const;
/**
* @return The length of the polyomino
@@ -100,10 +100,10 @@ class Polyomino {
bool operator<(const Polyomino& other) const;
/**
* Equality operator, two polyominos are equal if their positions are the same, that means two polyominos of the same shape at different places will not be equal
* Equality operator, two polyominoes are equal if their positions are the same, that means two polyominoes of the same shape at different places will not be equal
* @return If the polyomino is equal to another
*/
bool operator ==(const Polyomino& other) const;
bool operator==(const Polyomino& other) const;
/**
* Stream output operator, adds a 2D grid representing the polyomino

View File

@@ -62,6 +62,14 @@ inline bool operator==(const Position& left, const Position& right) {
return (left.x == right.x) && (left.y == right.y);
}
/**
* Inequality operator, two positions aren't equal if their coordinates aren't
* @return If the two positions aren't equals
*/
inline bool operator!=(const Position& left, const Position& right) {
return (left.x != right.x) || (left.y != right.y);
}
/**
* Stream output operator, adds the coordinates of the position to the stream
* @return A reference to the output stream

View File

@@ -17,7 +17,7 @@ enum Rotation {
* @return A rotation corresponding to doing both rotations
*/
inline Rotation operator+(const Rotation& left, const Rotation& right) {
return Rotation((left + right) % 4);
return Rotation(((int) left + (int) right) % 4);
}
/**

346
src/TextUI/TextApp.cpp Normal file
View File

@@ -0,0 +1,346 @@
#include "TextApp.h"
#include "../Core/Menu.h"
#include <map>
#include <set>
#include <string>
#include <sstream>
#include <algorithm>
#include <cstdlib>
#include <exception>
static const int FRAMES_PER_INPUT = FRAMES_PER_SECOND / 2; // the number of frames that will pass everytime the player gives an input
static const int MAXIMUM_PIECE_SIZE = 10; // the maximum size of pieces that will be loaded and utilizable
static const int DEFAULT_PIECE_SIZE = 4; // the default size of pieces that will be selected when first running the app
static const int MAXIMUM_BOARD_WIDTH = 30; // the maximum selectable width of the board
static const int MAXIMUM_BOARD_HEIGHT = 40; // the maximum selectable height of the board
static const Gamemode DEFAULT_GAMEMODE = SPRINT; // the gamemode that will be used when starting a new game
TextApp::TextApp() {
this->defaultKeybinds();
this->gameMenu.getPiecesList().loadPieces(MAXIMUM_PIECE_SIZE);
this->gameMenu.getPiecesList().selectAllPieces(DEFAULT_PIECE_SIZE);
this->gameMenu.getPlayerControls().setDAS(FRAMES_PER_INPUT);
this->gameMenu.getPlayerControls().setARR(FRAMES_PER_INPUT);
this->gameMenu.getPlayerControls().setSDR(0);
}
void TextApp::run() {
bool quit = false;
while (!quit) {
std::cout << "\n\n\n";
std::cout << "===| WELCOME TO JMINOS! |===" << std::endl;
std::cout << "1- Change pieces" << std::endl;
std::cout << "2- Change board" << std::endl;
std::cout << "3- See controls" << std::endl;
std::cout << "4- Start game" << std::endl;
std::cout << "5- Quit" << std::endl;
std::cout << "Choice: ";
std::string answer;
std::getline(std::cin, answer);
int selectedAnswer = 0;
try {
selectedAnswer = std::stoi(answer);
}
catch (std::exception ignored) {}
switch (selectedAnswer) {
case 1 : {this->choosePieces(); break;}
case 2 : {this->chooseBoardSize(); break;}
case 3 : {this->seeKeybinds(); break;}
case 4 : {this->startGame(); break;}
case 5 : {quit = true; break;}
default : std::cout << "Invalid answer!" << std::endl;
}
}
std::cout << "===| SEE YA NEXT TIME! |===";
}
void TextApp::choosePieces() {
std::cout << "\n\n\n";
std::cout << "Choose which piece sizes to play with (from 1 to " << MAXIMUM_PIECE_SIZE << "), separate mutltiple sizes with blank spaces." << std::endl;
std::cout << "Choice: ";
std::string answer;
std::getline(std::cin, answer);
this->gameMenu.getPiecesList().unselectAll();
std::cout << "Selected pieces of sizes:";
std::stringstream answerStream(answer);
for (std::string size; std::getline(answerStream, size, ' ');) {
try {
int selectedSize = std::stoi(size);
if (selectedSize >= 1 && selectedSize <= MAXIMUM_PIECE_SIZE) {
if (this->gameMenu.getPiecesList().selectAllPieces(selectedSize)) {
std::cout << " " << selectedSize;
}
}
}
catch (std::exception ignored) {}
}
std::string waiting;
std::getline(std::cin, waiting);
}
void TextApp::chooseBoardSize() {
std::string answer;
std::cout << "\n\n\n";
std::cout << "Current board width and height: " << this->gameMenu.getBoardWidth() << "x" << this->gameMenu.getBoardHeight() << std::endl;
std::cout << "Choose the width of the board (from 1 to " << MAXIMUM_BOARD_WIDTH << ")." << std::endl;
std::cout << "Choice: ";
std::getline(std::cin, answer);
try {
int selectedSize = std::stoi(answer);
if (selectedSize >= 1 && selectedSize <= MAXIMUM_BOARD_WIDTH) {
this->gameMenu.setBoardWidth(selectedSize);
}
}
catch (std::exception ignored) {}
std::cout << "Choose the height of the board (from 1 to " << MAXIMUM_BOARD_HEIGHT << ")." << std::endl;
std::cout << "Choice: ";
std::getline(std::cin, answer);
try {
int selectedSize = std::stoi(answer);
if (selectedSize >= 1 && selectedSize <= MAXIMUM_BOARD_HEIGHT) {
this->gameMenu.setBoardHeight(selectedSize);
}
}
catch (std::exception ignored) {}
std::cout << "New board width and height: " << this->gameMenu.getBoardWidth() << "x" << this->gameMenu.getBoardHeight();
std::string waiting;
std::getline(std::cin, waiting);
}
void TextApp::seeKeybinds() const {
std::cout << "\n\n\n";
std::cout << "Quit/Pause/Retry: quit/pause/retry" << std::endl;
std::cout << "Hold : h" << std::endl;
std::cout << "Soft/Hard drop : sd/hd" << std::endl;
std::cout << "Move left/right : l/r" << std::endl;
std::cout << "Rotate 0/CW/180/CCW: c/cw/cc/ccw" << std::endl;
std::cout << "\n";
std::cout << "To do several actions at the same time, separe them with blank spaces." << std::endl;
std::string waiting;
std::getline(std::cin, waiting);
}
void TextApp::defaultKeybinds() {
this->keybinds.clear();
this->keybinds.insert({"quit", QUIT});
this->keybinds.insert({"pause", PAUSE});
this->keybinds.insert({"retry", RETRY});
this->keybinds.insert({"h", HOLD});
this->keybinds.insert({"sd", SOFT_DROP});
this->keybinds.insert({"hd", HARD_DROP});
this->keybinds.insert({"l", MOVE_LEFT});
this->keybinds.insert({"r", MOVE_RIGHT});
this->keybinds.insert({"c", ROTATE_0});
this->keybinds.insert({"cw", ROTATE_CW});
this->keybinds.insert({"cc", ROTATE_180});
this->keybinds.insert({"ccw", ROTATE_CCW});
}
void TextApp::startGame() const {
Game game = this->gameMenu.startGame(DEFAULT_GAMEMODE);
game.start();
std::cout << "\n\n\n";
this->printGame(game);
bool quit = false;
bool paused = false;
std::string answer;
while (!quit) {
std::cout << "Actions: ";
std::getline(std::cin, answer);
std::stringstream answerStream(answer);
std::vector<std::string> actions;
for (std::string action; std::getline(answerStream, action, ' ');) {
actions.push_back(action);
}
std::set<Action> playerActions;
std::set<Action> lastFrameActions;
std::set<Action> metaActions;
for (std::string action : actions) {
try {
Action playerAction = this->keybinds.at(action);
if (playerAction == PAUSE || playerAction == RETRY || playerAction == quit) {
metaActions.insert(playerAction);
}
else {
if (playerAction == SOFT_DROP || playerAction == MOVE_LEFT || playerAction == MOVE_RIGHT) {
playerActions.insert(playerAction);
lastFrameActions.insert(playerAction);
}
else if (playerAction == HOLD || playerAction == HARD_DROP) {
lastFrameActions.insert(playerAction);
}
else {
playerActions.insert(playerAction);
}
}
}
catch (std::exception ignored) {}
}
if (metaActions.contains(PAUSE)) {
paused = (!paused);
}
if (!paused) {
if (metaActions.contains(QUIT)) {
quit = true;
}
else if (metaActions.contains(RETRY)) {
game.reset();
game.start();
}
else {
for (int i = 0; i < (FRAMES_PER_INPUT - 1); i++) {
game.nextFrame(playerActions);
}
game.nextFrame(lastFrameActions);
}
}
if (!quit) {
std::cout << "\n\n\n";
if (paused) {
std::cout << "--<[PAUSED]>--" << std::endl;
}
this->printGame(game);
}
if (game.hasLost()) {
quit = true;
std::cout << "You lost!" << std::endl;
}
else if (game.hasWon()) {
quit = true;
std::cout << "You won!" << std::endl;
}
}
}
void TextApp::printGame(const Game& game) const {
int maxHeight = game.getBoard().getGridHeight();
if (game.getActivePiece() != nullptr) {
for (const Position& position : game.getActivePiece()->getPositions()) {
maxHeight = std::max(maxHeight, position.y + game.getActivePiecePosition().y);
}
}
bool lineCountPrinted = false;
bool holdBoxStartedPrinting = false;
bool holdBoxFinishedPrinting = false;
bool nextQueueStartedPrinting = false;
bool nextQueueFinishedPrinting = false;
int nextQueuePrintedPiece;
int printedPieceLineHeight;
for (int y = maxHeight; y >= 0; y--) {
for (int x = 0; x < game.getBoard().getWidth(); x++) {
/* BOARD PRINTING */
bool isActivePieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position{x, y} - game.getActivePiecePosition()));
bool isGhostPieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position{x, y} - game.ghostPiecePosition()));
Block block = (isActivePieceHere || isGhostPieceHere) ? game.getActivePiece()->getBlockType() : game.getBoard().getBlock(Position{x, y});
if (isActivePieceHere || isGhostPieceHere) {
std::cout << getConsoleColorCode(block);
}
else {
std::cout << getResetConsoleColorCode();
}
if (block != NOTHING && (!(isGhostPieceHere && !isActivePieceHere))) {
std::cout << "*";
}
else {
if (y < game.getBoard().getBaseHeight()) {
if (isGhostPieceHere) {
std::cout << "=";
}
else {
std::cout << "-";
}
}
else {
std::cout << " ";
}
}
}
if (y < game.getBoard().getGridHeight()) {
/* SIDEBAR PRINTING */
std::cout << " ";
if (!lineCountPrinted) {
std::cout << getResetConsoleColorCode() << "Lines: " << game.getClearedLines();
lineCountPrinted = true;
}
else if (!holdBoxFinishedPrinting) {
if (!holdBoxStartedPrinting) {
std::cout << getResetConsoleColorCode() << "Hold:";
printedPieceLineHeight = (game.getHeldPiece() == nullptr) ? -1 : (game.getHeldPiece()->getLength() - 1);
holdBoxStartedPrinting = true;
}
else {
for (int i = 0; i < game.getHeldPiece()->getLength(); i++) {
if (game.getHeldPiece()->getPositions().contains(Position{i, printedPieceLineHeight})) {
std::cout << getConsoleColorCode(game.getHeldPiece()->getBlockType()) << "*";
}
else {
std::cout << getResetConsoleColorCode() << "-";
}
}
printedPieceLineHeight--;
}
if (printedPieceLineHeight < 0) {
holdBoxFinishedPrinting = true;
}
}
else if (!nextQueueFinishedPrinting) {
if (!nextQueueStartedPrinting) {
std::cout << getResetConsoleColorCode() << "Next:";
printedPieceLineHeight = (game.getNextPieces().size() == 0) ? -1 : (game.getNextPieces().at(0).getLength() - 1);
nextQueuePrintedPiece = 0;
nextQueueStartedPrinting = true;
}
else {
for (int i = 0; i < game.getNextPieces().at(nextQueuePrintedPiece).getLength(); i++) {
if (game.getNextPieces().at(nextQueuePrintedPiece).getPositions().contains(Position{i, printedPieceLineHeight})) {
std::cout << getConsoleColorCode(game.getNextPieces().at(nextQueuePrintedPiece).getBlockType()) << "*";
}
else {
std::cout << getResetConsoleColorCode() << "-";
}
}
printedPieceLineHeight--;
}
if (printedPieceLineHeight < 0) {
nextQueuePrintedPiece++;
if (nextQueuePrintedPiece >= game.getNextPieces().size()) {
nextQueueFinishedPrinting = true;
}
else {
printedPieceLineHeight = game.getNextPieces().at(nextQueuePrintedPiece).getLength() - 1;
}
}
}
}
std::cout << "\n";
}
std::cout << getResetConsoleColorCode();
}

58
src/TextUI/TextApp.h Normal file
View File

@@ -0,0 +1,58 @@
#pragma once
#include "../Core/Menu.h"
#include <map>
#include <string>
/**
* Textual interface for the app
*/
class TextApp {
private:
Menu gameMenu; // the interface with the core of the app
std::map<std::string, Action> keybinds; // what the player needs to type to perform in-game actions
public:
/**
* Initializes the app with default settings
*/
TextApp();
/**
* Runs the app
*/
void run();
private:
/**
* Sub-menu to select which pieces to play with
*/
void choosePieces();
/**
* Sub-menu to change the size of the board
*/
void chooseBoardSize();
/**
* Sub-menu to see the in-game controls
*/
void seeKeybinds() const;
/**
* Sets the controls to their default values
*/
void defaultKeybinds();
/**
* Starts a new game with the current settings
*/
void startGame() const;
/**
* Prints the current state of a game to the console
*/
void printGame(const Game& game) const;
};

View File

@@ -1,13 +1,8 @@
#include "../Pieces/PiecesFiles.h"
#include "../Pieces/Generator.h"
#include "GameBoard.h"
#include "PiecesList.h"
#include "../Pieces/PiecesFiles.h"
#include "TextApp.h"
#include <chrono>
#include <string>
#include <algorithm>
#include <filesystem>
#include <fstream>
void testGeneratorForAllSizes(int amount);
@@ -15,27 +10,19 @@ void testGeneratorForOneSize(int size);
void testGeneratorByprintingAllNminos(int n);
void testStoringAndRetrievingPieces(int size);
void generateFilesForAllSizes(int amount);
void generateFilesForOneSize(int size);
void loadFromFilesForOneSize(int size);
void readStatsFromFilesForAllSizes(int amount);
int main(int argc, char** argv) {
std::srand(std::time(NULL));
int sizeSelected = 3;
PiecesList pli;
std::shared_ptr<PiecesList> pl = std::make_shared<PiecesList>(pli);
pl->loadPieces(sizeSelected);
for (int i = 0; i < pl->getNumberOfPiecesOfOneSize(sizeSelected); i++) {
pl->selectPieces(sizeSelected, i);
}
// dev: generate files if it hasn't been done before, UI will NOT generate the files
//generateFilesForAllSizes(10);
GameBoard gb(10, 4, pl, 1);
for (int i = 0; i < pl->getNumberOfPiecesOfOneSize(sizeSelected) * 3; i++) {
gb.spawnNextPiece();
std::cout << gb << std::endl;
}
TextApp UI;
UI.run();
return 0;
}
@@ -50,11 +37,11 @@ void testGeneratorForAllSizes(int amount) {
for (int i = 1; i <= amount; i++) {
auto t1 = high_resolution_clock::now();
std::vector<Polyomino> n_minos = generator.generatePolyominos(i);
std::vector<Polyomino> n_minos = generator.generatePolyominoes(i);
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << "generated " << n_minos.size() << " polyominos of size " << i << " in " << ms_double.count() << "ms" << std::endl;
std::cout << "generated " << n_minos.size() << " polyominoes of size " << i << " in " << ms_double.count() << "ms" << std::endl;
}
}
@@ -68,7 +55,7 @@ void testGeneratorForOneSize(int size) {
std::cout << "Generating " << size << "-minos" << std::endl;
for (int i = 0; i < 10; i++) {
auto t1 = high_resolution_clock::now();
std::vector<Polyomino> n_minos = generator.generatePolyominos(size);
std::vector<Polyomino> n_minos = generator.generatePolyominoes(size);
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
@@ -78,7 +65,7 @@ void testGeneratorForOneSize(int size) {
void testGeneratorByprintingAllNminos(int n) {
Generator generator;
std::vector<Polyomino> n_minos = generator.generatePolyominos(n);
std::vector<Polyomino> n_minos = generator.generatePolyominoes(n);
for (Polyomino& n_mino : n_minos) {
n_mino.goToSpawnPosition();
@@ -122,8 +109,8 @@ void generateFilesForAllSizes(int amount) {
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
PiecesFiles piecesFiles;
for (int i = 1; i <= amount; i++) {
auto t1 = high_resolution_clock::now();
piecesFiles.savePieces(i);
@@ -133,12 +120,12 @@ void generateFilesForAllSizes(int amount) {
std::cout << "Generated pieces files for size " << i << " in " << ms_double.count() << "ms" << std::endl;
}
std::vector<Piece> pieces;
std::vector<int> convexPieces;
std::vector<int> holelessPieces;
std::vector<int> otherPieces;
for (int i = 1; i <= amount; i++) {
auto t1 = high_resolution_clock::now();
std::vector<Piece> pieces;
std::vector<int> convexPieces;
std::vector<int> holelessPieces;
std::vector<int> otherPieces;
piecesFiles.loadPieces(i, pieces, convexPieces, holelessPieces, otherPieces);
auto t2 = high_resolution_clock::now();
@@ -147,6 +134,46 @@ void generateFilesForAllSizes(int amount) {
}
}
void generateFilesForOneSize(int size) {
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
PiecesFiles piecesFiles;
std::cout << "Generating " << size << "-minos files" << std::endl;
for (int i = 0; i < 10; i++) {
auto t1 = high_resolution_clock::now();
piecesFiles.savePieces(size);
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << ms_double.count() << "ms" << std::endl;
}
}
void loadFromFilesForOneSize(int size) {
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
PiecesFiles piecesFiles;
std::vector<Piece> pieces;
std::vector<int> convexPieces;
std::vector<int> holelessPieces;
std::vector<int> otherPieces;
std::cout << "Loading " << size << "-minos from files" << std::endl;
for (int i = 0; i < 10; i++) {
auto t1 = high_resolution_clock::now();
piecesFiles.loadPieces(size, pieces, convexPieces, holelessPieces, otherPieces);
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << ms_double.count() << "ms" << std::endl;
}
}
void readStatsFromFilesForAllSizes(int amount) {
PiecesFiles piecesFiles;
for (int i = 1; i <= amount; i++) {

View File

@@ -5,6 +5,7 @@ target("main")
set_rundir(".")
add_files("./src/Pieces/*.cpp")
add_files("./src/Core/*.cpp")
add_files("./src/TextUI/*.cpp")
set_optimize("fastest")