#include "minefield.hpp" #include #include #include #include // 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) { // Create cells m_cells.reserve(m_cols * m_rows); for (int i = 0; i < m_cols * m_rows; i++) { std::shared_ptr cell = std::make_shared(); m_cells.push_back(cell); } } MineField::~MineField() { m_cells.clear(); } void MineField::startTimer() { if (!m_timerRunning) { 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(now - m_startTime).count(); } else if (m_gameState == GameState::WON || m_gameState == GameState::LOST) { return std::chrono::duration_cast(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 = std::make_shared(); 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 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(positions.size())); for (int i = 0; i < minesToPlace; i++) { m_cells.at(positions[i])->isBomb = true; } } bool MineField::openCell(int x, int y) { // 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; // Check all 8 neighboring cells for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; 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 (isBomb(nx, ny)) { ++total; } } } } m_cells.at(x + y * m_cols)->bombsNearby = total; } void MineField::openNeighboorhood(int x, int y) { // Check all 8 neighboring cells for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; 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_cols)->isCleared; } bool MineField::isFlagged(int x, int y) { return m_cells.at(x + y * m_cols)->isFlagged; } bool MineField::isBomb(int x, int y) { return m_cells.at(x + y * m_cols)->isBomb; } int MineField::bombsNearby(int x, int y) { // 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_cols)->isCleared = true; openCellSignal.emit(x, y); ++m_openCells; checkGameWon(); } void MineField::checkGameWon() { // 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) { // 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; }