major refactor
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -4,51 +4,115 @@
|
||||
#include <memory>
|
||||
#include <sigc++/signal.h>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
// Game difficulty presets
|
||||
struct GameDifficulty {
|
||||
std::string name;
|
||||
int cols;
|
||||
int rows;
|
||||
int mines;
|
||||
};
|
||||
|
||||
// Game score for leaderboard
|
||||
struct GameScore {
|
||||
std::string playerName;
|
||||
std::string difficulty;
|
||||
int time;
|
||||
std::string date;
|
||||
|
||||
bool operator<(const GameScore& other) const {
|
||||
return time < other.time;
|
||||
}
|
||||
};
|
||||
|
||||
class MineField
|
||||
{
|
||||
public:
|
||||
enum class GameState {
|
||||
READY,
|
||||
PLAYING,
|
||||
WON,
|
||||
LOST
|
||||
};
|
||||
|
||||
struct Cell
|
||||
{
|
||||
bool isFlagged = false;
|
||||
bool isCleared = false;
|
||||
bool isBomb = false;
|
||||
int bombsNearby = -1;
|
||||
};
|
||||
private:
|
||||
struct Cell
|
||||
{
|
||||
bool isFlagged = false;
|
||||
bool isCleared = false;
|
||||
bool isBomb = false;
|
||||
int bombsNearby = -1;
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<Cell>> m_cells;
|
||||
int m_rows;
|
||||
int m_cols;
|
||||
int m_totalMines;
|
||||
int m_remainingFlags;
|
||||
int m_openCells;
|
||||
bool m_gameOver;
|
||||
bool m_gameWon;
|
||||
std::vector<std::shared_ptr<Cell>> m_cells;
|
||||
int m_rows;
|
||||
int m_cols;
|
||||
int m_totalMines;
|
||||
int m_remainingFlags;
|
||||
int m_openCells;
|
||||
GameState m_gameState;
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_startTime;
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_endTime;
|
||||
bool m_timerRunning;
|
||||
|
||||
void computeBombsNearby(int x, int y);
|
||||
void openNeighboorhood(int x, int y);
|
||||
void setOpenCell(int x, int y);
|
||||
void checkGameWon();
|
||||
void computeBombsNearby(int x, int y);
|
||||
void openNeighboorhood(int x, int y);
|
||||
void setOpenCell(int x, int y);
|
||||
void checkGameWon();
|
||||
void startTimer();
|
||||
void stopTimer();
|
||||
|
||||
public:
|
||||
MineField(int cols, int rows, int mines);
|
||||
~MineField();
|
||||
void initBombs(int x, int y);
|
||||
bool isBomb(int x, int y);
|
||||
bool isFlagged(int x, int y);
|
||||
bool isOpened(int x, int y);
|
||||
bool openCell(int x, int y);
|
||||
int bombsNearby(int x, int y);
|
||||
bool isGameOver() { return m_gameOver; };
|
||||
int getCols() { return m_cols; };
|
||||
int getRows() { return m_rows; };
|
||||
bool toggleFlag(int x, int y);
|
||||
int getRemainingFlags() { return m_remainingFlags; };
|
||||
int getTotalMines() { return m_totalMines; };
|
||||
void startNewGame(int cols, int rows, int mines);
|
||||
|
||||
sigc::signal<void(int, int)> openCellSignal;
|
||||
sigc::signal<void(int)> remainingFlagsSignal;
|
||||
sigc::signal<void(void)> gameWonSignal;
|
||||
sigc::signal<void(void)> gameOverSignal;
|
||||
MineField(int cols, int rows, int mines);
|
||||
~MineField();
|
||||
|
||||
void initBombs(int x, int y);
|
||||
bool isBomb(int x, int y);
|
||||
bool isFlagged(int x, int y);
|
||||
bool isOpened(int x, int y);
|
||||
bool openCell(int x, int y);
|
||||
int bombsNearby(int x, int y);
|
||||
|
||||
GameState getGameState() const { return m_gameState; }
|
||||
int getCols() const { return m_cols; }
|
||||
int getRows() const { return m_rows; }
|
||||
bool toggleFlag(int x, int y);
|
||||
int getRemainingFlags() const { return m_remainingFlags; }
|
||||
int getTotalMines() const { return m_totalMines; }
|
||||
int getOpenCells() const { return m_openCells; }
|
||||
|
||||
void reset();
|
||||
void startNewGame(int cols, int rows, int mines);
|
||||
|
||||
// Get elapsed time in milliseconds
|
||||
int getElapsedTime() const;
|
||||
|
||||
// Timer tick (for UI updates)
|
||||
void timerTick();
|
||||
|
||||
// Signal when game is reset
|
||||
sigc::signal<void()> resetSignal;
|
||||
|
||||
// Signal when a cell is opened
|
||||
sigc::signal<void(int, int)> openCellSignal;
|
||||
|
||||
// Signal when flags count changes
|
||||
sigc::signal<void(int)> remainingFlagsSignal;
|
||||
|
||||
// Signal when game is won
|
||||
sigc::signal<void(int)> gameWonSignal; // int parameter is elapsed time in ms
|
||||
|
||||
// Signal when game is lost
|
||||
sigc::signal<void()> gameOverSignal;
|
||||
|
||||
// Signal for timer updates
|
||||
sigc::signal<void(int)> timerSignal; // int parameter is elapsed time in ms
|
||||
|
||||
// Predefined difficulty levels
|
||||
static const GameDifficulty DIFFICULTY_EASY;
|
||||
static const GameDifficulty DIFFICULTY_MEDIUM;
|
||||
static const GameDifficulty DIFFICULTY_HARD;
|
||||
static const GameDifficulty DIFFICULTY_EXPERT;
|
||||
};
|
||||
|
||||
81
src/timer.cpp
Normal file
81
src/timer.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "timer.hpp"
|
||||
#include <iostream>
|
||||
#include <glibmm/main.h>
|
||||
|
||||
Timer::Timer() : m_running(false) {
|
||||
reset();
|
||||
}
|
||||
|
||||
Timer::~Timer() {
|
||||
stop();
|
||||
|
||||
// Wait for timer thread to finish
|
||||
if (m_timerThread.joinable()) {
|
||||
m_timerThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void Timer::start() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
if (!m_running) {
|
||||
m_running = true;
|
||||
m_startTime = std::chrono::steady_clock::now();
|
||||
|
||||
// Start timer thread if not already running
|
||||
if (!m_timerThread.joinable()) {
|
||||
m_timerThread = std::thread(&Timer::timerThread, this);
|
||||
} else {
|
||||
// Notify thread if already existing
|
||||
m_condition.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Timer::stop() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_running = false;
|
||||
m_condition.notify_one();
|
||||
}
|
||||
|
||||
void Timer::reset() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_startTime = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
int Timer::getElapsedTime() const {
|
||||
if (m_running) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(now - m_startTime).count();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Timer::timerThread() {
|
||||
while (true) {
|
||||
// Wait for timer to be running
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_condition.wait(lock, [this] { return m_running || !m_running; });
|
||||
|
||||
// Exit thread if timer is stopped
|
||||
if (!m_running) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Sleep for 100ms (10 updates per second)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
// Check if timer is still running
|
||||
if (m_running) {
|
||||
// Get elapsed time
|
||||
int time = getElapsedTime();
|
||||
|
||||
// Emit signal through Glib main loop
|
||||
Glib::signal_idle().connect_once([this, time] {
|
||||
timerSignal.emit(time);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/timer.hpp
Normal file
42
src/timer.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <sigc++/signal.h>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
Timer();
|
||||
~Timer();
|
||||
|
||||
// Start timer
|
||||
void start();
|
||||
|
||||
// Stop timer
|
||||
void stop();
|
||||
|
||||
// Reset timer
|
||||
void reset();
|
||||
|
||||
// Get elapsed time in milliseconds
|
||||
int getElapsedTime() const;
|
||||
|
||||
// Signal emitted on timer tick
|
||||
sigc::signal<void(int)> timerSignal;
|
||||
|
||||
private:
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_startTime;
|
||||
std::atomic<bool> m_running;
|
||||
|
||||
// Thread handling
|
||||
std::thread m_timerThread;
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_condition;
|
||||
|
||||
// Timer thread function
|
||||
void timerThread();
|
||||
};
|
||||
1349
src/window.cpp
1349
src/window.cpp
File diff suppressed because it is too large
Load Diff
131
src/window.hpp
131
src/window.hpp
@@ -1,53 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include "glibmm/dispatcher.h"
|
||||
#include "minefield.hpp"
|
||||
#include <memory>
|
||||
#include <gtkmm.h>
|
||||
#include <glibmm.h>
|
||||
#include <gdkmm.h>
|
||||
#include <sigc++/sigc++.h>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
#define PROJECT_NAME "minesweeper"
|
||||
|
||||
class MainWindow : public Gtk::Window
|
||||
class MainWindow : public Gtk::ApplicationWindow
|
||||
{
|
||||
Gtk::Box boxV{Gtk::Orientation::VERTICAL};
|
||||
Gtk::Box boxH{Gtk::Orientation::HORIZONTAL};
|
||||
std::vector<std::shared_ptr<Gtk::ToggleButton>> buttons;
|
||||
Gtk::Grid grid;
|
||||
Gtk::HeaderBar bar;
|
||||
Gtk::Button newGameButton;
|
||||
Gtk::Button optionButton;
|
||||
Gtk::Label flagLabel;
|
||||
Gtk::Label clockLabel;
|
||||
MineField field{16, 16, 1};
|
||||
int m_elapsedTime;
|
||||
bool newGame;
|
||||
std::shared_ptr<Gdk::Texture> m_textureBomb;
|
||||
std::shared_ptr<Gdk::Texture> m_textureFlag;
|
||||
std::shared_ptr<Gdk::Texture> m_textureFlagBomb;
|
||||
void updateCell(int x, int y);
|
||||
void openBombs();
|
||||
void updateFlagsLabel(int flags);
|
||||
void updateClockLabel();
|
||||
void handleClockSig(size_t);
|
||||
void gameWon();
|
||||
void gameOver();
|
||||
sigc::connection clockSignalConn;
|
||||
Glib::Dispatcher m_clockDispatch;
|
||||
// void OpenNearCells(int index);
|
||||
// void Explode();xo
|
||||
// bool AllCellsOpened();
|
||||
|
||||
public:
|
||||
MainWindow();
|
||||
// void OnNewButtonClick();
|
||||
void OnCellClick(int x, int y);
|
||||
void OnCellRightClick(int n_press, double n_x, double n_y, int index);
|
||||
// void ShowGameWonAnimation();
|
||||
// void ApplyStyles();
|
||||
// bool UpdateClockLabel();
|
||||
MainWindow();
|
||||
virtual ~MainWindow() = default;
|
||||
|
||||
private:
|
||||
// UI containers
|
||||
Gtk::Box m_boxMain{Gtk::Orientation::VERTICAL};
|
||||
Gtk::HeaderBar m_headerBar;
|
||||
Gtk::Box m_statusBox{Gtk::Orientation::HORIZONTAL};
|
||||
Gtk::Grid m_grid;
|
||||
Gtk::Overlay m_overlay;
|
||||
|
||||
// Game status widgets
|
||||
Gtk::Label m_minesLabel;
|
||||
Gtk::Label m_flagsLabel;
|
||||
Gtk::Label m_timeLabel;
|
||||
|
||||
// Header bar controls
|
||||
Gtk::Button m_newGameButton;
|
||||
Gtk::MenuButton m_difficultyButton;
|
||||
|
||||
// Game field and buttons
|
||||
std::unique_ptr<MineField> m_field;
|
||||
std::vector<std::shared_ptr<Gtk::ToggleButton>> m_buttons;
|
||||
|
||||
// Resources
|
||||
std::shared_ptr<Gdk::Texture> m_textureBomb;
|
||||
std::shared_ptr<Gdk::Texture> m_textureFlag;
|
||||
std::shared_ptr<Gdk::Texture> m_textureFlagBomb;
|
||||
std::shared_ptr<Gdk::Texture> m_textureExplosion;
|
||||
|
||||
// Game state tracking
|
||||
bool m_firstClick;
|
||||
sigc::connection m_timerConnection;
|
||||
|
||||
// Leaderboard
|
||||
std::vector<GameScore> m_leaderboard;
|
||||
Gtk::Dialog* m_leaderboardDialog;
|
||||
Gtk::Dialog* m_difficultyDialog;
|
||||
Gtk::Dialog* m_winDialog;
|
||||
Gtk::Dialog* m_nameDialog;
|
||||
std::string m_currentDifficulty;
|
||||
|
||||
// Methods
|
||||
void setupUI();
|
||||
void setupHeaderBar();
|
||||
void setupStatusBar();
|
||||
void setupGameBoard();
|
||||
void setupLeaderboard();
|
||||
void setupCSSProviders();
|
||||
void loadResources();
|
||||
|
||||
// Event handlers
|
||||
void onCellClick(int x, int y);
|
||||
void onCellRightClick(int n_press, double n_x, double n_y, int index);
|
||||
void onNewGameClick();
|
||||
void onDifficultySelected(const GameDifficulty& difficulty);
|
||||
void showDifficultyDialog();
|
||||
|
||||
// Game callbacks
|
||||
void updateCell(int x, int y);
|
||||
void updateFlagsLabel(int flags);
|
||||
void updateTimeLabel();
|
||||
bool updateTimer();
|
||||
void onGameOver();
|
||||
void onGameWon(int time);
|
||||
void showGameOverAnimation();
|
||||
void showGameWonAnimation();
|
||||
void revealAllBombs();
|
||||
|
||||
// Leaderboard methods
|
||||
void loadLeaderboard();
|
||||
void saveLeaderboard();
|
||||
void showLeaderboard();
|
||||
void addScoreToLeaderboard(const std::string& playerName, const std::string& difficulty, int time);
|
||||
std::string formatTime(int milliseconds) const;
|
||||
|
||||
// Helper methods
|
||||
void resetGame();
|
||||
void startNewGame(int cols, int rows, int mines, const std::string& difficulty);
|
||||
void clearBoard();
|
||||
void setupConfetti();
|
||||
void showNameInputDialog(int time);
|
||||
std::filesystem::path getConfigDir() const;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user