major refactor

This commit is contained in:
Bernardo Magri
2025-04-24 09:53:09 +01:00
parent 0cdc668629
commit a4dccdc2a3
11 changed files with 2069 additions and 474 deletions
+318 -99
View File
@@ -1,163 +1,382 @@
#include "minefield.hpp"
#include <ctime>
#include <random>
#include <algorithm>
#include <iostream>
MineField::MineField(int cols, int rows, int mines) : m_rows(rows),
m_cols(cols),
m_totalMines(mines),
m_remainingFlags(mines),
m_openCells(0),
m_gameOver(false)
// Define difficulty presets
const GameDifficulty MineField::DIFFICULTY_EASY = {"Beginner", 9, 9, 10};
const GameDifficulty MineField::DIFFICULTY_MEDIUM = {"Intermediate", 16, 16, 40};
const GameDifficulty MineField::DIFFICULTY_HARD = {"Expert", 30, 16, 99};
const GameDifficulty MineField::DIFFICULTY_EXPERT = {"Master", 30, 20, 145};
MineField::MineField(int cols, int rows, int mines) :
m_rows(rows),
m_cols(cols),
m_totalMines(mines),
m_remainingFlags(mines),
m_openCells(0),
m_gameState(GameState::READY),
m_timerRunning(false)
{
for (int i = 0; i < m_cols * m_rows; i++)
{
std::shared_ptr<Cell> cell = std::make_shared<Cell>();
m_cells.push_back(cell);
}
// Create cells
m_cells.reserve(m_cols * m_rows);
for (int i = 0; i < m_cols * m_rows; i++)
{
std::shared_ptr<Cell> cell = std::make_shared<Cell>();
m_cells.push_back(cell);
}
}
MineField::~MineField()
{
m_cells.clear();
m_cells.clear();
}
void MineField::initBombs(int x, int y)
void MineField::startTimer()
{
int remainingMines = m_totalMines;
int startPos = x + y * m_rows;
srand(time(NULL)); // initialize rand()
while (remainingMines > 0)
{
int position = rand() % (m_cols * m_rows);
if (isBomb(position % m_cols, position / m_cols) || position == startPos)
if (!m_timerRunning)
{
continue;
m_startTime = std::chrono::steady_clock::now();
m_timerRunning = true;
}
}
void MineField::stopTimer()
{
if (m_timerRunning)
{
m_endTime = std::chrono::steady_clock::now();
m_timerRunning = false;
}
}
int MineField::getElapsedTime() const
{
if (m_timerRunning)
{
auto now = std::chrono::steady_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(now - m_startTime).count();
}
else if (m_gameState == GameState::WON || m_gameState == GameState::LOST)
{
return std::chrono::duration_cast<std::chrono::milliseconds>(m_endTime - m_startTime).count();
}
return 0;
}
void MineField::timerTick()
{
if (m_timerRunning)
{
timerSignal.emit(getElapsedTime());
}
}
void MineField::reset()
{
// Reset all cells
for (auto& cell : m_cells)
{
cell->isBomb = false;
cell->isFlagged = false;
cell->isCleared = false;
cell->bombsNearby = -1;
}
// Reset game state
m_openCells = 0;
m_remainingFlags = m_totalMines;
m_gameState = GameState::READY;
m_timerRunning = false;
// Emit signals
resetSignal.emit();
remainingFlagsSignal.emit(m_remainingFlags);
}
void MineField::startNewGame(int cols, int rows, int mines)
{
// Store new dimensions
m_cols = cols;
m_rows = rows;
m_totalMines = mines;
m_remainingFlags = mines;
// Create new cells
m_cells.clear();
m_cells.reserve(m_cols * m_rows);
for (int i = 0; i < m_cols * m_rows; i++)
{
std::shared_ptr<Cell> cell = std::make_shared<Cell>();
m_cells.push_back(cell);
}
// Reset game state
m_openCells = 0;
m_gameState = GameState::READY;
m_timerRunning = false;
// Emit signals
resetSignal.emit();
remainingFlagsSignal.emit(m_remainingFlags);
}
void MineField::initBombs(int firstX, int firstY)
{
if (m_gameState != GameState::READY)
{
return;
}
// Start timer when first cell is clicked
startTimer();
m_gameState = GameState::PLAYING;
// Create a vector of all possible positions
std::vector<int> positions;
positions.reserve(m_cols * m_rows);
for (int i = 0; i < m_cols * m_rows; i++)
{
positions.push_back(i);
}
// Remove first clicked position and surrounding cells from available positions
positions.erase(
std::remove_if(positions.begin(), positions.end(),
[this, firstX, firstY](int pos) {
int x = pos % m_cols;
int y = pos / m_cols;
return std::abs(x - firstX) <= 1 && std::abs(y - firstY) <= 1;
}
),
positions.end()
);
// Use modern random generator
std::random_device rd;
std::mt19937 gen(rd());
// Shuffle positions
std::shuffle(positions.begin(), positions.end(), gen);
// Place mines
int minesToPlace = std::min(m_totalMines, static_cast<int>(positions.size()));
for (int i = 0; i < minesToPlace; i++)
{
m_cells.at(positions[i])->isBomb = true;
}
m_cells.at(position)->isBomb = true;
--remainingMines;
}
}
bool MineField::openCell(int x, int y)
{
if (isBomb(x, y))
{
m_gameOver = true;
gameOverSignal.emit();
// stopTimer();
return false;
}
setOpenCell(x, y);
if (bombsNearby(x, y) == 0)
{
openNeighboorhood(x, y);
}
return true;
// Ignore if game is over or cell is already open/flagged
if (m_gameState != GameState::PLAYING && m_gameState != GameState::READY)
{
return false;
}
if (isOpened(x, y) || isFlagged(x, y))
{
return false;
}
// Start timer on first click if not already started
if (m_gameState == GameState::READY)
{
initBombs(x, y);
}
// Check if bomb
if (isBomb(x, y))
{
// Only emit the game over signal if we haven't already
if (m_gameState != GameState::LOST) {
m_gameState = GameState::LOST;
stopTimer();
gameOverSignal.emit();
}
return false;
}
// Open cell
setOpenCell(x, y);
// If no bombs nearby, open surrounding cells
if (bombsNearby(x, y) == 0)
{
openNeighboorhood(x, y);
}
return true;
}
void MineField::computeBombsNearby(int x, int y)
{
int total = 0;
// compute bombs in neighboorhood
for (int i = -1; i < 2; i++)
{
for (int j = -1; j < 2; j++)
int total = 0;
// Check all 8 neighboring cells
for (int i = -1; i <= 1; i++)
{
if (x + i >= 0 && x + i < m_cols && y + j >= 0 && y + j < m_rows)
{
if (isBomb(x + i, y + j))
for (int j = -1; j <= 1; j++)
{
++total;
// Skip the cell itself
if (i == 0 && j == 0)
{
continue;
}
int nx = x + i;
int ny = y + j;
// Check if within bounds
if (nx >= 0 && nx < m_cols && ny >= 0 && ny < m_rows)
{
if (isBomb(nx, ny))
{
++total;
}
}
}
}
}
}
m_cells.at(x + y * m_rows)->bombsNearby = total;
m_cells.at(x + y * m_cols)->bombsNearby = total;
}
void MineField::openNeighboorhood(int x, int y)
{
// compute bombs in neighboorhood
for (int i = -1; i < 2; i++)
{
for (int j = -1; j < 2; j++)
// Check all 8 neighboring cells
for (int i = -1; i <= 1; i++)
{
if (x + i >= 0 && x + i < m_cols && y + j >= 0 && y + j < m_rows)
{
if ((isOpened(x + i, y + j) == false) && (isBomb(x + i, y + j) == false))
for (int j = -1; j <= 1; j++)
{
setOpenCell((x + i), (y + j));
if (bombsNearby(x + i, y + j) == 0)
{
openNeighboorhood(x + i, y + j);
}
// Skip the cell itself
if (i == 0 && j == 0)
{
continue;
}
int nx = x + i;
int ny = y + j;
// Check if within bounds
if (nx >= 0 && nx < m_cols && ny >= 0 && ny < m_rows)
{
// If cell is not opened and not a bomb, open it
if (!isOpened(nx, ny) && !isBomb(nx, ny))
{
setOpenCell(nx, ny);
// If no bombs nearby, recursively open surrounding cells
if (bombsNearby(nx, ny) == 0)
{
openNeighboorhood(nx, ny);
}
}
}
}
}
}
}
}
bool MineField::isOpened(int x, int y)
{
return m_cells.at(x + y * m_rows)->isCleared;
return m_cells.at(x + y * m_cols)->isCleared;
}
bool MineField::isFlagged(int x, int y)
{
return m_cells.at(x + y * m_rows)->isFlagged;
return m_cells.at(x + y * m_cols)->isFlagged;
}
bool MineField::isBomb(int x, int y)
{
return m_cells.at(x + y * m_rows)->isBomb;
return m_cells.at(x + y * m_cols)->isBomb;
}
int MineField::bombsNearby(int x, int y)
{
if (m_cells.at(x + y * m_rows)->bombsNearby == -1)
{
computeBombsNearby(x, y);
}
return m_cells.at(x + y * m_rows)->bombsNearby;
// Calculate bombs nearby if not already calculated
if (m_cells.at(x + y * m_cols)->bombsNearby == -1)
{
computeBombsNearby(x, y);
}
return m_cells.at(x + y * m_cols)->bombsNearby;
}
void MineField::setOpenCell(int x, int y)
{
m_cells.at(x + y * m_rows)->isCleared = true;
openCellSignal.emit(x, y);
++m_openCells;
checkGameWon();
m_cells.at(x + y * m_cols)->isCleared = true;
openCellSignal.emit(x, y);
++m_openCells;
checkGameWon();
}
void MineField::checkGameWon()
{
if ((m_openCells == (m_cols * m_rows - m_totalMines)) && (m_gameOver == false) && (m_remainingFlags == 0))
{
m_gameWon = true;
gameWonSignal.emit();
}
// Win condition: All non-bomb cells are opened
if (m_openCells == (m_cols * m_rows - m_totalMines) && m_gameState == GameState::PLAYING)
{
m_gameState = GameState::WON;
stopTimer();
// Auto-flag all remaining bombs
for (int i = 0; i < m_cols * m_rows; i++)
{
int x = i % m_cols;
int y = i / m_cols;
if (isBomb(x, y) && !isFlagged(x, y))
{
m_cells.at(i)->isFlagged = true;
}
}
m_remainingFlags = 0;
remainingFlagsSignal.emit(m_remainingFlags);
// Emit game won signal with elapsed time
gameWonSignal.emit(getElapsedTime());
}
}
bool MineField::toggleFlag(int x, int y)
{
if (m_cells.at(x + y * m_rows)->isFlagged == true)
{
m_cells.at(x + y * m_rows)->isFlagged = false;
++m_remainingFlags;
// Ignore if game is over or cell is already open
if (m_gameState != GameState::PLAYING && m_gameState != GameState::READY)
{
return false;
}
if (isOpened(x, y))
{
return false;
}
if (m_gameState == GameState::READY)
{
// Start timer on first action
startTimer();
m_gameState = GameState::PLAYING;
}
// Toggle flag state
if (m_cells.at(x + y * m_cols)->isFlagged)
{
m_cells.at(x + y * m_cols)->isFlagged = false;
++m_remainingFlags;
}
else if (m_remainingFlags > 0)
{
m_cells.at(x + y * m_cols)->isFlagged = true;
--m_remainingFlags;
}
else
{
// No remaining flags
return false;
}
remainingFlagsSignal.emit(m_remainingFlags);
return true;
}
else if (m_remainingFlags > 0)
{
m_cells.at(x + y * m_rows)->isFlagged = true;
--m_remainingFlags;
remainingFlagsSignal.emit(m_remainingFlags);
checkGameWon();
return true;
}
return false;
}