diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..536ae03
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,16 @@
+project('minesweeper', 'cpp',
+ version : '0.1',
+ default_options : ['warning_level=3'])
+
+gnome = import('gnome')
+
+res = gnome.compile_resources(
+ 'resources', 'resources/gresource.xml',
+ source_dir: 'resources',
+ c_name: 'gresources'
+)
+
+deps = dependency(['gtkmm-4.0', 'sigc++-3.0'])
+src = ['Window.cpp', 'window.hpp', 'MineField.hpp', 'MineField.cpp', res]
+exe = executable('minesweeper', src, dependencies : deps, install : true)
+
diff --git a/resources/bomb-solid.svg b/resources/bomb-solid.svg
new file mode 100644
index 0000000..7f7d9db
--- /dev/null
+++ b/resources/bomb-solid.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/resources/confetti.png b/resources/confetti.png
new file mode 100644
index 0000000..4fae300
Binary files /dev/null and b/resources/confetti.png differ
diff --git a/resources/explosion-flag.svg b/resources/explosion-flag.svg
new file mode 100644
index 0000000..9403eaa
--- /dev/null
+++ b/resources/explosion-flag.svg
@@ -0,0 +1,43 @@
+
+
diff --git a/resources/explosion-solid.svg b/resources/explosion-solid.svg
new file mode 100644
index 0000000..a535b76
--- /dev/null
+++ b/resources/explosion-solid.svg
@@ -0,0 +1,38 @@
+
+
diff --git a/resources/flag-bomb.svg b/resources/flag-bomb.svg
new file mode 100644
index 0000000..6c3505a
--- /dev/null
+++ b/resources/flag-bomb.svg
@@ -0,0 +1,40 @@
+
+
diff --git a/resources/flag-regular.svg b/resources/flag-regular.svg
new file mode 100644
index 0000000..b42054c
--- /dev/null
+++ b/resources/flag-regular.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/flag-solid.svg b/resources/flag-solid.svg
new file mode 100644
index 0000000..a59b3b4
--- /dev/null
+++ b/resources/flag-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/gresource.xml b/resources/gresource.xml
new file mode 100644
index 0000000..c221533
--- /dev/null
+++ b/resources/gresource.xml
@@ -0,0 +1,11 @@
+
+
+
+ bomb-solid.svg
+ explosion-solid.svg
+ flag-regular.svg
+ flag-solid.svg
+ explosion-flag.svg
+ flag-bomb.svg
+
+
diff --git a/src/MineField.cpp b/src/MineField.cpp
new file mode 100644
index 0000000..9d062ec
--- /dev/null
+++ b/src/MineField.cpp
@@ -0,0 +1,115 @@
+#include "MineField.hpp"
+
+MineField::MineField(int cols, int rows, int mines): m_rows(rows),
+ m_cols(cols),
+ m_totalMines(mines),
+ m_remainingFlags(mines),
+ m_exploded(false) {
+ for(int i=0; i< m_cols*m_rows; i++) {
+ std::shared_ptr cell = std::make_shared| ();
+ m_cells.push_back(cell);
+ }
+}
+
+
+void MineField::initBombs(int x, int y) {
+
+ int remainingMines = m_totalMines;
+ int startPos = x + y * m_rows;
+
+ srand(time(NULL)); //initialize rand()
+
+ while(remainingMines > 0) {
+ int position = rand() % (m_cols * m_rows);
+ if(isBomb(position % m_cols, position / m_rows) || position == startPos) {
+ continue;
+ }
+ m_cells.at(position)->isBomb = true;
+ --remainingMines;
+ }
+}
+
+bool MineField::clearCell(int x, int y) {
+ setClearCell(x, y);
+
+ if(isBomb(x, y)) {
+ m_exploded = true;
+ return false;
+ }
+
+ 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++) {
+ if(x+i >= 0 && x+i < m_cols && y+j >= 0 && y+j < m_rows) {
+ if(isBomb(x+i, y+j)){
+ ++total;
+ }
+ }
+ }
+ }
+ m_cells.at(x + y * m_rows)->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++) {
+ if(x+i >= 0 && x+i < m_cols && y+j >= 0 && y+j < m_rows) {
+ if((isCleared(x+i, y+j) == false) && (isBomb(x+i, y+j) == false)){
+ setClearCell((x+i), (y+j));
+ if(bombsNearby(x+i, y+j) == 0) {
+ openNeighboorhood(x+i, y+j);
+ }
+ }
+ }
+ }
+ }
+}
+
+bool MineField::isCleared(int x, int y) {
+ return m_cells.at(x + y * m_rows)->isCleared;
+}
+
+bool MineField::isFlagged(int x, int y) {
+ return m_cells.at(x + y * m_rows)->isFlagged;
+}
+
+bool MineField::isBomb(int x, int y) {
+ return m_cells.at(x + y * m_rows)->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;
+}
+
+void MineField::setClearCell(int x, int y) {
+ m_cells.at(x + y * m_rows)->isCleared = true;
+ clearCellSignal.emit(x, y);
+}
+
+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;
+ remainingFlagsChangedSignal.emit(m_remainingFlags);
+ return true;
+ }
+ else if(m_remainingFlags > 0) {
+ m_cells.at(x + y * m_rows)->isFlagged = true;
+ --m_remainingFlags;
+ remainingFlagsChangedSignal.emit(m_remainingFlags);
+ return true;
+ }
+ return false;
+}
diff --git a/src/MineField.hpp b/src/MineField.hpp
new file mode 100644
index 0000000..4f9f276
--- /dev/null
+++ b/src/MineField.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+//#include
+#include
+#include
+#include
+#include
+#include
+
+struct Cell {
+ bool isFlagged;
+ bool isCleared;
+ bool isBomb;
+ int bombsNearby;
+ Cell(): isFlagged(false), isCleared(false), isBomb(false), bombsNearby(-1) {};
+};
+
+class MineField {
+
+ std::vector> m_cells;
+ int m_rows;
+ int m_cols;
+ int m_totalMines;
+ int m_remainingFlags;
+ bool m_exploded;
+ void computeBombsNearby(int x, int y);
+ void openNeighboorhood(int x, int y);
+ void setClearCell(int x, int y);
+
+public:
+ MineField(int cols, int rows, int mines);
+ void initBombs(int x, int y);
+ bool isBomb(int x, int y);
+ bool isFlagged(int x, int y);
+ bool isCleared(int x, int y);
+ bool clearCell(int x, int y);
+ int bombsNearby(int x, int y);
+ bool isGameOver() {return m_exploded; };
+ 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; };
+ sigc::signal clearCellSignal;
+ sigc::signal remainingFlagsChangedSignal;
+};
diff --git a/src/Window.cpp b/src/Window.cpp
new file mode 100644
index 0000000..e243895
--- /dev/null
+++ b/src/Window.cpp
@@ -0,0 +1,339 @@
+#include "window.hpp"
+#include "gdkmm/pixbuf.h"
+#include "gtkmm/cssprovider.h"
+#include "gtkmm/image.h"
+#include "sigc++/adaptors/bind.h"
+#include "sigc++/functors/mem_fun.h"
+#include
+
+
+//}
+// 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.getRows();
+
+ if(field.isCleared(x, y) == false) {
+ field.toggleFlag(x, y);
+ if(field.isFlagged(x, y)) {
+ auto imgflag = Gtk::make_managed();
+ imgflag->set(m_pixbufFlag);
+ buttons.at(x + y * field.getRows())->set_child(*imgflag);
+ buttons.at(x + y * field.getRows())->set_active(true);
+ }
+ else {
+ buttons.at(x + y * field.getRows())->unset_child();
+ buttons.at(x+ y * field.getRows())->queue_draw();
+ buttons.at(x + y * field.getRows())->set_active(false);
+ }
+ }
+
+ // Glib::ustring msg = Glib::ustring::compose("Remaining flags: %1", field.getRemainingFlags());
+ // flagLabel.set_label(msg);
+}
+
+void MainWindow::updateFlagsLabel(int flags) {
+ 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::OpenNearCells(int index, std::set &visited) {
+// int cols = field->Cols();
+// int x = index % cols;
+// int y = index / cols;
+
+// if (visited.count(index)) return;
+
+// Cell* cell = field->GetCell(x, y);
+// if (!cell || cell->bombsNearby > 0 || cell->type == CellType::Bomb) return;
+
+// visited.insert(index);
+// buttons[index]->set_active(true);
+
+// for (int i = -1; i <= 1; i++) {
+// for (int j = -1; j <= 1; j++) {
+// if (i == 0 && j == 0) continue; // Skip the current cell
+
+// int nx = x + i;
+// int ny = y + j;
+// int newIndex = ny * cols + nx;
+// Cell* neighborCell = field->GetCell(nx, ny);
+
+// // Bounds check before recursive call
+// if (nx >= 0 && nx < cols && ny >= 0 && ny < cols) {
+// if (visited.count(newIndex) == 0) {
+// OpenNearCells(newIndex, visited);
+// }
+// if (neighborCell && !buttons[newIndex]->get_active() && !neighborCell->isFlag) {
+// OpenNearCells(newIndex, visited);
+// }
+// }
+// }
+// }
+// }
+
+
+
+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 if(field.isBomb(x, y)) {
+ openBombs();
+ }
+ else {
+ field.clearCell(x, y);
+ }
+}
+
+
+void MainWindow::openBombs() {
+ for(int i=0; i < field.getCols() * field.getRows(); i++) {
+ int x = i % field.getCols();
+ int y = i / field.getRows();
+
+ buttons.at(i)->set_sensitive(false);
+
+ if(field.isBomb(x, y)) {
+ if(field.isFlagged(x, y)) {
+ auto imgFlagBomb = std::make_shared();
+ imgFlagBomb->set(m_pixbufFlagBomb);
+ buttons.at(i)->set_child(*imgFlagBomb);
+ }
+ else {
+ auto imgBomb = std::make_shared();
+ imgBomb->set(m_pixbufBomb);
+ buttons.at(i)->set_child(*imgBomb);
+ }
+ buttons.at(i)->set_active(true);
+ }
+ }
+}
+
+void MainWindow::updateCell(int x, int y) {
+ if(field.isCleared(x, y)) {
+ if (field.bombsNearby(x, y) > 0) {
+ switch(field.bombsNearby(x, y)) {
+ case 1:
+ buttons.at(x + y * field.getRows())->get_style_context()->add_class("label-1");
+ break;
+ case 2:
+ buttons.at(x + y * field.getRows())->get_style_context()->add_class("label-2");
+ break;
+ case 3:
+ buttons.at(x + y * field.getRows())->get_style_context()->add_class("label-3");
+ break;
+ case 4:
+ buttons.at(x + y * field.getRows())->get_style_context()->add_class("label-4");
+ break;
+ case 5:
+ buttons.at(x + y * field.getRows())->get_style_context()->add_class("label-5");
+ break;
+ case 6:
+ buttons.at(x + y * field.getRows())->get_style_context()->add_class("label-6");
+ break;
+ case 7:
+ buttons.at(x + y * field.getRows())->get_style_context()->add_class("label-7");
+ break;
+ case 8:
+ buttons.at(x + y * field.getRows())->get_style_context()->add_class("label-8");
+ break;
+ }
+ buttons.at(x + y * field.getRows())->set_label(Glib::ustring::format(field.bombsNearby(x, y)));
+ }
+ buttons.at(x + y * field.getRows())->set_active(true);
+ buttons.at(x + y * field.getRows())->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();
+// 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; iget_active())
+// return false;
+// }
+// return true;
+// }
+
+
+// bool MainWindow::UpdateClockLabel()
+// {
+// if(gameOver) return false;
+
+// elapsedTime++;
+
+// int deciseconds = elapsedTime % 10;
+// int seconds = (elapsedTime / 10) % 60;
+// int minutes = (elapsedTime /600) % 60;
+
+// 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);
+
+// return true;
+// }
+
+MainWindow::MainWindow()
+{
+ // ApplyStyles(); // Load the CSS file
+ 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_pixbufBomb = Gdk::Pixbuf::create_from_resource("/minesweeper/bomb-solid");
+ m_pixbufFlag = Gdk::Pixbuf::create_from_resource("/minesweeper/flag-solid");
+ m_pixbufFlagBomb = Gdk::Pixbuf::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();
+ 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.clearCellSignal.connect(sigc::bind(sigc::mem_fun(*this, &MainWindow::updateCell)));
+ field.remainingFlagsChangedSignal.connect(sigc::bind(sigc::mem_fun(*this, \
+ &MainWindow::updateFlagsLabel)));
+ //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");
+
+ //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 main(int argc, char **argv) {
+ auto app = Gtk::Application::create("eu.minesweeper");
+ return app->make_window_and_run(argc, argv);
+}
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..c13ef29
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,30 @@
+#include "MineField.hpp"
+#include
+
+int main() {
+
+ MineField field(160,160, 100);
+
+ srand(time(NULL));
+
+ int x = rand() % 160;
+ int y = rand() % 160;
+
+ field.initBombs(x, y);
+
+ printf("Opened cell: %i %i\n", x, y);
+ printf("Neighboor bombs: %i\n", field.bombsNearby(x,y));
+
+ while(!field.isGameOver()) {
+ x = rand() % 160;
+ y = rand() % 160;
+
+ if(field.clearCell(x, y)) {
+ printf("Opened cell: %i %i\n", x, y);
+ printf("Neighboor bombs: %i\n", field.bombsNearby(x,y));
+ }
+ else {
+ printf("Bomb found in cell: %i %i\n", x, y);
+ }
+ }
+}
diff --git a/src/window.hpp b/src/window.hpp
new file mode 100644
index 0000000..60026c5
--- /dev/null
+++ b/src/window.hpp
@@ -0,0 +1,62 @@
+#pragma once
+
+#include "MineField.hpp"
+#include "gdkmm/pixbuf.h"
+#include
+//#include
+#include
+#include
+#include
+#include
+//#include
+//#include
+#include
+//#include
+// #include
+// #include
+//#include
+//#include
+// #include
+// #include
+// #include
+// #include
+// #include
+
+#define PROJECT_NAME "minesweeper"
+
+
+class MainWindow : public Gtk::Window
+{
+ Gtk::Box boxV{Gtk::Orientation::VERTICAL};
+ Gtk::Box boxH{Gtk::Orientation::HORIZONTAL};
+ std::vector> buttons;
+ Gtk::Grid grid;
+ Gtk::HeaderBar bar;
+ Gtk::Button newGameButton;
+ Gtk::Button optionButton;
+ Gtk::Label flagLabel;
+ Gtk::Label clockLabel;
+ MineField field {16, 16, 40};
+ int elapsedTime;
+ bool newGame;
+ std::shared_ptr m_pixbufBomb;
+ std::shared_ptr m_pixbufFlag;
+ std::shared_ptr m_pixbufFlagBomb;
+ void updateCell(int x, int y);
+ void openBombs();
+ void updateFlagsLabel(int flags);
+
+// sigc::connection clockConn;
+// void OpenNearCells(int index);
+// void Explode();
+// 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();
+};
| |