【小作品】Minesweeper

簡述

高中生涯就快告一段落了,在畢業前一週閒閒沒事,一直在想除了解題還可以幹嘛,所以更早之前做了一個地震報告的靜態網頁,現在又搞了一個踩地雷╰(*°▽°*)╯

主要會練習到:

  1. 圖論的 BFS or DFS
  2. C++ 時間戳轉當地時間
  3. 讀檔、寫檔(iofstream)

解說

好,其實我覺得我的程式碼沒有很乾淨,都是為了分區塊才寫成函式,然後我也不太會寫 class 物件,所以就硬寫。目前比較不人性化的一點就是它是用終端機在玩,所以只能輸入座標來開格子,而且如果要插旗子的話又要叫使用者調模式,變得很難玩,所以我只讓使用者開格子。啊也建議只玩簡單,因為困難的格子有 16*30 格 (?

還有一點不好的地方是,如過第一手就是炸彈,我沒有做重新洗炸彈位置的機制。

控制遊戲難度,在個別遊戲難度上將地圖大小和炸彈數做調整。(其實不是,就是呼叫 playingGame 這個 function 而已)

這段在寫遊戲階段,我是用 BFS 來開格子,遇到周圍有炸彈就停止往下搜尋。

這裡用到寫檔的技巧,將耗時、遊戲時間、難度都寫進去,這樣就可以記錄歷史遊戲。

這裡用到讀檔,還有轉換時間戳的方式。將獨到的數字放進結構是 history 的 vector 容器裡並排序排名,cmp 也是為此排序所寫的。

剩下的變數就隨便啦,就是一些地圖大小、炸彈數和搜尋方向的變數。

程式碼

給大家看又臭又長的程式碼,也可以到我的 GitHub 的 repo 上看。

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
250
251
252
253
254
255
256
257
258
259
#include <bits/stdc++.h>
using namespace std;

struct history {
int t;
int mode;
ll date;
};

struct timeFormater {
int y;
int m;
int d;
int hour;
int min;
int sec;
};

int dir[8][2] = {
{-1, -1},
{-1, 0},
{-1, 1},
{0, -1},
{0, 1},
{1, -1},
{1, 0},
{1, 1}};

int r = 8;
int c = 8;
int cntMine = 10;

bool cmp(history a, history b) {
if (a.mode == b.mode) return a.t < b.t;
return a.mode < b.mode;
}

void clearScreen();
void printMenu();
void difficalty();
void playingGame(int row, int col, int mine, int mode);
void writeFile(int st, int et, int m);
void showHistory();
void printArray(vector<vector<char>> arr);

int main() {
srand(time(NULL));

while (true) {
printMenu();

int option;
cin >> option;
if (option == 1) {
difficalty();
} else if (option == 2) {
showHistory();
} else if (option == 3) {
break;
}
}
}

void clearScreen() {
system("cls");
}

void printMenu() {
clearScreen();
cout << "\n\n\n"
<< " 1. Play " << '\n'
<< " 2. History " << '\n'
<< " 3. Exit " << '\n'
<< "\n\n\n";
}

void difficalty() {
clearScreen();
cout << "\n\n\n"
<< " 1. Eazy " << '\n'
<< " 2. Normal " << '\n'
<< " 3. Hard " << '\n'
<< "\n\n\n";
int option;
cin >> option;

if (option == 1) {
playingGame(8, 8, 10, 1);
} else if (option == 2) {
playingGame(16, 16, 40, 2);
} else if (option == 3) {
playingGame(16, 30, 99, 3);
}
}

void playingGame(int row, int col, int mine, int mode) {
// init
clearScreen();

r = row;
c = col;
cntMine = mine;

vector<vector<int>> grid;
vector<vector<char>> gameMap;
for (int i = 0; i < r; i++) {
grid.push_back({});
gameMap.push_back({});
for (int j = 0; j < c; j++) {
grid[i].push_back(0);
gameMap[i].push_back('#');
}
}
set<pair<int, int>> Mine;
while (Mine.size() != cntMine) {
int x = rand() % r;
int y = rand() % c;
if (Mine.find({x, y}) == Mine.end()) {
Mine.insert({x, y});
grid[x][y] = 1;
}
}

int x, y;
int cntUnopened = r * c - cntMine;
time_t startTime = time(0);

while (cntUnopened != 0) {
clearScreen();
printArray(gameMap);
cout << "Input the coordinate (e.g. 3 5): ";
cin >> x >> y;
if (x < 1 || x > r || y < 1 || y > c) {
cout << "超過範圍,請重新輸入\n";
system("pause");
continue;
}

queue<pair<int, int>> que;
que.push({x - 1, y - 1});
while (!que.empty()) {
auto now = que.front();
que.pop();

if (grid[now.first][now.second] == 1) {
cout << "BOOM!\n";
system("pause");
return;
}
if (grid[now.first][now.second] == -1) continue;

grid[now.first][now.second] = -1;
cntUnopened--;

// check for mine;
int cntNearMine = 0;
for (int k = 0; k < 8; k++) {
int nx = now.first + dir[k][0];
int ny = now.second + dir[k][1];

if (nx >= 0 && nx < r && ny >= 0 && ny < c) {
if (grid[nx][ny] == 1) cntNearMine++;
}
}
if (cntNearMine != 0) {
gameMap[now.first][now.second] = '0' + cntNearMine;
continue;
} else {
gameMap[now.first][now.second] = '_';
}
for (int k = 0; k < 8; k++) {
int nx = now.first + dir[k][0];
int ny = now.second + dir[k][1];
if (nx >= 0 && nx < r && ny >= 0 && ny < c) {
if (grid[nx][ny] == 0) {
que.push({nx, ny});
}
}
}
}
}
time_t endTime = time(0);
writeFile(startTime, endTime, mode);
cout << "You win!\n";
system("pause");
}

void writeFile(int st, int et, int m) {
ofstream out;
out.open("history.txt", ios::app);
if (out.fail()) {
cout << "output file opening failed\n";
return;
}
out << et - st << ' ' << st << ' ' << m << endl;
out.close();
}

void showHistory() {
clearScreen();
ifstream in;
in.open("history.txt");
if (in.fail()) {
cout << "input file opening failed\n";
return;
}
string str;
vector<history> rank;
history tmp;
while (in >> tmp.t >> tmp.date >> tmp.mode) rank.push_back(tmp);
sort(rank.begin(), rank.end(), cmp);

for (int i = 0; i < rank.size(); i++) {
// mode
switch (rank[i].mode) {
case 1:
cout << " 模式:簡單 \n";
break;
case 2:
cout << " 模式:普通 \n";
break;
case 3:
cout << " 模式:困難 \n";
break;
default:
break;
}

// time
cout << " 耗時:" << rank[i].t << '\n';

// date
time_t gameTime = rank[i].date;
tm* t = localtime(&gameTime);
cout << " 時間:";
cout << t->tm_year + 1900 << '/'
<< t->tm_mon + 1 << '/'
<< t->tm_mday << ' '
<< t->tm_hour << ':'
<< t->tm_min << ':'
<< t->tm_sec << '\n';
cout << "------------------------------\n";
}
in.close();
system("pause");
}

void printArray(vector<vector<char>> arr) {
cout << " ";
for (int j = 0; j < arr[0].size(); j++) cout << (j + 1) % 10 << ' ';
cout << '\n';
for (int i = 0; i < arr.size(); i++) {
cout << (i + 1) % 10 << ' ';
for (int j = 0; j < arr[i].size(); j++) {
cout << arr[i][j] << ' ';
}
cout << '\n';
}
}