Advertisement

C++扫雷小游戏(控制台版)

阅读量:

程序功能:

提供三种模式:初级、中级、高级

操作模式:wsad控制光标移动,空格键打开方块

提供扫雷地图的类

map.h

复制代码
 #ifndef MAP_H_

    
 #define MAP_H_
    
  
    
 #define MAX_LENGTH 32      //可以提供的地图最大长度
    
 #define MAX_WIDTH 18       //可以提供的地图最大宽度
    
 #define UP_EDGE 1          //上边界
    
 #define DOWN_EDGE _wid     //下边界
    
 #define LEFT_EDGE 1        //左边界
    
 #define RIGHT_EDGE _lng    //右边界
    
  
    
 void gotoxy(int, int);     //移动光标的接口函数
    
  
    
 struct Position{ 
    
     int x;
    
     int y;
    
 };
    
  
    
 struct Info{
    
     int n;           //用于标记雷、数字、空格的属性
    
     bool flag;       //用于标记是否要打开方块
    
 };
    
  
    
 class Map{
    
 private:
    
     int _lng, _wid;                   //长和宽
    
     int _mines, _blanks;              //雷数、未开启空格数目
    
     Position _pos = {1, 1};           //光标位置
    
     Info data[MAX_WIDTH][MAX_LENGTH]; //包含地图信息的矩阵
    
 public:
    
     void AcceptCond();           //选择模式
    
     void InitMap();              //初始化地图
    
     void SetMine();              //布置地雷
    
     void SetNumber();            //计算数字
    
     void SetPosition();          //移动光标至指示区域
    
     void ResetPosition();        //重置初始坐标
    
     void ShowMap();              //显示地图
    
     void ShowAll();              //显示全部地图,游戏失败时候调用
    
     void OpenBlock();            //打开方块,即将 flag 值设置为 true,在 ShowMap() 中将打开方块
    
     void FirstStep();            //预先处理游戏,防止第一步就触雷导致失败,这是无意义的
    
     bool PlayGame();             //提供的游戏操作接口
    
     bool Move(char);             //移动光标,同时改变 _pos 的值用于指代目前要访问(打开)的方块
    
     bool IfLose();               //游戏失败,则返回真
    
     bool IfWin();                //游戏成功,则返回真
    
 };
    
  
    
 #endif

实现思路:

1.接收游戏模式参数,确定地图规模

2.初始化地图,值全部设置为 0,flag 全部设置为 false,表示未曾打开

3.基于用户的操作行为,请识别出初始空白位置,并在此基础上进行后续的操作设置(即避免触发负面结果),这样的做法并无实际价值。

4.布雷采用生成随机数的方法

5.根据地雷分布计算其他空格所对应的数字

6.通过PlayGame() 接口进行游戏操作

Map类的实现

