major refactor
This commit is contained in:
120
.gitignore
vendored
120
.gitignore
vendored
@@ -1,4 +1,13 @@
|
|||||||
# ---> C++
|
# Build directories
|
||||||
|
build/
|
||||||
|
_build/
|
||||||
|
bin/
|
||||||
|
lib/
|
||||||
|
obj/
|
||||||
|
|
||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
# Compiled Object files
|
# Compiled Object files
|
||||||
*.slo
|
*.slo
|
||||||
*.lo
|
*.lo
|
||||||
@@ -16,6 +25,7 @@
|
|||||||
|
|
||||||
# Fortran module files
|
# Fortran module files
|
||||||
*.mod
|
*.mod
|
||||||
|
*.smod
|
||||||
|
|
||||||
# Compiled Static libraries
|
# Compiled Static libraries
|
||||||
*.lai
|
*.lai
|
||||||
@@ -27,6 +37,112 @@
|
|||||||
*.exe
|
*.exe
|
||||||
*.out
|
*.out
|
||||||
*.app
|
*.app
|
||||||
|
minesweeper
|
||||||
|
|
||||||
# Backup files
|
# 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/
|
||||||
|
|
||||||
|
#MacOS files
|
||||||
|
.cache/
|
||||||
|
.DS_Store
|
||||||
|
|||||||
94
README.md
94
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 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
|
||||||
sudo apt install libgtkmm-4.0-dev libsigc++-3.0-dev
|
|
||||||
|
### 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
|
||||||
```
|
```
|
||||||
|
|
||||||
Go to the project folder
|
### Fedora
|
||||||
|
|
||||||
|
Install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dnf install gcc-c++ meson ninja-build gtkmm4.0-devel libsigc++3-devel
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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
|
cd minesweeper
|
||||||
```
|
```
|
||||||
|
|
||||||
Setup meson and compile the project
|
Configure and build with Meson:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
meson setup build
|
meson setup build
|
||||||
meson compile -C 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.
|
||||||
|
|||||||
34
meson.build
34
meson.build
@@ -1,5 +1,5 @@
|
|||||||
project('minesweeper', 'cpp',
|
project('minesweeper', 'cpp',
|
||||||
version : '0.1',
|
version : '0.2.0',
|
||||||
default_options : ['warning_level=3', 'cpp_std=c++20'])
|
default_options : ['warning_level=3', 'cpp_std=c++20'])
|
||||||
|
|
||||||
gnome = import('gnome')
|
gnome = import('gnome')
|
||||||
@@ -10,9 +10,35 @@ res = gnome.compile_resources(
|
|||||||
c_name: 'gresources'
|
c_name: 'gresources'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
deps = dependency(['gtkmm-4.0', 'sigc++-3.0'])
|
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
resources/minesweeper.svg
Normal file
118
resources/minesweeper.svg
Normal file
@@ -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>
|
||||||
|
After Width: | Height: | Size: 3.4 KiB |
11
resources/org.gtkmm.minesweeper.desktop
Normal file
11
resources/org.gtkmm.minesweeper.desktop
Normal file
@@ -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
|
||||||
@@ -1,13 +1,26 @@
|
|||||||
#include "minefield.hpp"
|
#include "minefield.hpp"
|
||||||
|
#include <ctime>
|
||||||
|
#include <random>
|
||||||
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
MineField::MineField(int cols, int rows, int mines) : m_rows(rows),
|
// 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_cols(cols),
|
||||||
m_totalMines(mines),
|
m_totalMines(mines),
|
||||||
m_remainingFlags(mines),
|
m_remainingFlags(mines),
|
||||||
m_openCells(0),
|
m_openCells(0),
|
||||||
m_gameOver(false)
|
m_gameState(GameState::READY),
|
||||||
|
m_timerRunning(false)
|
||||||
{
|
{
|
||||||
|
// Create cells
|
||||||
|
m_cells.reserve(m_cols * m_rows);
|
||||||
for (int i = 0; i < m_cols * m_rows; i++)
|
for (int i = 0; i < m_cols * m_rows; i++)
|
||||||
{
|
{
|
||||||
std::shared_ptr<Cell> cell = std::make_shared<Cell>();
|
std::shared_ptr<Cell> cell = std::make_shared<Cell>();
|
||||||
@@ -20,80 +33,244 @@ MineField::~MineField()
|
|||||||
m_cells.clear();
|
m_cells.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MineField::initBombs(int x, int y)
|
void MineField::startTimer()
|
||||||
{
|
{
|
||||||
|
if (!m_timerRunning)
|
||||||
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);
|
m_startTime = std::chrono::steady_clock::now();
|
||||||
if (isBomb(position % m_cols, position / m_cols) || position == startPos)
|
m_timerRunning = true;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
m_cells.at(position)->isBomb = true;
|
}
|
||||||
--remainingMines;
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MineField::openCell(int x, int y)
|
bool MineField::openCell(int x, int y)
|
||||||
{
|
{
|
||||||
if (isBomb(x, y))
|
// Ignore if game is over or cell is already open/flagged
|
||||||
|
if (m_gameState != GameState::PLAYING && m_gameState != GameState::READY)
|
||||||
{
|
{
|
||||||
m_gameOver = true;
|
|
||||||
gameOverSignal.emit();
|
|
||||||
// stopTimer();
|
|
||||||
return false;
|
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);
|
setOpenCell(x, y);
|
||||||
|
|
||||||
|
// If no bombs nearby, open surrounding cells
|
||||||
if (bombsNearby(x, y) == 0)
|
if (bombsNearby(x, y) == 0)
|
||||||
{
|
{
|
||||||
openNeighboorhood(x, y);
|
openNeighboorhood(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MineField::computeBombsNearby(int x, int y)
|
void MineField::computeBombsNearby(int x, int y)
|
||||||
{
|
{
|
||||||
int total = 0;
|
int total = 0;
|
||||||
// compute bombs in neighboorhood
|
// Check all 8 neighboring cells
|
||||||
for (int i = -1; i < 2; i++)
|
for (int i = -1; i <= 1; i++)
|
||||||
{
|
{
|
||||||
for (int j = -1; j < 2; j++)
|
for (int j = -1; j <= 1; j++)
|
||||||
{
|
{
|
||||||
if (x + i >= 0 && x + i < m_cols && y + j >= 0 && y + j < m_rows)
|
// Skip the cell itself
|
||||||
|
if (i == 0 && j == 0)
|
||||||
{
|
{
|
||||||
if (isBomb(x + i, y + j))
|
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;
|
++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)
|
void MineField::openNeighboorhood(int x, int y)
|
||||||
{
|
{
|
||||||
// compute bombs in neighboorhood
|
// Check all 8 neighboring cells
|
||||||
for (int i = -1; i < 2; i++)
|
for (int i = -1; i <= 1; i++)
|
||||||
{
|
{
|
||||||
for (int j = -1; j < 2; j++)
|
for (int j = -1; j <= 1; j++)
|
||||||
{
|
{
|
||||||
if (x + i >= 0 && x + i < m_cols && y + j >= 0 && y + j < m_rows)
|
// Skip the cell itself
|
||||||
|
if (i == 0 && j == 0)
|
||||||
{
|
{
|
||||||
if ((isOpened(x + i, y + j) == false) && (isBomb(x + i, y + j) == false))
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nx = x + i;
|
||||||
|
int ny = y + j;
|
||||||
|
|
||||||
|
// Check if within bounds
|
||||||
|
if (nx >= 0 && nx < m_cols && ny >= 0 && ny < m_rows)
|
||||||
{
|
{
|
||||||
setOpenCell((x + i), (y + j));
|
// If cell is not opened and not a bomb, open it
|
||||||
if (bombsNearby(x + i, y + j) == 0)
|
if (!isOpened(nx, ny) && !isBomb(nx, ny))
|
||||||
{
|
{
|
||||||
openNeighboorhood(x + i, y + j);
|
setOpenCell(nx, ny);
|
||||||
|
|
||||||
|
// If no bombs nearby, recursively open surrounding cells
|
||||||
|
if (bombsNearby(nx, ny) == 0)
|
||||||
|
{
|
||||||
|
openNeighboorhood(nx, ny);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,31 +280,33 @@ void MineField::openNeighboorhood(int x, int y)
|
|||||||
|
|
||||||
bool MineField::isOpened(int x, int y)
|
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)
|
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)
|
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)
|
int MineField::bombsNearby(int x, int y)
|
||||||
{
|
{
|
||||||
if (m_cells.at(x + y * m_rows)->bombsNearby == -1)
|
// Calculate bombs nearby if not already calculated
|
||||||
|
if (m_cells.at(x + y * m_cols)->bombsNearby == -1)
|
||||||
{
|
{
|
||||||
computeBombsNearby(x, y);
|
computeBombsNearby(x, y);
|
||||||
}
|
}
|
||||||
return m_cells.at(x + y * m_rows)->bombsNearby;
|
|
||||||
|
return m_cells.at(x + y * m_cols)->bombsNearby;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MineField::setOpenCell(int x, int y)
|
void MineField::setOpenCell(int x, int y)
|
||||||
{
|
{
|
||||||
m_cells.at(x + y * m_rows)->isCleared = true;
|
m_cells.at(x + y * m_cols)->isCleared = true;
|
||||||
openCellSignal.emit(x, y);
|
openCellSignal.emit(x, y);
|
||||||
++m_openCells;
|
++m_openCells;
|
||||||
checkGameWon();
|
checkGameWon();
|
||||||
@@ -135,29 +314,69 @@ void MineField::setOpenCell(int x, int y)
|
|||||||
|
|
||||||
void MineField::checkGameWon()
|
void MineField::checkGameWon()
|
||||||
{
|
{
|
||||||
if ((m_openCells == (m_cols * m_rows - m_totalMines)) && (m_gameOver == false) && (m_remainingFlags == 0))
|
// Win condition: All non-bomb cells are opened
|
||||||
|
if (m_openCells == (m_cols * m_rows - m_totalMines) && m_gameState == GameState::PLAYING)
|
||||||
{
|
{
|
||||||
m_gameWon = true;
|
m_gameState = GameState::WON;
|
||||||
gameWonSignal.emit();
|
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)
|
bool MineField::toggleFlag(int x, int y)
|
||||||
{
|
{
|
||||||
if (m_cells.at(x + y * m_rows)->isFlagged == true)
|
// Ignore if game is over or cell is already open
|
||||||
|
if (m_gameState != GameState::PLAYING && m_gameState != GameState::READY)
|
||||||
{
|
{
|
||||||
m_cells.at(x + y * m_rows)->isFlagged = false;
|
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;
|
++m_remainingFlags;
|
||||||
remainingFlagsSignal.emit(m_remainingFlags);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (m_remainingFlags > 0)
|
else if (m_remainingFlags > 0)
|
||||||
{
|
{
|
||||||
m_cells.at(x + y * m_rows)->isFlagged = true;
|
m_cells.at(x + y * m_cols)->isFlagged = true;
|
||||||
--m_remainingFlags;
|
--m_remainingFlags;
|
||||||
remainingFlagsSignal.emit(m_remainingFlags);
|
|
||||||
checkGameWon();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No remaining flags
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remainingFlagsSignal.emit(m_remainingFlags);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,10 +4,40 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sigc++/signal.h>
|
#include <sigc++/signal.h>
|
||||||
#include <vector>
|
#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
|
class MineField
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
|
enum class GameState {
|
||||||
|
READY,
|
||||||
|
PLAYING,
|
||||||
|
WON,
|
||||||
|
LOST
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
struct Cell
|
struct Cell
|
||||||
{
|
{
|
||||||
bool isFlagged = false;
|
bool isFlagged = false;
|
||||||
@@ -22,33 +52,67 @@ class MineField
|
|||||||
int m_totalMines;
|
int m_totalMines;
|
||||||
int m_remainingFlags;
|
int m_remainingFlags;
|
||||||
int m_openCells;
|
int m_openCells;
|
||||||
bool m_gameOver;
|
GameState m_gameState;
|
||||||
bool m_gameWon;
|
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 computeBombsNearby(int x, int y);
|
||||||
void openNeighboorhood(int x, int y);
|
void openNeighboorhood(int x, int y);
|
||||||
void setOpenCell(int x, int y);
|
void setOpenCell(int x, int y);
|
||||||
void checkGameWon();
|
void checkGameWon();
|
||||||
|
void startTimer();
|
||||||
|
void stopTimer();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MineField(int cols, int rows, int mines);
|
MineField(int cols, int rows, int mines);
|
||||||
~MineField();
|
~MineField();
|
||||||
|
|
||||||
void initBombs(int x, int y);
|
void initBombs(int x, int y);
|
||||||
bool isBomb(int x, int y);
|
bool isBomb(int x, int y);
|
||||||
bool isFlagged(int x, int y);
|
bool isFlagged(int x, int y);
|
||||||
bool isOpened(int x, int y);
|
bool isOpened(int x, int y);
|
||||||
bool openCell(int x, int y);
|
bool openCell(int x, int y);
|
||||||
int bombsNearby(int x, int y);
|
int bombsNearby(int x, int y);
|
||||||
bool isGameOver() { return m_gameOver; };
|
|
||||||
int getCols() { return m_cols; };
|
GameState getGameState() const { return m_gameState; }
|
||||||
int getRows() { return m_rows; };
|
int getCols() const { return m_cols; }
|
||||||
|
int getRows() const { return m_rows; }
|
||||||
bool toggleFlag(int x, int y);
|
bool toggleFlag(int x, int y);
|
||||||
int getRemainingFlags() { return m_remainingFlags; };
|
int getRemainingFlags() const { return m_remainingFlags; }
|
||||||
int getTotalMines() { return m_totalMines; };
|
int getTotalMines() const { return m_totalMines; }
|
||||||
|
int getOpenCells() const { return m_openCells; }
|
||||||
|
|
||||||
|
void reset();
|
||||||
void startNewGame(int cols, int rows, int mines);
|
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;
|
sigc::signal<void(int, int)> openCellSignal;
|
||||||
|
|
||||||
|
// Signal when flags count changes
|
||||||
sigc::signal<void(int)> remainingFlagsSignal;
|
sigc::signal<void(int)> remainingFlagsSignal;
|
||||||
sigc::signal<void(void)> gameWonSignal;
|
|
||||||
sigc::signal<void(void)> gameOverSignal;
|
// 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();
|
||||||
|
};
|
||||||
1297
src/window.cpp
1297
src/window.cpp
File diff suppressed because it is too large
Load Diff
125
src/window.hpp
125
src/window.hpp
@@ -1,53 +1,98 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "glibmm/dispatcher.h"
|
|
||||||
#include "minefield.hpp"
|
#include "minefield.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <gtkmm.h>
|
#include <gtkmm.h>
|
||||||
#include <glibmm.h>
|
|
||||||
#include <gdkmm.h>
|
|
||||||
#include <sigc++/sigc++.h>
|
#include <sigc++/sigc++.h>
|
||||||
#include <iomanip>
|
#include <vector>
|
||||||
#include <iostream>
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
#define PROJECT_NAME "minesweeper"
|
class MainWindow : public Gtk::ApplicationWindow
|
||||||
|
|
||||||
class MainWindow : public Gtk::Window
|
|
||||||
{
|
{
|
||||||
Gtk::Box boxV{Gtk::Orientation::VERTICAL};
|
public:
|
||||||
Gtk::Box boxH{Gtk::Orientation::HORIZONTAL};
|
MainWindow();
|
||||||
std::vector<std::shared_ptr<Gtk::ToggleButton>> buttons;
|
virtual ~MainWindow() = default;
|
||||||
Gtk::Grid grid;
|
|
||||||
Gtk::HeaderBar bar;
|
private:
|
||||||
Gtk::Button newGameButton;
|
// UI containers
|
||||||
Gtk::Button optionButton;
|
Gtk::Box m_boxMain{Gtk::Orientation::VERTICAL};
|
||||||
Gtk::Label flagLabel;
|
Gtk::HeaderBar m_headerBar;
|
||||||
Gtk::Label clockLabel;
|
Gtk::Box m_statusBox{Gtk::Orientation::HORIZONTAL};
|
||||||
MineField field{16, 16, 1};
|
Gtk::Grid m_grid;
|
||||||
int m_elapsedTime;
|
Gtk::Overlay m_overlay;
|
||||||
bool newGame;
|
|
||||||
|
// 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_textureBomb;
|
||||||
std::shared_ptr<Gdk::Texture> m_textureFlag;
|
std::shared_ptr<Gdk::Texture> m_textureFlag;
|
||||||
std::shared_ptr<Gdk::Texture> m_textureFlagBomb;
|
std::shared_ptr<Gdk::Texture> m_textureFlagBomb;
|
||||||
void updateCell(int x, int y);
|
std::shared_ptr<Gdk::Texture> m_textureExplosion;
|
||||||
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:
|
// Game state tracking
|
||||||
MainWindow();
|
bool m_firstClick;
|
||||||
// void OnNewButtonClick();
|
sigc::connection m_timerConnection;
|
||||||
void OnCellClick(int x, int y);
|
|
||||||
void OnCellRightClick(int n_press, double n_x, double n_y, int index);
|
// Leaderboard
|
||||||
// void ShowGameWonAnimation();
|
std::vector<GameScore> m_leaderboard;
|
||||||
// void ApplyStyles();
|
Gtk::Dialog* m_leaderboardDialog;
|
||||||
// bool UpdateClockLabel();
|
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