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

时间:2022-11-07 13:25:37

本文为大家分享了C++实现扫雷小游戏的具体代码,供大家参考,具体内容如下

程序功能:

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

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

提供扫雷地图的类

map.h

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#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类的实现

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
#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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#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;
}

游戏截图

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/qq_35215641/article/details/80704045