Skip to content

Instantly share code, notes, and snippets.

@lewtds
Last active October 13, 2017 20:03
Show Gist options
  • Save lewtds/8a30c3211946d9395b263b3c6e406487 to your computer and use it in GitHub Desktop.
Save lewtds/8a30c3211946d9395b263b3c6e406487 to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <signal.h>
#include <math.h>
#include <sys/time.h>
#define STATUS_GOING 0
#define STATUS_LOST 1
#define STATUS_WON 2
#define MASK_HIDE 0
#define MASK_REVEAL 1
#define MASK_FLAG 2
// multidimensional array index
#define mi(i, j, n) (j) * (n) + (i)
#define printfattr(ATTR, ...) do {printf("\033[" ATTR "m"); printf(__VA_ARGS__); printf("\033[0;m");} while(0)
int cursorX = 0;
int cursorY = 0;
int boardStartCol = 20;
int boardStartRow = 2;
int boundX = 14; // 2 - 14
int boundY = 10; // 2 - 10
int gameStatus = STATUS_WON;
int minesCount = 10;
int flaggedMines = 0;
int flagsCount = 0;
int nonMinesRevealed = 0;
int isFirstMove = 1;
time_t startTime = 0;
time_t endTime = 0;
int *board = NULL;
int *mask = NULL;
void cleanUp();
void slideCursor(int deltaX, int deltaY);
void render();
void revealCurrentTile();
void flagCurrentTile();
void clearScreen();
void handleSigInt(int);
void handleMouseClick(int button, int row, int col);
void generateBoard(int bombsCount, int avoidX, int avoidY);
void resetGameState()
{
gameStatus = STATUS_GOING;
cursorX = 0;
cursorY = 0;
flaggedMines = 0;
flagsCount = 0;
nonMinesRevealed = 0;
isFirstMove = 1;
startTime = time(0);
endTime = startTime;
if (board == NULL)
board = malloc(boundY * boundX * sizeof(int));
memset(board, 0, boundX * boundY * sizeof(int));
if (mask == NULL)
mask = malloc(sizeof(int) * boundY * boundX);
for (int i = 0; i < boundX * boundY; i++)
mask[i] = MASK_HIDE;
}
int getchar_timeout(int msec, int *out) {
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = msec * 1000;
fd_set read_file_descriptors;
FD_ZERO(&read_file_descriptors);
FD_SET(0, &read_file_descriptors);
int retval = select(
1,
&read_file_descriptors,
NULL,
NULL,
&tv);
if (retval > 0) {
*out = getchar();
}
return retval;
}
int main(int argc, char const *argv[])
{
srand(time(NULL));
system("stty cbreak"); // make getchar() not wait for newline
system("stty -echo"); // no echo
printf("\033[?25l"); // hide terminal's blinking cursor
printf("\033[?1000h");
atexit(cleanUp);
signal(SIGINT, handleSigInt);
int button = 0, prevButton = 0;
int col = 0, row = 0;
resetGameState();
//printf("\033[M");
while (1) {
if (gameStatus == STATUS_GOING)
endTime = time(0);
render();
char c = 0;
int retval = getchar_timeout(100, &c);
if (retval < 0) {
perror("select()");
exit(-1);
}
if (retval == 0)
continue;
if (c == 'q')
break;
if (c == 'n') {
resetGameState();
continue;
}
if (c == '\33' && getchar() == '[') {
c = getchar();
if (c == 'A')
slideCursor(0, -1); // up
else if (c == 'B')
slideCursor(0, 1); // down
else if (c == 'C')
slideCursor(1, 0); // right
else if (c == 'D')
slideCursor(-1, 0); // left
else if (c == 'M') {
// The mouse button logic is a bit weird. When a mouse button
// is clicked and released, two events are sent, one press and
// one release event. But only the press event contains the
// information of which button is pressed. We only want to
// process the click when it is released so we have to store
// the previous button value.
prevButton = button;
button = getchar();
col = getchar() - 32;
row = getchar() - 32;
if ((button & 3) == 3)
handleMouseClick(prevButton, row, col);
}
}
else if (c == ' ')
revealCurrentTile();
else if (c == 'f')
flagCurrentTile();
// check for the wining conditions
// - if all the mines are flagged, or
// - if all non-mined tiles have been revealed
if ((flaggedMines == minesCount && minesCount == flagsCount)
|| (nonMinesRevealed == boundX * boundY - minesCount)) {
gameStatus = STATUS_WON;
revealAll();
}
}
render();
return 0;
}
void cleanUp() {
system("stty cooked");
system("stty echo");
printf("\33[?25h");
printf("\033[?1000l");
clearScreen();
}
void handleSigInt(int sig) {
cleanUp();
exit(0);
}
// -----------------
void moveCursor(int row, int col) {
printf("\033[%d;%dH", row, col);
}
void moveCursorToColumn(int col) {
printf("\033[%dG", col);
}
void clearScreen()
{
printf("\033[2J");
moveCursor(1, 1);
}
// -----------------
void gameCoordToScreenCoord(int x, int y, int *row, int *col) {
*row = (y + 1) * 2 + boardStartRow - 1;
*col = 3 + x * 4 + boardStartCol - 1;
}
int isValidPos(int x, int y) {
return x >= 0 && x < boundX && y >= 0 && y < boundY;
}
void generateBoard(int bombsCount, int avoidX, int avoidY)
{
for (int i = 0; i < boundX * boundY; i++) {
board[i] = -2;
}
// randomly place the bombs around the board
int bombsGenerated = 0;
while (bombsGenerated < bombsCount) {
int x = rand() % boundX;
int y = rand() % boundY;
int notCorner = !(x == 1 && y == 1)
&& !(x == boundX - 1 && y == 0)
&& !(x == 0 && y == boundY - 1)
&& !(x == boundX - 1 && y == boundY - 1);
if (x != avoidX && y != avoidY
&& board[mi(x, y, boundX)] == -2 && notCorner) {
board[mi(x, y, boundX)] = -1;
bombsGenerated++;
}
}
// go over each piece, count the mined neighbors
for (int x = 0; x < boundX; ++x)
for (int y = 0; y < boundY; ++y)
if (board[mi(x, y, boundX)] != -1) {
board[mi(x, y, boundX)] = 0;
for (int i = -1; i < 2; i++)
for (int j = -1; j < 2; j++)
if (isValidPos(x + i, y + j) && board[mi(x + i, y + j, boundX)] == -1)
board[mi(x, y, boundX)]++;
}
}
void drawMenu()
{
moveCursor(24, 0);
printf("\033[30;46;1m");
for (int i = 0; i < 80; ++i)
{
printf(" ");
}
printf("\033[0m");
moveCursor(24, 0);
printfattr("30;46;6", " NEW GAME (n) ");
printf("\033[C");
printfattr("30;46;6", " QUIT (q) ");
}
void drawBackground()
{
printf("\033[30;47;6m");
for (int i = 0; i < 80 * 23; i++) {
printf(" ");
}
}
void drawBoard()
{
moveCursor(boardStartRow, boardStartCol);
printf("\033[37m");
for (int c = 0; c < boundX; ++c)
{
printf(" ---");
}
printf("\n");
moveCursorToColumn(boardStartCol);
for (int r = 0; r < boundY; ++r)
{
for (int c = 0; c < boundX + 1; ++c)
{
printf("| ");
}
printf("\n");
moveCursorToColumn(boardStartCol);
for (int c = 0; c < boundX; ++c)
{
printf(" ---");
}
printf("\n");
moveCursorToColumn(boardStartCol);
}
printf("\n");
}
void drawTiles() {
moveCursor(boardStartRow, boardStartCol);
for (int y = 0; y < boundY; y++)
for (int x = 0; x < boundX; x++) {
int row, col;
gameCoordToScreenCoord(x, y, &row, &col);
moveCursor(row, col);
int value = board[mi(x, y, boundX)];
int m = mask[mi(x, y, boundX)];
if (m == MASK_HIDE) {
printfattr("30;47;6", "░");
} else if (m == MASK_REVEAL) {
if (value == 1)
// blue
printfattr("34;47;6;1", "%d", value);
else if (value == 2)
// green
printfattr("32;47;6", "%d", value);
else if (value == 3)
// red
printfattr("31;47;6", "%d", value);
else if (value == 4)
// purple
printfattr("34;47;6", "%d", value);
else if (value == 5)
// brown
printfattr("31;47;6", "%d", value);
else if (value == 6)
// cyan
printfattr("36;47;6", "%d", value);
else if (value == 7)
// black
printfattr("30;47;6", "%d", value);
else if (value == 8)
// grey
printfattr("37;47;6", "%d", value);
else if (value == -1)
printfattr("31;47;6;1", "*");
else if (value == 0)
printfattr("30;47;6;1", " ");
else {}
} else if (m == MASK_FLAG) {
printfattr("30;43;6;1", "?");
}
}
}
void drawCursor(int x, int y) {
int row, col;
gameCoordToScreenCoord(x, y, &row, &col);
moveCursor(row - 1, col);
printfattr("47;30", "-");
moveCursor(row + 1, col);
printfattr("47;30", "-");
moveCursor(row, col - 2);
printfattr("47;30", "|");
moveCursor(row, col + 2);
printfattr("47;30", "|");
}
void drawStatus()
{
moveCursor(2, 2);
printf("\033[30;47;6m");
printf("Use arrow keys to\n");
moveCursorToColumn(2);
printf("move pointer.\n\n");
moveCursorToColumn(2);
printf("SPACE/mouse left\n");
moveCursorToColumn(2);
printf("to reveal.\n\n");
moveCursorToColumn(2);
printf("F/mouse right to\n");
moveCursorToColumn(2);
printf("mark.\n\n");
printf("\033[0m");
time_t elapsed = endTime - startTime;
printfattr("30;47;6", " Time: %d secs\n", elapsed);
// printf("%d %d %d %d", cursorX, cursorY, board[mi(cursorX, cursorY, boundX)], mask[mi(cursorX, cursorY, boundX)]);
moveCursor(15, 2);
if (gameStatus == STATUS_WON) {
printfattr("30;47", "YOU SUCCESSFULLY\n");
moveCursorToColumn(2);
printfattr("30;47", "DEFUSED THE\n");
moveCursorToColumn(2);
printfattr("30;47", "MINE FIELD!\n");
} else if (gameStatus == STATUS_LOST) {
printf("\033[30;47;6m");
printf("YOU BLEW UP INTO\n");
moveCursorToColumn(2);
printf("SPECTACULAR BITS\n");
moveCursorToColumn(2);
printf("AND PIECES! YOUR\n");
moveCursorToColumn(2);
printf("COUNTRY IS\n");
moveCursorToColumn(2);
printf("GRATEFUL FOR\n");
moveCursorToColumn(2);
printf("YOUR SERVICE.\n");
moveCursorToColumn(2);
printf("\033[0m");
}
}
void slideCursor(int deltaX, int deltaY)
{
if (cursorX + deltaX >= 0 && cursorX + deltaX < boundX)
cursorX += deltaX;
if (cursorY + deltaY >= 0 && cursorY + deltaY < boundY)
cursorY += deltaY;
}
void revealAll()
{
for (int i = 0; i < boundX * boundY; i++)
mask[i] = MASK_REVEAL;
}
void gameOver() {
revealAll();
gameStatus = STATUS_LOST;
}
void revealTile(int x, int y)
{
if (isFirstMove) {
generateBoard(minesCount, x, y);
isFirstMove = 0;
}
mask[mi(x, y, boundX)] = MASK_REVEAL;
if (board[mi(x, y, boundX)] == -1) {
gameOver();
} else {
nonMinesRevealed++;
// Simple recursive flood-filling algorithm to reveal
// all adjacent empty tiles.
if (board[mi(x, y, boundX)] == 0) {
// loop through all neighbors
for (int i = -1; i < 2; i++)
for (int j = -1; j < 2; j++)
if (isValidPos(x + i, y + j)) {
if (mask[mi(x + i, y + j, boundX)] == MASK_HIDE)
revealTile(x + i, y + j);
}
}
}
}
void revealCurrentTile()
{
revealTile(cursorX, cursorY);
}
void flagTile(int x, int y)
{
if (mask[mi(x, y, boundX)] == MASK_HIDE) {
mask[mi(x, y, boundX)] = MASK_FLAG;
flagsCount++;
if (board[mi(x, y, boundX)] == -1) {
flaggedMines++;
}
} else if (mask[mi(x, y, boundX)] == MASK_FLAG) {
mask[mi(x, y, boundX)] = MASK_HIDE;
flagsCount--;
if (board[mi(x, y, boundX)] == -1) {
flaggedMines--;
}
}
}
void flagCurrentTile() {
flagTile(cursorX, cursorY);
}
void render() {
clearScreen();
drawBackground();
moveCursor(1, 1);
drawBoard();
drawTiles();
drawCursor(cursorX, cursorY);
drawMenu();
drawStatus();
}
void boardHandleMouseClick(int button, int row, int col)
{
int x = (col - boardStartCol - 1) / 4;
int y = (row - boardStartRow - 1) / 2;
if (isValidPos(x, y)) {
if ((button & 3) == 0) {
revealTile(x, y);
} else if ((button & 3) == 2) {
// right click
flagTile(x, y);
}
}
}
void newGameButtonHandleMouseClick(int button, int row, int col)
{
if (row == 24 && col < 15)
resetGameState();
}
void quitButtonHandleMouseClick(int button, int row, int col)
{
if (row == 24 && col > 15 && col < 26)
exit(0);
}
void handleMouseClick(int button, int row, int col)
{
boardHandleMouseClick(button, row, col);
newGameButtonHandleMouseClick(button, row, col);
quitButtonHandleMouseClick(button, row, col);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment