The Board
class
1: import java.util.Random;
2:
3: /**
4: * This class implements the basic behaviour of a MineSweeper game board;
5: * i.e. a grid of cells, each of which may contain a mine. The cells are
6: * initially covered (unknown) and can be selectively 'revealed'. A revealed
7: * cell either contains a mine or an integer indicating the number of mines in
8: * the (up to) eight adjacent cells. Cells may also be 'marked', to indicate
9: * cells that might contain a mine. No more cells can be marked than there
10: * are mines on the board.
11: *
12: * The board does not enforce any particular game rules (e.g. no special
13: * action is taken if a mine is revealed). Even the behaviour of the reveal()
14: * and mark() methods can be overridden if desired. In particular, the draw()
15: * method is abtract and must be implemented in a subclass to give the board
16: * any kind of visual representation.
17: */
18: public abstract class Board {
19:
20: // Subclasses have access to protected variables
21:
22: // Board size
23: protected int width, height;
24:
25: // Number of mines on the board
26: protected int numMines;
27:
28: // Number of cells currently marked
29: protected int numMarked;
30:
31: // Number of cells yet to be revealed
32: protected int numUnknown;
33:
34: // Indicates where the mines are hidden
35: protected boolean[][] mines;
36:
37: // The current state of the board
38: protected int[][] board;
39:
40: // Constants for cell contents. The MINE value might be returned by
41: // reveal(), the others are only used internally but will probably be
42: // required in subclasses.
43: public static final int UNKNOWN = -1;
44: public static final int MARKED = -2;
45: public static final int MINE = -3;
46:
47:
48: /**
49: * Create a new game board with the given size and number of mines. The
50: * mines are randomly distributed across the board. Currently no error
51: * checking is done, so if numMines > width*height the program will hang.
52: * C'est la vie.
53: */
54: public Board(int width, int height, int numMines) {
55:
56: // Initialise instance variables. Note the use of 'this' when parameters
57: // have the same name.
58: this.width = width;
59: this.height = height;
60: this.numMines = numMines;
61: this.numMarked = 0;
62: this.numUnknown = width * height;
63:
64: // Allocate storage for game board and mines
65: mines = new boolean[width][height];
66: board = new int[width][height];
67:
68: // Clear the board
69: for (int i = 0; i < width; i++) {
70: for (int j = 0; j < height; j++) {
71: mines[i][j] = false;
72: board[i][j] = UNKNOWN;
73: }
74: }
75:
76: // Randomly allocate mines. The loop runs until numMines mines have been
77: // placed on the board. The the purposes of this operation we treat the
78: // board as a width*height linear array of cells, and simply try again if
79: // the chosen cell already contains a mine.
80: int cells = width * height;
81: int temp = 0;
82: Random rand = new Random();
83:
84: while (temp < numMines) {
85: int cell = rand.nextInt();
86: cell = (cell < 0 ? -cell : cell)%cells;
87: if (!mines[cell%width][cell/width]) {
88: mines[cell%width][cell/width] = true;
89: temp++;
90: }
91: }
92: }
93:
94:
95: /**
96: * Print/draw/(speak?) some representation of the board that can be
97: * understood by a human player. This method must be implemented by
98: * subclasses.
99: */
100: public abstract void draw();
101:
102:
103: /**
104: * Reveal the contents of the cell at position (x, y) on the board. Returns
105: * the contents thus revealed, which may be an integer in the range 0..8
106: * indicating the number of neighbouring cells that contain a mine, or the
107: * special constant MINE indicating that the cell contains a mine.
108: */
109: public int reveal(int x, int y) {
110: switch (board[x][y]) {
111: case MARKED:
112: // If the cell was marked, unmark it
113: numMarked--;
114: case UNKNOWN:
115: // One less unknown cell now
116: numUnknown--;
117: if (mines[x][y]) {
118: board[x][y] = MINE;
119: }
120: else {
121: // How many mines in the vicinity?
122: board[x][y] = closeMines(x, y);
123: }
124: break;
125: }
126: // Return the revealed value
127: return board[x][y];
128: }
129:
130:
131: /**
132: * Reveal the contents of more cells around cell (x, y). For each of the
133: * (up to) eight cells surrounding (x, y):
134: * - if the cell contents are currently unknown and the cell does *not*
135: * contain a mine, the contents of the cell are revealed.
136: * - if the revealed cell is empty and has no neigbouring mines,
137: * revealMore() is called recursively on it.
138: * The behaviour of this method is best understood by using it!
139: */
140: public void revealMore(int x, int y) {
141: int minx, miny, maxx, maxy;
142: int result = 0;
143:
144: // Don't try to check beyond the edges of the board...
145: minx = (x <= 0 ? 0 : x - 1);
146: miny = (y <= 0 ? 0 : y - 1);
147: maxx = (x >= width - 1 ? width : x + 2);
148: maxy = (y >= height - 1 ? height : y + 2);
149:
150: // Loop over all surrounding cells
151: for (int i = minx; i < maxx; i++) {
152: for (int j = miny; j < maxy; j++) {
153: if (!mines[i][j] && board[i][j] == UNKNOWN) {
154: reveal(i, j);
155: if (board[i][j] == 0) {
156: // Call ourself recursively
157: revealMore(i, j);
158: }
159: }
160: }
161: }
162: }
163:
164:
165: /**
166: * 'Mark' the cell (x, y), probably to indicate that a player thinks there
167: * may be a mine there. Up to numMines cells may be marked simultaneously.
168: * Returns true is the mark succeeded, false otherwise.
169: */
170: public boolean mark(int x, int y) {
171: if ((numMines - numMarked) > 0 && board[x][y] == UNKNOWN) {
172: board[x][y] = MARKED;
173: numMarked++;
174: return true;
175: }
176: else {
177: return false;
178: }
179: }
180:
181:
182: /**
183: * Unmark the previously marked cell at (x, y). Returns try if the unmark
184: * succeeded (meaning that the cell was marked, false otherwise.
185: */
186: public boolean unmark(int x, int y) {
187: if (board[x][y] == MARKED) {
188: board[x][y] = UNKNOWN;
189: numMarked--;
190: return true;
191: }
192: else {
193: return false;
194: }
195: }
196:
197:
198: /**
199: * Return the width of the game board.
200: */
201: public int getWidth() {
202: return width;
203: }
204:
205: /**
206: * Return the height of the game board.
207: */
208: public int getHeight() {
209: return height;
210: }
211:
212: /**
213: * Return the number of mines on the board.
214: */
215: public int getMines() {
216: return numMines;
217: }
218:
219: /**
220: * Return the number of currently 'marked' cells on the game board.
221: */
222: public int getMarked() {
223: return numMarked;
224: }
225:
226: /**
227: * Return the number of 'unknown' (as in not yet revealed) cells on the game
228: * board.
229: */
230: public int getUnknown() {
231: return numUnknown;
232: }
233:
234:
235: /**
236: * Work out how many neighbours of cell (x, y) contain mines. Return the
237: * number of explosive neighbours.
238: */
239: private int closeMines(int x, int y) {
240: int minx, miny, maxx, maxy;
241: int result = 0;
242:
243: // Don't check outside the edges of the board
244: minx = (x <= 0 ? 0 : x - 1);
245: miny = (y <= 0 ? 0 : y - 1);
246: maxx = (x >= width - 1 ? width : x + 2);
247: maxy = (y >= height - 1 ? height : y + 2);
248:
249: // Check all immediate neighbours for mines
250: for (int i = minx; i < maxx; i++) {
251: for (int j = miny; j < maxy; j++) {
252: if (mines[i][j]) {
253: result++;
254: }
255: }
256: }
257: return result;
258: }
259: }
Scott Mitchell
Last modified: Wed Nov 4 22:13:14 GMT 1998