Created
September 15, 2019 17:45
-
-
Save qookei/8821fb9c0bcca09a99d256fb065db14a 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
#include <iostream> | |
#include <cassert> | |
#include <ctime> | |
#include <vector> | |
#include <termios.h> | |
#include <cstdio> | |
#include <utility> | |
#include <algorithm> | |
// helpers | |
int rand_between(int top, int bottom) { | |
auto v = (double)rand() / (double)RAND_MAX; | |
return (v * (double)(bottom - top)) + top; | |
} | |
struct game_board { | |
int width; | |
int height; | |
std::vector<bool> bombs; | |
void create(int w, int h) { | |
width = w; | |
height = h; | |
bombs.clear(); | |
bombs.resize(w * h); | |
} | |
void generate(int safe_x, int safe_y, int bomb_count) { | |
while (bomb_count) { | |
int x = rand_between(0, width); | |
int y = rand_between(0, height); | |
if (x == safe_x || y == safe_y) | |
continue; | |
if (bombs[x + y * width]) | |
continue; | |
bombs[x + y * width] = true; | |
bomb_count--; | |
} | |
} | |
}; | |
struct game_view { | |
std::vector<char> view; | |
std::vector<bool> visible; | |
int width; | |
int height; | |
int to_uncover; | |
void from_game_board(game_board &gb) { | |
assert(gb.bombs.size() == gb.width * gb.height); | |
view.clear(); | |
visible.clear(); | |
view.resize(gb.width * gb.height); | |
visible.resize(gb.width * gb.height); | |
width = gb.width; | |
height = gb.height; | |
to_uncover = 0; | |
for (int x = 0; x < gb.width; x++) { | |
for (int y = 0; y < gb.height; y++) { | |
auto idx = x + y * gb.width; | |
if (gb.bombs[idx]) { | |
view[idx] = '*'; | |
continue; | |
} | |
int count = 0; | |
for (int ay = y - 1; ay <= y + 1; ay++) { | |
for (int ax = x - 1; ax <= x + 1; ax++) { | |
if (ay < 0 || ax < 0) | |
continue; | |
if (ay >= gb.height || | |
ax >= gb.width) | |
continue; | |
count += gb.bombs[ax + ay * gb.width] ? 1 : 0; | |
} | |
} | |
assert(count <= 8); | |
if (!count) | |
view[idx] = '.'; | |
else | |
view[idx] = '0' + count; | |
to_uncover++; | |
} | |
} | |
} | |
void show() { | |
for (int x = 0; x < width; x++) { | |
for (int y = 0; y < height; y++) { | |
printf("%c ", | |
visible[x + y * width] ? | |
view[x + y * width] : | |
'#'); | |
} | |
printf("\n"); | |
} | |
} | |
private: | |
void uncover_dots(int x, int y) { | |
if (y < 0 || x < 0) | |
return; | |
if (y >= height || | |
x >= width) | |
return; | |
if (view[x + y * width] == '.' && !visible[x + y * width]) { | |
visible[x + y * width] = true; | |
to_uncover--; | |
uncover_dots(x + 1, y); | |
uncover_dots(x - 1, y); | |
uncover_dots(x, y + 1); | |
uncover_dots(x, y - 1); | |
} | |
} | |
public: | |
// returns: true if clicked a bomb | |
bool try_uncover(int x, int y) { | |
int idx = x + y * width; | |
if (view[idx] == '*') { | |
// uncover all bombs | |
for (int i = 0; i < view.size(); i++) | |
visible[i] = view[i] == '*' ? true : | |
visible[i]; | |
return true; | |
} else if (view[idx] == '.') { | |
// fill all visible spots that are next to '.' | |
uncover_dots(x, y); | |
} else { | |
visible[idx] = true; | |
to_uncover--; | |
} | |
return false; | |
} | |
}; | |
static struct termios old_term; | |
void reset_terminal() { | |
tcsetattr(0, TCSANOW, &old_term); | |
} | |
int main(int argc, char **argv) { | |
srand((unsigned)time(NULL)); // TODO: replace with better randomness | |
if (argc != 4) { | |
fprintf(stderr, "%s: usage: <width> <height> <bomb %>\n", argv[0]); | |
return 1; | |
} | |
int width = std::stoi(argv[1]); | |
int height = std::stoi(argv[2]); | |
int bomb_percent = std::stoi(argv[3]); | |
game_board gb; | |
game_view v; | |
gb.create(width, height); | |
gb.generate(width / 2, height / 2, width * height * bomb_percent / 100); | |
v.from_game_board(gb); | |
atexit(reset_terminal); | |
struct termios new_term; | |
tcgetattr(0, &old_term); | |
new_term = old_term; | |
new_term.c_lflag &= ~ICANON; | |
new_term.c_lflag &= ~ECHO; | |
tcsetattr(0, TCSANOW, &new_term); | |
printf("\ec"); | |
int cursor_x = width / 2, cursor_y = height / 2; | |
bool game_over = false; | |
while (true) { | |
printf("\e[1;1H"); | |
v.show(); | |
printf("\e[%d;1H\e[KSpots left: %d", height + 2, v.to_uncover); | |
printf("\e[%d;%dH", | |
cursor_y + 1, (cursor_x + 1) * 2 - 1); | |
char c = getchar(); | |
switch(c) { | |
case 'h': | |
case 'H': | |
cursor_x--; | |
break; | |
case 'j': | |
case 'J': | |
cursor_y++; | |
break; | |
case 'k': | |
case 'K': | |
cursor_y--; | |
break; | |
case 'l': | |
case 'L': | |
cursor_x++; | |
break; | |
case ' ': | |
break; // avoid going into the default case | |
default: | |
continue; | |
} | |
if (cursor_x < 0) cursor_x = 0; | |
if (cursor_y < 0) cursor_y = 0; | |
if (cursor_x >= width) cursor_x = width - 1; | |
if (cursor_y >= height) cursor_y = height - 1; | |
if (c == ' ') { | |
// trying to uncover something | |
// x and y are flipped... | |
bool bomb = v.try_uncover(cursor_y, cursor_x); | |
if (bomb) { | |
game_over = true; | |
break; | |
} | |
if(!v.to_uncover) | |
break; | |
} | |
} | |
// redraw game board to show final state | |
printf("\e[1;1H"); | |
v.show(); | |
printf("\e[%d;1H", height + 3); | |
if (game_over) | |
printf("You lost!\n"); | |
else | |
printf("You won!\n"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment