-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMineSweeper.java
More file actions
310 lines (274 loc) · 10 KB
/
MineSweeper.java
File metadata and controls
310 lines (274 loc) · 10 KB
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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
import java.util.*;
public class MineSweeper {
private GUI gui;
private int[][] fieldInvisible; // -1 = mine, 0–8 = number of surrounding mines
private int[][] fieldVisible; // 0 = hidden, otherwise reveals what's underneath
private int mines;
private int totalNonMines;
private int revealedCount;
private boolean[][] visible;
public MineSweeper() {
int[] starting = GUI.startPrompt();
if (starting == null) {
// User cancelled the start prompt -> exit gracefully instead of causing NPE
System.exit(0);
return;
}
int rows = starting[0];
int cols = starting[1];
fieldInvisible = new int[rows][cols];
fieldVisible = new int[rows][cols];
gui = new GUI(rows, cols, this);
clearGrid();
visible = new boolean[rows][cols];
}
public MineSweeper(int rows, int cols) {
fieldInvisible = new int[rows][cols];
fieldVisible = new int[rows][cols];
gui = new GUI(rows, cols, this);
clearGrid();
visible = new boolean[rows][cols];
}
public void clearGrid() //clears the entire grid. used in the reset and runGame methods
{
mines = 0;
totalNonMines = 0;
revealedCount = 0;
for (int i = 0; i < fieldInvisible.length; i++) {
for (int j = 0; j < fieldInvisible[0].length; j++) {
fieldInvisible[i][j] = 0;
fieldVisible[i][j] = 0;
if (visible != null && i < visible.length && j < visible[0].length) {
visible[i][j] = false;
}
}
}
}
public void updateGrid() //updates the visible grid (game board) in the GUI
{
for (int i = 0; i < fieldVisible.length; i++) {
for (int j = 0; j < fieldVisible[0].length; j++) {
gui.setValue(i, j, fieldVisible[i][j]);
}
}
}
public void placeMines(int numMines, int row, int col) //places mines randomly on the grid, ensuring that the mine is not placed in the first cell clicked by the user
{
// Build a list of allowed positions (exclude first-click cell and its adjacent cells)
ArrayList<Integer> allowed = new ArrayList<>();
int rows = fieldInvisible.length;
int cols = fieldInvisible[0].length;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (!isAdjacent(row, col, r, c)) {
allowed.add((r << 16) | c);
}
}
}
// Shuffle and take the first numMines positions (faster and avoids retries)
Collections.shuffle(allowed, new Random());
int placed = 0;
for (int i = 0; i < allowed.size() && placed < numMines; i++) {
int code = allowed.get(i);
int r = code >> 16;
int c = code & 0xFFFF;
if (fieldInvisible[r][c] != -1) {
fieldInvisible[r][c] = -1;
updateAdjacent(r, c);
placed++;
}
}
mines = placed;
totalNonMines = rows * cols - mines;
gui.updateFlagCount(-1 * numMines);
}
public void updateAdjacent(int row, int col) //updates the adjacent cells of a mine to keep count of the number of mines surrounding it
{
for (int i = row - 1; i <= row + 1; i++) {
for (int j = col - 1; j <= col + 1; j++) {
if (i >= 0 && i < fieldInvisible.length && j >= 0 && j < fieldInvisible[0].length) {
if (fieldInvisible[i][j] != -1) {
fieldInvisible[i][j]++;
}
}
}
}
}
public void revealCell(int row, int col) { //reveals a cell in the grid, updating the visible grid and checking for game over or win conditions
if (fieldVisible[row][col] == -2) return;
if (fieldInvisible[row][col] == -1) {
revealAllMines();
gui.gameOver();
} else {
fieldVisible[row][col] = fieldInvisible[row][col];
gui.setValue(row, col, fieldInvisible[row][col]);
gui.turnTan(row, col, fieldInvisible[row][col]);
if (!visible[row][col]) {
visible[row][col] = true;
revealedCount++;
}
if (fieldInvisible[row][col] == 0) {
revealSurrounding(row, col);
}
}
updateGrid();
if (checkWin()) {
revealAllMines();
gui.success();
gui.stopTimer();
}
}
public void revealSurrounding(int row, int col) { //reveals all surrounding cells using iterative BFS
int rows = fieldInvisible.length;
int cols = fieldInvisible[0].length;
boolean[][] visitedLocal = new boolean[rows][cols];
ArrayDeque<int[]> queue = new ArrayDeque<>();
queue.add(new int[] {row, col});
visitedLocal[row][col] = true;
while (!queue.isEmpty()) {
int[] p = queue.removeFirst();
int r = p[0];
int c = p[1];
if (fieldInvisible[r][c] == -1) continue;
if (fieldVisible[r][c] == 0) {
fieldVisible[r][c] = fieldInvisible[r][c];
gui.setValue(r, c, fieldInvisible[r][c]);
gui.turnTan(r, c, fieldInvisible[r][c]);
if (!visible[r][c]) {
visible[r][c] = true;
if (fieldInvisible[r][c] != -1) revealedCount++;
}
}
if (fieldInvisible[r][c] == 0) {
for (int dr = -1; dr <= 1; dr++) {
for (int dc = -1; dc <= 1; dc++) {
if (dr == 0 && dc == 0) continue;
int nr = r + dr;
int nc = c + dc;
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && !visitedLocal[nr][nc]) {
visitedLocal[nr][nc] = true;
queue.add(new int[] {nr, nc});
}
}
}
}
}
if (checkWin()) {
revealAllMines();
gui.success();
gui.stopTimer();
}
}
public void runGame(int rows, int cols, int x, int y) { //starts the game by clearing the grid, placing mines, and revealing the first cell clicked by the user
clearGrid();
do {
clearGrid();
placeMines(rows * cols / 6, x, y);
} while (fieldInvisible[x][y] == -1);
revealCell(x, y);
updateGrid();
}
public void resetGame() { //resets the game by clearing the grid and resetting the GUI
gui.reset();
clearGrid();
}
public boolean checkWin() { //checks if game is won if all non-mine cells are revealed
return revealedCount == totalNonMines;
}
// for game over
public void revealAllMines() {
for (int i = 0; i < fieldInvisible.length; i++) {
for (int j = 0; j < fieldInvisible[0].length; j++) {
if (fieldInvisible[i][j] == -1) {
gui.setValue(i, j, -1);
gui.revealMine(i, j);
}
}
}
}
// for mass revealing
public void chord(int row, int col) {
if (fieldVisible[row][col] != fieldInvisible[row][col]) return;
if (fieldInvisible[row][col] <= 0) return;
int flagCount = countFlagsAround(row, col);
if (flagCount == fieldInvisible[row][col]) {
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
int r = row + i;
int c = col + j;
if (r >= 0 && r < fieldVisible.length && c >= 0 && c < fieldVisible[0].length) {
if (!gui.isFlag(r, c) && fieldVisible[r][c] == 0) {
revealCell(r, c);
}
}
}
}
}
}
// Count the number of flags around a cell. Used for the chord method.
public int countFlagsAround(int row, int col) {
int count = 0;
for (int i = row - 1; i <= row + 1; i++) {
for (int j = col - 1; j <= col + 1; j++) {
if (i >= 0 && i < fieldVisible.length && j >= 0 && j < fieldVisible[0].length) {
if (fieldVisible[i][j] == -2) {
count++;
}
}
}
}
return count;
}
// flag and unflag a cell.
public void flagCell(int row, int col) {
if (fieldVisible[row][col] == 0) {
fieldVisible[row][col] = -2; // -2 for flagged cell
gui.setValue(row, col, -2);
} else if (fieldVisible[row][col] == -2) {
fieldVisible[row][col] = 0;
gui.setValue(row, col, 0);
}
}
// Getters and setters
public void setFieldInvisible(int[][] fieldInvisible) {
this.fieldInvisible = fieldInvisible;
}
public int[][] getFieldInvisible() {
return fieldInvisible;
}
public int getFieldInvisibleLength() {
return fieldInvisible.length;
}
public int getFieldInvisibleWidth() {
return fieldInvisible[0].length;
}
public int getFieldInvisible(int row, int col) {
return gui.getValue(row, col);
}
public int getNumMines() {
return mines;
}
public boolean isMine(int row, int col) {
if (fieldInvisible[row][col] == -1) {
return true;
}
return false;
}
// Check if two cells are adjacent
public boolean isAdjacent(int r1, int c1, int r2, int c2) {
for (int i = r1 - 1; i <= r1 + 1; i++) {
for (int j = c1 - 1; j <= c1 + 1; j++) {
if (i >= 0 && i < fieldVisible.length && j >= 0 && j < fieldVisible[0].length) {
if (i == r2 && j == c2) {
return true;
}
}
}
}
return false;
}
// Main method (demo)
public static void main(String[] args) {
new MineSweeper();
}
}