Bernardo Magri 10 месяцев назад
Родитель
Сommit
2c13567747

+ 1 - 1
meson.build

@@ -11,6 +11,6 @@ res = gnome.compile_resources(
 )
 
 deps = dependency(['gtkmm-4.0', 'sigc++-3.0'])
-src = ['src/Window.cpp', 'src/window.hpp', 'src/MineField.hpp', 'src/MineField.cpp', res]
+src = ['src/window.cpp', 'src/window.hpp', 'src/minefield.hpp', 'src/minefield.cpp', res]
 exe = executable('minesweeper', src, dependencies : deps, install : true)
 

Разница между файлами не показана из-за своего большого размера
+ 3 - 0
src/.minefield.cpp.~undo-tree~


Разница между файлами не показана из-за своего большого размера
+ 11 - 0
src/.minefield.hpp.~undo-tree~


Разница между файлами не показана из-за своего большого размера
+ 9 - 0
src/.window.cpp.~undo-tree~


Разница между файлами не показана из-за своего большого размера
+ 3 - 0
src/.window.hpp.~undo-tree~


+ 115 - 0
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> cell = std::make_shared<Cell>();
+    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;
+}

+ 0 - 0
src/MineField.cpp → src/minefield.cpp~


+ 46 - 0
src/minefield.hpp

@@ -0,0 +1,46 @@
+#pragma once
+
+// #include <emmintrin.h>
+#include <sigc++/signal.h>
+#include <vector>
+#include <cstdlib>     
+#include <ctime>
+#include <memory>
+
+struct Cell {
+  bool isFlagged;
+  bool isCleared;
+  bool isBomb;
+  int  bombsNearby;
+  Cell(): isFlagged(false), isCleared(false), isBomb(false), bombsNearby(-1) {}; 
+};
+
+class MineField {
+
+  std::vector<std::shared_ptr<Cell>> 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<void(int, int)> clearCellSignal;
+  sigc::signal<void(int)> remainingFlagsChangedSignal; 
+};

+ 0 - 0
src/MineField.hpp → src/minefield.hpp~


+ 333 - 0
src/window.cpp

@@ -0,0 +1,333 @@
+#include "window.hpp"
+
+
+//}
+// 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<Gtk::Image>();
+      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<int> &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<Gtk::Image>();
+	imgFlagBomb->set(m_pixbufFlagBomb);
+	buttons.at(i)->set_child(*imgFlagBomb);
+      }
+      else {
+	auto imgBomb = std::make_shared<Gtk::Image>();
+	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<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;
+// }
+
+
+// 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<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.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<MainWindow>(argc, argv);
+}

+ 0 - 0
src/Window.cpp → src/window.cpp~


+ 1 - 14
src/window.hpp

@@ -1,26 +1,13 @@
 #pragma once
 
-#include "MineField.hpp"
+#include "minefield.hpp"
 #include "gdkmm/pixbuf.h"
 #include <memory>
-//#include <gtkmm-4.0/gtkmm/window.h>
 #include <gtkmm.h>
 #include <glibmm.h>
 #include <gdkmm.h>
 #include <sigc++/sigc++.h>
-//#include <gtkmm-4.0/gtkmm/togglebutton.h>
-//#include <gtkmm-4.0/gtkmm/grid.h>
 #include <gtkmm-4.0/gtkmm/gestureclick.h>
-//#include <gtkmm-4.0/gtkmm/headerbar.h>
-// #include <gdk-pixbuf-2.0/gdk-pixbuf/gdk-pixbuf.h>
-// #include <gtkmm-4.0/gtkmm/image.h>
-//#include <gtkmm-4.0/gtkmm/box.h>
-//#include <gtkmm-4.0/gtkmm/label.h>
-// #include <glibmm-2.68/glibmm/ustring.h>
-// #include <glibmm-2.68/glibmm/main.h>
-// #include <gtkmm-4.0/gtkmm/cssprovider.h>
-// #include <gtkmm-4.0/gtkmm.h>
-// #include <gtkmm-4.0/gtkmm/application.h>
 
 #define PROJECT_NAME "minesweeper"
 

Некоторые файлы не были показаны из-за большого количества измененных файлов