401 lines
12 KiB
C++
401 lines
12 KiB
C++
#include "GameBoard.h"
|
|
|
|
#include "../Pieces/Piece.h"
|
|
#include "Board.h"
|
|
#include "Bag.h"
|
|
#include "LineClear.h"
|
|
|
|
#include <vector>
|
|
#include <set>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <optional>
|
|
|
|
|
|
GameBoard::GameBoard(int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& 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<Position> 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<Piece>(stored);
|
|
if (rotation == NONE) {
|
|
this->isLastMoveKick = false;
|
|
}
|
|
return (rotation == NONE);
|
|
}
|
|
|
|
bool GameBoard::tryKicking(bool testingBottom, const std::set<Position>& 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<Position>& 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<Position>& 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<Rotation> 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<Piece>(this->generator.lookNext());
|
|
}
|
|
else {
|
|
this->activePiece = std::make_shared<Piece>(this->nextQueue.front());
|
|
}
|
|
}
|
|
|
|
// try initial rotation
|
|
this->goToSpawnPosition();
|
|
if (initialRotation.has_value()) {
|
|
std::shared_ptr<Piece> 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<Piece> 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<Piece>(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<Piece>& GameBoard::getActivePiece() const {
|
|
return this->activePiece;
|
|
}
|
|
|
|
const Position& GameBoard::getActivePiecePosition() const {
|
|
return this->activePiecePosition;
|
|
}
|
|
|
|
const std::shared_ptr<Piece>& GameBoard::getHeldPiece() const {
|
|
return this->heldPiece;
|
|
}
|
|
|
|
const std::vector<Piece>& 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;
|
|
}
|