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