First, I have to say that this game lacks 5 features that I don't care:
- Flags
- Chording
- Timer
- Actual mouse movement
- Good graphics
When starting the program, the player is meeted with a "Choose difficulty" menu. They will move around by pressing ASWD, choose an option by pressing enter/space, and quit at anytime by pressing ESC. After that, the board is shown, and they play the game until winning, losing or quitting. When won/lost, they're asked to either play again with this difficulty, choose a new one at the start menu, or quit.
These are what I made to implement them
#include<iostream>
#include<string>
#include<conio.h> // controls and cls
#include<algorithm> // shuffle
#include <random> // rands for shuffle
#include <chrono> // seeds for rand
#define cls system("cls")
typedef unsigned short int us;
using namespace std;
bool cellnum[80][40]; // checks if the cell has bomb
string board,shownboard;
// both store info to cout the mine field, board is for modifying the cells, shownboard copies board and add cursor
us z,numcell; // z is num of bombs on the field, numcell is num of unrevealed non-bomb cells on the field
char p2; // player input
struct coord{us x,y;} // coordinates
xy; // this is the board/field size
void quit(){ // endscreen
cls;
cout<<(char)-55<<string(60,-51)<<(char)-69<<endl
<<(char)-70<<string(19,' ')<<"Thank you for playing!"<<string(19,' ')<<(char)-70<<endl
<<(char)-56<<string(60,-51)<<(char)-68<<endl;
exit(0);
}
class cursor{ // handles true coord of cursor, cursor shown on screen (dot/arrow) is based on this
public:
coord board,now;
// now: coordinate of cursor, board: limits of cursor's coordinates
// now<board in both x and y
void update(){ // when player tries to move around
switch(p2){
case 'a':case 'A':if(now.x)now.x--;return;
case 'w':case 'W':if(now.y)now.y--;return;
case 'd':case 'D':if(now.x<board.x-1)now.x++;return;
case 's':case 'S':if(now.y<board.y-1)now.y++;return;
case 27:{quit();} // for the "Press ESC at anytime to quit."
}
}
us code(){return now.x+now.y*board.x;} // some functions use 1 var, this is for those functions
}g_sor, // Game cursor
p_sor={{2,2},{0,0}}, // Choose-your-board cursor
s_sor={{5,1},{0,0}}; // Start menu cursor
us &cx=g_sor.now.x,&cy=g_sor.now.y; // g_sor.now.x is too long, so I added references with shorter names
bool enter(){ // for the "choose by pressing SPACE/ENTER"
p2=getch();
return (p2==' '||p2=='\r');
}
// because board/shownboard is 1D array, there should be a function translating 2D coordinates into those 2.
void modi(us &x,us &y,char c,bool m=1){ // bool m is to know which to be modified
if(m)shownboard[(x+y*xy.x)*2]=c;
else board[(x+y*xy.x)*2]=c;
}
// function for revealing a non-bomb cell and (possibly) its neighbors
void show(coord c){
if(board[(c.x+c.y*xy.x)*2]!=-2)return; // check if it's revealed
char i='0';
numcell--;
// check if cell is at any of the left-right-up-down border
bool le= c.x,
ri=(c.x)!=xy.x-1,
up= c.y,
dn=(c.y)!=xy.y-1;
// check if cell's neighbors are bombs
if(le){
if(up)i+=(cellnum[c.x-1][c.y-1]==1);
if(dn)i+=(cellnum[c.x-1][c.y+1]==1);
i+=(cellnum[c.x-1][c.y]==1);
}
if(ri){
if(up)i+=(cellnum[c.x+1][c.y-1]==1);
if(dn)i+=(cellnum[c.x+1][c.y+1]==1);
i+=(cellnum[c.x+1][c.y]==1);
}
if(up)i+=(cellnum[c.x][c.y-1]==1);
if(dn)i+=(cellnum[c.x][c.y+1]==1);
if(i!='0'){modi(c.x,c.y,i,0);return;}
// cell has 0 neighboring bombs => reveals its neighbors
modi(c.x,c.y,' ',0);
if(le){
show({us(c.x-1),c.y});
if(up)show({us(c.x-1),us(c.y-1)});
if(dn)show({us(c.x-1),us(c.y+1)});
}
if(ri){
show({us(c.x+1),c.y});
if(up)show({us(c.x+1),us(c.y-1)});
if(dn)show({us(c.x+1),us(c.y+1)});
}
if(up)show({c.x,us(c.y-1)});
if(dn)show({c.x,us(c.y+1)});
}
int main(){
cls;
cout<<"Move around by using ASWD. Choose by pressing ENTER/SPACE. Quit by pressing ESC."<<endl
<<"Beginner (10x10, 8 bombs) Intermediate (13x15, 40 bombs)\n"
<<((!p_sor.now.y)?(((p_sor.now.x)?string(44,' '):" ")+(char)24+'\n'):"\n") // check if the arrow
<<"Expert (16x30, 100 bombs) Custom (Whatever you want man)\n" // should be on above
<<(( p_sor.now.y)?(((p_sor.now.x)?string(44,' '):" ")+(char)24+'\n'):"\n"); // or below
// player moves around
if(!enter()){
p_sor.update();main();
return 0;
}
// player picks a board
switch(p_sor.code()){
case 0:xy={10,10};z=8 ;break;
case 1:xy={15,13};z=40 ;break;
case 2:xy={30,16};z=100;break;
default:
while(1){ // while loop ends when player's input is valid
cout<<"Enter the width and height of the board, and how many bombs you want: ";
cin>>xy.x>>xy.y>>z;
if(cin.fail()){
cin.clear();cin.ignore();
cin.clear();cin.ignore();
cin.clear();cin.ignore();
cout<<"Invalid. Those are not numbers.\n\n";
continue;
}
if(!(xy.x&&xy.y)){
cout<<"Invalid. Width and height must be atleast 1.\n\n";
continue;
}
if(z>xy.x*xy.y){
cout<<"Invalid. Too many bombs, the board explodes.\n\n";
continue;
}
if(max(xy.x,xy.y)>80||min(xy.x,xy.y)>40){ // Board with width 82 or height 42 has bad alignment
cout<<"Invalid. The width and height are capped at 80 and 40.\n\n";
continue;
}
// Input is valid, but to make a nice board (imo) x shouldn't be less than y
if(xy.x<xy.y)swap(xy.x,xy.y);
break;
}
}
retry: // for retrying at this difficulty
cout<<"\nYou are going to mienswipe a "<<xy.x<<'x'<<xy.y<<" board with "
<<z<<" bomb"<<((z==1)?".":"s.")
<<"\nPress anything to continue...\n";
if(getch()==27)quit();
// setting up the board and bombs
numcell=xy.x*xy.y;
g_sor.board=xy;
g_sor.now={(us)(xy.x/2),(us)(xy.y/2)};
// picking random coordinates for z+1 bombs. last bomb is for when player unluckily reveals a bomb 1st try
us bomb[numcell];for(us i=0;i<numcell;i++)bomb[i]=i;
unsigned seed = chrono::system_clock::now().time_since_epoch().count();
shuffle (&bomb[0],&bomb[numcell], default_random_engine(seed));
for(us i=0;i<z;i++)cellnum[bomb[i]%xy.x][bomb[i]/xy.x]=1;
for(us i=0;i<xy.y;i++){
for(us j=0;j<xy.x-1;j++){board+=(char)254;board+=' ';}
board+=(char)254;board+='\n';
}
numcell-=z;
// setup completes
// loops until all non-bomb cells are revealed, or a bomb is revealed
while(numcell){
shownboard=board;modi(cx,cy,-7); // shownboard copies board, add a dot as the screen's cursor
cls;cout<<shownboard;
if(enter()){
if(cellnum[cx][cy]==1){
if(xy.x*xy.y-z==numcell){ // if player unluckily picks a bomb 1st try
cellnum[cx][cy]=0;
cellnum[bomb[z]%xy.x][bomb[z]/xy.x]=1;
show(g_sor.now);
continue;
}
for(us i=0;i<z;i++)
board[(bomb[i]%xy.x+bomb[i]/xy.x*xy.x)*2]='#';
modi(cx,cy,'x',0);
break;
}
show(g_sor.now);
}
else g_sor.update();
}
//game ends. cout game over message and reset some global variables
cls;
cout<<board
<<((numcell)?"\nYou got swiped and lose! :(":"\nCongratulation, you won!")
<<"\nType \"MENU\" to go back to menu,\n"
<<" \"RETRY\" to try this difficulty again,\n"
<<" or \"QUIT\" to quit.\n";
board={};
for(us i=0;i<=z;i++)cellnum[bomb[i]%xy.x][bomb[i]/xy.x]=0;
while(1){
cin>>shownboard;
if(shownboard=="MENU" ||shownboard=="Menu" ||shownboard=="menu" ){main();exit(0);}
if(shownboard=="RETRY"||shownboard=="Retry"||shownboard=="retry"){goto retry;exit(0);}
if(shownboard=="QUIT" ||shownboard=="Quit" ||shownboard=="quit") quit();
}
}
ANSI.SYS
. This would let you use and write modern software. \$\endgroup\$ncurses
but there is probably something newer out there. \$\endgroup\$