Answered step by step
Verified Expert Solution
Link Copied!

Question

1 Approved Answer

*****USING ECLIPSE This is the version that you will work on for this lab. The mines package already defines a working version of MineSweeper, but

*****USING ECLIPSE

This is the version that you will work on for this lab. The mines package already defines a working version of MineSweeper, but it is unsatisfactory in one respect: If you visit a square and the number of bombs in neighboring squares is zero, then you know automatically that it's safe to click on every neighbor of that square. Since it can be done automatically, without thought, it would be nice if the computer would just do it. Furthermore, if one of the neighboring squares also has no mined neighbors, then you can automatically visit all the neighbors of that square. And so on.... This is a recursive procedure, and your job is to add a recursive method to the program to implement it. (There are ways to do the same thing non-recursively, but they are not as efficient or nice.) A completed, compiled version of the program can be found in the jar file MineSweeperComplete.jar in the source code directory, so you can see how much nicer the game is after the recursion is added. You should try the completed game before working on the program, to make sure that you know what is supposed to happen. Note that when you play the game, you start with a visited square in the upper left, and you have to work from there. Flagged squares, which you make by shift-clicking or right-clicking, are shown in pink with a "B" to indicate that you think there's a bomb there. If you step on a bomb, you lose the game, and then the locations of all the bombs are revealed by coloring them red. The main method for MineSweeper is in the file MineSweeper.java, and you should run that file to see how it works. The game is actually implemented in the file MineField.java, and that is where you will be working. (The other file, MineMenus.java, implements the menu bar for the program, and you can ignore it.) For the major part of the lab, you only need to modify the method mark(row,col), which is defined at the very bottom of MineField.java. This method is called from elsewhere in the program when the user clicks on a square; when the method is called, it has already been checked that the user is allowed to click there and that there is no mine in that square. Currently, the mark method contains the single line "state[row][col] = STATE_VISITED;" which is meant to record the fact that the user has visited the square in the specified row and column. You have to modify mark() so that in addition to marking just the one square where the user clicked, it will also recursively mark neighboring squares as visited if it is known that they don't contain any mines. (You only have to mark the squares by setting a value in an array; you don't have to do any re-drawing, as that is already taken care of elsewhere.) The MineField class uses the two-dimensional array named state to keep track of the state of each square on the board. The value of state[r][c] is the state of the square in row r and column c. The possible values are: STATE_FLAGGED, meaning that the user has flagged the square as probably containing a bomb; STATE_VISITED, meaning that the square has been visited and the user can see how many mines are in neighboring squares; and STATE_UNVISITED, which is the initial state and means that the square has not yet been visited or flagged. There is also a two-dimensional boolean array named mined that records the positions of the mines. The value of mined[r][c] is true if there is a mine in the square in row r and column c. Note that a method bombCount(row,col) is already defined for counting the number of bombs in the neighbors of a given square, so you won't have to do this counting yourself. Remember that the basic idea is that if you visit a square at (row,col) and if bombCount(row,col) is zero, then you should also automatically visit all the neighbors of that square. The recursive mark() method will be similar (but not identical!) to the "blob-counting" method that can be found in section 9.1.4 in the textbook. It will be rather short you can do it in fifteen lines -- but it's tricky to get everything right. Remember that you need base cases and that you have to avoid infinite recursion.

*****REQUIRED FILE #1

package mines;

import java.awt.Color;

import java.awt.Dimension;

import java.awt.Font;

import java.awt.Graphics;

import java.awt.event.MouseAdapter;

import java.awt.event.MouseEvent;

import javax.swing.JPanel;

public class MineField extends JPanel {

private final static int ROWS = 20; // number of rows on the board

private final static int COLS = 20; // number of columns on the board

private final static Color LIGHT_GREEN = new Color(180,255,180); // color for unvisited squares

private final static int STATE_UNVISITED = 0; // constant for use in state array

private final static int STATE_FLAGGED = 1; // constant for use in state array

private final static int STATE_VISITED = 2; // constant for use in state array

private int[][] state; // This array holds one of the values STATE_UNVISITED, STATE_FLAGGED,

// or STATE_VISITED for each sqaure on the board. The initial value

// is STATE_UNVISITED. If the user drops a "flag" on the square, indicating

// that the user thinks there is a bomb there, the state is changed to

// STATE_FLAGGED. When the user visits a square by clicking on it (and

// doesn't get blown up), the state is changed to STATE_VISITED.

// THIS VERY IMPORTANT ARRAY IS USED THROUGHOUT THIS CLASS.

private boolean[][] mined; // mined[r][c] is true if there is a mine in square (r,c)

private int mineCount; // number of mines on the board, set when game is started

private boolean gameInProgress; // set to true while a game is in progress

private boolean userWon; // when gameInprogress == false, this tells whether user won

/**

* Create a board that has 50 mines scatterd in it at random positions,

* and start the first game. This constructor just calls {@link #MineField(int)}

* with a parameter value of 50.

*/

public MineField() {

this(50);

}

/**

* Create a board that has a specified number of mines scatterd in it at

* random positions, and start the first game.

* @param mineCount the number of mines that are to be placed on the board.

* The value should be "reasonable" (not too large or too small), but no

* error checking is done. Values that are too large or too small might

* cause infinite loops as the program tries to find a legal board with

* the specified number of mines.

*/

public MineField(int mineCount) {

setPreferredSize( new Dimension(2 + 24*COLS, 2 + 24*ROWS ));

startGame(mineCount);

addMouseListener( new MouseAdapter() {

public void mousePressed(MouseEvent evt) {

doMouseClick(evt);

}

});

}

/**

* Returns the number of mines on the board. This number is set when a

* game begins, and does not change until a new game is startetd.

*/

public int getMineCount() {

return mineCount;

}

/**

* This method processes the event that occurs when the user clicks the board.

*/

private void doMouseClick(MouseEvent evt) {

int row = (evt.getY() - 1) / 24; // row where the user clicked

int col = (evt.getX() - 1) / 24; // column where the user clicked

if ( row < 0 || row >= ROWS || col < 0 || col >= COLS)

return; // ?? click is not actually on the board.

if (state[row][col] == STATE_VISITED)

return; // The user has already visited the square, so click has no effect.

if (! hasVisitedNeighbor(row,col))

return; // The user can only move horizontally or vertically from an already visited square

if (evt.isMetaDown() || evt.isShiftDown()) {

// When the user right-clicks or shift-clicks, it means that the user wants

// to flag the square as containing a bomb, or, if the square is already flagged,

// to remove the flag. (Note that the user might be wrong!)

if (state[row][col] == STATE_UNVISITED)

state[row][col] = STATE_FLAGGED;

else // The state must be STATE_FLAGGED, since STATE_VISITED has already been ruled out.

state[row][col] = STATE_UNVISITED;

}

else {

if (state[row][col] == STATE_FLAGGED)

return; // The user is protected from accidently visiting a flagged square.

visit(row, col); // Mark the square (and maybe some other squares) as visited.

}

repaint(); // Redraw the board to show its changed status.

}

/**

* Draw the board, based on the current state of all the squares and on whether or

* not the game is in progress. This method is called by the system and is not meant

* to be called directrly.

*/

protected void paintComponent(Graphics g) {

g.setColor(Color.GRAY);

g.fillRect(0, 0, getWidth(), getHeight());

for (int r = 0; r < ROWS; r++)

for (int c = 0; c < COLS; c++) {

if (mined[r][c] && !gameInProgress)

g.setColor(Color.RED);

else if (state[r][c] == STATE_FLAGGED)

g.setColor(Color.PINK);

else

g.setColor(LIGHT_GREEN);

if (state[r][c] == STATE_VISITED)

g.fill3DRect( 2+24*c, 2+24*r, 23, 23, false );

else

g.fill3DRect( 2+24*c, 2+24*r, 23, 23, true );

g.setColor(Color.BLACK);

if (state[r][c] == STATE_FLAGGED)

g.drawString("B", 6+24*c, 15+24*r);

else if (state[r][c] == STATE_VISITED) {

int bombs = bombCount(r,c);

if (bombs > 0)

g.drawString("" + bombs, 6+24*c, 15+24*r);

}

}

if (!gameInProgress) {

g.setColor(Color.BLUE);

g.setFont(new Font("SERIF", Font.BOLD, 36));

g.drawString("Game Over.", 30, 60);

if (userWon)

g.drawString("YOU WIN!", 30, 120);

else

g.drawString("YOU LOSE!", 30, 120);

}

}

/**

* Beging a new game with a specified number of mines. If a game is

* in progress, that game is aborted and no warning or error message is

* given.

* @param mineCount The number of mines to place on the board. The value

* should be "reasonable"; see the comments on the construtor {@link #MineField(int)}.

*/

public void startGame(int mineCount) {

System.out.println("Start a game with " + mineCount + " mines.");

this.mineCount = mineCount;

while (true) { // This loop ends when a valid board is created.

gameInProgress = true;

mined = new boolean[ROWS][COLS]; // All values are initially false.

state = new int[ROWS][COLS]; // All values are initially STATE_UNVISITED.

for (int i = 0; i < mineCount; i++) { // Place a mine at a random position.

int r,c;

while (true) {

r = (int)(ROWS * Math.random()); // randomly selected row number

c = (int)(COLS * Math.random()); // randomly selected column number

if ( (r + c > 2) && (r < ROWS-1 || c < COLS-1) && ! mined[r][c] ) {

// End the loop if the randomly selected position is OK, otherwise,

// try again with a different position. The test "r + c > 2" ensures

// that positions (0,0), (1,0), (0,1), (1,1), (0,2), and (2,0) are

// not mined, and in particular that the upper-left corner is not

// mined and has no neighbors that are mined. The other two tests

// ensure that the lower-right "home" position is not mined and

// that there is not already a mine in the selected square.

break;

}

}

mined[r][c] = true;

}

visit(0,0); // Get the user started by automatically visiting the upper-left square.

if (configOK()) // Checks whether the board is valid.

break;

}

repaint();

}

/**

* The user can only visit squares that are next to already visited (or flagged)

* squares. This method checks that a given square is in fact next to a visited square.

*/

private boolean hasVisitedNeighbor(int row, int col) {

if (row > 0 && state[row-1][col] > STATE_UNVISITED)

return true;

if (row < ROWS-1 && state[row+1][col] > STATE_UNVISITED)

return true;

if (col > 0 && state[row][col-1] > STATE_UNVISITED)

return true;

if (col < COLS-1 && state[row][col+1] > STATE_UNVISITED)

return true;

return false;

}

/**

* This method is called when a square is visisted by the user.

*/

private void visit( int row, int col ) {

if (mined[row][col]) { // The user has stepped on a mine and gets blown up.

gameInProgress = false;

userWon = false;

}

else { // It's OK for the user to step on this square. Mark it as visited.

mark(row,col);

if (state[ROWS - 1][COLS -1] == STATE_VISITED) { // User has reached the home sqaure!

gameInProgress = false;

userWon = true;

}

}

}

/**

* Counts the bombs in the squares that neighbor position (row,col).

*/

private int bombCount(int row, int col) {

int ct = 0;

if (row > 0) {

if (col > 0 && mined[row-1][col-1])

ct++;

if (mined[row-1][col])

ct++;

if (col < COLS-1 && mined[row-1][col+1])

ct++;

}

if (col > 0 && mined[row][col-1])

ct++;

if (col < COLS-1 && mined[row][col+1])

ct++;

if (row < ROWS-1) {

if (col > 0 && mined[row+1][col-1])

ct++;

if (mined[row+1][col])

ct++;

if (col < COLS-1 && mined[row+1][col+1])

ct++;

}

return ct;

}

/**

* Tests whehter a new game board is valid. This is called by startGame() to

* decide whether to use a random board that it has created, or to discard

* the board and try again.

*/

private boolean configOK() {

if ( state[ROWS-1][COLS-1] == STATE_VISITED ) {

System.out.println("Already solved.");

return false;

}

return true;

}

/**

* Marks the square in position (row, col) as visited.

*/

private void mark(int row, int col) {

state[row][col] = STATE_VISITED;

}

}

*****REQUIRED FILE#2

package mines;

import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;

import javax.swing.Action;

import javax.swing.JMenu;

import javax.swing.JMenuBar;

import javax.swing.KeyStroke;

/**

* Represents a menu bar to be used with the MineSweeper game.

*/

public class MineMenus extends JMenuBar {

private MineField mineField;

/**

* Create a menu bar containing one menu that has commands for starting

* new games of MineSweeper, with varying numbers of mines. The

* first command in the menus is "New Game", which starts a game using

* the same number of mines as the current game.

* @param board The board on which the games are played. When a New Game

* command is selected, a new game is started on this board.

*/

public MineMenus( MineField board ) {

mineField = board;

JMenu menu = new JMenu("Game");

add(menu);

setupGameMenu(menu);

}

/**

* Adds the various New Game commands to the menu.

*/

private void setupGameMenu(JMenu menu) {

AbstractAction newGameAction = new AbstractAction("New Game") {

public void actionPerformed(ActionEvent evt) {

int currentMineCount = mineField.getMineCount();

mineField.startGame(currentMineCount);

}

};

if (System.getProperty("mrj.version") == null) // test whether this is Mac OS

newGameAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("ctrl N"));

else

newGameAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("meta N"));

menu.add(newGameAction);

menu.addSeparator();

menu.add( new AbstractAction("New Game / 30 Mines"){

public void actionPerformed(ActionEvent evt) {

mineField.startGame(30);

}

});

menu.add( new AbstractAction("New Game / 40 Mines"){

public void actionPerformed(ActionEvent evt) {

mineField.startGame(40);

}

});

menu.add( new AbstractAction("New Game / 50 Mines"){

public void actionPerformed(ActionEvent evt) {

mineField.startGame(50);

}

});

menu.add( new AbstractAction("New Game / 60 Mines"){

public void actionPerformed(ActionEvent evt) {

mineField.startGame(60);

}

});

menu.add( new AbstractAction("New Game / 75 Mines"){

public void actionPerformed(ActionEvent evt) {

mineField.startGame(75);

}

});

menu.add( new AbstractAction("New Game / 100 Mines"){

public void actionPerformed(ActionEvent evt) {

mineField.startGame(100);

}

});

}

}

*****REQIRED FILE #3

package mines;

import java.awt.Dimension;

import java.awt.Toolkit;

import javax.swing.JFrame;

/**

* This program is a MineSweeper game in which the user starts

* in the upper left corner tries to get home (the lower right

* corner) without stepping on a mine. The game can be ended

* by clicking the window's close box, in the title bar.

*/

public class MineSweeper {

public static void main(String[] args) {

JFrame window = new JFrame("MineSweeper a la Mac");

MineField board = new MineField();

window.setContentPane(board);

window.setJMenuBar( new MineMenus(board) );

window.pack();

window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

window.setLocation( (screenSize.width - window.getWidth()) / 2,

(screenSize.height - window.getHeight()) / 2 );

window.setVisible(true);

}

}

Step by Step Solution

There are 3 Steps involved in it

Step: 1

blur-text-image

Get Instant Access to Expert-Tailored Solutions

See step-by-step solutions with expert insights and AI powered tools for academic success

Step: 2

blur-text-image

Step: 3

blur-text-image

Ace Your Homework with AI

Get the answers you need in no time with our AI-driven, step-by-step assistance

Get Started

Recommended Textbook for

More Books

Students also viewed these Databases questions

Question

2. Identify conflict triggers in yourself and others

Answered: 1 week ago