Hi, I am making a Minesweeper game using SFML 2.0.
I am just a beginner in C++ and started using the framework for 2 weeks now, so it is safe to say I may have made lots of mistake especially when it comes to data types or algorithm implementations.
Before I start, I'll have to explain this two setting variables to avoid confusions:
CELLS_OFFSET = where the grid of cells position will generate. (can be used to center the cells)
CELLS_SIZE = What is the row and columns length. (not the scale value of the cell sprites)
I would really appreciate if you could take your time and review these (sort of messy) codes, and telling any mistake or bad practices^^
Settings.hpp
const unsigned short CELLS_SIZE = 14;
const unsigned short CELLS_BOMBS = 24;
int8_t CELLS_DATA[CELLS_SIZE][CELLS_SIZE]; // The hidden cell values
int8_t CELLS_SDATA[CELLS_SIZE][CELLS_SIZE]; // The cells on top of the hidden cell values
const sf::Vector2i CELLS_OFFSET = sf::Vector2i(202, 16); // The position where the grid of cells will generate
Cell.hpp & Cell.cpp
// Cell.hpp
#include <SFML/Graphics.hpp>
#ifndef CELL_HPP
#define CELL_HPP
class Cell
{
private:
sf::Sprite sprite;
sf::Texture texture;
public:
Cell();
void change(int8_t index);
void draw(sf::RenderWindow &window, sf::Vector2i position, sf::Vector2i offset);
};
#endif // CELL_HPP
// Cell.cpp
#include "Cell.hpp"
Cell::Cell()
{
this->texture.loadFromFile("Assets/Cells.png");
this->sprite.setTextureRect(sf::IntRect(0, 0, 16, 16));
this->sprite.setTexture(texture);
}
void Cell::change(int8_t index)
{
this->sprite.setTextureRect(sf::IntRect(16 * index, 0, 16, 16));
}
void Cell::draw(sf::RenderWindow &window, sf::Vector2i position, sf::Vector2i offset)
{
this->sprite.setPosition(position.x * 32 + offset.x, position.y * 32 + offset.y);
this->sprite.setScale(2, 2);
window.draw(this->sprite);
}
Main.cpp
#include <SFML/Graphics.hpp>
#include "Cell.hpp"
#include "Settings.hpp"
int main()
{
sf::RenderWindow window(sf::VideoMode(852, 480), "Minesweeper", sf::Style::Titlebar | sf::Style::Close);
window.setVerticalSyncEnabled(true);
// Define the cell class and randomly seed the bombs
Cell cell; srand(time(0));
// Fill Bomb Cells
for (short x = 0; x < CELLS_BOMBS; x++)
{
int8_t &cell_location = CELLS_DATA[rand() % CELLS_SIZE][rand() % CELLS_SIZE];
if (cell_location == 9)
x--;
else
cell_location = 9;
}
// Fill Number Cells
for (short x = 0; x < CELLS_SIZE; x++) for (short y = 0; y < CELLS_SIZE; y++)
{
int8_t &cell_location = CELLS_DATA[x][y];
if (cell_location == 9) continue; // Skip if this cell is a bomb
for (int8_t rx = -1; rx < 2; rx++) for (int8_t ry = -1; ry < 2; ry++) {
// Skip if this cell is out of bounds
if (x + rx > CELLS_SIZE - 1 || y + ry > CELLS_SIZE - 1 || x + rx < 0 || y + ry < 0 || (rx == 0 && ry == 0)) continue;
// Increases the number if the neighbor cell has a bomb
if (CELLS_DATA[x + rx][y + ry] == 9) cell_location++;
}
}
// Game Loop
while (window.isOpen())
{
sf::Vector2i cell_position = sf::Vector2i(std::floor((sf::Mouse::getPosition(window).x - CELLS_OFFSET.x) / 32.0), std::floor((sf::Mouse::getPosition(window).y - CELLS_OFFSET.y) / 32.0)); // I would appreciate if someone optimize this line
bool cell_bounds = cell_position.x >= 0 && cell_position.x < CELLS_SIZE && cell_position.y >= 0 && cell_position.y < CELLS_SIZE; // Used to check if the cursor position is inside the grid of cells
int8_t &cell_location = CELLS_DATA[cell_position.x][cell_position.y];
int8_t &cell_slocation = CELLS_SDATA[cell_position.x][cell_position.y];
sf::Event event; while (window.pollEvent(event))
{
// Cells Input
if (event.type == sf::Event::MouseButtonPressed && cell_bounds) switch(event.mouseButton.button)
{
case sf::Mouse::Left: // Cell Opening
if (cell_slocation != 0) continue;
cell_slocation = -1;
// Flood Fill (BFS)
if (cell_location != 0) continue;
{
std::vector<sf::Vector2i> queue = {cell_position};
while (true)
{
// Get the first queue vector2 for checking it's neighbors
sf::Vector2i queue_first_position = sf::Vector2i(queue.at(0).x, queue.at(0).y);
// Loop through neigboring cells
for (int8_t rx = -1; rx < 2; rx++) for (int8_t ry = -1; ry < 2; ry++) {
// The cell must not be on out of bounds
if (queue_first_position.x + rx > CELLS_SIZE - 1 || queue_first_position.y + ry > CELLS_SIZE - 1 || queue_first_position.x + rx < 0 || queue_first_position.y + ry < 0 || (rx == 0 && ry == 0)) continue;
int8_t &neighbor_location = CELLS_DATA[queue_first_position.x + rx][queue_first_position.y + ry];
int8_t &neighbor_slocation = CELLS_SDATA[queue_first_position.x + rx][queue_first_position.y + ry];
// The cell must be already opened, and it is not a bomb
if (neighbor_slocation == -1 || neighbor_location == 9) continue;
// Open the cell
neighbor_slocation = -1;
// The cell must empty
if (neighbor_location != 0) continue;
// Add the cell's vector2 to queue
queue.push_back(sf::Vector2i(queue_first_position.x + rx, queue_first_position.y + ry));
}
// Remove the first element in the queue for checking if done filling
queue.erase(queue.begin());
if (queue.size() <= 0) break;
}
}
break;
case sf::Mouse::Right: // Cell Flagging
if (cell_slocation == 1)
cell_slocation = 0;
else if (cell_slocation == 0)
cell_slocation = 1;
break;
}
if (event.type == sf::Event::Closed) window.close();
}
// Cells Render
for (short x = 0; x < CELLS_SIZE; x++) for (short y = 0; y < CELLS_SIZE; y++)
{
switch(CELLS_SDATA[x][y])
{
case 0: cell.change(10); break; // Unopened Cell
case 1: cell.change(11); break; // Flagged Cell
default: cell.change(CELLS_DATA[x][y]); // Numbered Cell
}
cell.draw(window, sf::Vector2i(x, y), CELLS_OFFSET);
}
// Cells Highlight
if (cell_bounds && ((cell_slocation == -1 && cell_location != 0) || (cell_slocation != -1)))
{
sf::RectangleShape highlight(sf::Vector2f(32, 32));
highlight.setFillColor(sf::Color(255, 255, 255, 30));
highlight.setPosition(cell_position.x * 32 + CELLS_OFFSET.x, cell_position.y * 32 + CELLS_OFFSET.y);
window.draw(highlight);
}
window.display();
window.clear();
}
return EXIT_SUCCESS;
}
Regarding about the code, I kept using this data type int8_t to store the value of the cells. I wonder if it that's a good practice or not.

uint8_tthroughout, but the C++ type is calledstd::uint8_t(and isn't available on platforms that don't have a type that's exactly 8 bits). You may want to make ausingalias for the type you want. \$\endgroup\$