Last active
November 8, 2017 01:17
-
-
Save phoemur/97ec666ed12f8ee72c3078006a662e3c to your computer and use it in GitHub Desktop.
Minesweeper comand line C++
This file contains hidden or 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
#include <deque> | |
#include <iostream> | |
#include <iomanip> | |
#include <random> | |
#include <sstream> | |
#include <string> | |
#include <utility> | |
#include <vector> | |
struct Cell { //represents one cell in the minefield | |
int val = 0; // 0 - 8 for showing adjacent mines. | |
bool is_open = false; | |
bool is_flag = false; | |
bool is_mine = false; | |
}; | |
class MineField { | |
private: | |
int side_x; | |
int side_y; | |
int mines; // number of mines | |
std::random_device seeder {}; | |
std::mt19937 engine; | |
std::uniform_int_distribution<int> dist; | |
std::vector<std::vector<Cell>> nodes {}; // the minefield matrix | |
public: | |
explicit MineField(int x, int y, int m) | |
: side_x{x}, side_y{y}, mines{m}, engine(this->seeder()), dist(0, (side_x*side_y)) | |
{ | |
if (mines > side_x*side_y) {throw std::runtime_error("More mines than cells");} | |
if (mines <= 0 || side_x <= 0 || side_y <= 0) { | |
throw std::runtime_error("Cannot be zero or less"); | |
} | |
create_nodes(side_x, side_y); | |
insert_mines(); | |
fill_cells(); | |
} | |
explicit MineField() : MineField(8, 8, 10) {} //default constructor | |
void print() const noexcept //output to stdout | |
{ | |
int i = 1; | |
for (auto& vec: nodes) { | |
std::cout << std::setfill('0') << std::setw(2) << i++ << ": "; | |
for (auto& c: vec) { | |
if (!c.is_open) {std::cout << '?' << " ";} | |
else { | |
if (c.is_mine) {std::cout << '*' << " ";} | |
else {std::cout << c.val << " ";} | |
} | |
} | |
std::cout << "\n"; | |
} | |
} | |
bool open(int x, int y) | |
{ | |
//check and return early | |
if (x<=0 || y<=0 || x>side_x || y>side_y) {return true;} | |
if (nodes[x-1][y-1].is_mine) { | |
std::cout << "\n\nYOU LOOSE!!!\n\n"; | |
return false; | |
} | |
nodes[x-1][y-1].is_open = true; | |
if (nodes[x-1][y-1].val == 0) { | |
open_zeroes(x-1, y-1); | |
} | |
if (check_end()) { | |
std::cout << "\n\nYOU WIN!!!\n\n"; | |
return false; | |
} | |
return true; | |
} | |
private: | |
void create_nodes(unsigned x, unsigned y) | |
// populates the matrix with default values | |
{ | |
using sz_t = std::vector<std::vector<Cell>>::size_type; | |
nodes.reserve(y); | |
for (sz_t i = 0; i < y; ++i) { | |
nodes.emplace_back(std::vector<Cell>(x)); | |
} | |
} | |
void insert_mines() // insert n random mines in the matrix | |
{ | |
int a, b, counter = 0; | |
while (counter < mines) { | |
a = dist(engine) % side_y; | |
b = dist(engine) % side_x; | |
if (!nodes[a][b].is_mine) { | |
nodes[a][b].is_mine = true; | |
++counter; | |
} | |
} | |
} | |
inline bool is_valid_coord(int a, int b) const noexcept | |
// checks if is within the matrix | |
{ | |
if (a >= 0 && a < side_y && b >= 0 && b < side_x) { | |
return true; | |
}else {return false;} | |
} | |
void fill_cells() | |
/* This function populates the cell values according to | |
the neighbouring mines */ | |
{ | |
using sz_t = std::vector<std::vector<Cell>>::size_type; | |
for (sz_t i=0; i<nodes.size(); ++i) { | |
for (sz_t j=0; j<nodes[0].size(); ++j) { | |
for (int b=-1; b<=1; ++b) { | |
for (int a=-1; a<=1; ++a) { | |
if (is_valid_coord(b+i, a+j)) { | |
if (nodes[b+i][a+j].is_mine) { | |
nodes[i][j].val++; | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
void open_zeroes(int i, int j) | |
{ // open all adjacent zeroes (Breadth First Search algorithm) | |
using namespace std; | |
vector<vector<bool>> visited (side_y, vector<bool>(side_x, false)); | |
deque<pair<int,int>> c_queue; | |
c_queue.push_back(pair<int,int>(i,j)); | |
while(!c_queue.empty()) { | |
auto p = c_queue.front(); | |
c_queue.pop_front(); | |
visited[p.first][p.second] = true; | |
for (int b=-1; b<=1; ++b) { | |
for (int a=-1; a<=1; ++a) { | |
if (is_valid_coord(b+p.first, a+p.second)) { | |
nodes[b+p.first][a+p.second].is_open = true; | |
if (!visited[b+p.first][a+p.second] && | |
nodes[b+p.first][a+p.second].val == 0) { | |
c_queue.push_back(pair<int,int>(b+p.first, a+p.second)); | |
visited[b+p.first][a+p.second] = true; | |
} | |
} | |
} | |
} | |
} | |
} | |
bool check_end() const noexcept | |
{ // end is when there is no cell without a mine to open | |
using sz_t = std::vector<std::vector<Cell>>::size_type; | |
int counter = 0; | |
for (sz_t i=0; i<nodes.size(); ++i) { | |
for (sz_t j=0; j<nodes[0].size(); ++j) { | |
if (nodes[i][j].is_open && !nodes[i][j].is_mine) { | |
counter++; | |
} | |
} | |
} | |
return counter >= (side_x*side_y)-mines ? true: false; | |
} | |
}; // class MineField | |
inline void parse_input(std::string& s, int& x, int& y) | |
{ | |
std::stringstream ss {s}; | |
char c; | |
ss >> x >> c >> y; | |
ss.clear(); | |
} | |
int main() | |
{ /*The 3 levels that are used for time-world-rankings are: | |
8x8_10 (WR 0.31 s but luck too much involved) | |
16x16_40 (WR 7.03 s) | |
16x30_99 (WR 31.13 s) | |
*/ | |
using namespace std; | |
MineField m(8, 8, 10); // X,Y,mines count | |
cout << "--< Minesweeper CLI 0.1 >--"; | |
int x, y; | |
string input; | |
do { | |
cout << "\n\n"; | |
m.print(); | |
cout << "Input coordinates (row,cols - comma separated) to open: "; | |
getline(cin, input); | |
parse_input(input, x, y); | |
} while (m.open(x, y)); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment