import java.util.Random; // Java core packages import java.awt.*; import java.awt.event.*; // Java extension packages import javax.swing.*; import javax.swing.border.*; // Java util import java.util.*; /* Pete Dobbins Section: xxxx Group: 0 Partner: None */ class P4 { public static void main(String[] args) { int row = 9; int col = 9; int numMines = 10; int buttonSize = 20; int fixSizeX = 10; int fixSizeY = 80; if ( args.length >= 3 ) { try { row = Integer.valueOf( args[0] ); col = Integer.valueOf( args[1] ); numMines = Integer.valueOf( args[2] ); } catch (NumberFormatException e) { row = col = numMines = 10; } } else { System.out.println( "\nTo set the number of rows, columns, and mines" ); System.out.println( "for example" ); System.out.println( "java P4 15 10 32" ); System.out.println( "for 15 rows, 10 columns, and 32 mines" ); System.out.println( "" ); System.out.println( "Left click for revealing the space." ); System.out.println( "Middle click for revealing spaces around the reveal space with correct flag counts." ); System.out.println( "Right click for flagging or questioning the space." ); System.out.println( "Enjoy! :-)" ); } // Create minesweeper game Minesweeper minesweeper = new Minesweeper( row, col, numMines ); // width then height minesweeper.setSize(col*buttonSize + fixSizeX, row*buttonSize + fixSizeY); //minesweeper.pack(); minesweeper.setVisible( true ); minesweeper.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } } // END: class P4 // For counting the play time class TimeCounter extends Thread { private int secs; private boolean isStop; private JTextField tf; public TimeCounter( JTextField tf ) { reset(); this.tf = tf; } public void reset() { secs = 0; isStop = false; } public void stopTime() { isStop = true; } public void run() { try { while( true ) { if ( !isStop ) { sleep(1000); ++secs; if ( secs > 99 ) tf.setText( "" + secs ); else if ( secs > 9 ) tf.setText( "0" + secs ); else tf.setText( "00" + secs ); } } } catch ( InterruptedException e ) { System.out.println( e ); } } } class Minesweeper extends JFrame implements ActionListener { // Game properties private TimeCounter time; private int p_iNumMines; private int p_iMineLeft; private MinesweeperGridElement [][] p_board; // Game menu buttons private JButton p_btnNewGame; private JTextField p_txtFldNumMines; private JTextField p_txtFldTime; // Game menu items private JMenuItem p_menuItemNew; private JMenuItem p_menuItemGiveup; private JMenuItem p_menuItemAbout; private JMenuItem p_menuItemExit; // get total number of mines public int getTotalMines() { return p_iNumMines; } // get number of mines left public int getNumMinesLeft() { return p_iMineLeft; } // increase/decrease number of unmarked mines public void increaseNumMines() { p_txtFldNumMines.setText( "" + ++p_iMineLeft ); } public void decreaseNumMines() { p_txtFldNumMines.setText( "" + --p_iMineLeft ); } // def ctor public Minesweeper() { this( 9, 9, 10 ); } // non-def ctor public Minesweeper(int row, int col, int numMines) { p_board = new MinesweeperGridElement[row][col]; p_iNumMines = p_iMineLeft = numMines; setGUIs(); } // private void setGUIs() { // set container Container container = getContentPane(); container.setLayout( new BorderLayout() ); // infobar container.add( createInfoBar(), BorderLayout.NORTH ); // board container.add( createBoard(), BorderLayout.CENTER ); resetBoard(); // Set up menu createMenu(); // Time Counter MinesweeperGridElement.time = time; time.start(); } // Create board in a JPanel private JPanel createBoard() { // create panel for the board int row = p_board.length; int col = p_board[0].length; JPanel boardPanel = new JPanel( new GridLayout(row, col) ); // create buttons MinesweeperGridElement.setMinesweeper( this ); MinesweeperGridElement.setBoard( p_board ); for ( int r = 0; r < row; ++r ) { for ( int c = 0; c < col; ++c ) { p_board[r][c] = new MinesweeperGridElement( r, c ); boardPanel.add( p_board[r][c] ); } } return boardPanel; } // Reset board private void resetBoard() { p_txtFldTime.setText( "000" ); MinesweeperGridElement.setEndOfGame( false ); MinesweeperGridElement.setBoard( p_board ); p_iMineLeft = p_iNumMines; p_txtFldNumMines.setText( "" + p_iMineLeft ); int row = p_board.length; int col = p_board[0].length; // initialize mine positions int countMines = 0; int [][] solns = new int[row][col]; for ( int r = 0; r < row; ++r ) { for ( int c = 0; c < col; ++c ) { solns[r][c] = 0; } } // randomly placing the mines Random random = new Random( System.currentTimeMillis() ); while ( countMines < p_iNumMines ) { int r = random.nextInt( row ); int c = random.nextInt( col ); if ( solns[r][c] >= 0 ) { solns[r][c] = -1; // mine is represented by -1 ++countMines; } } //=========================================== // START: Initialize Solution // Remark: This code is more efficient than // just using one nested for loop. // But with the price of more programing // time. //------------------------------------------- // initialize solutions -- inside boundary for ( int r = 1; r < row-1; ++r ) { for ( int c = 1; c < col-1; ++c ) { if ( solns[r][c] >= 0 ) { if ( solns[r-1][c-1] < 0 ) ++solns[r][c]; if ( solns[r-1][c ] < 0 ) ++solns[r][c]; if ( solns[r-1][c+1] < 0 ) ++solns[r][c]; if ( solns[r ][c-1] < 0 ) ++solns[r][c]; if ( solns[r ][c+1] < 0 ) ++solns[r][c]; if ( solns[r+1][c-1] < 0 ) ++solns[r][c]; if ( solns[r+1][c ] < 0 ) ++solns[r][c]; if ( solns[r+1][c+1] < 0 ) ++solns[r][c]; } } } // initialize solutions -- top boundary (except corners) for ( int r = 0; r <= 0; ++r ) { for ( int c = 1; c < col-1; ++c ) { if ( solns[r][c] >= 0 ) { if ( solns[r ][c-1] < 0 ) ++solns[r][c]; if ( solns[r ][c+1] < 0 ) ++solns[r][c]; if ( solns[r+1][c-1] < 0 ) ++solns[r][c]; if ( solns[r+1][c ] < 0 ) ++solns[r][c]; if ( solns[r+1][c+1] < 0 ) ++solns[r][c]; } } } // initialize solutions -- bottom boundary (except corners) for ( int r = row-1; r <= row-1; ++r ) { for ( int c = 1; c < col-1; ++c ) { if ( solns[r][c] >= 0 ) { if ( solns[r-1][c-1] < 0 ) ++solns[r][c]; if ( solns[r-1][c ] < 0 ) ++solns[r][c]; if ( solns[r-1][c+1] < 0 ) ++solns[r][c]; if ( solns[r ][c-1] < 0 ) ++solns[r][c]; if ( solns[r ][c+1] < 0 ) ++solns[r][c]; } } } // initialize solutions -- left boundary (except corners) for ( int r = 1; r < row-1; ++r ) { for ( int c = 0; c <= 0; ++c ) { if ( solns[r][c] >= 0 ) { if ( solns[r-1][c ] < 0 ) ++solns[r][c]; if ( solns[r-1][c+1] < 0 ) ++solns[r][c]; if ( solns[r ][c+1] < 0 ) ++solns[r][c]; if ( solns[r+1][c ] < 0 ) ++solns[r][c]; if ( solns[r+1][c+1] < 0 ) ++solns[r][c]; } } } // initialize solutions -- right boundary (except corners) for ( int r = 1; r < row-1; ++r ) { for ( int c = col-1; c <= col-1; ++c ) { if ( solns[r][c] >= 0 ) { if ( solns[r-1][c-1] < 0 ) ++solns[r][c]; if ( solns[r-1][c ] < 0 ) ++solns[r][c]; if ( solns[r ][c-1] < 0 ) ++solns[r][c]; if ( solns[r+1][c-1] < 0 ) ++solns[r][c]; if ( solns[r+1][c ] < 0 ) ++solns[r][c]; } } } // initialize solutions -- four corners { int c, r; // top-left corner if ( solns[0][0] >= 0 ) { if ( solns[0][1] < 0 ) ++solns[0][0]; if ( solns[1][1] < 0 ) ++solns[0][0]; if ( solns[1][0] < 0 ) ++solns[0][0]; } // top-right corner c = col-1; if ( solns[0][c] >= 0 ) { if ( solns[0][c-1] < 0 ) ++solns[0][c]; if ( solns[1][c-1] < 0 ) ++solns[0][c]; if ( solns[1][c ] < 0 ) ++solns[0][c]; } // bottom-left corner r = row-1; if ( solns[r][0] >= 0 ) { if ( solns[r ][1] < 0 ) ++solns[r][0]; if ( solns[r-1][1] < 0 ) ++solns[r][0]; if ( solns[r-1][0] < 0 ) ++solns[r][0]; } // bottom-right corner r = row-1; c = col-1; if ( solns[r][c] >= 0 ) { if ( solns[r ][c-1] < 0 ) ++solns[r][c]; if ( solns[r-1][c-1] < 0 ) ++solns[r][c]; if ( solns[r-1][c ] < 0 ) ++solns[r][c]; } } //------------------------------------------- // END: Initialize Solution //=========================================== // set solutions to the buttons for ( int r = 0; r < row; ++r ) { for ( int c = 0; c < col; ++c ) { p_board[r][c].reset(); p_board[r][c].solution = solns[r][c]; } } // Time Counter time.reset(); } private void revealSolution() { for ( int r = 0; r < p_board.length; ++r ) { for ( int c = 0; c < p_board[0].length; ++c ) { if ( p_board[r][c].status == MinesweeperGridElement.FLAG ) p_board[r][c].status = MinesweeperGridElement.CLICKABLE; p_board[r][c].actionReveal(); } } } // Create info bar in a JPanel private JPanel createInfoBar() { p_btnNewGame = new JButton("new"); p_txtFldNumMines = new JTextField("" + p_iMineLeft); p_txtFldTime = new JTextField("000"); time = new TimeCounter( p_txtFldTime ); JPanel infoBar = new JPanel( new FlowLayout() ); infoBar.add( p_txtFldNumMines ); infoBar.add( p_btnNewGame ); infoBar.add( p_txtFldTime ); // Set colors p_txtFldNumMines.setForeground( Color.red ); p_txtFldNumMines.setBackground( Color.black ); p_txtFldTime.setForeground( Color.red ); p_txtFldTime.setBackground( Color.black ); p_txtFldNumMines.setEditable( false ); p_txtFldTime.setEditable( false ); // Set font Font fontInfo = new Font( "Serif", Font.BOLD, 20 ); p_txtFldNumMines.setFont( fontInfo ); p_txtFldTime.setFont( fontInfo ); // Add action listeners p_btnNewGame.addActionListener( this ); return infoBar; } // Menu private void createMenu() { // Items of game menu p_menuItemNew = new JMenuItem( "New" ); p_menuItemNew.setMnemonic( 'N' ); p_menuItemGiveup = new JMenuItem( "Give up" ); p_menuItemGiveup.setMnemonic( 'u' ); p_menuItemAbout = new JMenuItem( "About..." ); p_menuItemAbout.setMnemonic( 'A' ); p_menuItemExit = new JMenuItem( "Exit" ); p_menuItemExit.setMnemonic( 'x' ); // Game menu JMenu gameMenu = new JMenu( "Game" ); gameMenu.setMnemonic( 'G' ); gameMenu.add( p_menuItemNew ); gameMenu.addSeparator(); gameMenu.add( p_menuItemGiveup ); gameMenu.addSeparator(); gameMenu.add( p_menuItemAbout ); gameMenu.addSeparator(); gameMenu.add( p_menuItemExit ); JMenuBar bar = new JMenuBar(); setJMenuBar( bar ); bar.add( gameMenu ); // Add action listeners p_menuItemNew.addActionListener( this ); p_menuItemGiveup.addActionListener( this ); p_menuItemAbout.addActionListener( this ); p_menuItemExit.addActionListener( this ); } // Method from ActionListener public void actionPerformed( ActionEvent event ) { if ( event.getSource() == p_btnNewGame ) { resetBoard(); } else if ( event.getSource() == p_menuItemNew ) { resetBoard(); } else if ( event.getSource() == p_menuItemGiveup ) { revealSolution(); } else if ( event.getSource() == p_menuItemAbout ) { JOptionPane.showMessageDialog( null, // parent component "Minesweeper (P4)\nCIS3023FA07", // message "About Minewsweeper", // title JOptionPane.INFORMATION_MESSAGE // message type ); } else if ( event.getSource() == p_menuItemExit ) { System.exit( 0 ); } } } // END: class Minesweeper // Minesweeper grid buttons class MinesweeperGridElement extends JButton implements ActionListener, MouseListener { // Board array static private Minesweeper minesweeper; static private MinesweeperGridElement [][] p_board; static private int p_iNumSpacesLeft; static private int p_iNumFlags; static boolean p_bEndOfGame; // Time Counter static public TimeCounter time; // Stack static Stack stack = new Stack(); // Status static final public int CLICKABLE = 0; static final public int FLAG = 1; static final public int QMARKED = 2; static final public int REVEALED = -1; static final public int MINE = -1; // static properties static private Color foregoundColor1 = new Color( 0, 0, 250 ); static private Color foregoundColor2 = new Color( 0, 200, 0 ); static private Color foregoundColor3 = new Color( 250, 0, 0 ); static private Color foregoundColor4 = new Color( 0, 0, 150 ); static private Color foregoundColor5 = new Color( 0, 100, 0 ); static private Color foregoundColor6 = new Color( 150, 0, 0 ); static private Color foregoundColor7 = new Color( 0, 200, 200 ); static private Color foregoundColor8 = new Color( 250, 0, 250 ); static private Color backgoundColor = new Color( 200, 200, 200 ); static private Color backgoundColorRevealed = new Color( 225, 225, 225 ); static private Font font = new Font( "Serif", Font.BOLD, 12 ); static private Font fontMine = new Font( "Serif", Font.BOLD, 20 ); //static Border border = BorderFactory.createLineBorder(Color.black); //static private Border border = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED); //static private Border border = BorderFactory.createEtchedBorder(EtchedBorder.RAISED); static private Border border = BorderFactory.createRaisedBevelBorder(); static private Border borderRevealed = BorderFactory.createLoweredBevelBorder(); // Properties private int p_rowNo; private int p_colNo; public int status; // (0) clickable, (1) flag, (2) questionmark; (-1) revealed public int solution; // (0-8) adjacent to mines, (-1) mine // Nondef ctor public MinesweeperGridElement( int row, int col ) { p_rowNo = row; p_colNo = col; addActionListener( this ); addMouseListener( this ); reset(); } // Method from ActionListener public void actionPerformed( ActionEvent event ) { if ( status == FLAG || status == QMARKED ) return; if ( solution == MINE ) { if ( MinesweeperGridElement.getEndOfGame() ) return; MinesweeperGridElement.setEndOfGame( true ); revealSolution(); setBackground( Color.RED ); setForeground( Color.BLACK ); time.stopTime(); JOptionPane.showMessageDialog( null, // parent component "Sorry for your lost", // message "Game Over!", // title JOptionPane.INFORMATION_MESSAGE // message type ); } // Using the recursive method //actionReveal(); //else if ( solution == 0 ) actionRevealRecursive(); // Using the iterative method revealByStack( this ); } // Get Minesweeper game control public static void setMinesweeper( Minesweeper game ) { minesweeper = game; } // Get board control public static void setBoard( MinesweeperGridElement[][] board ) { p_board = board; p_iNumSpacesLeft = p_board.length * p_board[0].length; p_iNumFlags = 0; } // Get/Set End of game public static boolean getEndOfGame() { return p_bEndOfGame; } public static void setEndOfGame( boolean b ) { p_bEndOfGame = b; } // Methods from ActionListener public void mouseClicked( MouseEvent event ) {} public void mouseReleased( MouseEvent event ) {} public void mouseEntered( MouseEvent event ) {} public void mouseExited( MouseEvent event ) {} // We need only mousePressed method public void mousePressed( MouseEvent event ) { if ( p_bEndOfGame ) return; switch ( event.getButton() ) { case MouseEvent.BUTTON1: // left button actionReveal(); break; case MouseEvent.BUTTON2: // middle button revealBaseOnNumFlags(); break; case MouseEvent.BUTTON3: // right button actionChangeStatus(); break; } } // Reset public void reset() { status = 0; solution = 0; setText( "" ); setEnabled( true ); setFont( font ); setBorder( border ); setForeground( Color.BLACK ); setBackground( backgoundColor ); } // Reveal the space public void actionReveal() { if ( status != REVEALED && status != FLAG && status != QMARKED ) { //setVisible( false ); //setEnabled( false ); switch ( solution ) { case MINE: setForeground( Color.RED ); //setBackground( Color.BLACK ); setText( "*" ); setFont( fontMine ); break; case 0: setText( "" ); break; case 1: setForeground( foregoundColor1 ); setText( "" + solution ); break; case 2: setForeground( foregoundColor2 ); setText( "" + solution ); break; case 3: setForeground( foregoundColor3 ); setText( "" + solution ); break; case 4: setForeground( foregoundColor4 ); setText( "" + solution ); break; case 5: setForeground( foregoundColor5 ); setText( "" + solution ); break; case 6: setForeground( foregoundColor6 ); setText( "" + solution ); break; case 7: setForeground( foregoundColor7 ); setText( "" + solution ); break; case 8: setForeground( foregoundColor8 ); setText( "" + solution ); break; } setBackground( backgoundColorRevealed ); setBorder( borderRevealed ); status = REVEALED; --p_iNumSpacesLeft; // Winner checkWinning(); } } // A method for revealing neighbors with help from a stack. // So that we can use an iterative method instead of a recursive method. public void revealByStack( MinesweeperGridElement start ) { int row = p_board.length; int col = p_board[0].length; MinesweeperGridElement element; stack.push( start ); while ( !stack.empty() ) { element = stack.pop(); element.actionReveal(); if ( element.solution == 0 ) { int r = element.p_rowNo; int c = element.p_colNo; int [] rr = new int[]{ r-1, r-1, r-1, r , r , r+1, r+1, r+1 }; int [] cc = new int[]{ c-1, c , c+1, c-1, c+1, c-1, c , c+1 }; for ( int i = 0; i < 8; ++i ) { if ( 0 <= rr[i] && rr[i] < row && 0 <= cc[i] && cc[i] < col ) { if ( p_board[rr[i]][cc[i]].status != REVEALED && p_board[rr[i]][cc[i]].status != FLAG && p_board[rr[i]][cc[i]].status != QMARKED && p_board[rr[i]][cc[i]].solution >= 0 ) { stack.push( p_board[rr[i]][cc[i]] ); } } } } } } // A recursive method for revealing neighbors. private void actionRevealRecursive() { int row = p_board.length; int col = p_board[0].length; int r = p_rowNo; int c = p_colNo; int [] rr = new int[]{ r-1, r-1, r-1, r , r , r+1, r+1, r+1 }; int [] cc = new int[]{ c-1, c , c+1, c-1, c+1, c-1, c , c+1 }; // Can be made more efficiently //=========================================== // START: Recursive Solution // Remark: This code is using just one for // loop. But it is not efficient // since there is if statement inside // the loop. The if statement can be // removed from the loop with the // price of more coding. // See "Initialize Solution" in // resetBoard method //------------------------------------------- // Can you identify the base case(s), // recursive case(s), and recursive step(s)? //------------------------------------------- for ( int i = 0; i < 8; ++i ) { if ( 0 <= rr[i] && rr[i] < row && 0 <= cc[i] && cc[i] < col ) { if ( p_board[rr[i]][cc[i]].status != REVEALED && p_board[rr[i]][cc[i]].status != FLAG && p_board[rr[i]][cc[i]].status != QMARKED && p_board[rr[i]][cc[i]].solution >= 0 ) { p_board[rr[i]][cc[i]].actionReveal(); if ( p_board[rr[i]][cc[i]].solution == 0 ) { p_board[rr[i]][cc[i]].actionRevealRecursive(); } } } } //------------------------------------------- // END: Recursive Solution //=========================================== } public void revealSolution() { for ( int r = 0; r < p_board.length; ++r ) { for ( int c = 0; c < p_board[0].length; ++c ) { if ( p_board[r][c].status == MinesweeperGridElement.FLAG || p_board[r][c].status == MinesweeperGridElement.QMARKED ) p_board[r][c].status = MinesweeperGridElement.CLICKABLE; p_board[r][c].actionReveal(); } } } // called when the middle mouse is pressed public void revealBaseOnNumFlags() { if ( this.status != REVEALED ) return; int row = p_board.length; int col = p_board[0].length; int r = p_rowNo; int c = p_colNo; int [] rr = new int[]{ r-1, r-1, r-1, r , r , r+1, r+1, r+1 }; int [] cc = new int[]{ c-1, c , c+1, c-1, c+1, c-1, c , c+1 }; int countFlags = 0; for ( int i = 0; i < 8; ++i ) { if ( 0 <= rr[i] && rr[i] < row && 0 <= cc[i] && cc[i] < col ) { if ( p_board[rr[i]][cc[i]].status == FLAG ) ++countFlags; } } if ( countFlags == this.solution ) { for ( int i = 0; i < 8; ++i ) { if ( 0 <= rr[i] && rr[i] < row && 0 <= cc[i] && cc[i] < col ) { p_board[rr[i]][cc[i]].actionReveal(); if ( p_board[rr[i]][cc[i]].status == REVEALED && p_board[rr[i]][cc[i]].solution == MINE ) { MinesweeperGridElement.setEndOfGame( true ); //revealSolution(); p_board[rr[i]][cc[i]].setBackground( Color.RED ); p_board[rr[i]][cc[i]].setForeground( Color.BLACK ); JOptionPane.showMessageDialog( null, // parent component "Sorry for your lost", // message "Game Over!", // title JOptionPane.INFORMATION_MESSAGE // message type ); } } } } } // Change status private void actionChangeStatus() { if ( status != REVEALED ) { ++status; if ( status > QMARKED ) status = CLICKABLE; switch ( status ) { case CLICKABLE: setText(""); break; case FLAG: setText("f"); minesweeper.decreaseNumMines(); ++p_iNumFlags; // Winner checkWinning(); break; case QMARKED: setText("?"); minesweeper.increaseNumMines(); --p_iNumFlags; break; } } } public void checkWinning() { if ( minesweeper.getTotalMines() == p_iNumSpacesLeft && p_iNumSpacesLeft == p_iNumFlags ) { p_bEndOfGame = true; time.stopTime(); JOptionPane.showMessageDialog( null, // parent component "Winner!", // message "Game Over!", // title JOptionPane.INFORMATION_MESSAGE // message type ); } } } // END: class MinesweeperGridElement