Browse Source

major refactor

Bernardo Magri 8 months ago
parent
commit
a4dccdc2a3
11 changed files with 2086 additions and 491 deletions
  1. 119 3
      .gitignore
  2. 84 8
      README.md
  3. 31 5
      meson.build
  4. 118 0
      resources/minesweeper.svg
  5. 11 0
      resources/org.gtkmm.minesweeper.desktop
  6. 314 95
      src/minefield.cpp
  7. 105 41
      src/minefield.hpp
  8. 81 0
      src/timer.cpp
  9. 42 0
      src/timer.hpp
  10. 1093 296
      src/window.cpp
  11. 88 43
      src/window.hpp

+ 119 - 3
.gitignore

@@ -1,4 +1,13 @@
-# ---> C++
+# Build directories
+build/
+_build/
+bin/
+lib/
+obj/
+
+# Prerequisites
+*.d
+
 # Compiled Object files
 *.slo
 *.lo
@@ -16,6 +25,7 @@
 
 # Fortran module files
 *.mod
+*.smod
 
 # Compiled Static libraries
 *.lai
@@ -27,6 +37,112 @@
 *.exe
 *.out
 *.app
+minesweeper
+
+# CMake and build system
+CMakeCache.txt
+CMakeFiles/
+cmake_install.cmake
+compile_commands.json
+Makefile
+meson.build.user
+meson-build/
+meson-logs/
+meson-private/
+
+# IDE and text editor files
+.vscode/
+.idea/
+*.kdev4
+.kdev4/
+
+# Emacs files
+*~
+\#*\#
+/.emacs.desktop
+/.emacs.desktop.lock
+*.elc
+auto-save-list
+tramp
+.\#*
+
+# Emacs directory configuration
+.dir-locals.el
+
+# Emacs backup files
+*~
+
+# Emacs org-mode
+.org-id-locations
+*_archive
+
+# Emacs flymake
+*_flymake*
+
+# Emacs eshell files
+/eshell/history
+/eshell/lastdir
+
+# Emacs elpa packages
+/elpa/
+
+# Emacs projectile files
+.projectile
+
+# macOS specific
+.DS_Store
+.AppleDouble
+.LSOverride
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# Linux specific
+*~
+.fuse_hidden*
+.directory
+.Trash-*
+.nfs*
+
+# Windows specific
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+*.stackdump
+[Dd]esktop.ini
+$RECYCLE.BIN/
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+*.lnk
+
+# Project specific
+.gresource
+*.gresource
+
+# Leaderboard and config files
+.config/minesweeper/
 
-# Backup files
-*~
+#MacOS files
+.cache/
+.DS_Store

+ 84 - 8
README.md

@@ -1,25 +1,101 @@
-# minesweeper
+# MineSweeper
 
-Minesweeper game in C++ and GTK4
+A modern GTK4/C++ implementation of the classic Minesweeper game with multiple difficulty levels, customizable board sizes, animations, and a leaderboard system.
 
 ![Game Screenshot](screenshots/screen1.png)
 
-## Instructions to build
+## Features
 
-Install dependencies
+- Multiple difficulty levels: Beginner, Intermediate, Expert, and Master
+- Custom board size and mine count configuration
+- Win/lose animations
+- Persistent leaderboard to track best scores
+- Modern GTK4 user interface
+- Modern C++20 implementation
 
+## Building from Source
+
+### Dependencies
+
+- GTK 4.0 or later
+- gtkmm 4.0 or later
+- sigc++ 3.0 or later
+- Meson build system
+- Ninja build system
+
+### Ubuntu/Debian
+
+Install dependencies:
+
+```bash
+sudo apt install build-essential meson ninja-build libgtkmm-4.0-dev libsigc++-3.0-dev
 ```
-sudo apt install libgtkmm-4.0-dev libsigc++-3.0-dev
+
+### Fedora
+
+Install dependencies:
+
+```bash
+sudo dnf install gcc-c++ meson ninja-build gtkmm4.0-devel libsigc++3-devel
 ```
 
-Go to the project folder
+### Arch Linux
+
+Install dependencies:
+
+```bash
+sudo pacman -S base-devel meson ninja gtkmm-4.0 libsigc++-3.0
 ```
+
+### Building the Project
+
+Clone the repository:
+
+```bash
+git clone https://github.com/username/minesweeper.git
 cd minesweeper
 ```
 
-Setup meson and compile the project
+Configure and build with Meson:
 