复制代码
 #include <cstdlib>

    
 #include <cstdio>
    
 #include <ctime>   //提供时间函数
    
 #include <conio.h>  //提供getch()
    
 #include <windows.h>
    
 #include <iostream>
    
 #include "map.h"
    
  
    
 #define GOTO(pos) gotoxy(2 * (pos.x) - 1, (pos.y) - 1)  //定义用于移动光标的 宏
    
 //由于一个方块占 2 个格子,所以 pos.x 每加 1,则光标要移动 2 格
    
  
    
 using std::cout;
    
 using std::cin;
    
 using std::endl;
    
  
    
 void gotoxy(int x, int y) {      //移动光标的接口
    
     COORD pos = { short(x), short(y) };
    
     HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    
     SetConsoleCursorPosition(hOut, pos);
    
 }
    
  
    
  
    
 void Map::AcceptCond() {              //接收游戏模式参数
    
     cout << "Choose Mode" << endl;
    
     cout << "1 : Beginner" << endl;
    
     cout << "2 : Intermediate" << endl;
    
     cout << "3 : Expert" << endl << "Mode: ";
    
     char mode;
    
     cin >> mode;
    
     while (('1' != mode) && ('2' != mode) && ('3' != mode)) {     //仅仅接受 1, 2, 3,其他字符跳过
    
     cout << "Wrong Mode, Enter number again\n Mode: ";
    
     cin >> mode;
    
     }
    
     switch (mode) {
    
     case '1' : _lng = 8; _wid = 8; _mines = 10; break;
    
     case '2' : _lng = 16; _wid = 16; _mines = 40; break;
    
     default: _lng = 30; _wid = 16; _mines = 99;
    
     }
    
     _blanks = _lng * _wid - _mines;        //计算空格数,用于判断是否赢,_blanks = 0 时判定赢
    
 }
    
  
    
  
    
 void Map::InitMap() {     //初始化地图,显示的地图下标从 1 - wid, 1 - _lng, 边界外面还有空格,用于计算空格对应数字的,边界相当于0
    
     for (int i = 0; i < _wid + 2; i++) {
    
     for (int j = 0; j < _lng + 2; j++) {
    
         data[i][j].n = 0;
    
         data[i][j].flag = false;
    
     }
    
     }
    
 }
    
  
    
  
    
 void Map::SetMine() {
    
     int i, j;
    
     int m = _mines;
    
     srand(time(NULL));
    
     while (m)
    
     {
    
     i = rand() % _wid + 1;
    
     j = rand() % _lng + 1;
    
     if ((-1 != data[i][j].n) && (j != _pos.x && i != _pos.y)) {  //后面的条件用于避免用户第一个打开的空格处布置地雷
    
         data[i][j].n = -1;
    
         m--;
    
     }
    
     }
    
 }
    
  
    
  
    
 void Map::SetNumber() {
    
     for (int i = 1; i <= _wid; i++) {
    
     for (int j = 1; j <= _lng; j++) {   //依次检查周围的 8 个空格的雷数
    
         if (-1 == data[i][j].n) continue;
    
         if (-1 == data[i-1][j-1].n) data[i][j].n++;
    
         if (-1 == data[i][j-1].n)   data[i][j].n++;
    
         if (-1 == data[i+1][j-1].n) data[i][j].n++;
    
         if (-1 == data[i-1][j].n)   data[i][j].n++;
    
         if (-1 == data[i+1][j].n)   data[i][j].n++;
    
         if (-1 == data[i-1][j+1].n) data[i][j].n++;
    
         if (-1 == data[i][j+1].n)   data[i][j].n++;
    
         if (-1 == data[i+1][j+1].n) data[i][j].n++;
    
     }
    
     }
    
 }
    
  
    
  
    
 void Map::SetPosition() {
    
     GOTO(_pos);
    
 }
    
  
    
  
    
 void Map::ResetPosition() {
    
     _pos.x = _pos.y = 1;
    
 }
    
  
    
  
    
 void Map::ShowMap() {
    
     system("cls");            //清屏
    
     system("color 03");       //调整控制台显示颜色
    
     SetConsoleOutputCP(437);  //使方块能够正常显示
    
     for (int i = 1; i <= _wid; i++) {
    
     cout << '|';     //左边界
    
     for (int j = 1; j <= _lng; j++) {
    
         if (data[i][j].flag) {
    
             switch (data[i][j].n) {
    
                 case 0 : cout << "  "; break;    //由于方块占两个格子,因此其他的输出,如空格、数字等也要占2个格子,对齐
    
                 default: cout << data[i][j].n << ' ';
    
             }
    
         }
    
         else printf("%c", 219);
    
     }
    
     cout << '|' << endl;  //右边界
    
     }
    
     gotoxy(0, _wid+2);  //在地图下方输出坐标信息和空格数
    
     printf("Position : (%d, %d)\n Blanks : %d", _pos.x, _pos.y, _blanks);
    
     GOTO(_pos);  //归位到原先地图坐标对应的位置
    
 }
    
  
    
  
    
 void Map::ShowAll() {    //类似上面的ShowMap(),但在游戏失败时调用
    
     system("cls");
    
     system("color 03");
    
     SetConsoleOutputCP(437);
    
     for (int i = 1; i <= _wid; i++) {
    
     cout << '|';
    
     for (int j = 1; j <= _lng; j++) {
    
         switch (data[i][j].n) {
    
             case 0 : printf("%c", 219); break;
    
             case -1: 
    
                 if (i ==  _pos.y && j == _pos.x)
    
                     cout << "X ";
    
                 else
    
                     cout << "* ";
    
                 break;
    
             default: cout << data[i][j].n << ' ';
    
         }
    
     }
    
     cout << '|' << endl;
    
     }
    
 }
    
  
    
 #define SOLVE_IT(t) {stack[++top] = (t); data[(t).y][(t).x].flag = true; _blanks--;}
    
 #define FALSE_FLAG(t) !data[(t).y][(t).x].flag
    
  
    
 void Map::OpenBlock() {                     //用栈来将连着的空格区域遍历一遍,并将其 flag 值置为 true
    
     if (data[_pos.y][_pos.x].flag) return;  //如果已经打开过就不需要再次打开,否则 _blanks--; 会多次执行,无法判断赢
    
     Position stack[_lng * _wid << 1];
    
     Position t;
    
     int top = 0;
    
     
    
     stack[top] = _pos;
    
     data[_pos.y][_pos.x].flag = true;
    
     _blanks--;
    
     while (top != -1) {
    
     t = stack[top--];
    
     if (0 == data[t.y][t.x].n) {  //如果该位置为 0 ,那么它周围的格子都要打开
    
         t.y--;  //判断上方三个格子
    
         if (t.y >= UP_EDGE) {  //如果上方三个格子 y 不越界
    
             if (FALSE_FLAG(t)) SOLVE_IT(t)
    
             t.x--;
    
             if (t.x >= LEFT_EDGE && FALSE_FLAG(t)) SOLVE_IT(t)
    
             t.x += 2;
    
             if (t.x <= RIGHT_EDGE && FALSE_FLAG(t)) SOLVE_IT(t)
    
             t.x--;
    
         }
    
             
    
         t.y++; t.x--;//判断左右两个格子, 此时 t.y 复原
    
         if (t.x >= LEFT_EDGE && FALSE_FLAG(t)) SOLVE_IT(t)
    
         t.x += 2;
    
         if (t.x <= RIGHT_EDGE && FALSE_FLAG(t)) SOLVE_IT(t)
    
  
    
         t.y++; //下方三个格子, 此时 t.x 是最右边的格子
    
         if (t.y <= DOWN_EDGE) {  //如果下方三个格子 y 不越界, 与上面判断基本相同
    
             if (t.x <= RIGHT_EDGE && FALSE_FLAG(t)) SOLVE_IT(t)
    
             t.x--;
    
             if (FALSE_FLAG(t)) SOLVE_IT(t)
    
             t.x--;
    
             if (t.x >= LEFT_EDGE && FALSE_FLAG(t)) SOLVE_IT(t)
    
         }
    
     }
    
     }
    
 }
    
  
    
  
    
 void Map::FirstStep() {   //函数结束后将改变 _pos,就是我们用的预先处理函数,防止第一步就触雷的
    
     char op;
    
     do {
    
     op = getch();
    
     while ((op != 'a') && (op != 's') && (op != 'd') && (op != 'w') && (op !=' '))
    
         op = getch();
    
     } while (Move(op));
    
 }
    
  
    
  
    
 bool Map::Move(char op) {
    
     switch (op) {   //通过不同的操作,改变坐标,然后再通过 GOTO宏 移动到该位置上
    
     case ' ':
    
         return false;
    
     case 'w':
    
         if (UP_EDGE != _pos.y) _pos.y--;
    
         break;
    
     case 'a':
    
         if (LEFT_EDGE != _pos.x) _pos.x--;
    
         break;
    
     case 's':
    
         if (DOWN_EDGE != _pos.y) _pos.y++;
    
         break;
    
     default:
    
         if (RIGHT_EDGE != _pos.x) _pos.x++;
    
     }
    
     gotoxy(0, _wid + 2);
    
     printf("Position : (%d, %d)\n Blanks : %d", _pos.x, _pos.y, _blanks);
    
     GOTO(_pos);
    
     return true;
    
 }
    
  
    
  
    
 bool Map::IfLose() {
    
     return -1 == data[_pos.y][_pos.x].n;
    
 }
    
  
    
  
    
 bool Map::IfWin() {
    
     return 0 == _blanks;
    
 }
    
  
    
  
    
 bool Map::PlayGame() {
    
     char op;
    
     float start, end;
    
     while (!IfWin()) {
    
     do {
    
         op = getch();
    
         while ((op != 'a') && (op != 's') && (op != 'd') && (op != 'w') && (op !=' '))
    
             op = getch();
    
     } while (Move(op));
    
  
    
     if (IfLose()) {  //触雷
    
         ShowAll(); gotoxy (0, _wid + 3);
    
         return false;
    
     }
    
     else {
    
         OpenBlock();   //打开方块,实质上时将 flag 的值置为 true,接着 ShowMap()将可以显示该方块信息
    
         ShowMap();
    
         GOTO(_pos);
    
     }
    
     }
    
     gotoxy(0, _wid + 3);
    
     return true;
    
 }

