Mine Mania

A fanatic is one who can’t change his mind and won’t change the subject.

Sir Winston Leonard Spencer-Churchill, KG, OM, CH, TD, FRS

minesweeper2Not to let our rodent friends have all the fun mine-sweeping, I’ve created a basic C++ ncurses minesweeper game. This diverges some from the theme of Standard Template Library exploration, although it does make use of the std::max and std::min functions.

Good data-structures and prototypes are the cornerstone of any program. Let’s define ours.

#define SIZE 20
 
typedef struct _position
{
   bool mine;
   bool activated;
} position;
typedef enum _gamestate { init, run, finish } gamestate;
typedef position minefield[SIZE][SIZE];
 
void draw_field(minefield&,int,int,gamestate);
int get_neighbors(minefield&,int,int);

The main loop implements an extremely simple state machine to control the gameplay stages. It draws the field and processes user input, managing the current location of the cursor. std::min and std::max are used to keep the cursor within bounds.

int main()
{
   initscr(); // Initialize the ncurses screen.
   raw(); // Disable screen buffering.
   noecho(); // Do not show user input.
   keypad(stdscr, TRUE); // Enable directional keys.
   srand(time(NULL));
 
   minefield field;
   gamestate state = init;
   int xpos = 0, ypos = 0; // Cursor position.
 
   while(true)
   {
      draw_field(field, xpos, ypos, state);
      move(xpos, ypos*2);
      refresh();
      char c = getch();
      if(init == state)
         state = run;  // We're initialized, advance to next state.
      else if(finish == state)
         break; // We've drawn the finish display, exit.
      switch(c)
      {
      case KEY_UP:
         xpos = std::max(--xpos, 0);
         break;
      case KEY_DOWN:
         xpos = std::min(++xpos, SIZE-1);
         break;
      case KEY_LEFT:
         ypos = std::max(--ypos, 0);
         break;
      case KEY_RIGHT:
         ypos = std::min(++ypos, SIZE-1);
         break;
      case ' ':
         if(field[xpos][ypos].mine == true)
         { // Player hit a mine.
            mvprintw(xpos, ypos*2, "*");
            getch();
            state = finish;
         }
         field[xpos][ypos].activated = true;
         break;
      default:
         state = finish;
      }
   }
   endwin(); // Clean up ncurses window.
   return 0;
}

Drawing the field consists of looping through the map and writing the appropriate symbols to the screen. The ncurses mvprintw function is indispensable for this. I find it helpful to picture a Cartesian plane when looking at this code.

void draw_field(minefield &field, int xpos, int ypos, gamestate state)
{
   for(int x = 0; x < SIZE; x++)
   {
      for(int y = 0; y < SIZE; y++)
      {
         char sym[3] = { '.', ' ', 0 };
         if(init == state)
         {
            field[x][y].mine = (rand()%3 == 0);
            field[x][y].activated = false;
         }
         else if(finish == state)
         {
            sym[0] = field[x][y].mine ? '*' : ' ';
         }
         else if(field[x][y].activated)
            sprintf(&sym[0], "%d", get_neighbors(field, x, y));
         mvprintw(x, y*2, &sym[0]);
      }
   }
   refresh();
}

minesweeper

Notice that the minefield is passed between the functions by reference, indicated by the & prefix in the function prototype (eg, minefield &field). Passing by reference affords us two general advantages: the entire minefield structure is not copied onto the callstack, and we can modify the minefield easily inside functions we have passed it to. In this case, passing by reference also affords us two advantages over pointers: we do not have to use a pointer dereference (* or ->), and we know the minefield will never be NULL or otherwise invalid, so we don’t need to add more logic to handle invalid pointers. Passing by reference, when used appropriately, can result in simpler and more robust programs than those using exclusively pointers.

Use the directional keys to navigate the board, space to test for presence of a mine, and any other key to exit. See the full source for the implementation of the get_neighbors function and some other miscellany.