fixed game logic

This commit is contained in:
2025-03-05 19:02:51 +01:00
parent 2fbe4a6052
commit 74797e935a
15 changed files with 186 additions and 138 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -1,7 +1,7 @@
# 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
@@ -26,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.
@@ -40,7 +65,7 @@ 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:

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,19 +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 (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 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

@@ -26,6 +26,8 @@ void Bag::jumpToNextBag() {
this->currentBag.push_back(pieceIndex);
}
this->nextBag.clear();
this->prepareNext();
}
Piece Bag::lookNext() {

View File

@@ -64,6 +64,7 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
if (this->leftARETime == 0) {
if (AREJustEnded) {
this->lost = this->board.spawnNextPiece();
this->resetPiece(true);
}
/* IRS and IHS */
@@ -73,33 +74,17 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
+ ((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;
}
else {
this->lost = true;
}
this->lost = (!this->board.hold(initialRotation));
}
else {
if (initialRotation != NONE || this->initialActions.contains(ROTATE_0)) {
Position before = this->board.getActivePiecePosition();
if (this->board.rotate(initialRotation)) {
this->totalLockDelay = 0;
if (before != this->board.getActivePiecePosition()) {
this->subVerticalPosition = 0;
}
}
else {
this->lost = true;
}
if ((initialRotation != NONE) || this->initialActions.contains(ROTATE_0)) {
this->lost = (!this->board.rotate(initialRotation));
}
}
if (this->lost) {
if (initialRotation == NONE && (!this->initialActions.contains(ROTATE_0))) {
this->board.rotate(NONE);
if (initialRotation == NONE) {
this->lost = (!this->board.rotate(initialRotation));
}
}
if (this->lost) {
@@ -110,23 +95,22 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
/* 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 */
Position before = this->board.getActivePiecePosition();
if (playerActions.contains(MOVE_LEFT)) {
this->movePiece(-1);
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;
}
else if (playerActions.contains(MOVE_RIGHT)) {
this->movePiece(1);
}
else {
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;
}
if (before != this->board.getActivePiecePosition()) {
@@ -134,31 +118,17 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
}
/* ROTATIONS */
before = this->board.getActivePiecePosition();
if (playerActions.contains(ROTATE_0) && (!this->heldActions.contains(ROTATE_0))) {
if (this->board.rotate(NONE)) {
this->totalLockDelay = 0;
}
this->rotatePiece(NONE);
}
if (playerActions.contains(ROTATE_CW) && (!this->heldActions.contains(ROTATE_CW))) {
if (this->board.rotate(CLOCKWISE)) {
this->totalLockDelay = 0;
}
this->rotatePiece(CLOCKWISE);
}
if (playerActions.contains(ROTATE_180) && (!this->heldActions.contains(ROTATE_180))) {
if (this->board.rotate(DOUBLE)) {
this->totalLockDelay = 0;
}
this->rotatePiece(DOUBLE);
}
if (playerActions.contains(ROTATE_CCW) && (!this->heldActions.contains(ROTATE_CCW))) {
if (this->board.rotate(COUNTERCLOCKWISE)) {
this->totalLockDelay = 0;
}
}
if (before != this->board.getActivePiecePosition()) {
this->subVerticalPosition = 0;
this->rotatePiece(COUNTERCLOCKWISE);
}
/* SOFT DROP */
@@ -238,45 +208,61 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
this->initialActions.insert(action);
}
if (playerActions.contains(MOVE_LEFT)) {
this->heldDAS = std::min(-1, this->heldDAS - 1);
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 (playerActions.contains(MOVE_RIGHT)) {
this->heldDAS = std::max(1, this->heldDAS + 1);
}
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::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) {
std::cout << movement << std::endl;
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) == appliedDAS + 1) {
if (movement == -1) this->board.moveLeft();
if (movement == 1) this->board.moveRight();
}
if (abs(this->heldDAS) > appliedDAS + 1) {
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();
@@ -285,6 +271,17 @@ void Game::movePiece(int movement) {
}
}
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);
@@ -300,16 +297,12 @@ void Game::lockPiece() {
this->score += clearScore;
}
this->B2BChain = B2BConditionsAreMet;
this->subVerticalPosition = 0;
this->totalLockDelay = 0;
this->totalForcedLockDelay = 0;
this->heldARR = 0;
if (!this->hasWon()) {
this->leftARETime = this->parameters.getARE();
if (this->leftARETime == 0) {
this->lost = this->board.spawnNextPiece();
this->resetPiece(true);
}
}
}

View File

@@ -61,10 +61,20 @@ class Game {
private:
/**
* Move the piece in the specified direction
* Resets the piece's parameter
*/
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
*/

View File

@@ -4,17 +4,17 @@
#include "Player.h"
#include "Game.h"
static const int FRAMES_PER_SECOND = 60; // the number of frames per second, all the values in the game were choosen with this number in mind
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 the UI and the core of the game
* The interface between an UI and the core of the app
*/
class Menu {
private:
std::shared_ptr<PiecesList> piecesList; // the list of pieces in the game
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

View File

@@ -11,19 +11,19 @@
Generator::Generator() {
}
std::vector<Polyomino> Generator::generatePolyominos(int polyominoSize) {
this->validPolyominos.clear();
std::vector<Polyomino> Generator::generatePolyominoes(int polyominoSize) {
this->validPolyominoes.clear();
this->currentTestedShape.clear();
// a polyomino has at least 1 square
if (polyominoSize < 1) return this->validPolyominos;
if (polyominoSize < 1) return this->validPolyominoes;
// 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(int polyominoSize, int lastAddedPositionNumber, int nextAvaibleNumber, std::map<Position, int> candidatePositions) {
@@ -31,7 +31,7 @@ void Generator::generate(int polyominoSize, int lastAddedPositionNumber, int nex
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++) {
@@ -43,7 +43,7 @@ void Generator::generate(int polyominoSize, int lastAddedPositionNumber, int nex
// 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;

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,14 +22,14 @@ 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(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(int polyominoSize, int lastAddedPositionNumber, int nextAvaibleNumber, std::map<Position, int> candidatePositions);

View File

@@ -25,9 +25,9 @@ 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();
}
@@ -64,7 +64,7 @@ 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 polyominos (size 1-2-3) don't all have the same color
// 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);

View File

@@ -100,7 +100,7 @@ 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;

View File

@@ -24,7 +24,7 @@ TextApp::TextApp() {
this->gameMenu.getPiecesList().loadPieces(MAXIMUM_PIECE_SIZE);
this->gameMenu.getPiecesList().selectAllPieces(DEFAULT_PIECE_SIZE);
this->gameMenu.getPlayerControls().setDAS(FRAMES_PER_INPUT - 1);
this->gameMenu.getPlayerControls().setDAS(FRAMES_PER_INPUT);
this->gameMenu.getPlayerControls().setARR(FRAMES_PER_INPUT);
this->gameMenu.getPlayerControls().setSDR(0);
}
@@ -130,7 +130,6 @@ void TextApp::seeKeybinds() const {
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::cout << "Remember that for certains actions like hard dropping, you need to release it before using it again, even if a different piece spawned.";
std::string waiting;
std::getline(std::cin, waiting);
}
@@ -171,28 +170,47 @@ void TextApp::startGame() const {
}
std::set<Action> playerActions;
std::set<Action> lastFrameActions;
std::set<Action> metaActions;
for (std::string action : actions) {
if (this->keybinds.contains(action)) {
playerActions.insert(this->keybinds.at(action));
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 (playerActions.contains(PAUSE)) {
if (metaActions.contains(PAUSE)) {
paused = (!paused);
}
if (!paused) {
if (playerActions.contains(QUIT)) {
if (metaActions.contains(QUIT)) {
quit = true;
}
else if (playerActions.contains(RETRY)) {
else if (metaActions.contains(RETRY)) {
game.reset();
game.start();
}
else {
for (int i = 0; i < FRAMES_PER_INPUT; i++) {
for (int i = 0; i < (FRAMES_PER_INPUT - 1); i++) {
game.nextFrame(playerActions);
}
game.nextFrame(lastFrameActions);
}
}

View File

@@ -37,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;
}
}
@@ -55,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;
@@ -65,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();