Skip to content

Instantly share code, notes, and snippets.

@qookei
Created September 15, 2019 17:45
Show Gist options
  • Save qookei/8821fb9c0bcca09a99d256fb065db14a to your computer and use it in GitHub Desktop.
Save qookei/8821fb9c0bcca09a99d256fb065db14a to your computer and use it in GitHub Desktop.
#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