Last active
February 14, 2018 13:59
-
-
Save luitzenhietkamp/c502bd59c76ae9c008de189be210e9d3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*========================================================== | |
// 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