Files
jminos/src/Core/GameBoard.cpp
Persson-dev 46b9b8dd65
Some checks failed
Linux arm64 / Build (push) Failing after 2m4s
begin optimize Generator
2025-07-19 23:39:22 +02:00

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;
}