-```
+```bash
 meson setup build
 meson compile -C build
 ```
+
+Run the game:
+
+```bash
+./build/minesweeper
+```
+
+### Installing
+
+To install system-wide:
+
+```bash
+meson install -C build
+```
+
+## Using Nix
+
+If you use the Nix package manager, you can build and run the application with:
+
+```bash
+nix-build
+./result/bin/minesweeper
+```
+
+Or install it with:
+
+```bash
+nix-env -i -f .
+```
+
+## Contributing
+
+Contributions are welcome! Please feel free to submit a Pull Request.
+
+## License
+
+This project is licensed under the MIT License - see the LICENSE file for details.

+ 31 - 5
meson.build

@@ -1,5 +1,5 @@
 project('minesweeper', 'cpp',
-  version : '0.1',
+  version : '0.2.0',
   default_options : ['warning_level=3', 'cpp_std=c++20'])
 
 gnome = import('gnome')
@@ -10,9 +10,35 @@ res = gnome.compile_resources(
      c_name: 'gresources'
 )
 
+# Dependencies
 deps = dependency(['gtkmm-4.0', 'sigc++-3.0'])
-src = ['src/window.cpp', 'src/window.hpp', 'src/minefield.hpp', 'src/minefield.cpp',
-       'src/timer.hpp', 'src/timer.cpp', res]
-       
-exe = executable('minesweeper', src, dependencies : deps, install : true)
 
+# Source files
+src = [
+    'src/window.cpp', 
+    'src/window.hpp', 
+    'src/minefield.hpp', 
+    'src/minefield.cpp',
+    'src/timer.hpp', 
+    'src/timer.cpp',
+    res
+]
+
+# Executable
+executable('minesweeper', 
+    src, 
+    dependencies : deps, 
+    install : true
+)
+
+# Install icons
+install_data(
+    'resources/minesweeper.svg',
+    install_dir: join_paths(get_option('datadir'), 'icons/hicolor/scalable/apps')
+)
+
+# Install desktop file
+install_data(
+    'resources/org.gtkmm.minesweeper.desktop',
+    install_dir: join_paths(get_option('datadir'), 'applications')
+)

+ 118 - 0
resources/minesweeper.svg

@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 100 100"
+   width="100"
+   height="100"
+   version="1.1"
+   id="svg9"
+   sodipodi:docname="minesweeper.svg"
+   xml:space="preserve"
+   inkscape:version="1.3.2 (091e20e, 2023-11-25)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"><defs
+     id="defs9" /><sodipodi:namedview
+     id="namedview9"
+     pagecolor="#4c524e"
+     bordercolor="#eeeeee"
+     borderopacity="1"
+     inkscape:showpageshadow="0"
+     inkscape:pageopacity="0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     inkscape:zoom="10.337901"
+     inkscape:cx="34.097831"
+     inkscape:cy="41.207591"
+     inkscape:window-width="2560"
+     inkscape:window-height="1387"
+     inkscape:window-x="0"
+     inkscape:window-y="25"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg9" /><rect
+     x="4.573761"
+     y="5.6444345"
+     width="90"
+     height="90"
+     rx="15"
+     fill="#f9db9b"
+     stroke="#6d4c41"
+     stroke-width="4"
+     id="rect1" /><rect
+     x="9.4985218"
+     y="10.021999"
+     width="25"
+     height="25"
+     fill="#f44336"
+     rx="5"
+     id="rect2" /><circle
+     cx="54.573761"
+     cy="55.644436"
+     r="18"
+     fill="#4e342e"
+     id="circle2" /><path
+     d="m 59.573761,40.644434 q 5,-10 10,-5"
+     stroke="#4e342e"
+     stroke-width="3"
+     fill="none"
+     id="path2" /><line
+     x1="69.34153"
+     y1="29.115864"
+     x2="64.542198"
+     y2="27.71357"
+     stroke="#ff9800"
+     stroke-width="2"
+     id="line2" /><line
+     x1="72.705803"
+     y1="25.333492"
+     x2="70.999474"
+     y2="20.633656"
+     stroke="#ff9800"
+     stroke-width="2"
+     id="line3" /><line
+     x1="79.007629"
+     y1="27.03516"
+     x2="82.793045"
+     y2="23.768559"
+     stroke="#ff9800"
+     stroke-width="2"
+     id="line4" /><line
+     x1="79.630043"
+     y1="33.17028"
+     x2="84.25367"
+     y2="35.073471"
+     stroke="#ff9800"
+     stroke-width="2"
+     id="line5" /><line
+     x1="75.123314"
+     y1="35.76083"
+     x2="76.230385"
+     y2="40.63673"
+     stroke="#ff9800"
+     stroke-width="2"
+     id="line6" /><line
+     x1="72.073761"
+     y1="31.314306"
+     x2="74.573761"
+     y2="26.98418"
+     stroke="#ff9800"
+     stroke-width="2"
+     id="line7" /><circle
+     cx="29.573761"
+     cy="75.644432"
+     r="4"
+     fill="#4e342e"
+     id="circle7" /><circle
+     cx="74.573761"
+     cy="75.644432"
+     r="4"
+     fill="#4e342e"
+     id="circle8" /><circle
+     cx="74.573761"
+     cy="30.644434"
+     r="4"
+     fill="#4e342e"
+     id="circle9" /><path
+     d="m 17.36416,15.724382 c 0,-0.571957 -0.462088,-1.034051 -1.034044,-1.034051 -0.571963,0 -1.03405,0.462094 -1.03405,1.034051 v 1.034043 9.823461 3.619167 c 0,0.571963 0.462087,1.03405 1.03405,1.03405 0.571956,0 1.034044,-0.462087 1.034044,-1.03405 v -4.136188 l 2.077796,-0.52026 c 1.328101,-0.332834 2.73376,-0.177725 3.958464,0.433007 1.428274,0.714144 3.085984,0.80139 4.578889,0.239123 l 1.121298,-0.420079 c 0.403926,-0.151878 0.672137,-0.536414 0.672137,-0.969421 v -8.000947 c 0,-0.743225 -0.782001,-1.227935 -1.447674,-0.895095 l -0.31021,0.155103 c -1.496138,0.749688 -3.257253,0.749688 -4.75339,0 -1.134225,-0.568725 -2.436478,-0.710906 -3.667644,-0.403921 l -2.229666,0.559029 z"
+     id="path1"
+     style="fill:#6d4c41;fill-opacity:1;stroke-width:0.032314" /></svg>

+ 11 - 0
resources/org.gtkmm.minesweeper.desktop

@@ -0,0 +1,11 @@
+[Desktop Entry]
+Name=MineSweeper
+GenericName=Minesweeper Game
+Comment=Classic minesweeper game with modern GTK UI
+Keywords=game;puzzle;mine;
+Exec=minesweeper
+Icon=minesweeper
+Terminal=false
+Type=Application
+Categories=Game;LogicGame;
+StartupNotify=true

+ 314 - 95
src/minefield.cpp

@@ -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()
 {
+    if (!m_timerRunning)
+    {
+        m_startTime = std::chrono::steady_clock::now();
+        m_timerRunning = true;
+    }
+}
 
-  int remainingMines = m_totalMines;
-  int startPos = x + y * m_rows;
+void MineField::stopTimer()
+{
+    if (m_timerRunning)
+    {
+        m_endTime = std::chrono::steady_clock::now();
+        m_timerRunning = false;
+    }
+}
 
-  srand(time(NULL)); // initialize rand()
+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;
+}
 
-  while (remainingMines > 0)
-  {
-    int position = rand() % (m_cols * m_rows);
-    if (isBomb(position % m_cols, position / m_cols) || position == startPos)
+void MineField::timerTick()
+{
+    if (m_timerRunning)
     {
-      continue;
+        timerSignal.emit(getElapsedTime());
     }
-    m_cells.at(position)->isBomb = true;
-    --remainingMines;
-  }
 }
 
-bool MineField::openCell(int x, int y)
+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)
 {
-  if (isBomb(x, y))
-  {
-    m_gameOver = true;
-    gameOverSignal.emit();
-    // stopTimer();
-    return false;
-  }
+    // 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);
+}
 
-  setOpenCell(x, y);
+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;
 
-  if (bombsNearby(x, y) == 0)
-  {
-    openNeighboorhood(x, y);
-  }
-  return true;
+    // 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;
+    }
+}
+
+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;
-  // 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;
-    remainingFlagsSignal.emit(m_remainingFlags);
-    return true;
-  }
-  else if (m_remainingFlags > 0)
-  {
-    m_cells.at(x + y * m_rows)->isFlagged = true;
-    --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);
-    checkGameWon();
     return true;
-  }
-  return false;
 }

+ 105 - 41
src/minefield.hpp

@@ -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
+    };
+
+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;
+    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;
 
-  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;
-
-  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 - 0
src/timer.cpp

@@ -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 - 0
src/timer.hpp

@@ -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();
+};

+ 1093 - 296
src/window.cpp

@@ -1,329 +1,1126 @@
+
 #include "window.hpp"
-#include "gdkmm/texture.h"
-#include "sigc++/adaptors/bind.h"
-#include "sigc++/functors/mem_fun.h"
-
-//}
-// void MainWindow::ApplyStyles() {
-//     // Load and apply the CSS file
-//     auto css_provider = Gtk::CssProvider::create();
-//     css_provider->load_from_path("style.css");
-//     Gtk::StyleContext::add_provider_for_display(Gdk::Display::get_default(), css_provider, GTK_STYLE_PROVIDER_PRIORITY_USER);
-// }
-
-void MainWindow::OnCellRightClick(int n_press, double n_x, double n_y, int index)
-{
-  (void)n_press, (void)n_x, (void)n_y;
-  int x = index % field.getCols();
-  int y = index / field.getCols();
-  int pos = x + y * field.getRows();
-
-  if (field.isOpened(x, y) == false)
-  {
-    field.toggleFlag(x, y);
-    if (field.isFlagged(x, y))
-    {
-      auto imgflag = Gtk::make_managed<Gtk::Image>();
-      imgflag->set(m_textureFlag);
-      buttons.at(pos)->set_child(*imgflag);
-      buttons.at(pos)->set_active(true);
+#include <iostream>
+#include <sstream>
+#include <algorithm>
+#include <iomanip>
+#include <ctime>
+#include <chrono>
+#include <random>
+#include <filesystem>
+
+MainWindow::MainWindow()
+{
+    // Initialize the game field with default settings
+    m_field = std::make_unique<MineField>(MineField::DIFFICULTY_MEDIUM.cols,
+                                         MineField::DIFFICULTY_MEDIUM.rows,
+                                         MineField::DIFFICULTY_MEDIUM.mines);
+    m_currentDifficulty = MineField::DIFFICULTY_MEDIUM.name;
+    m_firstClick = true;
+    
+    // Setup UI components
+    setupUI();
+    loadResources();
+    setupCSSProviders();
+    setupHeaderBar();
+    setupStatusBar();
+    setupGameBoard();
+    loadLeaderboard();
+    
+    // Connect signals
+    m_field->openCellSignal.connect(sigc::mem_fun(*this, &MainWindow::updateCell));
+    m_field->remainingFlagsSignal.connect(sigc::mem_fun(*this, &MainWindow::updateFlagsLabel));
+    m_field->gameOverSignal.connect(sigc::mem_fun(*this, &MainWindow::onGameOver));
+    m_field->gameWonSignal.connect(sigc::mem_fun(*this, &MainWindow::onGameWon));
+    m_field->resetSignal.connect(sigc::mem_fun(*this, &MainWindow::resetGame));
+    
+    // Initial update
+    updateFlagsLabel(m_field->getRemainingFlags());
+    updateTimeLabel();
+}
+
+void MainWindow::setupUI()
+{
+    // Configure main window
+    set_title("MineSweeper");
+    set_default_size(400, 400);
+    set_resizable(true);
+    
+    // Configure main layout
+    m_boxMain.set_orientation(Gtk::Orientation::VERTICAL);
+    m_boxMain.set_spacing(8);
+    m_boxMain.set_margin(10);
+    
+    // Set up overlay for animations
+    m_overlay.set_child(m_grid);
+    
+    // Add components to main box
+    m_boxMain.append(m_statusBox);
+    m_boxMain.append(m_overlay);
+    
+    // Set window content
+    set_child(m_boxMain);
+}
+
+void MainWindow::setupHeaderBar()
+{
+    // Create header bar
+    m_headerBar.set_show_title_buttons(true);
+    
+    // Add title widget
+    auto titleLabel = Gtk::make_managed<Gtk::Label>("MineSweeper");
+    titleLabel->add_css_class("title");
+    m_headerBar.set_title_widget(*titleLabel);
+    
+    // New game button
+    m_newGameButton.set_label("New Game");
+    m_newGameButton.add_css_class("suggested-action");
+    m_newGameButton.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::onNewGameClick));
+    
+    // Difficulty button
+    m_difficultyButton.set_label("Difficulty");
+    
+    // Create menu model
+    auto difficultyMenu = Gio::Menu::create();
+    
+    // Add difficulty options
+    auto easyAction = Gio::SimpleAction::create("easy");
+    easyAction->signal_activate().connect([this](const Glib::VariantBase&) {
+        onDifficultySelected(MineField::DIFFICULTY_EASY);
+    });
+    add_action(easyAction);
+    difficultyMenu->append("Beginner", "win.easy");
+    
+    auto mediumAction = Gio::SimpleAction::create("medium");
+    mediumAction->signal_activate().connect([this](const Glib::VariantBase&) {
+        onDifficultySelected(MineField::DIFFICULTY_MEDIUM);
+    });
+    add_action(mediumAction);
+    difficultyMenu->append("Intermediate", "win.medium");
+    
+    auto hardAction = Gio::SimpleAction::create("hard");
+    hardAction->signal_activate().connect([this](const Glib::VariantBase&) {
+        onDifficultySelected(MineField::DIFFICULTY_HARD);
+    });
+    add_action(hardAction);
+    difficultyMenu->append("Expert", "win.hard");
+    
+    auto expertAction = Gio::SimpleAction::create("expert");
+    expertAction->signal_activate().connect([this](const Glib::VariantBase&) {
+        onDifficultySelected(MineField::DIFFICULTY_EXPERT);
+    });
+    add_action(expertAction);
+    difficultyMenu->append("Master", "win.expert");
+    
+    auto customAction = Gio::SimpleAction::create("custom");
+    customAction->signal_activate().connect([this](const Glib::VariantBase&) {
+        showDifficultyDialog();
+    });
+    add_action(customAction);
+    difficultyMenu->append("Custom...", "win.custom");
+    
+    // Add a separator (using a different item since append_separator is not available)
+    difficultyMenu->append("───────────", "");
+    
+    // Add leaderboard option
+    auto leaderboardAction = Gio::SimpleAction::create("leaderboard");
+    leaderboardAction->signal_activate().connect([this](const Glib::VariantBase&) {
+        showLeaderboard();
+    });
+    add_action(leaderboardAction);
+    difficultyMenu->append("Leaderboard", "win.leaderboard");
+    
+    m_difficultyButton.set_menu_model(difficultyMenu);
+    
+    // Add buttons to header bar
+    m_headerBar.pack_start(m_newGameButton);
+    m_headerBar.pack_end(m_difficultyButton);
+    
+    // Set header bar as titlebar
+    set_titlebar(m_headerBar);
+}
+
+void MainWindow::setupStatusBar()
+{
+    // Configure status bar
+    m_statusBox.set_orientation(Gtk::Orientation::HORIZONTAL);
+    m_statusBox.set_spacing(10);
+    m_statusBox.set_margin(5);
+    m_statusBox.set_halign(Gtk::Align::FILL);
+    m_statusBox.set_hexpand(true);
+    
+    // Mines label
+    m_minesLabel.set_label(Glib::ustring::compose("Total mines: %1", m_field->getTotalMines()));
+    m_minesLabel.set_halign(Gtk::Align::START);
+    m_minesLabel.set_hexpand(true);
+    
+    // Timer label
+    m_timeLabel.set_label("Time: 00:00.0");
+    m_timeLabel.set_halign(Gtk::Align::CENTER);
+    m_timeLabel.set_hexpand(true);
+    
+    // Flags label
+    m_flagsLabel.set_label(Glib::ustring::compose("Flags: %1/%2", 
+                                                m_field->getRemainingFlags(), 
+                                                m_field->getTotalMines()));
+    m_flagsLabel.set_halign(Gtk::Align::END);
+    m_flagsLabel.set_hexpand(true);
+    
+    // Add labels to status bar
+    m_statusBox.append(m_minesLabel);
+    m_statusBox.append(m_timeLabel);
+    m_statusBox.append(m_flagsLabel);
+}
+
+void MainWindow::setupGameBoard()
+{
+    // Clear existing buttons
+    m_buttons.clear();
+    
+    // Remove all children from grid
+    while (auto child = m_grid.get_first_child()) {
+        m_grid.remove(*child);
     }
-    else
-    {
-      buttons.at(pos)->unset_child();
-      buttons.at(pos)->queue_draw();
-      buttons.at(pos)->set_active(false);
+    
+    // Configure grid
+    m_grid.set_row_homogeneous(true);
+    m_grid.set_column_homogeneous(true);
+    m_grid.set_margin(8);
+    
+    // Create cell buttons
+    int cols = m_field->getCols();
+    int rows = m_field->getRows();
+    
+    // Calculate appropriate button size based on window size
+    int buttonSize = std::max(30, std::min(50, 500 / std::max(cols, rows)));
+    
+    // Create buttons for each cell
+    for (int y = 0; y < rows; y++) {
+        for (int x = 0; x < cols; x++) {
+            int index = x + y * cols;
+            
+            // Create toggle button
+            auto button = std::make_shared<Gtk::ToggleButton>();
+            button->set_size_request(buttonSize, buttonSize);
+            button->set_has_frame(true);
+            button->add_css_class("cell-button");
+            
+            // Connect left click
+            button->signal_clicked().connect(
+                sigc::bind(sigc::mem_fun(*this, &MainWindow::onCellClick), x, y)
+            );
+            
+            // Connect right click
+            auto rightClickGesture = Gtk::GestureClick::create();
+            rightClickGesture->set_button(3);  // Right mouse button
+            rightClickGesture->signal_released().connect(
+                sigc::bind(sigc::mem_fun(*this, &MainWindow::onCellRightClick), index)
+            );
+            button->add_controller(rightClickGesture);
+            
+            // Add button to grid and vector
+            m_grid.attach(*button, x, y);
+            m_buttons.push_back(button);
+        }
     }
-  }
+    
+    // Show all widgets
+    m_grid.show();
 }
 
-void MainWindow::updateFlagsLabel(int flags)
+void MainWindow::loadResources()
 {
-  Glib::ustring msg = Glib::ustring::compose("Remaining flags: %1", flags);
-  flagLabel.set_label(msg);
-}
-// void MainWindow::OnNewButtonClick() {
-//     newGame = true;
-//     gameOver = false;
-
-//     for (auto &button : buttons) {
-//         button->set_active(false);
-//         button->set_sensitive(true);
-//         button->set_label("");
-//     }
-
-//     //field->remainingFlags = MINES;
-//     Glib::ustring msg = Glib::ustring::compose("Remaining flags: %1", field->remainingFlags);
-//     flagLabel.set_label(msg);
-
-//     if (clockConn.connected()) clockConn.disconnect();
-//     elapsedTime = 0;
-//     clockConn = Glib::signal_timeout().connect(sigc::mem_fun(*this, &MainWindow::UpdateClockLabel), 100);
-// }
-
-void MainWindow::OnCellClick(int x, int y)
-{
-  if (newGame)
-  {
-    field.initBombs(x, y);
-    newGame = false;
-  }
-
-  if (field.isFlagged(x, y))
-  {
-    buttons.at(x + y * field.getRows())->set_active(true);
-  }
-  else
-  {
-    field.openCell(x, y);
-    if (field.isBomb(x, y))
-    {
-      openBombs();
+    // Load textures
+    try {
+        m_textureBomb = Gdk::Texture::create_from_resource("/minesweeper/bomb-solid");
+        m_textureFlag = Gdk::Texture::create_from_resource("/minesweeper/flag-solid");
+        m_textureFlagBomb = Gdk::Texture::create_from_resource("/minesweeper/flag-bomb");
+        m_textureExplosion = Gdk::Texture::create_from_resource("/minesweeper/explosion-solid");
+    } catch (const std::exception& e) {
+        std::cerr << "Failed to load resources: " << e.what() << std::endl;
     }
-  }
-}
-
-void MainWindow::openBombs()
-{
-  for (int i = 0; i < field.getCols() * field.getRows(); i++)
-  {
-    int x = i % field.getCols();
-    int y = i / field.getCols();
-
-    buttons.at(i)->set_sensitive(false);
-
-    if (field.isBomb(x, y))
-    {
-      if (field.isFlagged(x, y))
-      {
-        auto imgFlagBomb = std::make_shared<Gtk::Image>();
-        imgFlagBomb->set(m_textureFlagBomb);
-        buttons.at(i)->set_child(*imgFlagBomb);
-      }
-      else
-      {
-        auto imgBomb = std::make_shared<Gtk::Image>();
-        imgBomb->set(m_textureBomb);
-        buttons.at(i)->set_child(*imgBomb);
-      }
-      buttons.at(i)->set_active(true);
+}
+
+void MainWindow::setupCSSProviders()
+{
+    auto css_provider = Gtk::CssProvider::create();
+    
+    // Define CSS styles
+    css_provider->load_from_data(
+        "button.cell-button { border-radius: 0; margin: 1px; padding: 0; }"
+        ".cell-button:checked { background-color: #e0e0e0; }"
+        ".label-1 { font-weight: bold; font-size: 1.2em; color: blue; }"
+        ".label-2 { font-weight: bold; font-size: 1.2em; color: green; }"
+        ".label-3 { font-weight: bold; font-size: 1.2em; color: darkorange; }"
+        ".label-4 { font-weight: bold; font-size: 1.2em; color: purple; }"
+        ".label-5 { font-weight: bold; font-size: 1.2em; color: red; }"
+        ".label-6 { font-weight: bold; font-size: 1.2em; color: salmon; }"
+        ".label-7 { font-weight: bold; font-size: 1.2em; color: turquoise; }"
+        ".label-8 { font-weight: bold; font-size: 1.2em; color: magenta; }"
+        ".confetti { opacity: 0.8; }"
+    );
+    
+    // Add CSS provider to display
+    auto display = Gdk::Display::get_default();
+    Gtk::StyleContext::add_provider_for_display(
+        display, css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
+    );
+}
+
+void MainWindow::onCellClick(int x, int y)
+{
+    // Ignore if game is over
+    if (m_field->getGameState() == MineField::GameState::LOST || 
+        m_field->getGameState() == MineField::GameState::WON) {
+        return;
+    }
+    
+    // Start timer on first click
+    if (m_firstClick) {
+        m_timerConnection = Glib::signal_timeout().connect(
+            sigc::mem_fun(*this, &MainWindow::updateTimer), 100
+        );
+        m_firstClick = false;
+    }
+    
+    // Open cell
+    m_field->openCell(x, y);
+}
+
+void MainWindow::onCellRightClick(int n_press, double n_x, double n_y, int index)
+{
+    (void)n_press; (void)n_x; (void)n_y;
+    
+    // Calculate x and y from index
+    int cols = m_field->getCols();
+    int x = index % cols;
+    int y = index / cols;
+    
+    // Ignore if game is over
+    if (m_field->getGameState() == MineField::GameState::LOST || 
+        m_field->getGameState() == MineField::GameState::WON) {
+        return;
+    }
+    
+    // Start timer on first action
+    if (m_firstClick) {
+        m_timerConnection = Glib::signal_timeout().connect(
+            sigc::mem_fun(*this, &MainWindow::updateTimer), 100
+        );
+        m_firstClick = false;
+    }
+    
+    // Toggle flag
+    if (m_field->toggleFlag(x, y)) {
+        if (m_field->isFlagged(x, y)) {
+            // Set flag image
+            auto imgFlag = Gtk::make_managed<Gtk::Image>();
+            imgFlag->set(m_textureFlag);
+            m_buttons[index]->set_child(*imgFlag);
+            m_buttons[index]->set_active(true);
+        } else {
+            // Remove flag image
+            m_buttons[index]->unset_child();
+            m_buttons[index]->set_active(false);
+        }
     }
-  }
 }
 
 void MainWindow::updateCell(int x, int y)
 {
-  int pos = x + y * field.getRows();
-  if (field.isOpened(x, y))
-  {
-    if (field.bombsNearby(x, y) > 0)
-    {
-      switch (field.bombsNearby(x, y))
-      {
-      case 1:
-        buttons.at(pos)->get_style_context()->add_class("label-1");
-        break;
-      case 2:
-        buttons.at(pos)->get_style_context()->add_class("label-2");
-        break;
-      case 3:
-        buttons.at(pos)->get_style_context()->add_class("label-3");
-        break;
-      case 4:
-        buttons.at(pos)->get_style_context()->add_class("label-4");
-        break;
-      case 5:
-        buttons.at(pos)->get_style_context()->add_class("label-5");
-        break;
-      case 6:
-        buttons.at(pos)->get_style_context()->add_class("label-6");
-        break;
-      case 7:
-        buttons.at(pos)->get_style_context()->add_class("label-7");
-        break;
-      case 8:
-        buttons.at(pos)->get_style_context()->add_class("label-8");
-        break;
-      }
-      buttons.at(pos)->set_label(Glib::ustring::format(field.bombsNearby(x, y)));
+    int cols = m_field->getCols();
+    int index = x + y * cols;
+    
+    if (index >= 0 && index < static_cast<int>(m_buttons.size())) {
+        auto button = m_buttons[index];
+        
+        // Cell is opened
+        if (m_field->isOpened(x, y)) {
+            // Show bombs nearby
+            int bombs = m_field->bombsNearby(x, y);
+            if (bombs > 0) {
+                button->set_label(Glib::ustring::format(bombs));
+                button->get_style_context()->add_class("label-" + Glib::ustring::format(bombs));
+            }
+            
+            button->set_active(true);
+            button->set_sensitive(false);
+        }
     }
-    buttons.at(pos)->set_active(true);
-    buttons.at(pos)->set_sensitive(false);
-  }
 }
-// void MainWindow::ShowGameWonAnimation() {
-//     // Limit the number of confetti images to 10
-//     int confettiCount = 10;
-//     for (int i = 0; i < confettiCount; ++i) {
-//         Glib::signal_timeout().connect_once([this]() {
-//             auto confetti = Gtk::make_managed<Gtk::Image>();
-//             confetti->set_from_resource("/mineSweeper/confetti");
-//             // Randomize position on the grid or overlay.
-//             grid->attach(*confetti, rand() % COLS, rand() % COLS);
-//             grid->queue_draw();
-//         }, i * 100); // Add confetti with a delay of 100ms each
-//     }
-// }
 
-// bool MainWindow::AllCellsOpened()
-// {
-//   for(int i=0; i<COLS * COLS; i++) {
-//     if (!buttons[i]->get_active())
-//       return false;
-//   }
-//   return true;
-// }
-void MainWindow::gameOver()
+void MainWindow::updateFlagsLabel(int flags)
 {
-  // clockSignalConn.disconnect();
-  // std::cout << "Signal gameOver emmited\n";
+    m_flagsLabel.set_label(Glib::ustring::compose("Flags: %1/%2", 
+                                               flags, 
+                                               m_field->getTotalMines()));
 }
 
-void MainWindow::updateClockLabel()
+bool MainWindow::updateTimer()
 {
+    // Update timer every 100ms
+    if (m_field->getGameState() == MineField::GameState::PLAYING) {
+        m_field->timerTick();
+        updateTimeLabel();
+        return true;
+    }
+    return false;
+}
 
-  size_t time = 100; // field.getCurrentTime();
+void MainWindow::updateTimeLabel()
+{
+    int time = m_field->getElapsedTime();
+    
+    // Format time as MM:SS.d
+    int deciseconds = (time / 100) % 10;
+    int seconds = (time / 1000) % 60;
+    int minutes = (time / 60000) % 60;
+    
+    m_timeLabel.set_label(Glib::ustring::compose("Time: %1:%2.%3",
+        Glib::ustring::format(std::setfill(L'0'), std::setw(2), minutes),
+        Glib::ustring::format(std::setfill(L'0'), std::setw(2), seconds),
+        Glib::ustring::format(deciseconds)
+    ));
+}
 
-  int deciseconds = (time / 100) % 10;
-  int seconds = (time / 1000) % 60;
-  int minutes = (time / 60000) % 60;
+void MainWindow::onGameOver()
+{
+    // Disconnect timer
+    m_timerConnection.disconnect();
+    
+    // Show all bombs
+    revealAllBombs();
+    
+    // Show game over animation
+    showGameOverAnimation();
+}
 
-  Glib::ustring msg = Glib::ustring::compose("Elapsed time: %1:%2.%3",
-                                             Glib::ustring::format(std::setfill(L'0'), std::setw(2), minutes),
-                                             Glib::ustring::format(std::setfill(L'0'), std::setw(2), seconds),
-                                             Glib::ustring::format(std::setfill(L'0'), std::setw(1), deciseconds));
-  clockLabel.set_label(msg);
+void MainWindow::onGameWon(int time)
+{
+    // Disconnect timer
+    m_timerConnection.disconnect();
+    
+    // Update timer display with final time
+    updateTimeLabel();
+    
+    // Show win animation
+    showGameWonAnimation();
+    
+    // Show name input dialog for leaderboard
+    showNameInputDialog(time);
 }
 
-void MainWindow::handleClockSig(size_t time)
+void MainWindow::revealAllBombs()
 {
-  (void)time;
-  m_clockDispatch.emit();
+    int cols = m_field->getCols();
+    int rows = m_field->getRows();
+    
+    for (int y = 0; y < rows; y++) {
+        for (int x = 0; x < cols; x++) {
+            int index = x + y * cols;
+            
+            // Disable all buttons
+            m_buttons[index]->set_sensitive(false);
+            
+            if (m_field->isBomb(x, y)) {
+                // Show bomb or flagged bomb based on state
+                if (m_field->isFlagged(x, y)) {
+                    auto imgFlagBomb = Gtk::make_managed<Gtk::Image>();
+                    imgFlagBomb->set(m_textureFlagBomb);
+                    m_buttons[index]->set_child(*imgFlagBomb);
+                } else {
+                    auto imgBomb = Gtk::make_managed<Gtk::Image>();
+                    imgBomb->set(m_textureBomb);
+                    m_buttons[index]->set_child(*imgBomb);
+                }
+                m_buttons[index]->set_active(true);
+            }
+        }
+    }
 }
 
-MainWindow::MainWindow()
+void MainWindow::showGameOverAnimation()
+{
+    // Find first non-flagged bomb
+    int cols = m_field->getCols();
+    int rows = m_field->getRows();
+    bool dialogShown = false;  // Track if dialog was already shown
+    
+    for (int y = 0; y < rows && !dialogShown; y++) {
+        for (int x = 0; x < cols; x++) {
+            int index = x + y * cols;
+            
+            if (m_field->isBomb(x, y) && !m_field->isFlagged(x, y)) {
+                // Show explosion on the first bomb
+                auto imgExplosion = Gtk::make_managed<Gtk::Image>();
+                imgExplosion->set(m_textureExplosion);
+                m_buttons[index]->set_child(*imgExplosion);
+                
+                // Only show one dialog
+                if (!dialogShown) {
+                    // Use a standard Dialog instead of AlertDialog
+                    auto dialog = Gtk::make_managed<Gtk::MessageDialog>(*this, "Game Over!", 
+                                                                    false, Gtk::MessageType::INFO);
+                    dialog->set_secondary_text("You hit a mine! Better luck next time.");
+                    dialog->add_button("Try Again", 1);
+                    dialog->add_button("Change Difficulty", 2);
+                    dialog->set_default_response(1);
+                    
+                    dialog->signal_response().connect([this, dialog](int response) {
+                        dialog->close();  // Use close() instead of hide()
+                        if (response == 1) {
+                            // Reset game with same settings
+                            onNewGameClick();
+                        } else {
+                            // Show difficulty dialog
+                            showDifficultyDialog();
+                        }
+                    });
+                    
+                    dialog->show();
+                    dialogShown = true;
+                }
+                break;
+            }
+        }
+    }
+}
+
+
+void MainWindow::setupConfetti()
+{
+    // Create confetti pieces
+    std::random_device rd;
+    std::mt19937 gen(rd());
+    std::uniform_int_distribution<int> colorDist(0, 5);
+    std::uniform_int_distribution<int> sizeDist(10, 20);
+    std::uniform_int_distribution<int> xDist(0, m_field->getCols() - 1);
+    std::uniform_int_distribution<int> yDist(0, m_field->getRows() - 1);
+    
+    const std::vector<std::string> colors = {
+        "#FF5252", "#FFEB3B", "#4CAF50", "#2196F3", "#9C27B0", "#FF9800"
+    };
+    
+    // Create 20 confetti pieces
+    for (int i = 0; i < 20; i++) {
+        // Create drawing area for confetti
+        auto confetti = Gtk::make_managed<Gtk::DrawingArea>();
+        confetti->set_content_width(sizeDist(gen));
+        confetti->set_content_height(sizeDist(gen));
+        confetti->add_css_class("confetti");
+        
+        // Set confetti color
+        std::string color = colors[colorDist(gen)];
+        
+        // Draw confetti
+        confetti->set_draw_func([color](const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
+            cr->set_source_rgba(1, 1, 1, 0.8);
+            cr->rectangle(0, 0, width, height);
+            cr->fill();
+            
+            // Parse color using simple RGB format
+            double r = 0, g = 0, b = 0;
+            
+            // Parse hex color format (#RRGGBB)
+            if (color.length() == 7 && color[0] == '#') {
+                int ri, gi, bi;
+                if (sscanf(color.c_str(), "#%02x%02x%02x", &ri, &gi, &bi) == 3) {
+                    r = ri / 255.0;
+                    g = gi / 255.0;
+                    b = bi / 255.0;
+                }
+            }
+            
+            cr->set_source_rgb(r, g, b);
+            cr->rectangle(0, 0, width, height);
+            cr->fill();
+        });
+        
+        // Get random position
+        int x = xDist(gen);
+        int y = yDist(gen);
+        
+        // Add to overlay at random position
+        m_overlay.add_overlay(*confetti);
+        confetti->set_halign(Gtk::Align::START);
+        confetti->set_valign(Gtk::Align::START);
+        confetti->set_margin_start(x * 30 + 10);
+        confetti->set_margin_top(y * 30 + 10);
+        
+        // Animate confetti
+        auto duration = std::uniform_int_distribution<int>(800, 2000)(gen);
+        Glib::signal_timeout().connect_once([confetti]() {
+            confetti->unparent();
+        }, duration);
+    }
+}
+
+void MainWindow::showGameWonAnimation()
+{
+    // Show confetti animation
+    setupConfetti();
+    
+    // Update all buttons to show flags on bombs
+    int cols = m_field->getCols();
+    int rows = m_field->getRows();
+    
+    for (int y = 0; y < rows; y++) {
+        for (int x = 0; x < cols; x++) {
+            if (m_field->isBomb(x, y)) {
+                int index = x + y * cols;
+                
+                // Show flag on bomb
+                auto imgFlag = Gtk::make_managed<Gtk::Image>();
+                imgFlag->set(m_textureFlag);
+                m_buttons[index]->set_child(*imgFlag);
+                m_buttons[index]->set_active(true);
+            }
+        }
+    }
+}
+
+void MainWindow::onNewGameClick()
+{
+    // Reset the current game
+    m_field->reset();
+    
+    // Clear board UI
+    clearBoard();
+    
+    // Reset game state
+    m_firstClick = true;
+    if (m_timerConnection) {
+        m_timerConnection.disconnect();
+    }
+    
+    // Update labels
+    updateFlagsLabel(m_field->getRemainingFlags());
+    m_timeLabel.set_label("Time: 00:00.0");
+}
+
+void MainWindow::clearBoard()
+{
+    int cols = m_field->getCols();
+    int rows = m_field->getRows();
+    
+    for (int y = 0; y < rows; y++) {
+        for (int x = 0; x < cols; x++) {
+            int index = x + y * cols;
+            
+            if (index < static_cast<int>(m_buttons.size())) {
+                auto button = m_buttons[index];
+                
+                // Reset button state
+                button->set_active(false);
+                button->set_sensitive(true);
+                button->set_label("");
+                button->unset_child();
+                
+                // Remove style classes
+                for (int i = 1; i <= 8; i++) {
+                    button->get_style_context()->remove_class("label-" + Glib::ustring::format(i));
+                }
+            }
+        }
+    }
+}
+
+void MainWindow::onDifficultySelected(const GameDifficulty& difficulty)
+{
+    // Start new game with selected difficulty
+    startNewGame(difficulty.cols, difficulty.rows, difficulty.mines, difficulty.name);
+}
+
+void MainWindow::startNewGame(int cols, int rows, int mines, const std::string& difficulty)
+{
+    // Update current difficulty
+    m_currentDifficulty = difficulty;
+    
+    // Create new game field
+    m_field = std::make_unique<MineField>(cols, rows, mines);
+    
+    // Connect signals
+    m_field->openCellSignal.connect(sigc::mem_fun(*this, &MainWindow::updateCell));
+    m_field->remainingFlagsSignal.connect(sigc::mem_fun(*this, &MainWindow::updateFlagsLabel));
+    m_field->gameOverSignal.connect(sigc::mem_fun(*this, &MainWindow::onGameOver));
+    m_field->gameWonSignal.connect(sigc::mem_fun(*this, &MainWindow::onGameWon));
+    m_field->resetSignal.connect(sigc::mem_fun(*this, &MainWindow::resetGame));
+    
+    // Reset game state
+    m_firstClick = true;
+    if (m_timerConnection) {
+        m_timerConnection.disconnect();
+    }
+    
+    // Update labels
+    m_minesLabel.set_label(Glib::ustring::compose("Total mines: %1", m_field->getTotalMines()));
+    updateFlagsLabel(m_field->getRemainingFlags());
+    m_timeLabel.set_label("Time: 00:00.0");
+
+    // Calculate appropriate window size based on grid dimensions
+    int windowWidth = cols * 30 + 40;  // 30px per button + margins
+    int windowHeight = rows * 30 + 100; // Additional space for header/status bar
+    
+    // Resize window
+    set_default_size(windowWidth, windowHeight);
+
+    // If the window is already visible, we need to queue a resize
+    queue_resize();
+	
+    // Setup new game board
+    setupGameBoard();
+}
+
+void MainWindow::showDifficultyDialog()
+{
+    // Create custom difficulty dialog
+    auto dialog = Gtk::make_managed<Gtk::Dialog>("Custom Difficulty", *this, true);
+    dialog->set_default_size(300, 200);
+    
+    // Add content area
+    auto contentArea = dialog->get_content_area();
+    contentArea->set_margin(10);
+    contentArea->set_spacing(10);
+    
+    // Create input fields
+    auto widthBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 5);
+    auto widthLabel = Gtk::make_managed<Gtk::Label>("Width:");
+    widthLabel->set_halign(Gtk::Align::START);
+    auto widthSpinner = Gtk::make_managed<Gtk::SpinButton>();
+    widthSpinner->set_range(5, 50);
+    widthSpinner->set_increments(1, 5);
+    widthSpinner->set_value(16);
+    widthBox->append(*widthLabel);
+    widthBox->append(*widthSpinner);
+    widthSpinner->set_hexpand(true);
+    
+    auto heightBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 5);
+    auto heightLabel = Gtk::make_managed<Gtk::Label>("Height:");
+    heightLabel->set_halign(Gtk::Align::START);
+    auto heightSpinner = Gtk::make_managed<Gtk::SpinButton>();
+    heightSpinner->set_range(5, 30);
+    heightSpinner->set_increments(1, 5);
+    heightSpinner->set_value(16);
+    heightBox->append(*heightLabel);
+    heightBox->append(*heightSpinner);
+    heightSpinner->set_hexpand(true);
+    
+    auto minesBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 5);
+    auto minesLabel = Gtk::make_managed<Gtk::Label>("Mines:");
+    minesLabel->set_halign(Gtk::Align::START);
+    auto minesSpinner = Gtk::make_managed<Gtk::SpinButton>();
+    minesSpinner->set_range(1, 500);
+    minesSpinner->set_increments(1, 10);
+    minesSpinner->set_value(40);
+    minesBox->append(*minesLabel);
+    minesBox->append(*minesSpinner);
+    minesSpinner->set_hexpand(true);
+    
+    // Calculate max mines based on board size
+    auto updateMaxMines = [widthSpinner, heightSpinner, minesSpinner]() {
+        int width = widthSpinner->get_value_as_int();
+        int height = heightSpinner->get_value_as_int();
+        int maxMines = static_cast<int>(width * height * 0.8); // Max 80% of cells can be mines
+        
+        minesSpinner->set_range(1, maxMines);
+        
+        // Adjust mines if necessary
+        if (minesSpinner->get_value_as_int() > maxMines) {
+            minesSpinner->set_value(maxMines);
+        }
+    };
+    
+    widthSpinner->signal_value_changed().connect(updateMaxMines);
+    heightSpinner->signal_value_changed().connect(updateMaxMines);
+    
+    // Add widgets to dialog
+    contentArea->append(*widthBox);
+    contentArea->append(*heightBox);
+    contentArea->append(*minesBox);
+    
+    // Add buttons
+    dialog->add_button("Cancel", Gtk::ResponseType::CANCEL);
+    dialog->add_button("Start Game", Gtk::ResponseType::OK);
+    dialog->set_default_response(Gtk::ResponseType::OK);
+    
+    // Show dialog and handle response
+    dialog->signal_response().connect([this, dialog, widthSpinner, heightSpinner, minesSpinner]
+        (int response) {
+        if (response == Gtk::ResponseType::OK) {
+            int width = widthSpinner->get_value_as_int();
+            int height = heightSpinner->get_value_as_int();
+            int mines = minesSpinner->get_value_as_int();
+            
+            startNewGame(width, height, mines, "Custom");
+        }
+        dialog->close();
+    });
+    
+    dialog->show();
+}
+
+void MainWindow::resetGame()
+{
+    // Reset UI
+    clearBoard();
+    
+    // Reset game state
+    m_firstClick = true;
+    if (m_timerConnection) {
+        m_timerConnection.disconnect();
+    }
+    
+    // Update labels
+    updateFlagsLabel(m_field->getRemainingFlags());
+    m_timeLabel.set_label("Time: 00:00.0");
+}
+
+std::string MainWindow::formatTime(int milliseconds) const
 {
-  //  ApplyStyles(); // Load the CSS file
-  m_elapsedTime = 0;
-  newGame = true;
-  set_title("MineSweeper");
-  set_default_size(400, 400);
-  set_resizable(false);
-
-  boxV = Gtk::Box(Gtk::Orientation::VERTICAL);
-  boxH = Gtk::Box(Gtk::Orientation::HORIZONTAL);
-
-  boxH.set_hexpand(true);
-
-  boxV.append(boxH);
-  boxH.set_expand(true);
-
-  Gtk::Label labelMines;
-  labelMines.set_margin_top(12);
-  labelMines.set_margin_start(12);
-  labelMines.set_label(Glib::ustring::compose("Total mines: %1", field.getTotalMines()));
-  // labelMines.set_hexpand(true);
-
-  Glib::ustring msg = Glib::ustring::compose("Remaining flags: %1", field.getRemainingFlags());
-  flagLabel = Gtk::Label(msg);
-  flagLabel.set_margin_top(12);
-  flagLabel.set_margin_start(12);
-  flagLabel.set_margin_end(12);
-  // flagLabel.set_hexpand(true);
-
-  clockLabel.set_margin_top(12);
-  // clockLabel.set_margin_start(12);
-  // Clocklabel.set_margin_end(12);
-  clockLabel.set_hexpand(true);
-  Glib::ustring clockmsg = Glib::ustring::compose("Elapsed time: 00:00.0");
-  clockLabel.set_label(clockmsg);
-
-  boxH.append(labelMines);
-  boxH.append(clockLabel);
-  boxH.append(flagLabel);
-
-  // TODO check if it's okay to mix std::shared_ptr with Gdk::ptr
-  m_textureBomb = Gdk::Texture::create_from_resource("/minesweeper/bomb-solid");
-  m_textureFlag = Gdk::Texture::create_from_resource("/minesweeper/flag-solid");
-  m_textureFlagBomb = Gdk::Texture::create_from_resource("/minesweeper/flag-bomb");
-
-  // bombPix.set_from_resource("/minesweeper/bomb-solid");
-
-  auto css_provider = Gtk::CssProvider::create();
-  css_provider->load_from_data(
-      ".label-1 { font-weight: bold; font-size: 1.5em; color: Blue; }\
-       .label-2 { font-weight: bold; font-size: 1.5em; color: Green; }\
-       .label-3 { font-weight: bold; font-size: 1.5em; color: Darkorange; }\
-       .label-4 { font-weight: bold; font-size: 1.5em; color: Purple; }\
-       .label-5 { font-weight: bold; font-size: 1.5em; color: Red; }\
-       .label-6 { font-weight: bold; font-size: 1.5em; color: Salmon; }\
-       .label-7 { font-weight: bold; font-size: 1.5em; color: Turquoise; }\
-       .label-8 { font-weight: bold; font-size: 1.5em; color: Magenta; }");
-
-  auto display = Gdk::Display::get_default();
-  Gtk::StyleContext::add_provider_for_display(display, css_provider, GTK_STYLE_PROVIDER_PRIORITY_USER);
-
-  for (int i = 0; i < field.getCols() * field.getRows(); i++)
-  {
-    auto button = std::make_shared<Gtk::ToggleButton>();
-    button->set_size_request(50, 40);
-    button->set_sensitive(true);
-    button->set_active(false);
-    int x = i % field.getCols();
-    int y = i / field.getRows();
-    button->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &MainWindow::OnCellClick), x, y));
-
-    // button->get_style_context()->add_class("fixed-button");
-
-    auto gesture = Gtk::GestureClick::create();
-    gesture->set_button(3);
-    gesture->signal_released().connect(sigc::bind(sigc::mem_fun(*this,
-                                                                &MainWindow::OnCellRightClick),
-                                                  i));
-    button->add_controller(gesture);
-
-    buttons.push_back(button);
-
-    grid.attach(*button, x, y);
-  }
-
-  field.openCellSignal.connect(sigc::bind(sigc::mem_fun(*this, &MainWindow::updateCell)));
-  field.remainingFlagsSignal.connect(sigc::bind(sigc::mem_fun(*this, &MainWindow::updateFlagsLabel)));
-  field.gameOverSignal.connect(sigc::bind(sigc::mem_fun(*this, &MainWindow::gameOver)));
-  // newGameButton.set_label("New");
-  // newGameButton.add_css_class("suggested-action");
-  // newGameButton.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::OnNewButtonClick));
-
-  // optionButton.set_icon_name("open-menu");
-
-  // field.timerSignal.connect(sigc::bind(sigc::mem_fun(*this, &MainWindow::handleClockSig)));
-  m_clockDispatch.connect(sigc::bind(sigc::mem_fun(*this, &MainWindow::updateClockLabel)));
-  // field.timerSignal.connect(sigc::bind(sigc::mem_fun(*this, &MainWindow::updateClockLabel)));
-  // if (clockSignalConn.connected()) clockSignalConn.disconnect();
-  // elapsedTime = 0;
-  // clockSignalConn = Glib::signal_timeout().connect(sigc::mem_fun(*this, &MainWindow::updateClockLabel), 100);
-  // }
-  // create the minefield
-  // field = new MineField(COLS, MINES);
-
-  // bar.pack_start(newGameButton);
-  // bar.pack_end(optionButton);
-
-  // grid.set_row_homogeneous(false);
-  // grid.set_column_homogeneous(false);
-  grid.set_margin(10);
-  // grid.set_vexpand(true);
-  // grid.set_hexpand(true);
-  //   grid.set_fill(false);
-
-  boxV.append(grid);
-
-  this->set_titlebar(bar);
-  this->set_child(boxV);
+    int seconds = milliseconds / 1000;
+    int minutes = seconds / 60;
+    seconds %= 60;
+    
+    std::stringstream ss;
+    ss << std::setfill('0') << std::setw(2) << minutes << ":"
+       << std::setfill('0') << std::setw(2) << seconds;
+    return ss.str();
+}
+
+std::filesystem::path MainWindow::getConfigDir() const
+{
+    // Get config directory
+    std::filesystem::path configDir;
+    
+    // Check XDG_CONFIG_HOME environment variable
+    const char* xdgConfigHome = std::getenv("XDG_CONFIG_HOME");
+    if (xdgConfigHome && *xdgConfigHome) {
+        configDir = xdgConfigHome;
+    } else {
+        // Fallback to ~/.config
+        const char* homeDir = std::getenv("HOME");
+        if (homeDir && *homeDir) {
+            configDir = std::filesystem::path(homeDir) / ".config";
+        } else {
+            // Last resort: use current directory
+            configDir = ".";
+        }
+    }
+    
+    // Create minesweeper config directory
+    configDir = configDir / "minesweeper";
+    std::filesystem::create_directories(configDir);
+    
+    return configDir;
+}
+
+void MainWindow::loadLeaderboard()
+{
+    m_leaderboard.clear();
+    
+    // Get leaderboard file path
+    std::filesystem::path leaderboardPath = getConfigDir() / "leaderboard.txt";
+    
+    // Open file
+    std::ifstream file(leaderboardPath);
+    if (!file.is_open()) {
+        return;
+    }
+    
+    // Read leaderboard entries
+    std::string line;
+    while (std::getline(file, line)) {
+        std::istringstream iss(line);
+        GameScore score;
+        
+        // Format: name,difficulty,time,date
+        std::getline(iss, score.playerName, ',');
+        std::getline(iss, score.difficulty, ',');
+        std::string timeStr;
+        std::getline(iss, timeStr, ',');
+        score.time = std::stoi(timeStr);
+        std::getline(iss, score.date);
+        
+        m_leaderboard.push_back(score);
+    }
+    
+    // Sort leaderboard by time (ascending)
+    std::sort(m_leaderboard.begin(), m_leaderboard.end());
+}
+
+void MainWindow::saveLeaderboard()
+{
+    // Get leaderboard file path
+    std::filesystem::path leaderboardPath = getConfigDir() / "leaderboard.txt";
+    
+    // Open file
+    std::ofstream file(leaderboardPath);
+    if (!file.is_open()) {
+        std::cerr << "Failed to save leaderboard" << std::endl;
+        return;
+    }
+    
+    // Write leaderboard entries
+    for (const auto& score : m_leaderboard) {
+        file << score.playerName << ","
+             << score.difficulty << ","
+             << score.time << ","
+             << score.date << std::endl;
+    }
+}
+
+void MainWindow::showLeaderboard()
+{
+    // Create leaderboard dialog
+    auto dialog = Gtk::make_managed<Gtk::Dialog>("Leaderboard", *this, true);
+    dialog->set_default_size(500, 400);
+    
+    // Create scrolled window
+    auto scrolledWindow = Gtk::make_managed<Gtk::ScrolledWindow>();
+    scrolledWindow->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
+    scrolledWindow->set_hexpand(true);
+    scrolledWindow->set_vexpand(true);
+    scrolledWindow->set_margin(5);
+    
+    // Create a GTK4-compatible list view
+    // Using a simple TreeView-like implementation 
+    // (Since ColumnView is not available or has compatibility issues)
+    auto listBox = Gtk::make_managed<Gtk::ListBox>();
+    listBox->set_selection_mode(Gtk::SelectionMode::NONE);
+    listBox->set_show_separators(true);
+    
+    // Add header row
+    auto headerRow = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 10);
+    headerRow->add_css_class("header-row");
+    
+    auto rankHeader = Gtk::make_managed<Gtk::Label>("Rank");
+    rankHeader->set_hexpand(false);
+    rankHeader->set_width_chars(5);
+    rankHeader->set_halign(Gtk::Align::START);
+    rankHeader->add_css_class("header");
+    
+    auto nameHeader = Gtk::make_managed<Gtk::Label>("Player");
+    nameHeader->set_hexpand(true);
+    nameHeader->set_halign(Gtk::Align::START);
+    nameHeader->add_css_class("header");
+    
+    auto difficultyHeader = Gtk::make_managed<Gtk::Label>("Difficulty");
+    difficultyHeader->set_hexpand(true);
+    difficultyHeader->set_halign(Gtk::Align::START);
+    difficultyHeader->add_css_class("header");
+    
+    auto timeHeader = Gtk::make_managed<Gtk::Label>("Time");
+    timeHeader->set_hexpand(true);
+    timeHeader->set_halign(Gtk::Align::START);
+    timeHeader->add_css_class("header");
+    
+    auto dateHeader = Gtk::make_managed<Gtk::Label>("Date");
+    dateHeader->set_hexpand(true);
+    dateHeader->set_halign(Gtk::Align::START);
+    dateHeader->add_css_class("header");
+    
+    headerRow->append(*rankHeader);
+    headerRow->append(*nameHeader);
+    headerRow->append(*difficultyHeader);
+    headerRow->append(*timeHeader);
+    headerRow->append(*dateHeader);
+    
+    // Add CSS for header
+    auto css_provider = Gtk::CssProvider::create();
+    css_provider->load_from_data(
+        ".header { font-weight: bold; }"
+        ".header-row { margin: 5px; }"
+        ".score-row { margin: 5px; }"
+    );
+    
+    auto display = Gdk::Display::get_default();
+    Gtk::StyleContext::add_provider_for_display(
+        display, css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
+    );
+    
+    auto headerItem = Gtk::make_managed<Gtk::ListBoxRow>();
+    headerItem->set_child(*headerRow);
+    listBox->append(*headerItem);
+    
+    // Add score rows
+    for (size_t i = 0; i < m_leaderboard.size(); i++) {
+        const auto& score = m_leaderboard[i];
+        
+        auto row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 10);
+        row->add_css_class("score-row");
+        
+        auto rank = Gtk::make_managed<Gtk::Label>(std::to_string(i + 1));
+        rank->set_hexpand(false);
+        rank->set_width_chars(5);
+        rank->set_halign(Gtk::Align::START);
+        
+        auto name = Gtk::make_managed<Gtk::Label>(score.playerName);
+        name->set_hexpand(true);
+        name->set_halign(Gtk::Align::START);
+        
+        auto difficulty = Gtk::make_managed<Gtk::Label>(score.difficulty);
+        difficulty->set_hexpand(true);
+        difficulty->set_halign(Gtk::Align::START);
+        
+        auto time = Gtk::make_managed<Gtk::Label>(formatTime(score.time));
+        time->set_hexpand(true);
+        time->set_halign(Gtk::Align::START);
+        
+        auto date = Gtk::make_managed<Gtk::Label>(score.date);
+        date->set_hexpand(true);
+        date->set_halign(Gtk::Align::START);
+        
+        row->append(*rank);
+        row->append(*name);
+        row->append(*difficulty);
+        row->append(*time);
+        row->append(*date);
+        
+        auto item = Gtk::make_managed<Gtk::ListBoxRow>();
+        item->set_child(*row);
+        listBox->append(*item);
+    }
+    
+    scrolledWindow->set_child(*listBox);
+    
+    // Add to dialog
+    dialog->get_content_area()->append(*scrolledWindow);
+    
+    // Add close button
+    dialog->add_button("Close", Gtk::ResponseType::CLOSE);
+    
+    // Show dialog
+    dialog->signal_response().connect([dialog](int) {
+        dialog->close();
+    });
+    
+    dialog->show();
+}
+
+void MainWindow::showNameInputDialog(int time)
+{
+    // Create name input dialog
+    auto dialog = Gtk::make_managed<Gtk::Dialog>("You Won!", *this, true);
+    dialog->set_default_size(300, 200);
+    
+    // Add content area
+    auto contentArea = dialog->get_content_area();
+    contentArea->set_margin(10);
+    contentArea->set_spacing(10);
+    
+    // Add congratulations message
+    auto congratsLabel = Gtk::make_managed<Gtk::Label>();
+    congratsLabel->set_markup("<span size='large'>Congratulations!</span>");
+    congratsLabel->set_margin(10);
+    
+    // Add time message
+    auto timeLabel = Gtk::make_managed<Gtk::Label>();
+    timeLabel->set_markup(Glib::ustring::compose(
+        "You completed the game in <b>%1</b>", formatTime(time)
+    ));
+    
+    // Add difficulty message
+    auto difficultyLabel = Gtk::make_managed<Gtk::Label>();
+    difficultyLabel->set_markup(Glib::ustring::compose(
+        "Difficulty: <b>%1</b>", m_currentDifficulty
+    ));
+    
+    // Add name input field
+    auto nameBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 5);
+    auto nameLabel = Gtk::make_managed<Gtk::Label>("Your name:");
+    nameLabel->set_halign(Gtk::Align::START);
+    auto nameEntry = Gtk::make_managed<Gtk::Entry>();
+    nameEntry->set_text("Player");
+    nameEntry->set_hexpand(true);
+    nameBox->append(*nameLabel);
+    nameBox->append(*nameEntry);
+    
+    // Add widgets to dialog
+    contentArea->append(*congratsLabel);
+    contentArea->append(*timeLabel);
+    contentArea->append(*difficultyLabel);
+    contentArea->append(*nameBox);
+    
+    // Add buttons
+    dialog->add_button("Skip", Gtk::ResponseType::CANCEL);
+    dialog->add_button("Save Score", Gtk::ResponseType::OK);
+    dialog->set_default_response(Gtk::ResponseType::OK);
+    
+    // Show dialog and handle response
+    dialog->signal_response().connect([this, dialog, nameEntry, time](int response) {
+        if (response == Gtk::ResponseType::OK) {
+            std::string playerName = nameEntry->get_text();
+            if (playerName.empty()) {
+                playerName = "Anonymous";
+            }
+            
+            // Add score to leaderboard
+            addScoreToLeaderboard(playerName, m_currentDifficulty, time);
+            
+            // Show leaderboard
+            showLeaderboard();
+        }
+        
+        dialog->close();
+        
+        // Ask if player wants to play again
+        auto newGameDialog = Gtk::make_managed<Gtk::MessageDialog>(
+            *this, "New Game?", false, Gtk::MessageType::QUESTION, Gtk::ButtonsType::NONE
+        );
+        newGameDialog->set_secondary_text("Would you like to play again?");
+        newGameDialog->add_button("Same Difficulty", 1);
+        newGameDialog->add_button("Change Difficulty", 2);
+        newGameDialog->set_default_response(1);
+        
+        newGameDialog->signal_response().connect([this, newGameDialog](int response) {
+            newGameDialog->close();
+            if (response == 1) {
+                // Reset game with same settings
+                onNewGameClick();
+            } else {
+                // Show difficulty dialog
+                showDifficultyDialog();
+            }
+        });
+        
+        newGameDialog->show();
+    });
+    
+    dialog->show();
+}
+
+void MainWindow::addScoreToLeaderboard(const std::string& playerName, const std::string& difficulty, int time)
+{
+    // Create new score
+    GameScore score;
+    score.playerName = playerName;
+    score.difficulty = difficulty;
+    score.time = time;
+    
+    // Add date
+    auto now = std::chrono::system_clock::now();
+    auto time_t = std::chrono::system_clock::to_time_t(now);
+    std::stringstream ss;
+    ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d");
+    score.date = ss.str();
+    
+    // Add to leaderboard
+    m_leaderboard.push_back(score);
+    
+    // Sort by time (ascending)
+    std::sort(m_leaderboard.begin(), m_leaderboard.end());
+    
+    // Limit to top 100 scores
+    if (m_leaderboard.size() > 100) {
+        m_leaderboard.resize(100);
+    }
+    
+    // Save leaderboard
+    saveLeaderboard();
 }
 
 int main(int argc, char **argv)
 {
-  auto app = Gtk::Application::create("eu.bernardomagri.minesweeper");
-  return app->make_window_and_run<MainWindow>(argc, argv);
+    auto app = Gtk::Application::create("org.gtkmm.minesweeper");
+    return app->make_window_and_run<MainWindow>(argc, argv);
 }

+ 88 - 43
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;
 };