Last active
October 13, 2017 20:03
-
-
Save lewtds/8a30c3211946d9395b263b3c6e406487 to your computer and use it in GitHub Desktop.
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 <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