ajouté interface textuelle

This commit is contained in:
2025-03-02 23:36:20 +01:00
parent 857f90d646
commit 1033f3a64c
21 changed files with 646 additions and 113 deletions

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

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

View File

@@ -44,14 +44,14 @@ class Board {
void clearBoard();
/**
* @return A copy of the block at the specified position
* @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

View File

@@ -25,7 +25,7 @@ Game::Game(Gamemode gamemode, const Player& controls, int boardWidth, int boardH
void Game::start() {
this->started = true;
this->lost = this->board.spawnNextPiece();
this->leftARETime = 1;
}
void Game::reset() {
@@ -48,7 +48,6 @@ void Game::initialize() {
this->heldDAS = 0;
this->heldARR = 0;
this->subVerticalPosition = 0;
this->leftARETime = 0;
this->totalLockDelay = 0;
this->totalForcedLockDelay = 0;
}
@@ -64,14 +63,14 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
if (this->leftARETime == 0) {
if (AREJustEnded) {
this->board.spawnNextPiece();
this->lost = 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;
+ ((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)) {
@@ -79,13 +78,35 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
this->totalLockDelay = 0;
this->heldARR = 0;
}
else {
this->lost = true;
}
}
else {
if (initialRotation != NONE) {
this->board.rotate(initialRotation);
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 (this->lost) {
if (initialRotation == NONE && (!this->initialActions.contains(ROTATE_0))) {
this->board.rotate(NONE);
}
}
if (this->lost) {
this->framesPassed++;
return;
}
/* HOLD */
if (playerActions.contains(HOLD) && (!this->heldActions.contains(HOLD))) {
if (this->board.hold()) {
@@ -96,25 +117,48 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
}
/* MOVE LEFT/RIGHT */
Position before = this->board.getActivePiecePosition();
if (playerActions.contains(MOVE_LEFT)) {
this->movePiece(-1, (this->heldDAS >= 0));
this->movePiece(-1);
}
if (playerActions.contains(MOVE_RIGHT)) {
this->movePiece(1, (this->heldDAS <= 0));
else if (playerActions.contains(MOVE_RIGHT)) {
this->movePiece(1);
}
else {
this->heldDAS = 0;
}
if (before != this->board.getActivePiecePosition()) {
this->totalLockDelay = 0;
}
/* ROTATIONS */
before = this->board.getActivePiecePosition();
if (playerActions.contains(ROTATE_0) && (!this->heldActions.contains(ROTATE_0))) {
if (this->board.rotate(NONE)) {
this->totalLockDelay = 0;
}
}
if (playerActions.contains(ROTATE_CW) && (!this->heldActions.contains(ROTATE_CW))) {
this->board.rotate(CLOCKWISE);
if (this->board.rotate(CLOCKWISE)) {
this->totalLockDelay = 0;
}
}
if (playerActions.contains(ROTATE_180) && (!this->heldActions.contains(ROTATE_180))) {
this->board.rotate(DOUBLE);
if (this->board.rotate(DOUBLE)) {
this->totalLockDelay = 0;
}
}
if (playerActions.contains(ROTATE_CCW) && (!this->heldActions.contains(ROTATE_CCW))) {
this->board.rotate(COUNTERCLOCKWISE);
if (this->board.rotate(COUNTERCLOCKWISE)) {
this->totalLockDelay = 0;
}
}
if (before != this->board.getActivePiecePosition()) {
this->subVerticalPosition = 0;
}
/* SOFT DROP */
@@ -149,7 +193,7 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
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);
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) {
@@ -171,16 +215,18 @@ 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
// update remembered actions for next frame
if ((!this->started) || this->leftARETime > 0) {
for (Action action : playerActions) {
this->initialActions.insert(action);
@@ -189,19 +235,24 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
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->leftARETime > 0) {
if (playerActions.contains(MOVE_LEFT)) {
this->heldDAS = std::min(-1, this->heldDAS - 1);
}
else 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) {
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;
}
@@ -209,9 +260,12 @@ void Game::movePiece(int movement, bool resetDirection) {
this->heldDAS += movement;
}
if (abs(this->heldDAS) > this->parameters.getDAS()) {
int appliedARR = this->parameters.getARR();
if (abs(this->heldDAS) == appliedDAS + 1) {
if (movement == -1) this->board.moveLeft();
if (movement == 1) this->board.moveRight();
}
if (abs(this->heldDAS) > appliedDAS + 1) {
// ARR=0 -> instant movement
if (appliedARR == 0) {
if (movement == -1) while (this->board.moveLeft());
@@ -233,7 +287,6 @@ 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
@@ -246,16 +299,14 @@ void Game::lockPiece() {
}
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();
this->lost = this->board.spawnNextPiece();
}
}
@@ -291,22 +342,26 @@ bool Game::areBlocksBones() const {
return this->parameters.getBoneBlocks();
}
Board Game::getBoard() const {
Position Game::ghostPiecePosition() const {
return this->board.lowestPosition();
}
const Board& Game::getBoard() const {
return this->board.getBoard();
}
Piece Game::getActivePiece() const {
const std::shared_ptr<Piece>& Game::getActivePiece() const {
return this->board.getActivePiece();
}
Position Game::getActivePiecePosition() const {
const Position& Game::getActivePiecePosition() const {
return this->board.getActivePiecePosition();
}
Piece Game::getHeldPiece() const {
const std::shared_ptr<Piece>& Game::getHeldPiece() const {
return this->board.getHeldPiece();
}
std::vector<Piece> Game::getNextPieces() const {
const std::vector<Piece>& Game::getNextPieces() const {
return this->board.getNextPieces();
}

View File

@@ -63,7 +63,7 @@ class Game {
/**
* Move the piece in the specified direction
*/
void movePiece(int movement, bool resetDirection);
void movePiece(int movement);
/**
* Locks the piece, updates level and score and spawns the next piece if necessary
@@ -112,27 +112,32 @@ class Game {
bool areBlocksBones() const;
/**
* @return A copy of the board
* @return The position of the ghost piece
*/
Board getBoard() const;
Position ghostPiecePosition() const;
/**
* @return A copy of the active piece
* @return The board
*/
Piece getActivePiece() const;
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;
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 pieces 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;
};

View File

@@ -72,15 +72,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->activePieceInWall()) {
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);
@@ -91,7 +98,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;
@@ -99,6 +109,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;
@@ -107,7 +120,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) {
@@ -118,9 +134,9 @@ 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->activePieceOverlaps(safePositions, shift)) {
@@ -175,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->activePieceInWall()) {
this->activePiece = std::make_shared<Piece>(stored);
this->goToSpawnPosition();
// if the piece still can't spawn, abort holding
if (this->activePieceInWall()) {
if (isFirstTimeHolding) {
this->activePiece = nullptr;
}
std::swap(this->activePiece, this->heldPiece);
this->activePiecePosition = storedPosition;
return false;
}
}
@@ -200,6 +218,8 @@ bool GameBoard::hold(Rotation initialRotation) {
this->nextQueue.erase(this->nextQueue.begin());
}
this->heldPiece->defaultRotation();
// this piece has done nothing yet
this->isLastMoveKick = false;
@@ -207,10 +227,7 @@ bool GameBoard::hold(Rotation initialRotation) {
}
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());
@@ -219,13 +236,22 @@ bool GameBoard::spawnNextPiece() {
// this piece has done nothing yet
this->isLastMoveKick = false;
return !this->activePieceInWall();
return this->activePieceInWall();
}
bool GameBoard::touchesGround() {
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->activePieceInWall(Position{0, 1}) && this->activePieceInWall(Position{1, 0})
&& this->activePieceInWall(Position{-1, 0}) && this->activePieceInWall(Position{0, -1}));
@@ -248,23 +274,23 @@ void GameBoard::addGarbageRows(int number) {
}
}
Board GameBoard::getBoard() const {
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;
}
@@ -293,6 +319,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

@@ -60,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);
@@ -89,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
@@ -103,34 +109,34 @@ class GameBoard {
void addGarbageRows(int number);
/**
* @return A copy of the board
* @return The board
*/
Board getBoard() const;
const Board& getBoard() const;
/**
* @return A copy of the active piece
* @return A pointer to the active piece, can be null
*/
Piece getActivePiece() const;
const std::shared_ptr<Piece>& getActivePiece() const;
/**
* @return A copy of the position of the active piece
* @return The position of the active piece
*/
Position getActivePiecePosition() const;
const Position& getActivePiecePosition() const;
/**
* @return A copy of the held piece
* @return A pointer to the held piece, can be null
*/
Piece getHeldPiece() const;
const std::shared_ptr<Piece>& getHeldPiece() const;
/**
* @return A copy of the next piece queue
* @return The next piece queue, can be empty
*/
std::vector<Piece> getNextPieces() const;
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 activePieceInWall(const Position& shift = Position{0, 0}) const;

View File

@@ -118,7 +118,7 @@ void GameParameters::updateStats() {
if (this->level < 0) {
this->gravity = gravityPerLevel[0];
}
else if (this->gravity > 20) {
else if (this->level > 20) {
this->gravity = gravityPerLevel[20];
}
else {

View File

@@ -77,12 +77,12 @@ class PiecesList {
int getNumberOfPieces(int size) const;
/**
* @return The indexes of all selected pieces
* @return A copy of the indexes of all selected pieces
*/
std::vector<std::pair<int, int>> getSelectedPieces() const;
/**
* @return The piece corresponding to the specified index
* @return A copy of the piece corresponding to the specified index
*/
Piece getPiece(const std::pair<int, int>& pieceIndex) const;