initial commit
This commit is contained in:
18
src/Core/Action.h
Normal file
18
src/Core/Action.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/**
|
||||
* The list of actions that can be taken by the player
|
||||
*/
|
||||
enum Action {
|
||||
PAUSE,
|
||||
RETRY,
|
||||
HOLD,
|
||||
SOFT_DROP,
|
||||
HARD_DROP,
|
||||
MOVE_LEFT,
|
||||
MOVE_RIGHT,
|
||||
ROTATE_CW,
|
||||
ROTATE_180,
|
||||
ROTATE_CCW
|
||||
};
|
||||
50
src/Core/Bag.cpp
Normal file
50
src/Core/Bag.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "Bag.h"
|
||||
|
||||
#include "../Pieces/Piece.h"
|
||||
|
||||
#include <Vector>
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
Bag::Bag(const std::vector<Piece>& pieces) : pieces(pieces) {
|
||||
// initialize bags
|
||||
this->currentBag.clear();
|
||||
for (int i = 0; i < this->pieces.size(); i++) {
|
||||
this->currentBag.push_back(i);
|
||||
}
|
||||
this->nextBag.clear();
|
||||
|
||||
// prepare first piece
|
||||
this->prepareNext();
|
||||
}
|
||||
|
||||
Piece Bag::lookNext() {
|
||||
// return the next piece
|
||||
return this->pieces.at(this->next);
|
||||
}
|
||||
|
||||
Piece Bag::getNext() {
|
||||
// get the piece to return
|
||||
int nextIndex = this->next;
|
||||
|
||||
// prepare the piece even after the next
|
||||
this->prepareNext();
|
||||
|
||||
// return the next piece
|
||||
return this->pieces.at(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);
|
||||
}
|
||||
39
src/Core/Bag.h
Normal file
39
src/Core/Bag.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Pieces/Piece.h"
|
||||
|
||||
#include <Vector>
|
||||
|
||||
|
||||
/**
|
||||
* A litteral bag of pieces, in which you take each of its piece randomly one by one then start again with a new bag
|
||||
*/
|
||||
class Bag {
|
||||
private:
|
||||
std::vector<Piece> pieces; // the pieces the bag can dispense
|
||||
int next; // the next piece to give
|
||||
std::vector<int> currentBag; // the list of pieces that are still to be taken out before starting a new bag
|
||||
std::vector<int> nextBag; // the list of pieces that have been taken out of the current bag and have been placed in the next
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a new bag of the specified list of pieces
|
||||
*/
|
||||
Bag(const std::vector<Piece>& pieces);
|
||||
|
||||
/**
|
||||
* Looks at what the next picked piece will be
|
||||
*/
|
||||
Piece lookNext();
|
||||
|
||||
/**
|
||||
* Picks a new piece from the current bag
|
||||
*/
|
||||
Piece getNext();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Prepare the next picked piece in advance
|
||||
*/
|
||||
void prepareNext();
|
||||
};
|
||||
117
src/Core/Board.cpp
Normal file
117
src/Core/Board.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
#include "Board.h"
|
||||
|
||||
#include "../Pieces/Piece.h"
|
||||
|
||||
#include <Vector>
|
||||
#include <Set>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
Board::Board(int width, int height) : width(width), height(height) {
|
||||
std::vector<Color> emptyRow;
|
||||
for (int i = 0; i < width; i ++) {
|
||||
emptyRow.push_back(NOTHING);
|
||||
}
|
||||
|
||||
// initialize grid
|
||||
this->grid.clear();
|
||||
for (int j = 0; j < height; j++) {
|
||||
this->grid.push_back(emptyRow);
|
||||
}
|
||||
}
|
||||
|
||||
void Board::addBlock(const Cell& position, Color block) {
|
||||
// if the block is out of bounds we discard it
|
||||
if (position.x < 0 || position.x >= this->width || position.y < 0) return;
|
||||
|
||||
// resize the grid if needed
|
||||
if (position.y >= this->grid.size()) {
|
||||
std::vector<Color> emptyRow;
|
||||
for (int i = 0; i < width; i ++) {
|
||||
emptyRow.push_back(NOTHING);
|
||||
}
|
||||
for (int j = this->grid.size(); j <= position.y; j++) {
|
||||
this->grid.push_back(emptyRow);
|
||||
}
|
||||
}
|
||||
|
||||
// change the block in the grid
|
||||
this->grid.at(position.y).at(position.x) = block;
|
||||
}
|
||||
|
||||
int Board::clearRows() {
|
||||
std::vector<Color> emptyRow;
|
||||
for (int i = 0; i < width; i ++) {
|
||||
emptyRow.push_back(NOTHING);
|
||||
}
|
||||
|
||||
// check from top to bottom
|
||||
int clearedLines = 0;
|
||||
for (int j = this->grid.size() - 1; j >= 0; j--) {
|
||||
// check if a line has a block on every column
|
||||
bool isFull = true;
|
||||
for (int i = 0; i < this->width; i++) {
|
||||
if (this->grid.at(j).at(i) == NOTHING) {
|
||||
isFull = false;
|
||||
}
|
||||
}
|
||||
|
||||
// if it has, erase it and add a new row at the top
|
||||
if (isFull) {
|
||||
this->grid.erase(this->grid.begin() + j);
|
||||
if(this->grid.size() < height) this->grid.push_back(emptyRow);
|
||||
clearedLines++;
|
||||
}
|
||||
}
|
||||
|
||||
return clearedLines;
|
||||
}
|
||||
|
||||
Color Board::getBlock(const Cell& position) const {
|
||||
// if the block is out of bounds
|
||||
if (position.x < 0 || position.x >= this->width || position.y < 0) return OUT_OF_BOUND;
|
||||
|
||||
// if the block is higher than the current grid, since it can grow indefinitely we do as if it was there but empty
|
||||
if (position.y >= this->grid.size()) return NOTHING;
|
||||
|
||||
// else get the color in the grid
|
||||
return this->grid.at(position.y).at(position.x);
|
||||
}
|
||||
|
||||
std::vector<std::vector<Color>> Board::getBlocks() const {
|
||||
return this->grid;
|
||||
}
|
||||
|
||||
int Board::getGridHeight() const {
|
||||
return this->grid.size();
|
||||
}
|
||||
|
||||
int Board::getBaseHeight() const {
|
||||
return this->height;
|
||||
}
|
||||
|
||||
int Board::getWidth() const {
|
||||
return this->width;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Board& board) {
|
||||
// print the board
|
||||
for (int y = board.grid.size() - 1; y >= 0; y--) {
|
||||
for (int x = 0; x < board.width; x++) {
|
||||
Color block = board.grid.at(y).at(x);
|
||||
os << COLOR_CODES[block];
|
||||
if (block != NOTHING) {
|
||||
os << "*";
|
||||
}
|
||||
else {
|
||||
os << "-";
|
||||
}
|
||||
}
|
||||
os << std::endl;
|
||||
}
|
||||
|
||||
// reset console color
|
||||
os << COLOR_RESET;
|
||||
|
||||
return os;
|
||||
}
|
||||
63
src/Core/Board.h
Normal file
63
src/Core/Board.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Pieces/Piece.h"
|
||||
|
||||
#include <Vector>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
/**
|
||||
* A 2D grid of blocks
|
||||
*/
|
||||
class Board {
|
||||
private:
|
||||
std::vector<std::vector<Color>> grid; // the grid, (0,0) is downleft
|
||||
int width; // the width of the grid
|
||||
int height; // the base height of the grid, which can extends indefinitely
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a new board of the specified size
|
||||
*/
|
||||
Board(int width, int height);
|
||||
|
||||
/**
|
||||
* Change the color of the specified block, if the block is out of bounds it is simply ignored
|
||||
*/
|
||||
void addBlock(const Cell& position, Color block);
|
||||
|
||||
/**
|
||||
* Clears any complete row and moves down the rows on top, returns the number of cleared rows
|
||||
*/
|
||||
int clearRows();
|
||||
|
||||
/**
|
||||
* Returns the color of the block at the specified position
|
||||
*/
|
||||
Color getBlock(const Cell& position) const;
|
||||
|
||||
/**
|
||||
* Returns a copy of the grid
|
||||
*/
|
||||
std::vector<std::vector<Color>> getBlocks() const;
|
||||
|
||||
/**
|
||||
* Returns the actual height of the grid
|
||||
*/
|
||||
int getGridHeight() const;
|
||||
|
||||
/**
|
||||
* Returns the base height of the grid
|
||||
*/
|
||||
int getBaseHeight() const;
|
||||
|
||||
/**
|
||||
* Returns the width of the grid
|
||||
*/
|
||||
int getWidth() const;
|
||||
|
||||
/**
|
||||
* Stream output operator, adds a 2D grid representing the board
|
||||
*/
|
||||
friend std::ostream& operator<<(std::ostream& os, const Board& board);
|
||||
};
|
||||
300
src/Core/Game.cpp
Normal file
300
src/Core/Game.cpp
Normal file
@@ -0,0 +1,300 @@
|
||||
#include "Game.h"
|
||||
|
||||
#include "GameBoard.h"
|
||||
#include "GameParameters.h"
|
||||
#include "Action.h"
|
||||
|
||||
#include <Vector>
|
||||
|
||||
static const int SUBPX_PER_ROW = 60; // the number of position the active piece can take "between" two rows
|
||||
static const int SOFT_DROP_SCORE = 1; // the score gained by line soft dropped
|
||||
static const int HARD_DROP_SCORE = 2; // the score gained by line hard dropped
|
||||
static const int LINE_CLEAR_BASE_SCORE = 100; // the score value of clearing a single line
|
||||
static const int B2B_SCORE_MULTIPLIER = 2; // by how much havaing B2B on multiplies the score of the line clear
|
||||
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::vector<Piece>& bag) : parameters(gamemode, controls), board(boardWidth, boardHeight, bag, parameters.getNextQueueLength()) {
|
||||
// the game has not yet started
|
||||
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() {
|
||||
// starts the game
|
||||
this->started = true;
|
||||
this->lost = this->board.spawnNextPiece();
|
||||
}
|
||||
|
||||
void Game::nextFrame(const std::set<Action>& playerActions) {
|
||||
if (this->lost || this->hasWon()) return;
|
||||
|
||||
if (this->started) {
|
||||
bool AREJustEnded = (this->leftARETime == 1);
|
||||
if (this->leftARETime > 0) {
|
||||
this->leftARETime--;
|
||||
}
|
||||
|
||||
if (this->leftARETime == 0) {
|
||||
if (AREJustEnded) {
|
||||
this->board.spawnNextPiece();
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
if (this->initialActions.contains(HOLD)) {
|
||||
if (this->board.hold(initialRotation)) {
|
||||
this->subVerticalPosition = 0;
|
||||
this->totalLockDelay = 0;
|
||||
this->heldARR = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (initialRotation != NONE) {
|
||||
this->board.rotate(initialRotation);
|
||||
}
|
||||
}
|
||||
|
||||
/* HOLD */
|
||||
if (playerActions.contains(HOLD) && (!this->heldActions.contains(HOLD))) {
|
||||
if (this->board.hold()) {
|
||||
this->subVerticalPosition = 0;
|
||||
this->totalLockDelay = 0;
|
||||
this->heldARR = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* MOVE LEFT/RIGHT */
|
||||
if (playerActions.contains(MOVE_LEFT)) {
|
||||
this->movePiece(-1, (this->heldDAS >= 0));
|
||||
}
|
||||
if (playerActions.contains(MOVE_RIGHT)) {
|
||||
this->movePiece(1, (this->heldDAS <= 0));
|
||||
}
|
||||
else {
|
||||
this->heldDAS = 0;
|
||||
}
|
||||
|
||||
/* ROTATIONS */
|
||||
if (playerActions.contains(ROTATE_CW) && (!this->heldActions.contains(ROTATE_CW))) {
|
||||
this->board.rotate(CLOCKWISE);
|
||||
}
|
||||
if (playerActions.contains(ROTATE_180) && (!this->heldActions.contains(ROTATE_180))) {
|
||||
this->board.rotate(DOUBLE);
|
||||
}
|
||||
if (playerActions.contains(ROTATE_CCW) && (!this->heldActions.contains(ROTATE_CCW))) {
|
||||
this->board.rotate(COUNTERCLOCKWISE);
|
||||
}
|
||||
|
||||
/* SOFT DROP */
|
||||
if (playerActions.contains(SOFT_DROP)) {
|
||||
int appliedSDR = this->parameters.getSDR();
|
||||
|
||||
// SDR=0 -> instant drop
|
||||
if (appliedSDR == 0) {
|
||||
while (this->board.moveDown()) {
|
||||
this->score += SOFT_DROP_SCORE;
|
||||
}
|
||||
}
|
||||
// SDR>1 -> move down by specified amount
|
||||
else {
|
||||
this->subVerticalPosition += (SUBPX_PER_ROW / appliedSDR);
|
||||
while (this->subVerticalPosition >= SUBPX_PER_ROW) {
|
||||
this->subVerticalPosition -= SUBPX_PER_ROW;
|
||||
this->score += (this->board.moveDown() * SOFT_DROP_SCORE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* HARD DROP */
|
||||
// needs to be done last because we can enter ARE period afterwards
|
||||
if (this->initialActions.contains(HARD_DROP) || (playerActions.contains(HARD_DROP) && (!this->heldActions.contains(HARD_DROP)))) {
|
||||
while (this->board.moveDown()) {
|
||||
this->score += HARD_DROP_SCORE;
|
||||
}
|
||||
this->lockPiece();
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
|
||||
/* LOCK DELAY */
|
||||
if (this->board.touchesGround()) {
|
||||
this->totalLockDelay++;
|
||||
this->totalForcedLockDelay++;
|
||||
}
|
||||
else {
|
||||
this->totalLockDelay = 0;
|
||||
}
|
||||
|
||||
if ((this->totalLockDelay > this->parameters.getLockDelay()) || (this->totalForcedLockDelay > this->parameters.getForcedLockDelay())) {
|
||||
this->lockPiece();
|
||||
}
|
||||
}
|
||||
|
||||
// remove initial actions only once they've been applied
|
||||
if (AREJustEnded) {
|
||||
this->initialActions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
this->framesPassed++;
|
||||
}
|
||||
|
||||
// update remembered actions
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void Game::movePiece(int movement, bool resetDirection) {
|
||||
if (resetDirection) {
|
||||
this->heldDAS = movement;
|
||||
this->heldARR = 0;
|
||||
}
|
||||
else {
|
||||
this->heldDAS += movement;
|
||||
}
|
||||
|
||||
if (abs(this->heldDAS) > this->parameters.getDAS()) {
|
||||
int appliedARR = this->parameters.getARR();
|
||||
|
||||
// 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) {
|
||||
this->heldARR = 0;
|
||||
if (movement == -1) this->board.moveLeft();
|
||||
if (movement == 1) this->board.moveRight();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Game::lockPiece() {
|
||||
LineClear clear = this->board.lockPiece();
|
||||
this->parameters.clearLines(clear.lines);
|
||||
|
||||
// update B2B and score
|
||||
bool B2BConditions = ((clear.lines > B2B_MIN_LINE_NUMBER) || clear.isSpin || clear.isMiniSpin);
|
||||
if (clear.lines > 0) {
|
||||
/* clearing one more line is worth 2x more
|
||||
clearing with a spin is worth as much as clearing 2x more lines */
|
||||
long int clearScore = LINE_CLEAR_BASE_SCORE;
|
||||
clearScore = clearScore << (clear.lines << (clear.isSpin));
|
||||
|
||||
if (this->B2BChain && B2BConditions) clearScore *= B2B_SCORE_MULTIPLIER;
|
||||
this->score += clearScore;
|
||||
}
|
||||
this->B2BChain = B2BConditions;
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
bool Game::hasWon() {
|
||||
return this->parameters.hasWon(this->framesPassed);
|
||||
}
|
||||
|
||||
bool Game::hasLost() {
|
||||
return this->lost;
|
||||
}
|
||||
|
||||
int Game::getClearedLines() {
|
||||
return this->parameters.getClearedLines();
|
||||
}
|
||||
|
||||
int Game::getLevel() {
|
||||
return this->parameters.getLevel();
|
||||
}
|
||||
|
||||
int Game::getFramesPassed() {
|
||||
return this->framesPassed;
|
||||
}
|
||||
|
||||
int Game::getScore() {
|
||||
return this->score;
|
||||
}
|
||||
|
||||
bool Game::isOnB2BChain() {
|
||||
return this->B2BChain;
|
||||
}
|
||||
|
||||
bool Game::areBlocksBones() {
|
||||
return this->parameters.getBoneBlocks();
|
||||
}
|
||||
|
||||
Board Game::getBoard() {
|
||||
return this->board.getBoard();
|
||||
}
|
||||
|
||||
Piece Game::getActivePiece() {
|
||||
return this->board.getActivePiece();
|
||||
}
|
||||
|
||||
Cell Game::getActivePiecePosition() {
|
||||
return this->board.getActivePiecePosition();
|
||||
}
|
||||
|
||||
Piece Game::getHeldPiece() {
|
||||
return this->board.getHeldPiece();
|
||||
}
|
||||
|
||||
std::vector<Piece> Game::getNextPieces() {
|
||||
return this->board.getNextPieces();
|
||||
}
|
||||
124
src/Core/Game.h
Normal file
124
src/Core/Game.h
Normal file
@@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
|
||||
#include "GameBoard.h"
|
||||
#include "GameParameters.h"
|
||||
#include "Action.h"
|
||||
|
||||
#include <Vector>
|
||||
|
||||
|
||||
/**
|
||||
* Interprets the player action into the game, depending on the state of the board and the current gamemode
|
||||
*/
|
||||
class Game {
|
||||
private:
|
||||
GameParameters parameters; // the current parameters of the game
|
||||
GameBoard board; // the board in which the game is played
|
||||
bool started; // wheter the game has started
|
||||
bool lost; // wheter the game is lost
|
||||
long int score; // the current score
|
||||
int framesPassed; // how many frames have passed since the start of the game
|
||||
bool B2BChain; // wheter the player is currently on a B2B chain
|
||||
std::set<Action> heldActions; // the list of actions that were pressed last frame
|
||||
std::set<Action> initialActions; // the list of actions that have been pressed while there was no active piece
|
||||
int heldDAS; // the number of frames DAS has been held, positive for right or negative for left
|
||||
int heldARR; // the number of frames ARR has been held
|
||||
int subVerticalPosition; // how far the active piece is to go down one line
|
||||
int leftARETime; // how many frames are left before ARE period finishes
|
||||
int totalLockDelay; // how many frames has the active piece touched the ground without moving
|
||||
int totalForcedLockDelay; // how many frames the active piece has touched the ground since the last spawned piece
|
||||
|
||||
public:
|
||||
/**
|
||||
* Initialize the parameters and creates a new board
|
||||
*/
|
||||
Game(Gamemode gamemode, const Player& controls, int boardWidth, int boardHeight, const std::vector<Piece>& bag);
|
||||
|
||||
/**
|
||||
* Starts the game
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Advance to the next frame while excecuting the actions taken by the player,
|
||||
* this is where the main game logic takes place
|
||||
*/
|
||||
void nextFrame(const std::set<Action>& playerActions);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Move the piece in the specified direction
|
||||
*/
|
||||
void movePiece(int movement, bool resetDirection);
|
||||
|
||||
/**
|
||||
* Locks the piece, updates level and score and spawn the next piece if necessary
|
||||
*/
|
||||
void lockPiece();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Returns wheter the player has won
|
||||
*/
|
||||
bool hasWon();
|
||||
|
||||
/**
|
||||
* Returns wheter the player has lost
|
||||
*/
|
||||
bool hasLost();
|
||||
|
||||
/**
|
||||
* Returns the current level
|
||||
*/
|
||||
int getLevel();
|
||||
|
||||
/**
|
||||
* Returns the current number of cleared lines
|
||||
*/
|
||||
int getClearedLines();
|
||||
|
||||
/**
|
||||
* Returns the number of frames passed since the start of the game
|
||||
*/
|
||||
int getFramesPassed();
|
||||
|
||||
/**
|
||||
* Returns the current score
|
||||
*/
|
||||
int getScore();
|
||||
|
||||
/**
|
||||
* Returns wheter the player is currently on a B2B chain
|
||||
*/
|
||||
bool isOnB2BChain();
|
||||
|
||||
/**
|
||||
* Returns wheter all blocks are currently bone blocks
|
||||
*/
|
||||
bool areBlocksBones();
|
||||
|
||||
/**
|
||||
* Returns a copy of the board
|
||||
*/
|
||||
Board getBoard();
|
||||
|
||||
/**
|
||||
* Returns a copy of the active piece
|
||||
*/
|
||||
Piece getActivePiece();
|
||||
|
||||
/**
|
||||
* Returns a copy of the active piece position
|
||||
*/
|
||||
Cell getActivePiecePosition();
|
||||
|
||||
/**
|
||||
* Returns a copy of the held piece
|
||||
*/
|
||||
Piece getHeldPiece();
|
||||
|
||||
/**
|
||||
* Return a copy of the next pieces queue
|
||||
*/
|
||||
std::vector<Piece> getNextPieces();
|
||||
};
|
||||
360
src/Core/GameBoard.cpp
Normal file
360
src/Core/GameBoard.cpp
Normal file
@@ -0,0 +1,360 @@
|
||||
#include "GameBoard.h"
|
||||
|
||||
#include "../Pieces/Piece.h"
|
||||
#include "Board.h"
|
||||
#include "Bag.h"
|
||||
#include "LineClear.h"
|
||||
|
||||
#include <Vector>
|
||||
#include <Set>
|
||||
#include <memory>
|
||||
|
||||
|
||||
GameBoard::GameBoard(int boardWidth, int boardHeight, const std::vector<Piece>& bag, int nextQueueLength) : board(boardWidth, boardHeight), generator(bag), nextQueueLength(nextQueueLength) {
|
||||
// initialize queue
|
||||
this->nextQueue.clear();
|
||||
for (int i = 0; i < nextQueueLength; i++) {
|
||||
this->nextQueue.push_back(this->generator.getNext());
|
||||
}
|
||||
}
|
||||
|
||||
bool GameBoard::moveLeft() {
|
||||
// check if the piece can be moved one cell left
|
||||
if (this->isActivePieceInWall(Cell{-1, 0})) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
this->activePiecePosition.x -= 1;
|
||||
this->isLastMoveKick = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool GameBoard::moveRight() {
|
||||
// check if the piece can be moved one cell right
|
||||
if (this->isActivePieceInWall(Cell{1, 0})) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
this->activePiecePosition.x += 1;
|
||||
this->isLastMoveKick = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool GameBoard::moveDown() {
|
||||
// check if the piece can be moved one cell down
|
||||
if (this->isActivePieceInWall(Cell{0, -1})) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
this->activePiecePosition.y -= 1;
|
||||
this->isLastMoveKick = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool GameBoard::rotate(Rotation rotation) {
|
||||
// copy the original piece before rotating it
|
||||
Piece stored = *this->activePiece;
|
||||
this->rotate(rotation);
|
||||
|
||||
// check if the piece can rotate
|
||||
if (!this->isActivePieceInWall()) {
|
||||
this->isLastMoveKick = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// get the list of cells that touches the original piece
|
||||
std::set<Cell> safeCells;
|
||||
for (Cell cell : stored.getPositions()) {
|
||||
Cell cellInGrid(cell + this->activePiecePosition);
|
||||
safeCells.insert(cellInGrid);
|
||||
safeCells.insert(cellInGrid + Cell{0, 1});
|
||||
safeCells.insert(cellInGrid + Cell{1, 0});
|
||||
safeCells.insert(cellInGrid + Cell{0, -1});
|
||||
safeCells.insert(cellInGrid + Cell{-1, 0});
|
||||
}
|
||||
|
||||
// try kicking the piece down
|
||||
bool suceeded = this->tryKicking(true, safeCells);
|
||||
if (suceeded) {
|
||||
this->isLastMoveKick = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// if it doesn't work try kicking the piece up
|
||||
suceeded = this->tryKicking(false, safeCells);
|
||||
if (suceeded) {
|
||||
this->isLastMoveKick = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// if it still doesn't work, abort the rotation
|
||||
this->activePiece = std::make_shared<Piece>(stored);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameBoard::tryKicking(bool testingBottom, const std::set<Cell>& safeCells) {
|
||||
// we try from the original height of the piece, moving vertically as long as the kicked piece touches the original
|
||||
bool overlapsVertically = true;
|
||||
int j = 0;
|
||||
do {
|
||||
// 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;
|
||||
do {
|
||||
// check right before right arbitrarly, we don't decide this with rotations since it would still be arbitrary with 180° rotations
|
||||
if (overlapsRight) {
|
||||
Cell shift{+i, j};
|
||||
// the kicked position must touch the original piece
|
||||
if (!this->activePieceOverlapsOneCell(safeCells, shift)) {
|
||||
overlapsLeft = false;
|
||||
}
|
||||
else {
|
||||
// if the position is valid we place the active piece there
|
||||
if (!this->isActivePieceInWall(shift)) {
|
||||
this->activePiecePosition += shift;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// do the same on the left side
|
||||
if (overlapsLeft) {
|
||||
Cell shift{-i, j};
|
||||
if (!this->activePieceOverlapsOneCell(safeCells, shift)) {
|
||||
overlapsLeft = false;
|
||||
}
|
||||
else {
|
||||
if (!this->isActivePieceInWall(shift)) {
|
||||
this->activePiecePosition += shift;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
} while (overlapsLeft && overlapsRight);
|
||||
|
||||
// test if no position touched the original piece
|
||||
if (i == 1) {
|
||||
overlapsVertically = false;
|
||||
}
|
||||
|
||||
// move one line up or down
|
||||
(testingBottom) ? j-- : j++;
|
||||
} while (overlapsVertically);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameBoard::hold(Rotation initialRotation) {
|
||||
// swap with held piece
|
||||
std::swap(this->activePiece, this->heldPiece);
|
||||
|
||||
// if it's the first time holding try the next piece
|
||||
bool isFirstTimeHolding = false;
|
||||
if (this->activePiece == nullptr) {
|
||||
isFirstTimeHolding = true;
|
||||
|
||||
// if no pieces in next queue look at what the next would be
|
||||
if (this->nextQueueLength == 0) {
|
||||
this->activePiece = std::make_shared<Piece>(this->generator.lookNext());
|
||||
}
|
||||
else {
|
||||
this->activePiece = std::make_shared<Piece>(this->nextQueue.front());
|
||||
}
|
||||
}
|
||||
|
||||
// set the spawned piece to the correct position
|
||||
this->goToSpawnPosition();
|
||||
|
||||
// apply initial rotation
|
||||
Piece stored = *this->activePiece;
|
||||
this->rotate(initialRotation);
|
||||
|
||||
// if the piece can't spawn, abort initial rotation
|
||||
if (this->isActivePieceInWall()) {
|
||||
this->activePiece = std::make_shared<Piece>(stored);
|
||||
|
||||
// if the piece still can't spawn, abort holding
|
||||
if (this->isActivePieceInWall()) {
|
||||
if (isFirstTimeHolding) {
|
||||
this->activePiece = nullptr;
|
||||
}
|
||||
std::swap(this->activePiece, this->heldPiece);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if it's the first time holding, confirm we keep this piece
|
||||
if (isFirstTimeHolding) {
|
||||
if (this->nextQueueLength == 0) {
|
||||
this->generator.getNext();
|
||||
}
|
||||
else {
|
||||
this->spawnNextPiece();
|
||||
}
|
||||
}
|
||||
|
||||
// this piece has done nothing yet
|
||||
this->isLastMoveKick = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GameBoard::spawnNextPiece() {
|
||||
// add a piece to the queue
|
||||
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());
|
||||
|
||||
// set the spawned piece to the correct position
|
||||
this->goToSpawnPosition();
|
||||
|
||||
// this piece has done nothing yet
|
||||
this->isLastMoveKick = false;
|
||||
|
||||
// returns wheter the piece can spawn correctly
|
||||
return !this->isActivePieceInWall();
|
||||
}
|
||||
|
||||
bool GameBoard::touchesGround() {
|
||||
return this->isActivePieceInWall(Cell{0, -1});
|
||||
}
|
||||
|
||||
LineClear GameBoard::lockPiece() {
|
||||
// check if the piece is locked in place
|
||||
bool isLocked = (this->isActivePieceInWall(Cell{0, 1}) && this->isActivePieceInWall(Cell{1, 0}) &&
|
||||
this->isActivePieceInWall(Cell{-1, 0}) && this->isActivePieceInWall(Cell{0, -1}));
|
||||
|
||||
// put the piece in the board
|
||||
for (Cell cell : this->activePiece->getPositions()) {
|
||||
this->board.addBlock(cell + this->activePiecePosition, this->activePiece->getColor());
|
||||
}
|
||||
|
||||
// check for lines to clear
|
||||
return LineClear{this->board.clearRows(), isLocked, (!isLocked) && this->isLastMoveKick};
|
||||
}
|
||||
|
||||
Board GameBoard::getBoard() const {
|
||||
return this->board;
|
||||
}
|
||||
|
||||
Piece GameBoard::getActivePiece() const {
|
||||
return *this->activePiece;
|
||||
}
|
||||
|
||||
Cell GameBoard::getActivePiecePosition() const {
|
||||
return this->activePiecePosition;
|
||||
}
|
||||
|
||||
Piece GameBoard::getHeldPiece() const {
|
||||
return *this->heldPiece;
|
||||
}
|
||||
|
||||
std::vector<Piece> GameBoard::getNextPieces() const {
|
||||
return this->nextQueue;
|
||||
}
|
||||
|
||||
bool GameBoard::isActivePieceInWall(const Cell& shift) const {
|
||||
// check if every cell of the active piece is in an empty spot
|
||||
for (Cell cell : this->activePiece->getPositions()) {
|
||||
if (this->board.getBlock(cell + this->activePiecePosition + shift) != NOTHING) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameBoard::activePieceOverlapsOneCell(const std::set<Cell>& safeCells, const Cell& shift) const {
|
||||
// check if one cell of the translated active piece overlaps with one cell of the given piece set
|
||||
for (Cell cell : this->activePiece->getPositions()) {
|
||||
if (safeCells.contains(cell + shift)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GameBoard::goToSpawnPosition() {
|
||||
// get the lowest cell of the piece
|
||||
int lowestCell = this->activePiece->getLength() - 1;
|
||||
for (Cell cell : this->activePiece->getPositions()) {
|
||||
if (cell.y < lowestCell) lowestCell = cell.y;
|
||||
}
|
||||
|
||||
// set the piece one line above the board
|
||||
this->activePiecePosition.y = this->board.getBaseHeight() - lowestCell;
|
||||
|
||||
// center the piece horizontally, biased towards left
|
||||
this->activePiecePosition.x = (this->board.getWidth() - this->activePiece->getLength()) / 2;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const GameBoard& gameboard) {
|
||||
// print over the board (only the active piece if it is there)
|
||||
if (gameboard.activePiece != nullptr) {
|
||||
|
||||
// change to the color of the active piece
|
||||
Color pieceColor = gameboard.activePiece->getColor();
|
||||
os << COLOR_CODES[pieceColor];
|
||||
|
||||
// print only the cell were the active piece is
|
||||
for (int y = gameboard.activePiecePosition.y + gameboard.activePiece->getLength() - 1; y >= gameboard.board.getBaseHeight(); y--) {
|
||||
for (int x = 0; x < gameboard.board.getWidth(); x++) {
|
||||
bool hasActivePiece = gameboard.activePiece->getPositions().contains(Cell{x, y} - gameboard.activePiecePosition);
|
||||
if (hasActivePiece) {
|
||||
os << "*";
|
||||
}
|
||||
else {
|
||||
os << " ";
|
||||
}
|
||||
}
|
||||
os << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// print the board
|
||||
Color pieceColor = (gameboard.activePiece == nullptr) ? NOTHING : gameboard.activePiece->getColor();
|
||||
for (int y = gameboard.board.getBaseHeight() - 1; y >= 0; y--) {
|
||||
for (int x = 0; x < gameboard.board.getWidth(); x++) {
|
||||
bool hasActivePiece = (gameboard.activePiece == nullptr) ? false : gameboard.activePiece->getPositions().contains(Cell{x, y} - gameboard.activePiecePosition);
|
||||
|
||||
// if the active piece is on this cell, print it
|
||||
if (hasActivePiece) {
|
||||
os << COLOR_CODES[pieceColor];
|
||||
os << "*";
|
||||
}
|
||||
|
||||
// else print the cell of the board
|
||||
else {
|
||||
Color block = gameboard.board.getBlock(Cell{x, y});
|
||||
os << COLOR_CODES[block];
|
||||
if (block != NOTHING) {
|
||||
os << "*";
|
||||
}
|
||||
else {
|
||||
os << "-";
|
||||
}
|
||||
}
|
||||
}
|
||||
os << std::endl;
|
||||
}
|
||||
|
||||
// print held piece
|
||||
os << "Hold:" << std::endl;
|
||||
if (!(gameboard.heldPiece == nullptr)) {
|
||||
os << *gameboard.heldPiece;
|
||||
}
|
||||
|
||||
// print next queue
|
||||
os << "Next:" << std::endl;
|
||||
for (const Piece& piece : gameboard.nextQueue) {
|
||||
os << piece;
|
||||
}
|
||||
|
||||
// reset console color
|
||||
os << COLOR_RESET;
|
||||
|
||||
return os;
|
||||
}
|
||||
126
src/Core/GameBoard.h
Normal file
126
src/Core/GameBoard.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Pieces/Piece.h"
|
||||
#include "Board.h"
|
||||
#include "Bag.h"
|
||||
#include "LineClear.h"
|
||||
|
||||
#include <Vector>
|
||||
#include <memory>
|
||||
|
||||
|
||||
/**
|
||||
* Links a board with the pieces moving in it
|
||||
*/
|
||||
class GameBoard {
|
||||
private:
|
||||
Board board; // the board in which pieces moves, (0, 0) is downleft
|
||||
Bag generator; // the piece generator
|
||||
std::shared_ptr<Piece> activePiece; // the piece currently in the board
|
||||
Cell activePiecePosition; // the position of the piece currently in the board
|
||||
std::shared_ptr<Piece> heldPiece; // a piece being holded
|
||||
int nextQueueLength; // the number of next pieces seeable at a time
|
||||
std::vector<Piece> nextQueue; // the list of the next pieces to spawn in the board
|
||||
bool isLastMoveKick; // wheter the last action the piece did was kicking
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a new board, generator, and next queue
|
||||
*/
|
||||
GameBoard(int boardWidth, int boardHeight, const std::vector<Piece>& bag, int nextQueueLength);
|
||||
|
||||
/**
|
||||
* Try moving the piece one cell to the left, and returns wheter it was sucessfull
|
||||
*/
|
||||
bool moveLeft();
|
||||
|
||||
/**
|
||||
* Try moving the piece one cell to the right, and returns wheter it was sucessfull
|
||||
*/
|
||||
bool moveRight();
|
||||
|
||||
/**
|
||||
* Try moving the piece one cell down, and returns wheter it was sucessfull
|
||||
*/
|
||||
bool moveDown();
|
||||
|
||||
/**
|
||||
* Try rotating the piece and kicking it if necessary, and returns wheter it was sucessfull
|
||||
*/
|
||||
bool rotate(Rotation rotation);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Try kicking the piece, testing position either above or below the piece's initial position
|
||||
*/
|
||||
bool tryKicking(bool testingBottom, const std::set<Cell>& safeCells);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Try holding the active piece or swapping it if one was already stocked, while trying to apply an initial rotation to the newly spawned piece,
|
||||
* and returns wheter it was sucessfull
|
||||
*/
|
||||
bool hold(Rotation initialRotation = NONE);
|
||||
|
||||
/**
|
||||
* Spawns the next piece from the queue, and returns wheter it spawns in a wall
|
||||
*/
|
||||
bool spawnNextPiece();
|
||||
|
||||
/**
|
||||
* Returns wheter the active piece is touching walls directly below it
|
||||
*/
|
||||
bool touchesGround();
|
||||
|
||||
/**
|
||||
* Lock the active piece into the board and returns the resulting line clear
|
||||
*/
|
||||
LineClear lockPiece();
|
||||
|
||||
/**
|
||||
* Returns a copy of the board
|
||||
*/
|
||||
Board getBoard() const;
|
||||
|
||||
/**
|
||||
* Returns a copy of the active piece
|
||||
*/
|
||||
Piece getActivePiece() const;
|
||||
|
||||
/**
|
||||
* Returns a copy of the position of the active piece
|
||||
*/
|
||||
Cell getActivePiecePosition() const;
|
||||
|
||||
/**
|
||||
* Returns a copy of the held piece
|
||||
*/
|
||||
Piece getHeldPiece() const;
|
||||
|
||||
/**
|
||||
* Returns a copy of the next piece queue
|
||||
*/
|
||||
std::vector<Piece> getNextPieces() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Returns wheter the translated active piece is in a wall
|
||||
*/
|
||||
bool isActivePieceInWall(const Cell& shift = Cell{0, 0}) const;
|
||||
|
||||
/**
|
||||
* Returns wheter the translated active piece overlaps with at least one of the cells
|
||||
*/
|
||||
bool activePieceOverlapsOneCell(const std::set<Cell>& safeCells, const Cell& shift = Cell{0, 0}) const;
|
||||
|
||||
/**
|
||||
* Sets the active piece to its spawn position
|
||||
*/
|
||||
void goToSpawnPosition();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Stream output operator, adds the board, the hold box and the next queue
|
||||
*/
|
||||
friend std::ostream& operator<<(std::ostream& os, const GameBoard& gameboard);
|
||||
};
|
||||
237
src/Core/GameParameters.cpp
Normal file
237
src/Core/GameParameters.cpp
Normal file
@@ -0,0 +1,237 @@
|
||||
#include "GameParameters.h"
|
||||
|
||||
#include "Gamemode.h"
|
||||
#include "Player.h"
|
||||
|
||||
|
||||
GameParameters::GameParameters(Gamemode gamemode, const Player& controls) : gamemode(gamemode), controls(controls) {
|
||||
// initialize lines and level
|
||||
this->clearedLines = 0;
|
||||
switch (this->gamemode) {
|
||||
// lowest gravity
|
||||
case SPRINT : {this->level = 1; break;}
|
||||
// lowest gravity
|
||||
case ULTRA : {this->level = 1; break;}
|
||||
// goes from level 1 to 20
|
||||
case MARATHON : {this->level = 1; break;}
|
||||
// goes from level 20 to 39
|
||||
case MASTER : {this->level = 20; break;}
|
||||
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;
|
||||
|
||||
// level increments every 10 lines, stats only changes on level up
|
||||
if (previousLines / 10 < this->clearedLines / 10) {
|
||||
this->level = this->clearedLines / 10;
|
||||
this->updateStats();
|
||||
}
|
||||
break;
|
||||
}
|
||||
// other modes
|
||||
default : this->clearedLines += lineNumber;
|
||||
}
|
||||
}
|
||||
|
||||
bool GameParameters::hasWon(int framesPassed) {
|
||||
switch (this->gamemode) {
|
||||
// win once 40 lines have been cleared
|
||||
case SPRINT : return this->clearedLines >= 40;
|
||||
// win once 2mn have passed
|
||||
case ULTRA : return (framesPassed / 60) >= 120;
|
||||
// win once 200 lines have been cleared
|
||||
case MARATHON : return this->clearedLines >= 200;
|
||||
// win once 200 lines have been cleared
|
||||
case MASTER : return this->clearedLines >= 200;
|
||||
default : return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GameParameters::updateStats() {
|
||||
/* NEXT QUEUE */
|
||||
switch (this->gamemode) {
|
||||
// 5 for rapidity gamemodes
|
||||
case SPRINT :
|
||||
case ULTRA : {
|
||||
this->nextQueueLength = 5;
|
||||
break;
|
||||
}
|
||||
// 3 for endurance gamemodes
|
||||
case MARATHON :
|
||||
case MASTER : {
|
||||
this->nextQueueLength = 3;
|
||||
break;
|
||||
}
|
||||
default : this->nextQueueLength = 1;
|
||||
}
|
||||
|
||||
/* BONE BLOCKS */
|
||||
switch (this->gamemode) {
|
||||
// blocks turns into bone blocks at level 30
|
||||
case MASTER : this->boneBlocks = (this->level >= 30);
|
||||
default : this->boneBlocks = false;
|
||||
}
|
||||
|
||||
/* GRAVITY */
|
||||
if (level >= 20) {
|
||||
// all levels above 20 are instant gravity
|
||||
this->gravity = 20 * 60;
|
||||
}
|
||||
else {
|
||||
// get gravity for an assumed 20-rows board
|
||||
switch (this->level) {
|
||||
case 1 : {this->gravity = 1; break;} // 60f/line, 20s total
|
||||
case 2 : {this->gravity = 2; break;} // 30f/line, 10s total
|
||||
case 3 : {this->gravity = 3; break;} // 20f/line, 6.66s total
|
||||
case 4 : {this->gravity = 4; break;} // 15f/line, 5s total
|
||||
case 5 : {this->gravity = 5; break;} // 12f/line, 4s total
|
||||
case 6 : {this->gravity = 6; break;} // 10f/line, 3.33 total
|
||||
case 7 : {this->gravity = 7; break;} // 8.57f/line, 2.85s total
|
||||
case 8 : {this->gravity = 8; break;} // 7.5f/line, 2.5s total
|
||||
case 9 : {this->gravity = 10; break;} // 6f/line, 2s total
|
||||
case 10 : {this->gravity = 12; break;} // 5f/line, 1.66s total
|
||||
case 11 : {this->gravity = 14; break;} // 4.28f/line, 1.42s total
|
||||
case 12 : {this->gravity = 17; break;} // 3.52f/line, 1.17s total
|
||||
case 13 : {this->gravity = 20; break;} // 3f/line, 60f total
|
||||
case 14 : {this->gravity = 24; break;} // 2.5f/line, 50f total
|
||||
case 15 : {this->gravity = 30; break;} // 2f/line, 40f total
|
||||
case 16 : {this->gravity = 40; break;} // 1.5f/line, 30f total
|
||||
case 17 : {this->gravity = 1 * 60; break;} // 1line/f, 20f total
|
||||
case 18 : {this->gravity = 2 * 60; break;} // 2line/f, 10f total
|
||||
case 19 : {this->gravity = 4 * 60; break;} // 4line/f, 5f total
|
||||
default : this->gravity = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* LOCK DELAY */
|
||||
switch (this->gamemode) {
|
||||
// starts at 500ms (30f) at lvl 20 and ends at 183ms (11f) at lvl 39
|
||||
case MASTER : {this->lockDelay = 30 - (this->level - 20); break;}
|
||||
// 1s by default
|
||||
default : this->lockDelay = 60;
|
||||
}
|
||||
|
||||
/* FORCED LOCK DELAY */
|
||||
this->forcedLockDelay = this->lockDelay * 10;
|
||||
|
||||
/* ARE */
|
||||
switch (this->gamemode) {
|
||||
// starts at 400ms (24f) at lvl 1 and ends at 083ms (5f) at lvl 20
|
||||
case MARATHON : {this->ARE = 24 - (this->level - 1); break;}
|
||||
// starts at 400ms (24f) at lvl 20 and ends at 083ms (5f) at lvl 39
|
||||
case MASTER : {this->ARE = 24 - (this->level - 20); break;}
|
||||
// no ARE by default
|
||||
default : this->ARE = 0;
|
||||
}
|
||||
|
||||
/* LINE ARE */
|
||||
this->lineARE = this->ARE * 2;
|
||||
|
||||
/* DAS */
|
||||
this->DAS = this->controls.getDAS();
|
||||
switch (this->gamemode) {
|
||||
// for modes with reduced lock delay, ensure DAS is lower than lock delay, but at least 1
|
||||
case MASTER : {
|
||||
if (this->lockDelay <= this->DAS) {
|
||||
this->DAS = this->lockDelay - 6; // give 6f (100ms) to change directions
|
||||
}
|
||||
if (this->DAS < 1) {
|
||||
this->DAS = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// modes with no reduced lock delay
|
||||
default : break;
|
||||
}
|
||||
|
||||
/* ARR */
|
||||
this->ARR = this->controls.getARR();
|
||||
switch (this->gamemode) {
|
||||
// for modes with reduced lock delay, ensure ARR is lower than DAS, but not lower than 1
|
||||
case MASTER : {
|
||||
if (this->DAS <= this->ARR) {
|
||||
this->ARR = this->DAS - 1;
|
||||
}
|
||||
if (this->ARR < 1) {
|
||||
this->ARR = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// modes with no reduced lock delay
|
||||
default : break;
|
||||
}
|
||||
|
||||
/* SDR */
|
||||
this->SDR = this->controls.getSDR();
|
||||
switch (this->gamemode) {
|
||||
// modes where we don't want instant soft drop to be possible
|
||||
case MARATHON : {
|
||||
if (this->SDR < 1) {
|
||||
this->SDR = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// modes where we don't care
|
||||
default : break;
|
||||
}
|
||||
}
|
||||
|
||||
int GameParameters::getClearedLines() {
|
||||
return this->clearedLines;
|
||||
}
|
||||
|
||||
int GameParameters::getLevel() {
|
||||
return this->level;
|
||||
}
|
||||
|
||||
int GameParameters::getNextQueueLength() {
|
||||
return this->nextQueueLength;
|
||||
}
|
||||
|
||||
bool GameParameters::getBoneBlocks() {
|
||||
return this->boneBlocks;
|
||||
}
|
||||
|
||||
int GameParameters::getGravity() {
|
||||
return this->gravity;
|
||||
}
|
||||
|
||||
int GameParameters::getLockDelay() {
|
||||
return this->lockDelay;
|
||||
}
|
||||
|
||||
int GameParameters::getForcedLockDelay() {
|
||||
return this->forcedLockDelay;
|
||||
}
|
||||
|
||||
int GameParameters::getARE() {
|
||||
return this->ARE;
|
||||
}
|
||||
|
||||
int GameParameters::getLineARE() {
|
||||
return this->lineARE;
|
||||
}
|
||||
|
||||
int GameParameters::getDAS() {
|
||||
return this->DAS;
|
||||
}
|
||||
|
||||
int GameParameters::getARR() {
|
||||
return this->ARR;
|
||||
}
|
||||
|
||||
int GameParameters::getSDR() {
|
||||
return this->SDR;
|
||||
}
|
||||
110
src/Core/GameParameters.h
Normal file
110
src/Core/GameParameters.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include "Gamemode.h"
|
||||
#include "Player.h"
|
||||
|
||||
|
||||
/**
|
||||
* Computes the parameters of the game depending on the current gamemode and the player's controls,
|
||||
* this is basically where all the hard-coded values are stored
|
||||
*/
|
||||
class GameParameters {
|
||||
private:
|
||||
Gamemode gamemode; // the current gamemode
|
||||
Player controls; // the player's controls
|
||||
int clearedLines; // the number of cleared lines
|
||||
int level; // the current level
|
||||
int nextQueueLength; // the number of pieces visibles in the next queue
|
||||
bool boneBlocks; // wheter all blocks are bone blocks
|
||||
int gravity; // the gravity at which pieces drop
|
||||
int lockDelay; // the time before the piece lock in place
|
||||
int forcedLockDelay; // the forced time before the piece lock in place
|
||||
int ARE; // the time before the next piece spawn
|
||||
int lineARE; // the time before the next piece spawn, after clearing a line
|
||||
int DAS; // the time before the piece repeats moving
|
||||
int ARR; // the rate at which the piece repeats moving
|
||||
int SDR; // the rate at which the piece soft drops
|
||||
|
||||
public:
|
||||
/**
|
||||
* Sets the current gamemode and the player's controls
|
||||
*/
|
||||
GameParameters(Gamemode gamemode, const Player& controls);
|
||||
|
||||
/**
|
||||
* Count the newly cleared lines and update level and stats if needed
|
||||
*/
|
||||
void clearLines(int lineNumber);
|
||||
|
||||
/**
|
||||
* Returns wheter the game ended
|
||||
*/
|
||||
bool hasWon(int framesPassed);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Updates all the parameters
|
||||
*/
|
||||
void updateStats();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Returns the current number of cleared line
|
||||
*/
|
||||
int getClearedLines();
|
||||
|
||||
/**
|
||||
* Returns the current level
|
||||
*/
|
||||
int getLevel();
|
||||
|
||||
/**
|
||||
* Returns the length of the next queue
|
||||
*/
|
||||
int getNextQueueLength();
|
||||
|
||||
/**
|
||||
* Returns wheter the blocks are currently bone blocks
|
||||
*/
|
||||
bool getBoneBlocks();
|
||||
|
||||
/**
|
||||
* Returns the current gravity for a 20-line high board
|
||||
*/
|
||||
int getGravity();
|
||||
|
||||
/**
|
||||
* Returns the current lock delay
|
||||
*/
|
||||
int getLockDelay();
|
||||
|
||||
/**
|
||||
* Returns the current forced lock delay
|
||||
*/
|
||||
int getForcedLockDelay();
|
||||
|
||||
/**
|
||||
* Returns the current ARE
|
||||
*/
|
||||
int getARE();
|
||||
|
||||
/**
|
||||
* Returns the current line ARE
|
||||
*/
|
||||
int getLineARE();
|
||||
|
||||
/**
|
||||
* Returns the current DAS
|
||||
*/
|
||||
int getDAS();
|
||||
|
||||
/**
|
||||
* Returns the current ARR
|
||||
*/
|
||||
int getARR();
|
||||
|
||||
/**
|
||||
* Returns the current SDR
|
||||
*/
|
||||
int getSDR();
|
||||
};
|
||||
12
src/Core/Gamemode.h
Normal file
12
src/Core/Gamemode.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/**
|
||||
* Every gamemode supported by the game
|
||||
*/
|
||||
enum Gamemode {
|
||||
SPRINT,
|
||||
MARATHON,
|
||||
ULTRA,
|
||||
MASTER
|
||||
};
|
||||
11
src/Core/LineClear.h
Normal file
11
src/Core/LineClear.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/**
|
||||
* Specify how many lines were cleared and how
|
||||
*/
|
||||
struct LineClear {
|
||||
int lines; // the number of lines cleared
|
||||
bool isSpin; // if the move was a spin
|
||||
bool isMiniSpin; // if the move was a spin mini
|
||||
};
|
||||
49
src/Core/Player.cpp
Normal file
49
src/Core/Player.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "Player.h"
|
||||
|
||||
static const int DAS_MIN_VALUE = 0;
|
||||
static const int DAS_MAX_VALUE = 30;
|
||||
static const int ARR_MIN_VALUE = 0;
|
||||
static const int ARR_MAX_VALUE = 30;
|
||||
static const int SDR_MIN_VALUE = 0;
|
||||
static const int SDR_MAX_VALUE = 6;
|
||||
|
||||
|
||||
Player::Player() {
|
||||
// default settings
|
||||
this->DAS = 15; // 250ms
|
||||
this->ARR = 3; // 50ms
|
||||
this->SDR = 2; // 33ms
|
||||
}
|
||||
|
||||
bool Player::setDAS(int DAS) {
|
||||
if (DAS < DAS_MIN_VALUE || DAS > DAS_MAX_VALUE) return false;
|
||||
|
||||
this->DAS = DAS;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Player::setARR(int ARR) {
|
||||
if (ARR < ARR_MIN_VALUE || ARR > ARR_MAX_VALUE) return false;
|
||||
|
||||
this->ARR = ARR;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Player::setSDR(int SDR) {
|
||||
if (SDR < SDR_MIN_VALUE || SDR > SDR_MAX_VALUE) return false;
|
||||
|
||||
this->SDR = SDR;
|
||||
return true;
|
||||
}
|
||||
|
||||
int Player::getDAS() {
|
||||
return this->DAS;
|
||||
}
|
||||
|
||||
int Player::getARR() {
|
||||
return this->ARR;
|
||||
}
|
||||
|
||||
int Player::getSDR() {
|
||||
return this->SDR;
|
||||
}
|
||||
48
src/Core/Player.h
Normal file
48
src/Core/Player.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/**
|
||||
* The controls of a player
|
||||
*/
|
||||
class Player {
|
||||
private:
|
||||
int DAS; // Delayed Auto-Shift, goes from 0 (instant) to 30 (500ms)
|
||||
int ARR; // Auto-Repeat Rate, goes from 0 (instant) to 30 (500ms)
|
||||
int SDR; // Soft Drop Rate, goes from 0 (instant) to 6 (100ms)
|
||||
|
||||
public:
|
||||
/**
|
||||
* Sets default controls
|
||||
*/
|
||||
Player();
|
||||
|
||||
/**
|
||||
* Try setting DAS to the desired value, and returns wheter it is possible
|
||||
*/
|
||||
bool setDAS(int DAS);
|
||||
|
||||
/**
|
||||
* Try setting ARR to the desired value, and returns wheter it is possible
|
||||
*/
|
||||
bool setARR(int ARR);
|
||||
|
||||
/**
|
||||
* Try setting SDR to the desired value, and returns wheter it is possible
|
||||
*/
|
||||
bool setSDR(int SDR);
|
||||
|
||||
/**
|
||||
* Returns DAS value
|
||||
*/
|
||||
int getDAS();
|
||||
|
||||
/**
|
||||
* Returns ARR value
|
||||
*/
|
||||
int getARR();
|
||||
|
||||
/**
|
||||
* Returns SDR value
|
||||
*/
|
||||
int getSDR();
|
||||
};
|
||||
165
src/Core/main.cpp
Normal file
165
src/Core/main.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#include "../Pieces/PiecesFiles.h"
|
||||
#include "../Pieces/Generator.h"
|
||||
#include "GameBoard.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
void testGeneratorForAllSizes(int amount);
|
||||
void testGeneratorForOneSize(int size);
|
||||
void testGeneratorByprintingAllNminos(int n);
|
||||
void testStoringAndRetrievingPieces(int size);
|
||||
void generateFilesForAllSizes(int amount);
|
||||
void readStatsFromFilesForAllSizes(int amount);
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
std::srand(std::time(NULL));
|
||||
|
||||
using std::chrono::high_resolution_clock;
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::duration;
|
||||
using std::chrono::milliseconds;
|
||||
|
||||
PiecesFiles pf;
|
||||
std::vector<Piece> pieces;
|
||||
std::vector<int> convexPieces;
|
||||
std::vector<int> holelessPieces;
|
||||
std::vector<int> otherPieces;
|
||||
pf.loadPieces(13, pieces, convexPieces, holelessPieces, otherPieces);
|
||||
|
||||
auto t1 = high_resolution_clock::now();
|
||||
Bag bg(pieces);
|
||||
auto t2 = high_resolution_clock::now();
|
||||
duration<double, std::milli> ms_double = t2 - t1;
|
||||
std::cout << ms_double.count() << "ms" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void testGeneratorForAllSizes(int amount) {
|
||||
using std::chrono::high_resolution_clock;
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::duration;
|
||||
using std::chrono::milliseconds;
|
||||
Generator generator;
|
||||
|
||||
for (int i = 1; i <= amount; i++) {
|
||||
auto t1 = high_resolution_clock::now();
|
||||
std::vector<Polyomino> n_minos = generator.generatePolyominos(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;
|
||||
}
|
||||
}
|
||||
|
||||
void testGeneratorForOneSize(int size) {
|
||||
using std::chrono::high_resolution_clock;
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::duration;
|
||||
using std::chrono::milliseconds;
|
||||
Generator generator;
|
||||
|
||||
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);
|
||||
auto t2 = high_resolution_clock::now();
|
||||
|
||||
duration<double, std::milli> ms_double = t2 - t1;
|
||||
std::cout << ms_double.count() << "ms" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void testGeneratorByprintingAllNminos(int n) {
|
||||
Generator generator;
|
||||
std::vector<Polyomino> n_minos = generator.generatePolyominos(n);
|
||||
|
||||
for (Polyomino& n_mino : n_minos) {
|
||||
n_mino.goToSpawnPosition();
|
||||
}
|
||||
std::sort(n_minos.begin(), n_minos.end());
|
||||
|
||||
for (Polyomino& n_mino : n_minos) {
|
||||
|
||||
std::cout << n_mino << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void testStoringAndRetrievingPieces(int size) {
|
||||
PiecesFiles piecesFiles;
|
||||
piecesFiles.savePieces(size);
|
||||
|
||||
std::vector<Piece> pieces;
|
||||
std::vector<int> convexPieces;
|
||||
std::vector<int> holelessPieces;
|
||||
std::vector<int> otherPieces;
|
||||
piecesFiles.loadPieces(size, pieces, convexPieces, holelessPieces, otherPieces);
|
||||
|
||||
std::cout << "Convex " << size << "-minos:" << std::endl;
|
||||
for (int index : convexPieces) {
|
||||
std::cout << pieces.at(index);
|
||||
}
|
||||
|
||||
std::cout << "Holeless " << size << "-minos:" << std::endl;
|
||||
for (int index : holelessPieces) {
|
||||
std::cout << pieces.at(index);
|
||||
}
|
||||
|
||||
std::cout << "Others " << size << "-minos:" << std::endl;
|
||||
for (int index : otherPieces) {
|
||||
std::cout << pieces.at(index);
|
||||
}
|
||||
}
|
||||
|
||||
void generateFilesForAllSizes(int amount) {
|
||||
using std::chrono::high_resolution_clock;
|
||||
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);
|
||||
auto t2 = high_resolution_clock::now();
|
||||
|
||||
duration<double, std::milli> ms_double = t2 - t1;
|
||||
std::cout << "Generated pieces files for size " << i << " in " << ms_double.count() << "ms" << std::endl;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
duration<double, std::milli> ms_double = t2 - t1;
|
||||
std::cout << "Read pieces from files for size " << i << " in " << ms_double.count() << "ms" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void readStatsFromFilesForAllSizes(int amount) {
|
||||
PiecesFiles piecesFiles;
|
||||
for (int i = 1; i <= amount; i++) {
|
||||
std::vector<Piece> pieces;
|
||||
std::vector<int> convexPieces;
|
||||
std::vector<int> holelessPieces;
|
||||
std::vector<int> otherPieces;
|
||||
piecesFiles.loadPieces(i, pieces, convexPieces, holelessPieces, otherPieces);
|
||||
|
||||
std::cout << i << "-minos : " << pieces.size() << std::endl;
|
||||
std::cout << "Convex " << i << "-minos : " << convexPieces.size() << std::endl;
|
||||
std::cout << "Holeless " << i << "-minos : " << holelessPieces.size() << std::endl;
|
||||
std::cout << "Others " << i << "-minos : " << otherPieces.size() << std::endl;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user