Skip to content

Instantly share code, notes, and snippets.

@luitzenhietkamp
Last active February 14, 2018 13:59
Show Gist options
  • Save luitzenhietkamp/c502bd59c76ae9c008de189be210e9d3 to your computer and use it in GitHub Desktop.
Save luitzenhietkamp/c502bd59c76ae9c008de189be210e9d3 to your computer and use it in GitHub Desktop.
/*==========================================================
// My implementation of minesweeper using SFML.
//
// Center mouse button functionality has some bugs.
// Also want to implement the same functionality for
// left and right buttons pressed simultaneously.
//
// Still has a lot of clutter in main(), needs to me moved
// to separate functions.
//
// Still want to implement some functionality such as
// timer, high score, difficulty settings, etc.
===========================================================*/
#include <SFML/Graphics.hpp>
#include <vector>
#include <ctime>
#include <iostream>
// struct that holds information
// about each square in the field
struct Square{
bool has_mine;
int state;
// state = 0..8: number of neighbouring mines
// state = 9: has mine itself
// state = 10: not revealed yet
// state = 11: has flag
};
std::vector<int> find_neighbours(int location, int width, int height);
// struct that holds information on the state
// of the middle mouse button
struct Mouse_Middle{
int location;
bool hold_is_on;
std::vector<int> toggled_neighbours;
};
std::vector<int> find_neighbours(int location, int width, int height) {
std::vector<int> ret;
if(location > width && location % width != 0) {
ret.push_back(location - width - 1);
}
if(location >= width) {
ret.push_back(location - width);
}
if(location >= width && location % width + 1 != width) {
ret.push_back(location - width + 1);
}
if(location % width != 0) {
ret.push_back(location - 1);
}
if(location % width + 1 != width) {
ret.push_back(location + 1);
}
if(location < width * height - width && location % width != 0) {
ret.push_back(location + width - 1);
}
if(location < width * height - width) {
ret.push_back(location + width);
}
if(location < width * height - width && location % width + 1 != width) {
ret.push_back(location + width + 1);
}
return ret;
}
// function that adds squares to the backlog and
// removes it from the vector with remaining squares
void add_to_backlog(int location, std::vector<int>& backlog) {
bool can_be_added = true;
for(const auto &item : backlog) {
if(location == item) {
can_be_added = false;
// error: location is already on the backlog
}
}
if(can_be_added) {
backlog.push_back(location);
}
}
// function to generate a field
std::vector<Square> generate_field(int field_size) {
std::vector<Square> ret;
for (int i = 0; i != field_size; ++i) {
Square create_square;
create_square.has_mine = false;
create_square.state = 10;
ret.push_back(create_square);
}
return ret;
}
// function to initialize the field after the first click
void initialize_field(std::vector<Square>& field, int first_click, int field_size, int mines) {
// create a helper vector possible_locations with all
// the locations in a width * height field that
// potentially contain a mine
std::vector<int> possible_locations;
for (int i = 0; i != field_size; ++i) {
possible_locations.push_back(i);
}
std::vector<int> ret = possible_locations;
possible_locations.erase(possible_locations.begin() + first_click);
// create a vector that holds the locations of all the mines
std::vector<int> locations_of_mines;
// generate a random number between 0..possible_locations.size()
// get a random location from the locations vector
// and move it to the locations_of_mines vector
srand(time(0));
for (int i = 0; i != mines; ++i) {
int n = rand() % possible_locations.size();
locations_of_mines.push_back(possible_locations[n]);
possible_locations.erase(possible_locations.begin() + n);
}
// now that we know the locations of the mines we
// need to flag all squares in the vector field
// that have a mine as such
for (const auto &item : locations_of_mines) {
field[item].has_mine = true;
}
}
// function to determine how many neighbouring squares has mines
int neighbours_with_mines(int square, std::vector<Square>& field, int width, int height) {
std::vector<int> ret;
std::vector<int> neighbours = find_neighbours(square, width, height);
int count_mines{0};
for(const auto &item : neighbours) {
if(field[item].has_mine) {
++count_mines;
}
}
// set the amount of neighbouring mines for the location
field[square].state = count_mines;
return count_mines;
}
// function to evaluate squares
void evaluate_square(int location, std::vector<int>& backlog, std::vector<Square>& field, int width, int height) {
if (field[location].has_mine) {
field[location].state = 9;
} else {
int neighbouring_mines = neighbours_with_mines(location, field, width, height);
field[location].state = neighbouring_mines;
if(!neighbouring_mines) {
std::vector<int> neighbours = find_neighbours(location, width, height);
for(const auto &item : neighbours) {
if(field[item].state > 9) {
add_to_backlog(item, backlog);
}
}
}
}
}
// function to process the backlog
void process_backlog(std::vector<int>& backlog, std::vector<Square>& field, int width, int height) {
while (backlog.size() != 0) {
evaluate_square(*(backlog.begin()), backlog, field, width, height);
backlog.erase(backlog.begin());
}
}
void gameover(const std::vector<Square>& field, std::vector<int>& backlog) {
for (std::vector<Square>::size_type i = 0; i != field.size(); ++i) {
if(field[i].state > 9) {
add_to_backlog(i, backlog);
}
}
}
bool is_complete(std::vector<Square>& field) {
bool success = true;
for(const auto& item : field) {
if(item.state > 8 && !item.has_mine) {
success = false;
}
}
if(success){
std::cout << "Congratulations! You have won the game."; // Temporary console message to test out the code
for(auto &item : field) {
if(item.has_mine) {
item.state = 11;
}
}
}
return success;
}
int main() {
int width{9}, height{9}, mines{10}, tile_size{50};
bool field_initialized{false}, game_over{false};
Mouse_Middle middle_button;
middle_button.hold_is_on = false;
// generate the field
std::vector<Square> field = generate_field(width * height);
std::vector<int> backlog;
sf::RenderWindow app(sf::VideoMode(580, 580), "Minesweeper!");
sf::Texture t;
t.loadFromFile("images/all_tiles.jpg");
sf::Sprite s(t);
while (app.isOpen()) {
sf::Vector2i mouse_position = sf::Mouse::getPosition(app);
int mouse_location = width*(mouse_position.y / tile_size - 1) + mouse_position.x / tile_size - 1;
std::vector<int> backlog;
if(!game_over) {
game_over = is_complete(field);
}
sf::Event e;
while(app.pollEvent(e)) {
if(e.type == sf::Event::Closed) {
app.close();
}
if (e.type == sf::Event::MouseButtonPressed && !game_over) {
if(e.key.code == sf::Mouse::Left) {
// actions for left mouse button pressed
if(!field_initialized) {
initialize_field(field, mouse_location, width * height, mines);
field_initialized = true;
add_to_backlog(mouse_location, backlog);
} else if(field[mouse_location].state == 10){
add_to_backlog(mouse_location, backlog);
if(field[mouse_location].has_mine) {
gameover(field, backlog);
game_over = true;
}
}
} else if(e.key.code == sf::Mouse::Right) {
// actions for right mouse button pressed
if(field[mouse_location].state == 10) {
field[mouse_location].state = 11;
} else if(field[mouse_location].state == 11) {
field[mouse_location].state = 10;
}
} else if(e.key.code == sf::Mouse::Middle) {
// actions for middle mouse button pressed
middle_button.hold_is_on = true;
middle_button.location = mouse_location;
std::vector<int> neighbours = find_neighbours(mouse_location, width, height);
std::vector<int> affected_neighbours;
for(std::vector<int>::size_type i = 0; i != neighbours.size(); ++i) {
if(field[neighbours[i]].state == 10) {
affected_neighbours.push_back(neighbours[i]);
field[neighbours[i]].state = 0;
}
}
middle_button.toggled_neighbours = affected_neighbours;
}
} else if(e.type == sf::Event::MouseButtonReleased) {
if(e.key.code == sf::Mouse::Middle) {
// actions for middle mouse button released
middle_button.hold_is_on = false;
if(middle_button.toggled_neighbours.size() != 0) {
int flags{0};
std::vector<int> neighbours = find_neighbours(mouse_location, width, height);
for(const auto &item : neighbours) {
if(field[item].state == 11) {
++flags;
}
}
for(const auto &item : middle_button.toggled_neighbours) {
if(flags != field[mouse_location].state) {
field[item].state = 10;
} else {
if(field[item].has_mine) {
gameover(field, backlog);
} else {
add_to_backlog(item, backlog);
}
}
}
}
}
}
}
if(backlog.size() != 0) {
process_backlog(backlog, field, width, height);
}
// draw and display the field
app.clear(sf::Color::White);
for(std::vector<Square>::size_type i = 0; i != field.size(); ++i) {
s.setTextureRect(sf::IntRect(field[i].state * tile_size, 0, tile_size, tile_size));
s.setPosition((i % width + 1) * tile_size, (i / width + 1) * tile_size );
app.draw(s);
}
app.display();
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment