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:


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;
}
}
}
}
}
view raw Board.java hosted with ❤ by GitHub
Well that's a lot of code! But that's inevitable :) and finally the MineSweeper class:

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 

Comments

  1. 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.
    Data Science Course in Pune

    ReplyDelete
  2. Thanks for Sharing a Very Informative Post & I read Your Article & I must say that is very helpful post for us.
    Best AWS Training in Pune
    Best RPA Training in Pune
    Selenium Training in Pune

    ReplyDelete

Post a Comment

Popular posts from this blog

Beginner's guide to Solving the N-Queens problem using backtracking method

Guide to Solving Maximal Subarray Problem using Kadane's Algorithm

PvP Chain reaction game using Python