|
#include <ncurses.h> |
|
#include <sstream> |
|
#include <iostream> |
|
#include <random> |
|
|
|
const char empty = ' '; |
|
const char safe = '0'; |
|
const char mine = '*'; |
|
const char flagged = 'F'; |
|
|
|
struct Cell { |
|
char character; |
|
bool shown, flagged; |
|
}; |
|
|
|
class Board { |
|
std::vector<std::vector<Cell>> board; |
|
bool first_click = true; |
|
|
|
bool is_mine(int x, int y) { |
|
return x >= 0 && y >= 0 && x < board.size() && y < board[x].size() && board[x][y].character == mine; |
|
} |
|
|
|
bool flood_fill(int x, int y) { |
|
if (x >= board.size() || y >= board[x].size()) { |
|
return false; |
|
} else if (board[x][y].character >= safe && board[x][y].character <= safe + 8) { |
|
return false; |
|
} else if (board[x][y].character == mine) { |
|
return true; |
|
} |
|
|
|
board[x][y].shown = true; |
|
board[x][y].character = '0'; |
|
char mines = 0; |
|
|
|
if (flood_fill(x, y - 1)) mines++; |
|
if (flood_fill(x, y + 1)) mines++; |
|
if (flood_fill(x - 1, y)) mines++; |
|
if (flood_fill(x + 1, y)) mines++; |
|
|
|
if (is_mine(x - 1, y - 1)) mines++; |
|
if (is_mine(x + 1, y - 1)) mines++; |
|
if (is_mine(x - 1, y + 1)) mines++; |
|
if (is_mine(x + 1, y + 1)) mines++; |
|
|
|
board[x][y].character += mines; |
|
|
|
return false; |
|
} |
|
|
|
public: |
|
bool flagging = false; |
|
|
|
Board(int rows, int columns) { |
|
std::mt19937 rng{std::random_device()()}; |
|
std::uniform_int_distribution<int> dist(0, 3); |
|
board.assign(rows, {}); |
|
std::generate(board.begin(), board.end(), [&rng, &dist, &columns]() { |
|
auto v = std::vector<Cell>(columns); |
|
std::generate(v.begin(), v.end(), [&rng, &dist]() { return dist(rng) > 1 ? Cell{empty} : Cell{mine}; }); |
|
return v; |
|
}); |
|
} |
|
|
|
void reveal() { |
|
for (auto &row : board) { |
|
for (auto &col : row) { |
|
col.shown = true; |
|
} |
|
} |
|
} |
|
|
|
void print() { |
|
std::stringstream header; |
|
header << "Mode: " << (flagging ? "Flagging" : "Searching") << '\n'; |
|
addstr(header.str().c_str()); |
|
addch(ACS_ULCORNER); |
|
bool tee = false; |
|
for (int i = 0; i < static_cast<int>(board.size()) * 4 - 1; i++) { |
|
addch(tee ? ACS_TTEE : ACS_HLINE); |
|
tee = !tee; |
|
} |
|
addch(ACS_URCORNER); |
|
addch('\n'); |
|
for (int i = 0; i < static_cast<int>(board.size()); i++) { |
|
addch(ACS_VLINE); |
|
for (auto col : board[i]) { |
|
addch(col.shown ? col.character : col.flagged ? flagged : empty); |
|
addch(ACS_VLINE); |
|
} |
|
addch('\n'); |
|
if (i != static_cast<int>(board.size()) - 1) { |
|
addch(ACS_LTEE); |
|
for (int j = 0; j < static_cast<int>(board[i].size()); j++) { |
|
addch(ACS_HLINE); |
|
addch(j != static_cast<int>(board[i].size()) - 1 ? ACS_PLUS : ACS_RTEE); |
|
} |
|
} else { |
|
addch(ACS_LLCORNER); |
|
for (int j = 0; j < static_cast<int>(board[i].size()); j++) { |
|
addch(ACS_HLINE); |
|
if (j != static_cast<int>(board[i].size()) - 1) { |
|
addch(ACS_BTEE); |
|
} |
|
} |
|
addch(ACS_LRCORNER); |
|
} |
|
addch('\n'); |
|
} |
|
} |
|
|
|
bool play(int x, int y) { |
|
// Make sure clicking on the header doesn't do anything |
|
if (y == 0) { |
|
return false; |
|
} |
|
|
|
int r = (y - 1) / 2; |
|
int c = (x - 1) / 2; |
|
|
|
if (r >= board.size() || c >= board[r].size()) { |
|
return false; |
|
} |
|
|
|
if (flagging) { |
|
board[r][c].flagged = !board[r][c].flagged; |
|
return false; |
|
} |
|
|
|
if (first_click) { |
|
board[r][c].character = empty; |
|
first_click = false; |
|
} |
|
|
|
return flood_fill(r, c); |
|
} |
|
|
|
bool won() { |
|
for (const auto &row : board) { |
|
for (const auto &col : row) { |
|
if (!col.shown && col.character != mine) { |
|
return false; |
|
} |
|
} |
|
} |
|
return true; |
|
} |
|
}; |
|
|
|
int main() { |
|
initscr(); |
|
clear(); |
|
curs_set(0); |
|
cbreak(); |
|
noecho(); |
|
keypad(stdscr, true); |
|
mousemask(ALL_MOUSE_EVENTS, nullptr); |
|
MEVENT event; |
|
|
|
Board board(11, 22); |
|
|
|
bool playing = true; |
|
bool lost = false; |
|
bool won = false; |
|
do { |
|
erase(); |
|
board.print(); |
|
refresh(); |
|
int c = getch(); |
|
switch (c) { |
|
case 'q': |
|
playing = false; |
|
break; |
|
case 'f': |
|
board.flagging = !board.flagging; |
|
break; |
|
case KEY_MOUSE: |
|
if (getmouse(&event) == OK && !lost) { |
|
if (event.bstate & BUTTON1_CLICKED) { |
|
if (board.play(event.x, event.y)) { |
|
lost = true; |
|
board.reveal(); |
|
} else if (board.won()) { |
|
won = true; |
|
playing = false; |
|
} |
|
} |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
} while (playing); |
|
endwin(); |
|
if (won) { |
|
std::cout << "You Won!" << std::endl; |
|
} |
|
return 0; |
|
} |