主程序

mineweeper.cpp

复制代码
 #include <iostream>

    
 #include <cstdlib>
    
 #include <conio.h>
    
 #include <ctime>
    
 #include "map.h"
    
  
    
 using namespace std;
    
  
    
 int main() {
    
     Map game;
    
     float start, end;
    
     char ch;
    
     while (1) {
    
     game.AcceptCond();     //选择模式
    
     game.InitMap();        //初始化
    
     game.ShowMap();        //显示地图。  注:此时地图未生成完毕
    
     game.FirstStep();      //预处理,防止第一步就触雷结束
    
     game.SetMine();        //设置地雷
    
     game.SetNumber();      //根据地雷分布计算数字
    
     game.OpenBlock();      //打开开局预先想要打开的第一个空
    
     start = clock();
    
     game.ShowMap();
    
     if (game.PlayGame()) {   //根据PlayGame()接口的返回值判定输赢
    
         cout << endl << "~ Congratulation ~\n ~ You Win ~" << endl;
    
     }
    
     else {
    
         cout << endl << "BOOM!!! ~ Game Over ~\n" << endl;
    
     }
    
     end = clock();
    
     printf("\nTime : %.2f\n", (end - start) / CLK_TCK);   //输出游戏所用时间
    
     cout << endl << "Please enter 'q' to quit, or any other keys to continue" << endl;
    
     game.SetPosition();  //用于触雷失败时,将光标返回到触雷的位置,提示哪一步失败,同时触碰的雷也将显示为 ‘X’
    
     ch = getch();
    
     if ('q' == ch) {    // q 用于退出游戏
    
         system("cls");
    
         cout << "~ Bye ~" << endl;
    
         break;
    
     }
    
     else {
    
         game.ResetPosition();
    
         system("cls");
    
     }
    
     }
    
     system("pause");
    
     return 0;
    
 }

游戏截图

全部评论 (0)

还没有任何评论哟~