#include "GameBoard.h" #include "../Pieces/Piece.h" #include "Board.h" #include "Bag.h" #include "LineClear.h" #include #include #include #include #include #include #include GameBoard::GameBoard(int boardWidth, int boardHeight, const std::shared_ptr& piecesList, int nextQueueLength) : board(boardWidth, boardHeight), generator(piecesList), nextQueueLength(nextQueueLength) { this->initialize(); } void GameBoard::reset() { this->board.clearBoard(); this->generator.jumpToNextBag(); this->initialize(); } void GameBoard::initialize() { this->nextQueue.clear(); for (int i = 0; i < nextQueueLength; i++) { this->nextQueue.push_back(this->generator.getNext()); } this->activePiece = nullptr; this->heldPiece = nullptr; this->isLastMoveKick = false; this->movedLeftLast = false; } bool GameBoard::moveLeft() { this->movedLeftLast = true; if (this->activePieceInWall(Position(-1, 0))) { return false; } else { this->activePiecePosition.x -= 1; this->isLastMoveKick = false; return true; } } bool GameBoard::moveRight() { this->movedLeftLast = false; if (this->activePieceInWall(Position(1, 0))) { return false; } else { this->activePiecePosition.x += 1; this->isLastMoveKick = false; return true; } } bool GameBoard::moveDown() { if (this->activePieceInWall(Position(0, -1))) { return false; } else { this->activePiecePosition.y -= 1; this->isLastMoveKick = false; return true; } } bool GameBoard::rotate(Rotation rotation) { Piece stored = *this->activePiece; this->activePiece->rotate(rotation); // before trying to kick, check if the piece can rotate without kicking if (rotation == NONE) { if (this->moveDown()) { this->isLastMoveKick = false; return true; } } else { if (!this->activePieceInWall()) { this->isLastMoveKick = false; return true; } } std::set safePositions; for (Position position : stored.getPositions()) { Position positionInGrid(position + this->activePiecePosition); safePositions.insert(positionInGrid); safePositions.insert(positionInGrid + Position(0, 1)); safePositions.insert(positionInGrid + Position(1, 0)); safePositions.insert(positionInGrid + Position(0, -1)); safePositions.insert(positionInGrid + Position(-1, 0)); } // first try kicking the piece down if (rotation == NONE) { this->activePiecePosition.y -= 1; } bool suceeded = this->tryKicking(true, safePositions); if (suceeded) { this->isLastMoveKick = true; return true; } // 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; return true; } // if it still doesn't work, abort the rotation this->activePiece = std::make_shared(stored); if (rotation == NONE) { this->isLastMoveKick = false; } return (rotation == NONE); } bool GameBoard::tryKicking(bool testingBottom, const std::set& safePositions) { // we try from the original height of the piece, moving vertically as long as the kicked piece touches the original at least once on this row 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 = (j == 0) ? 1 : 0; do { // we first check the side to which the player moved last if (movedLeftLast) { if (overlapsLeft) { if (this->tryFittingKickedPiece(safePositions, Position(-i, j), overlapsLeft)) return true; } if (overlapsRight) { if (this->tryFittingKickedPiece(safePositions, Position(+i, j), overlapsRight)) return true; } } else { if (overlapsRight) { if (this->tryFittingKickedPiece(safePositions, Position(+i, j), overlapsRight)) return true; } if (overlapsLeft) { if (this->tryFittingKickedPiece(safePositions, Position(-i, j), overlapsLeft)) return true; } } i++; } while (overlapsLeft && overlapsRight); if (i == 1) { overlapsVertically = false; } (testingBottom) ? j-- : j++; } while (overlapsVertically); return false; } bool GameBoard::tryFittingKickedPiece(const std::set& safePositions, const Position& shift, bool& overlaps) { if (!this->activePieceOverlaps(safePositions, shift)) { overlaps = false; } else { if (!this->activePieceInWall(shift)) { this->activePiecePosition += shift; return true; } } return false; } bool GameBoard::activePieceOverlaps(const std::set& safePositions, const Position& shift) const { for (Position position : this->activePiece->getPositions()) { if (safePositions.contains(position + this->activePiecePosition + shift)) return true; } return false; } bool GameBoard::hold(std::optional initialRotation) { Position storedPosition = this->activePiecePosition; std::swap(this->activePiece, this->heldPiece); bool isFirstTimeHolding = (this->activePiece == nullptr); if (isFirstTimeHolding) { if (this->nextQueueLength == 0) { this->activePiece = std::make_shared(this->generator.lookNext()); } else { this->activePiece = std::make_shared(this->nextQueue.front()); } } // try initial rotation this->goToSpawnPosition(); if (initialRotation.has_value()) { std::shared_ptr storedPiece(this->activePiece); this->rotate(initialRotation.value()); if (this->activePieceInWall()) { this->activePiece = storedPiece; } } // if the piece can't spawn, try 0° rotation if (this->activePieceInWall()) { std::shared_ptr storedPiece(this->activePiece); this->rotate(NONE); // if the piece still can't spawn, abort holding if (this->activePieceInWall()) { this->activePiece = (isFirstTimeHolding) ? nullptr : storedPiece; std::swap(this->activePiece, this->heldPiece); this->activePiecePosition = storedPosition; return false; } } if (isFirstTimeHolding) { this->nextQueue.push_back(this->generator.getNext()); this->nextQueue.erase(this->nextQueue.begin()); } this->heldPiece->defaultRotation(); this->isLastMoveKick = false; return true; } bool GameBoard::spawnNextPiece() { this->nextQueue.push_back(this->generator.getNext()); this->activePiece = std::make_shared(this->nextQueue.front()); this->nextQueue.erase(this->nextQueue.begin()); this->goToSpawnPosition(); this->isLastMoveKick = false; return this->activePieceInWall(); } bool GameBoard::activePieceInWall(const Position& shift) const { for (Position position : this->activePiece->getPositions()) { if (this->board.getBlock(position + this->activePiecePosition + shift) != NOTHING) return true; } return false; } bool GameBoard::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))); for (Position position : this->activePiece->getPositions()) { this->board.changeBlock(position + this->activePiecePosition, this->activePiece->getBlockType()); } this->activePiece = nullptr; return LineClear{this->board.clearRows(), isLockedInPlace, (!isLockedInPlace) && this->isLastMoveKick}; } void GameBoard::addGarbageRows(int number) { int holePosition = std::rand() % this->board.getWidth(); for (int i = 0; i < number; i++) { this->board.insertRow(0, holePosition, GARBAGE); if (this->touchesGround()) { this->activePiecePosition.y += 1; } } } const Board& GameBoard::getBoard() const { return this->board; } const std::shared_ptr& GameBoard::getActivePiece() const { return this->activePiece; } const Position& GameBoard::getActivePiecePosition() const { return this->activePiecePosition; } const std::shared_ptr& GameBoard::getHeldPiece() const { return this->heldPiece; } const std::vector& GameBoard::getNextPieces() const { return this->nextQueue; } void GameBoard::goToSpawnPosition() { int lowestPosition = this->activePiece->getLength() - 1; for (Position position : this->activePiece->getPositions()) { if (position.y < lowestPosition) lowestPosition = position.y; } // set the piece one line above the board this->activePiecePosition.y = this->board.getBaseHeight() - lowestPosition; // 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) { // print over the board (only the active piece if it is there) if (gameboard.activePiece != nullptr) { Block pieceBlockType = gameboard.activePiece->getBlockType(); os << getConsoleColorCode(pieceBlockType); // print only the position 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->containsSquare(Position(x, y) - gameboard.activePiecePosition); if (hasActivePiece) { os << "*"; } else { os << " "; } } os << std::endl; } } // print the board Block pieceBlockType = (gameboard.activePiece == nullptr) ? NOTHING : gameboard.activePiece->getBlockType(); 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->containsSquare(Position(x, y) - gameboard.activePiecePosition); // the active piece takes visual priority over the board if (hasActivePiece) { os << getConsoleColorCode(pieceBlockType); os << "*"; } else { Block block = gameboard.board.getBlock(Position(x, y)); os << getConsoleColorCode(block); if (block != NOTHING) { os << "*"; } else { os << "-"; } } } os << std::endl; } // print hold box 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; } os << getResetConsoleColorCode(); return os; }