Developing Minesweeper using Java
It's been a while since I've posted anything on my blog. The reason being that lately, I've been learning new languages like Python and Java. Well anyways, I've taken the time to develop Minesweeper game using Java applets. This game also got GUI which I've created using the components provided by Java. Here are a few pictures of the game.
Here's the video of my game:
There are two classes in my game:
- Board class: This class provides the backend logic of building the board for the minesweeper game. This class has also got methods using which user can play the game on command prompt itself. I've commented out the methods required for running the game on cmd (the * beside a method listed below denotes that the method was commented). If you're interested in playing on cmd, fiddle around with invoking methods in the main method(create one!) and see if you can run it :). The various methods in Board class are:
- Board() *: Constructor to initialize the board size and number of bombs by taking input.
- Board (int rows, int cols, int bombs): Constructor to initialize the board size and number of bombs based on arguments.
- fillBombs(int number): Method to fill the bombs on the board.
- void showBoard() *: Method to draw the board on cmd.
- getInput(int row_num, int col_num): Method to accept user's input(the mine which he has clicked).
- expand (int row_no, int col_no): The most interesting method! How to uncover mines based on user's input.
- MineSweeper class: This is the class where the math and logic take colour and shape! This class implements the MouseListener interface and also extends the Applet class.
- mousePressed(MouseEvent e): Method of the MouseListener interface that needs to be overridden.
- mouseReleased(MouseEvent e): Method of the MouseListener interface that needs to be overridden.
- mouseClicked(MouseEvent e): Method of the MouseListener interface that needs to be overridden. This is the only method among all the overridden methods of MouseListener interface as no other overridden method updates variables nor performs any useful computations.
- mouseEntered(MouseEvent e): Method of the MouseListener interface that needs to be overridden.
- mouseExited(MouseEvent e): Method of the MouseListener interface that needs to be overridden.
- init(): This method initially calls the setup() method.
- setup(): Method used to set up the menu.
- paint(Graphics g): Method used to repaint the screen after each action is performed.
- drawBoard (Graphics g, Board game): Method used to graphically draw the board.
Coming to the implementation of the methods, here's the code for the Board class:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Board { | |
int rows_count; | |
int cols_count; | |
int no_of_bombs; | |
int no_of_moves; | |
int[][] board; | |
int tiles_shown; | |
boolean isAlive = false; | |
boolean didWin = false; | |
/* | |
//for cmd prompt: | |
Board() { | |
isAlive = true; | |
Scanner s = new Scanner(System.in); | |
System.out.println("BOARD SETUP:"); | |
while((rows_count < 5 || cols_count < 5) || (rows_count > 17 || cols_count > 17)) { | |
System.out.print("enter the number of rows: "); | |
rows_count = s.nextInt(); | |
System.out.print("enter the number of cols: "); | |
cols_count = s.nextInt(); | |
if ((rows_count < 5 || cols_count < 5) || (rows_count > 17 || cols_count > 17)) { | |
System.out.println("ERROR: Invalid entry. Number of rows and columns must be atleast 5 and atmost 17."); | |
} | |
} | |
board = new int[rows_count][cols_count]; | |
while (no_of_bombs <= 0 || no_of_bombs > (rows_count * cols_count)) { | |
System.out.print("enter the number of bombs: "); | |
no_of_bombs = s.nextInt(); | |
if (no_of_bombs <= 0 || no_of_bombs > (rows_count * cols_count) - 1) { | |
System.out.println("ERROR: Invalid entry. Number of bombs must be > 0 and < " + ((rows_count * cols_count) - 1) + "."); | |
} | |
} | |
fillBombs(no_of_bombs); | |
for (int i = 0; i < rows_count; i++) { | |
for (int j = 0; j < cols_count; j++) { | |
if(board[i][j] == 0){ | |
board[i][j] = -(rows_count*cols_count); | |
} | |
} | |
} | |
} | |
*/ | |
Board (int rows, int cols, int bombs) { | |
isAlive = true; | |
didWin = false; | |
rows_count = rows; | |
cols_count = cols; | |
board = new int[rows_count][cols_count]; | |
no_of_bombs = bombs; | |
fillBombs(no_of_bombs); | |
for (int i = 0; i < rows_count; i++) { | |
for (int j = 0; j < cols_count; j++) { | |
if(board[i][j] == 0){ | |
board[i][j] = -(rows_count*cols_count); | |
} | |
} | |
} | |
} | |
protected void fillBombs(int number) { | |
int size = rows_count * cols_count; | |
int[] tiles = new int[size]; | |
for (int i = 0; i < size; i++) { | |
tiles[i] = i; | |
} | |
for(int i = 0; i < number; i++) { | |
int random_number = (int)((double)size * Math.random()); | |
int row_num = tiles[random_number] / cols_count; | |
int col_num = tiles[random_number] % cols_count; | |
//TESTING: display bomb spots | |
//System.out.printf("%d: (%d,%d)\n", tiles[random_number], row_num, col_num); | |
board[row_num][col_num] = -((rows_count * cols_count) + 1); | |
if (row_num-1 >= 0 && col_num - 1 >= 0) { | |
board[row_num-1][col_num-1] -= 1; | |
} | |
if (row_num-1 >= 0) { | |
board[row_num-1][col_num] -= 1; | |
} | |
if (row_num-1 >= 0 && col_num + 1 < cols_count) { | |
board[row_num-1][col_num+1] -=1; | |
} | |
if (col_num-1 >=0) { | |
board[row_num][col_num-1] -= 1; | |
} | |
if (col_num+1 < cols_count) { | |
board[row_num][col_num+1] -= 1; | |
} | |
if (row_num+1 < rows_count && col_num - 1 >= 0) { | |
board[row_num+1][col_num-1] -= 1; | |
} | |
if (row_num+1 < rows_count) { | |
board[row_num+1][col_num] -= 1; | |
} | |
if (row_num+1 < rows_count && col_num + 1 < cols_count) { | |
board[row_num+1][col_num+1] -=1; | |
} | |
int temp = tiles[size - 1]; | |
tiles[size - 1] = tiles[random_number]; | |
tiles[random_number] = temp; | |
size--; | |
} | |
} | |
/* | |
//for showing board in cmd | |
void showBoard() { | |
System.out.println("no of bombs: " + no_of_bombs); | |
System.out.println("Mines yet to uncover: " + ((rows_count * cols_count) - no_of_bombs - tiles_shown)); | |
for (int i = 0; i < rows_count; i++) { | |
for (int j = 0; j < cols_count; j++) { | |
if (isAlive && board[i][j] < 0) { | |
System.out.print(" | - | "); | |
} | |
else { | |
//when dead, show bombs | |
if ((!isAlive || didWin) && board[i][j] < -(rows_count * cols_count)) { | |
System.out.print(" | X | "); | |
} | |
//when dead, need not reveal uncovered mines | |
else if (!isAlive && board[i][j] < 0) { | |
System.out.print(" | - | "); | |
} | |
//when alive and safe mine is selected; show it | |
else { | |
System.out.printf(" | %3d | ", board[i][j]); | |
} | |
} | |
} | |
System.out.println("\n"); | |
} | |
} | |
*/ | |
void getInput(int row_num, int col_num) { | |
if (row_num > rows_count || row_num < 1 || col_num > cols_count || col_num < 1) { | |
//for cmd: | |
//System.out.println("ERROR: Invalid entry. Row and Column values must be <= " + rows_count + " and " + cols_count + " respectively."); | |
return; | |
} | |
row_num -= 1; | |
col_num -= 1; | |
if (board[row_num][col_num] >= 0) { | |
//for cmd: | |
//System.out.println("ERROR: Tile already revealed. Select only unrevealed tiles."); | |
return; | |
} | |
no_of_moves++; | |
//a tile adjacent to a bomb | |
if (board[row_num][col_num] > -9) { | |
board[row_num][col_num] *= -1; | |
tiles_shown++; | |
} | |
else if (board[row_num][col_num] < -(rows_count * cols_count)) { | |
//for cmd: | |
//System.out.println("OOPS! A bomb has been detonated.\n\n \t--Game over--\n\n"); | |
isAlive = false; | |
} | |
else { //white mine | |
board[row_num][col_num] = 0; | |
tiles_shown++; | |
expand(row_num, col_num); | |
} | |
if (tiles_shown + no_of_bombs == rows_count * cols_count) { | |
//for cmd: | |
//System.out.println("YAHOO! You won the game.\n\n \t--Game WON--\n\n"); | |
didWin = true; | |
} | |
} | |
void expand (int row_no, int col_no) { | |
//top left expansion | |
if (row_no - 1 >= 0 && col_no - 1 >= 0) { | |
if (board[row_no-1][col_no-1] == -(rows_count*cols_count)) { | |
board[row_no-1][col_no-1] = 0; | |
tiles_shown++; | |
expand(row_no-1, col_no-1); | |
} | |
else { | |
if (board[row_no-1][col_no-1] < 0) { | |
board[row_no-1][col_no-1] *= -1; | |
tiles_shown += 1; | |
} | |
} | |
} | |
//top expansion | |
if (row_no - 1 >= 0) { | |
if (board[row_no-1][col_no] == -(rows_count*cols_count)) { | |
board[row_no-1][col_no] = 0; | |
tiles_shown++; | |
expand(row_no-1, col_no); | |
} | |
else { | |
if (board[row_no-1][col_no] < 0) { | |
board[row_no-1][col_no] *= -1; | |
tiles_shown += 1; | |
} | |
} | |
} | |
//top right expansion | |
if (row_no - 1 >= 0 && col_no + 1 < cols_count) { | |
if (board[row_no-1][col_no+1] == -(rows_count*cols_count)) { | |
board[row_no-1][col_no+1] = 0; | |
tiles_shown++; | |
expand(row_no-1, col_no+1); | |
} | |
else { | |
if (board[row_no-1][col_no+1] < 0) { | |
board[row_no-1][col_no+1] *= -1; | |
tiles_shown += 1; | |
} | |
} | |
} | |
//left expansion | |
if (col_no - 1 >= 0) { | |
if (board[row_no][col_no-1] == -(rows_count*cols_count)) { | |
board[row_no][col_no-1] = 0; | |
tiles_shown++; | |
expand(row_no, col_no-1); | |
} | |
else { | |
if (board[row_no][col_no-1] < 0) { | |
board[row_no][col_no-1] *= -1; | |
tiles_shown += 1; | |
} | |
} | |
} | |
//right expansion | |
if (col_no + 1 < cols_count) { | |
if (board[row_no][col_no+1] == -(rows_count*cols_count)) { | |
board[row_no][col_no+1] = 0; | |
tiles_shown++; | |
expand(row_no, col_no+1); | |
} | |
else { | |
if (board[row_no][col_no+1] < 0) { | |
board[row_no][col_no+1] *= -1; | |
tiles_shown += 1; | |
} | |
} | |
} | |
//bottom left expansion | |
if (row_no + 1 < rows_count && col_no - 1 >= 0) { | |
if (board[row_no+1][col_no-1] == -(rows_count*cols_count)) { | |
board[row_no+1][col_no-1] = 0; | |
tiles_shown++; | |
expand(row_no+1, col_no-1); | |
} | |
else { | |
if (board[row_no+1][col_no-1] < 0) { | |
board[row_no+1][col_no-1] *= -1; | |
tiles_shown += 1; | |
} | |
} | |
} | |
//bottom expansion | |
if (row_no + 1 < rows_count) { | |
if (board[row_no+1][col_no] == -(rows_count*cols_count)) { | |
board[row_no+1][col_no] = 0; | |
tiles_shown++; | |
expand(row_no+1, col_no); | |
} | |
else { | |
if (board[row_no+1][col_no] < 0) { | |
board[row_no+1][col_no] *= -1; | |
tiles_shown += 1; | |
} | |
} | |
} | |
//bottom right expansion | |
if (row_no + 1 < rows_count && col_no + 1 < cols_count) { | |
if (board[row_no+1][col_no+1] == -(rows_count*cols_count)) { | |
board[row_no+1][col_no+1] = 0; | |
tiles_shown++; | |
expand(row_no+1, col_no+1); | |
} | |
else { | |
if (board[row_no+1][col_no+1] < 0) { | |
board[row_no+1][col_no+1] *= -1; | |
tiles_shown += 1; | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MineSweeper extends JApplet implements MouseListener, ActionListener { | |
/* | |
//NOTE: activate this to play only in 'Command Prompt' | |
//you need to comment the rest of the methods in this class | |
static void startGame () { | |
Board game = new Board(); | |
game.showBoard(); | |
Scanner s = new Scanner(System.in); | |
while (game.isAlive && !game.didWin) { | |
int row_num, col_num; | |
System.out.print("enter the row number: "); | |
row_num = s.nextInt(); | |
System.out.print("enter the column number: "); | |
col_num = s.nextInt(); | |
game.getInput(row_num, col_num); | |
game.showBoard(); | |
} | |
} | |
public static void main (String args[]) { | |
MineSweeper.startGame(); | |
} | |
*/ | |
//variables to know the position of pointer | |
int x = -1; | |
int y = -1; | |
//the game object | |
Board game; | |
//useful for game setup | |
TextField rows_input, cols_input, bombs_input; | |
Label rows_label, cols_label, bombs_label, warn_label; | |
Button create_btn; | |
int rows, cols, bombs; | |
//useful while restarting the game | |
boolean didRestart = false; | |
//if the game setup has been done or not | |
boolean didStart = false; | |
//OverRiding the methods in MouseListener class | |
public void mousePressed(MouseEvent e) | |
{ | |
x = e.getX(); | |
y = e.getY(); | |
} | |
public void mouseReleased(MouseEvent e) | |
{ | |
x = e.getX(); | |
y = e.getY(); | |
} | |
public void mouseClicked(MouseEvent e) | |
{ | |
x = e.getX(); | |
y = e.getY(); | |
x = ((x - 35) / 35) + 1; | |
y = ((y - 55) / 35) + 1; | |
//to analyze in cmd: | |
//System.out.println("click at " + x + ", " + y); | |
repaint(); | |
} | |
public void mouseEntered(MouseEvent e) | |
{ | |
x = e.getX(); | |
y = e.getY(); | |
} | |
public void mouseExited(MouseEvent e) | |
{ | |
x = e.getX(); | |
y = e.getY(); | |
} | |
//method for button action event | |
public void actionPerformed (ActionEvent ae) { | |
try { | |
rows = Integer.parseInt(rows_input.getText()); | |
cols = Integer.parseInt(cols_input.getText()); | |
bombs = Integer.parseInt(bombs_input.getText()); | |
if (rows < 5 || rows > 17 || cols < 5 || cols > 17) { | |
warn_label.setText("Number of rows and cols must be >= 5 and <= 17."); | |
return; | |
} | |
else if(bombs >= rows * cols || bombs < 1) { | |
warn_label.setText("Number of bombs must be <= to the number of fields(" + (rows * cols) + ") and > 0."); | |
return; | |
} | |
remove(rows_label); | |
remove(rows_input); | |
remove(cols_label); | |
remove(cols_input); | |
remove(bombs_label); | |
remove(bombs_input); | |
remove(warn_label); | |
remove(create_btn); | |
didStart = true; | |
game = new Board(rows, cols, bombs); | |
addMouseListener(this); | |
repaint(); | |
} catch (NumberFormatException e) { | |
warn_label.setText("Invalid Entry!"); | |
} | |
} | |
public void init() { | |
setup(); | |
} | |
public void setup() { | |
showStatus("Minesweeper setup. developed by Cherubim Anand."); | |
// Tell the applet not to use a layout manager. | |
setLayout(null); | |
// initialze the button and give it a text. | |
create_btn = new Button("CREATE"); | |
//labels | |
rows_label = new Label("Number of rows:"); | |
rows_label.setBackground(Color.white); | |
cols_label = new Label("Number of columns:"); | |
cols_label.setBackground(Color.white); | |
bombs_label = new Label("Number of bombs:"); | |
bombs_label.setBackground(Color.white); | |
warn_label = new Label("Enter the values"); | |
warn_label.setBackground(Color.white); | |
// text and length of the field | |
rows_input = new TextField("0",2); | |
cols_input = new TextField("0",2); | |
bombs_input = new TextField("0",2); | |
// now we will specify the positions of the GUI components. | |
// this is done by specifying the x and y coordinate and | |
//the width and height. | |
rows_label.setBounds(120,70,350,20); | |
cols_label.setBounds(105,120,350,20); | |
bombs_label.setBounds(110,170,350,20); | |
rows_input.setBounds(220,70,50,20); | |
cols_input.setBounds(220,120,50,20); | |
bombs_input.setBounds(220,170,50,20); | |
create_btn.setBounds(120,220,100,30); | |
warn_label.setBounds(50, 270, 600, 20); | |
// now that all is set we can add these components to the applet | |
add(create_btn); | |
add(rows_input); | |
add(cols_input); | |
add(bombs_input); | |
add(rows_label); | |
add(cols_label); | |
add(bombs_label); | |
add(warn_label); | |
create_btn.addActionListener(this); | |
} | |
public void paint(Graphics g) { | |
g.setFont(new Font("Monospace", Font.PLAIN, 15)); | |
if (didStart) { | |
if (game.isAlive && !game.didWin) { | |
game.getInput(y, x); | |
//to analyze in cmd: | |
//game.showBoard(); | |
drawBoard(g, game); | |
showStatus("Click at (row, col) : (" + y + ", " + x +")"); | |
} | |
else { | |
g.setColor(Color.white); | |
g.fillRect(0, 0, 1000, 1000); | |
didStart = false; | |
setup(); | |
//to analyze in cmd: | |
//game.showBoard(); | |
} | |
if (didStart && !game.isAlive) { | |
//to analyze in cmd: | |
//game.showBoard(); | |
g.setColor(Color.white); | |
g.fillRect(180, 180, 150, 40); | |
g.setColor(Color.red); | |
g.drawString("Game Over", 200, 200); | |
} | |
if (didStart && game.didWin) { | |
//to analyze in cmd: | |
//game.showBoard(); | |
g.setColor(Color.white); | |
g.fillRect(180, 180, 150, 40); | |
g.setColor(Color.green); | |
g.drawString("Game Won", 200, 200); | |
} | |
} | |
} | |
public void drawBoard (Graphics g, Board game){ | |
g.setColor(Color.white); | |
g.fillRect(0, 0, 1000, 1000); | |
String msg1 = "Number of bombs : " + game.no_of_bombs; | |
String msg2 = "Fields uncoverd : " + ((game.rows_count * game.cols_count) - game.no_of_bombs - game.tiles_shown); | |
String msg3 = "Player's Score : " + ((game.tiles_shown * 2) - game.no_of_moves + game.no_of_bombs); | |
String msg4 = "Number of moves : " + game.no_of_moves; | |
g.setColor(Color.blue); | |
g.drawString(msg1, 50, 20); | |
g.drawString(msg2, 50, 40); | |
g.setColor(Color.red); | |
g.drawString(msg3, 250, 20); | |
g.drawString(msg4, 250, 40); | |
g.setColor(Color.black); | |
g.fillRect(30, 50, (game.cols_count * 35) + 5, (game.rows_count * 35) + 7); | |
for (int i = 0; i < game.rows_count; i++) { | |
for (int j = 0; j < game.cols_count; j++) { | |
if (game.board[i][j] < 0) { | |
//draw a blank square | |
g.setColor(Color.cyan); | |
g.fillRect((j + 1) * 35, ((i+1) * 35) + 20, 30, 30); | |
} | |
else { | |
g.setColor(Color.green); | |
g.fillRect((j + 1) * 35, ((i+1) * 35) + 20, 30, 30); | |
if (game.board[i][j] != 0) { | |
char[] number = new char[1]; | |
number[0] = (char) ('0' + (char) game.board[i][j]); | |
g.setColor(Color.black); | |
g.drawChars(number, 0, 1, ((j + 1) * 35) + 10, ((i+1) * 35) + 20 + 20); | |
} | |
} | |
if (!game.isAlive || game.didWin) { | |
if (game.board[i][j] < -(game.rows_count * game.cols_count)) { | |
g.setColor(Color.red); | |
g.fillRect((j + 1) * 35, ((i+1) * 35) + 20, 30, 30); | |
char[] number = new char[1]; | |
number[0] = 'X'; | |
g.setColor(Color.black); | |
g.drawChars(number, 0, 1, ((j + 1) * 35) + 10, ((i+1) * 35) + 20 + 20); | |
} | |
} | |
} | |
} | |
} | |
} |
Well those we the two classes implementations. If you want to download java files or class files, here is the link (Note; that MineSweeper.java has got code for both the classes in it): DOWNLOAD
Wonderful article. Very interesting to read this article.I would like to thank you for the efforts you had made for writing this awesome article. This article resolved my all queries.
ReplyDeleteData Science Course in Pune
Thanks for Sharing a Very Informative Post & I read Your Article & I must say that is very helpful post for us.
ReplyDeleteBest AWS Training in Pune
Best RPA Training in Pune
Selenium Training in Pune