/* * simple ncurses minesweeper (Daniel Jones daniel@danieljon.es) * * this program is free software: you can redistribute it and/or modify * it under the terms of the gnu general public license as published by * the free software foundation, either version 3 of the license, or * (at your option) any later version. * * this program is distributed in the hope that it will be useful, * but without any warranty; without even the implied warranty of * merchantability or fitness for a particular purpose. see the * gnu general public license for more details. * * you should have received a copy of the gnu general public license * along with this program. if not, see . */ #include #include #include #define WIDTH 15 #define HEIGHT 15 #define MINECOUNT 35 #define TILEGAP 2 #define DOWN 0 #define UP 1 #define LEFT 2 #define RIGHT 3 enum STATE { HIDDEN = 1 << 0, MINE = 1 << 1, FLAGGED = 1 << 2, }; struct game { int width; int height; int minecount; } game; struct tile { enum STATE state; int x, y; int neighbormines; } *board = NULL; struct cursor { int x; int y; } cursor = {0}; WINDOW *window; int exitgame = 0; void draw(); int canmove(int dir); void generateboard(); void drawboard(); int reveal(int x, int y); void revealmines(); struct tile *getneighbors(struct tile *tile, struct tile **neighbors); struct tile *gettileat(int x, int y); int checkwin(); struct tile *getneighbors(struct tile *tile, struct tile **neighbors) { int badup = 0, baddown = 0, badleft = 0, badright = 0; if (tile->x-1<0) badleft = 1; if (tile->x+1>game.width-1) badright = 1; if (tile->y-1<0) badup = 1; if (tile->y+1>game.height-1) baddown = 1; if (!badleft && !badup) neighbors[0] = gettileat(tile->x-1, tile->y-1); if (!badup) neighbors[1] = gettileat(tile->x, tile->y-1); if (!badright && !badup) neighbors[2] = gettileat(tile->x+1, tile->y-1); if (!badleft) neighbors[3] = gettileat(tile->x-1, tile->y); if (!badright) neighbors[4] = gettileat(tile->x+1, tile->y); if (!badleft && !baddown) neighbors[5] = gettileat(tile->x-1, tile->y+1); if (!baddown) neighbors[6] = gettileat(tile->x, tile->y+1); if (!badright && !baddown) neighbors[7] = gettileat(tile->x+1, tile->y+1); return *neighbors; } int checkwin() { int allowedmines = game.minecount; int safetiles = (game.height * game.width) - game.minecount; int correctflags = 0; int correcttiles = 0; for (int y = 0; y < game.height; y++) { for (int x = 0; x < game.width; x++) { struct tile *tile = gettileat(x, y); if (tile->state & MINE && tile->state & FLAGGED) correctflags++; else if (!(tile->state & MINE) && !(tile->state & HIDDEN)) correcttiles++; } } return (correctflags == allowedmines) || (correcttiles == safetiles); } void generateboard() { srand(time(NULL)); /* place mines */ board = malloc(sizeof (struct tile) * (game.width * game.height)); for (int x = 0; x < game.minecount; x++) { int mx, my; mx = rand() % game.width; my = rand() % game.height; struct tile *tile = gettileat(mx ,my); tile->state |= MINE; } /* figure out neighbors */ for (int y = 0; y < game.height; y++) { for (int x = 0; x < game.width; x++) { struct tile *tile = gettileat(x, y); tile->x = x; tile->y = y; tile->state |= HIDDEN; struct tile *neighbors[8] = {NULL}; getneighbors(tile, neighbors); tile->neighbormines = 0; for (int i = 0; i < 8; i++) if (neighbors[i] != NULL && neighbors[i]->state & MINE) tile->neighbormines += 1; } } } struct tile * gettileat(int x, int y) { if (x < 0 || x > game.width-1 || y < 0 || y > game.height-1) return NULL; /* the board is single dimensional, so we map it as 2d */ return &board[game.width*x+y]; } int reveal(int x, int y) { struct tile *tile = gettileat(x, y); tile->state &= ~HIDDEN; if (tile->state & MINE) return 1; if (tile->neighbormines == 0) { tile->state &= ~HIDDEN; struct tile *neighbors[8] = {NULL}; getneighbors(tile, neighbors); for (int nc = 0; nc < 8; nc++) { struct tile *neighbor = neighbors[nc]; if (neighbor != NULL && !(neighbor->state & MINE) && neighbor->state & HIDDEN) { neighbor->state &= ~HIDDEN; if (neighbor->neighbormines == 0) { reveal(neighbor->x, neighbor->y); } } } } return 0; } void revealmines() { for (int y = 0; y < game.height; y++) { for (int x = 0; x < game.width; x++) { if (gettileat(x, y)->state & MINE) reveal(x, y); } } } int canmove(int dir) { /* check if cursor inside game region */ if (dir == LEFT && cursor.x <= 0) return 0; else if (dir == RIGHT && cursor.x >= game.width-1) return 0; else if (dir == UP && cursor.y <= 0) return 0; else if (dir == DOWN && cursor.y >= game.height-1) return 0; return 1; } void draw() { wclear(window); clear(); box(window, 0, 0); if (!exitgame) { mvprintw(game.height+3, 0, "The aim of the game is to reveal all non-mine tiles or flag every mine tile"); mvprintw(game.height+5, 0, "hjkl/wasd to move cursor\nspace to reveal tile\nf to flag tile"); } for (int x = 0; x < game.width; x++) { for (int y = 0; y < game.height; y++) { struct tile *tile = gettileat(x, y); char neighbormines = (char)tile->neighbormines+'0'; if (neighbormines == '0') neighbormines = ' '; if (tile->state & FLAGGED) mvwprintw(window, y+1, (x*TILEGAP)+1, "F"); else if (tile->state & HIDDEN) mvwprintw(window, y+1, (x*TILEGAP)+1, "."); else mvwprintw(window, y+1, (x*TILEGAP)+1, "%c", (tile->state & MINE) ? 'M' : neighbormines); } } wmove(window, cursor.y+1, (cursor.x*TILEGAP)+1); refresh(); wrefresh(window); } int main(void) { initscr(); noecho(); game.width = WIDTH; game.height = HEIGHT; game.minecount = MINECOUNT; window = newwin(game.height+TILEGAP, (game.width*TILEGAP)+1, 1, 8); generateboard(); int ch; while(!exitgame) { draw(); ch = getch(); /* blocking */ struct tile *tile = gettileat(cursor.x, cursor.y); switch(ch) { case 'k': case 'w': if (canmove(UP)) cursor.y--; break; case 'j': case 's': if (canmove(DOWN)) cursor.y++; break; case 'h': case 'a': if (canmove(LEFT)) cursor.x--; break; case 'l': case 'd': if (canmove(RIGHT)) cursor.x++; break; case 'f': { if (tile && tile->state & HIDDEN) { tile->state ^= FLAGGED; draw(); } break; } case ' ': if (tile && !(tile->state & FLAGGED)) exitgame = reveal(cursor.x, cursor.y); break; case 'q': exitgame = 1; break; default: break; } if (checkwin()) { revealmines(); draw(); mvprintw(game.height+3, 0, "you won"); break; } else if (exitgame) { revealmines(); draw(); mvprintw(game.height+3, 0, "you lost"); break; } } mvprintw(game.height+4, 0, "press any key to exit.."); getch(); endwin(); free(board); return 0; }