-
Notifications
You must be signed in to change notification settings - Fork 0
/
Grid.jack
281 lines (245 loc) · 9.57 KB
/
Grid.jack
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
// Copyright (c) 2018, Bill Mei
// Constants:
// CELL_CLOSED = 0 (nothing under this cell, not revealed)
// CELL_MINE = 1 (mine under this cell, not revealed)
// CELL_BOGEY = 2 (flag over a mine, not revealed)
// CELL_FLAG = 3 (flag over empty cell, not revealed)
// CELL_REVEALED_EMPTY = 4 (nothing under this cell, revealed)
// The main grid class, this is instantiated as a singleton.
class Grid {
field Array internalGrid;
field int colWidth, rowHeight, flagCount, mineProbability, mineCount;
field boolean firstMove;
constructor Grid new(int _colWidth, int _rowHeight, int _mineProbability) {
// mineProbability is the probability (out of 100) that any given cell is a mine.
var int j;
let j = 0;
// Set up field variables
let mineProbability = _mineProbability;
let colWidth = _colWidth;
let rowHeight = _rowHeight;
let internalGrid = Array.new(rowHeight);
let mineCount = 0;
let flagCount = 0;
let firstMove = true;
// Initialize the grid to all 0 at first
while (j < rowHeight) {
let internalGrid[j] = Array.new(colWidth);
do __placeMines__(internalGrid[j]);
let j = j + 1;
}
return this;
}
method void dispose() {
// Dispose each of the grid's rows
var int rowIdx;
var Array rowArr;
let rowIdx = 0;
while (rowIdx < rowHeight) {
let rowArr = internalGrid[rowIdx];
do rowArr.dispose();
let rowIdx = rowIdx + 1;
}
// Then dispose of the grid
do internalGrid.dispose();
do Memory.deAlloc(this);
return;
}
// Getter helper because the built-in Jack compiler
// doesn't know how to handle something like internalGrid[x][y]
method int get(int colIdx, int rowIdx) {
var Array rowArr;
let rowArr = internalGrid[rowIdx];
return rowArr[colIdx];
}
// Setter helper because the built-in Jack compiler
// doesn't know how to handle something like internalGrid[x][y]
method void set(int colIdx, int rowIdx, int value) {
var Array rowArr;
let rowArr = internalGrid[rowIdx];
let rowArr[colIdx] = value;
return;
}
// Get the width of the grid
method int getWidth() {
return colWidth;
}
// Get the height of the grid
method int getHeight() {
return rowHeight;
}
// Get the total number of mines
method int getMineCount() {
return mineCount;
}
// Get the number of flags planted so far
method int getFlagCount() {
return flagCount;
}
// Reveals (digs) the current cell.
// Returns true if the cell was a mine
// Returns false if the cell was not a mine
method boolean reveal(int x, int y) {
var int cell;
// If this is the first move, force the cell to not be a mine
if (firstMove) {
// CELL_CLOSED = 0
do set(x, y, 0);
let firstMove = false;
}
let cell = get(x, y);
// CELL_MINE = 1
if (cell = 1) {
return true;
}
// CELL_CLOSED = 0
if (cell = 0) {
// If there are zero neighboring mines, run a flood fill algorithm to
// reveal the surrounding cells
if (__neighborMineCount__(x, y) = 0) {
do __floodReveal__(x, y);
}
// CELL_REVEALED_EMPTY = 4
do set(x, y, 4);
}
// Only empty cells can be dug; do nothing for all other cells.
return false;
}
// Plant a flag at the current cell
// If a flag is already on the current cell, then it unplants the flag
// and restores the cell to what was previously under it.
method void flag(int x, int y) {
var int cell;
let cell = get(x, y);
if (cell = 0) { do set(x, y, 3); let flagCount = flagCount + 1; } // CELL_CLOSED → CELL_FLAG
if (cell = 3) { do set(x, y, 0); let flagCount = flagCount - 1; } // CELL_FLAG → CELL_CLOSED
if (cell = 2) { do set(x, y, 1); let flagCount = flagCount - 1; } // CELL_BOGEY → CELL_MINE
if (cell = 1) { do set(x, y, 2); let flagCount = flagCount + 1; } // CELL_MINE → CELL_BOGEY
// Flags cannot be planted on empty cells, so do nothing for empty cells.
return;
}
// Check if the game is won or not
method boolean gameWon() {
// The game is won if and only if the following conditions are met:
// (1) zero remaining cells have CELL_CLOSED set
// (2) zero remaining cells have CELL_FLAG set
var int rowIdx, colIdx, cell;
let rowIdx = 0;
while (rowIdx < rowHeight) {
let colIdx = 0;
while (colIdx < colWidth) {
let cell = get(colIdx, rowIdx);
if ((cell = 0) | (cell = 3)) {
return false;
}
let colIdx = colIdx + 1;
}
let rowIdx = rowIdx + 1;
}
return true;
}
// Draw the Grid to the screen
method void draw(boolean showMines) {
// Note: The screen is 32 cells wide by 16 cells tall
var int rowIdx, colIdx, cell;
let rowIdx = 0;
while (rowIdx < rowHeight) {
let colIdx = 0;
while (colIdx < colWidth) {
let cell = get(colIdx, rowIdx);
// CELL_CLOSED
if (cell = 0) {
do Sprites.drawClosedCell(Utils.cellToLocation(colIdx, rowIdx));
}
// CELL_MINE
if (cell = 1) {
if (showMines) {
do Sprites.drawMineCell(Utils.cellToLocation(colIdx, rowIdx));
} else {
do Sprites.drawClosedCell(Utils.cellToLocation(colIdx, rowIdx));
}
}
// CELL_BOGEY | CELL_FLAG
if ((cell = 2) | (cell = 3)) {
do Sprites.drawFlagCell(Utils.cellToLocation(colIdx, rowIdx));
}
// CELL_REVEALED_EMPTY
if (cell = 4) {
do Sprites.drawNumberedCell(__neighborMineCount__(colIdx, rowIdx), Utils.cellToLocation(colIdx, rowIdx));
}
let colIdx = colIdx + 1;
}
let rowIdx = rowIdx + 1;
}
return;
}
/* BEGIN PRIVATE METHODS */
// Randomly place mines along a row
method void __placeMines__(Array row) {
var int colIdx;
let colIdx = 0;
while (colIdx < colWidth) {
if (Random.randInt(0, 100) < mineProbability) {
// CELL_MINE = 1
let row[colIdx] = 1;
let mineCount = mineCount + 1;
} else {
// CELL_EMPTY = 0
let row[colIdx] = 0;
}
let colIdx = colIdx + 1;
}
return;
}
// Breadth-first search flood fill algorithm
method void __floodReveal__(int initialX, int initialY) {
// We can't use a recursive algorithm and we must use a queue
// because the HACK computer's call stack isn't big enough to handle
// the large number of recursive calls we want to make.
var Queue queue;
var int x, y, cell;
let queue = Queue.new();
do __enqueueNeighbors__(queue, initialX, initialY);
while (~queue.isEmpty()) {
// The queue's nodes come in a tuple pair with x first, y second.
let x = queue.dequeue();
let y = queue.dequeue();
// CELL_CLOSED = 0
if (get(x, y) = 0) {
// CELL_REVEALED_EMPTY = 4
do set(x, y, 4);
if (__neighborMineCount__(x, y) = 0) {
do __enqueueNeighbors__(queue, x, y);
}
}
}
do queue.dispose();
return;
}
// Enqueue all neighboring cells into the provided queue
method void __enqueueNeighbors__(Queue queue, int x, int y) {
if (((x - 1) > -1) & ((y - 1) > -1) ) { do queue.enqueue(x - 1); do queue.enqueue(y - 1); }
if (((x - 1) > -1) ) { do queue.enqueue(x - 1); do queue.enqueue(y ); }
if (((x - 1) > -1) & ((y + 1) < rowHeight)) { do queue.enqueue(x - 1); do queue.enqueue(y + 1); }
if ( ((y - 1) > -1) ) { do queue.enqueue(x ); do queue.enqueue(y - 1); }
if ( ((y + 1) < rowHeight)) { do queue.enqueue(x ); do queue.enqueue(y + 1); }
if (((x + 1) < colWidth) & ((y - 1) > -1) ) { do queue.enqueue(x + 1); do queue.enqueue(y - 1); }
if (((x + 1) < colWidth) ) { do queue.enqueue(x + 1); do queue.enqueue(y ); }
if (((x + 1) < colWidth) & ((y + 1) < rowHeight)) { do queue.enqueue(x + 1); do queue.enqueue(y + 1); }
return;
}
// Gives the number of neighboring cells which are mines
method int __neighborMineCount__(int x, int y) {
var int count;
if (((x - 1) > -1) & ((y - 1) > -1) ) { let count = count + Utils.mineCount(get(x - 1, y - 1)); }
if (((x - 1) > -1) ) { let count = count + Utils.mineCount(get(x - 1, y )); }
if (((x - 1) > -1) & ((y + 1) < rowHeight)) { let count = count + Utils.mineCount(get(x - 1, y + 1)); }
if ( ((y - 1) > -1) ) { let count = count + Utils.mineCount(get(x , y - 1)); }
if ( ((y + 1) < rowHeight)) { let count = count + Utils.mineCount(get(x , y + 1)); }
if (((x + 1) < colWidth) & ((y - 1) > -1) ) { let count = count + Utils.mineCount(get(x + 1, y - 1)); }
if (((x + 1) < colWidth) ) { let count = count + Utils.mineCount(get(x + 1, y )); }
if (((x + 1) < colWidth) & ((y + 1) < rowHeight)) { let count = count + Utils.mineCount(get(x + 1, y + 1)); }
return count;
}
/* END PRIVATE METHODS */
}