The Idea

Our team wishes to build a minesweeper game with a python backend that could consistently communicate with our front-end website to bring a new degree of sophistication and aesthetic to this classic Game. This project would be split into two main portions each with it's own challenge. The first portion would be making the game engine in python, while the latter portion would be to design and create an aesthetically pleasing and functional frontend to display the Game Status

The Frontend

The frontend aspect of this project should preferably display a grid of the game and incorporate some aspect of user input. Preliminary testing and experimentation could be done through textbox inputs of coordinate values, while prospective functions could include individual buttons on the Grid itself to provide a GUI for the user to interact with. In both cases, the frontend should be able to communicate with the python backend through JSON data to send and receive data.

More aspects about the Frontend aspects will be added with additional planning in the future.

The Backend

Arguably the more challenging half of this project, the backend would primarily serve to be the game engine of the minesweeper game. Additionally, the backend would also control and organize the various pages and menus on the website. In this writeup, we will only be focusing on the main goals and challenges faced by the Backend team.

  1. Using Object Oriented Programming to create individual objects for the game. Some aspects that could be represented by such objects would be the gameboard and also the individual cells and nodes present within the game board.
    1. Doing so would simplify the algorithmic aspect of the game, as the board itself could have methods and attributes to help control the game logic.
    2. Morever, using an object to represent an individual cell in the board would provide greater functionalities than just using a single variable
  2. Use Enumerated types with the python enum module to create different values for the type and status of each node. Each of these types could then be bound to a constant value which could then be printed on the screen

  3. Use Recursion to create an algorithm to recursively detect adjacent cells that are safe (AKA not mines). This algorithm would work in the following format

    1. Maintain a list of current cells already determined to be safe
    2. Verify if the four adjacent (up, down, left, right) cells next to the selected cell are safe or not, if safe, store the coordinate point in the list, if not, record the cell as a "border cell", terminate the recursive process, and run another helper function to determine the precise number of mines surrounding the cell
    3. Re-call the function for each of the surrounding adjacent cells to identify other consecutive cells who are safe.
    4. Return a list of the coordinates of a contiguous block of safe cells
    5. Mark the cells to be safe and calculate number of surrounding mines for border cells.
  4. Verify the Game status, Game is won if:

    1. All mines are flagged
    2. All safe cells are cleared Game is lost if:
    3. A mine was dug by the user
  5. Return the final result back to the front end. If a safe cell was dug, send out a JSON containing an array of the coordinates of the cell and it's safe neighbors. If a mine was dug, send a JSON containing a boolean value to signify the end of the game.

CHALLENGE 1: OOP

Below is the currently implemented code for the gameboard and each individual node.

Here is the code for each individual node in the minesweeper:

from enum import Enum
from random import randint
class MType(Enum):
    SAFE = 1
    MINE = 2     

class MStatus(Enum):
    UNKNOWN = 1
    DUG = 2  
    FLAG = 3  

class Node:
    def __init__(self):
        self.type = MType.SAFE
        self.status = MStatus.UNKNOWN
        self.value = "-"
    
    def setInitialValue(self):
        if self.type == MType.MINE:
            self.value="M"

Here is the Code for the Minesweeper gameboard and its methods.

class NotInBoard(Exception):
    pass

class Minesweeper:
    # Class constructor, containing game data
    def __init__(self):
        self.mine_coords = []
        self.board = []
        self.rows = 0
        self.cols = 0
        self.gameWin = False
        self.gameOver = False

    # Create the gameboard on a given input of rows and columns
    def generateBoard(self,r,c):
        self.rows = r
        self.cols = c
        self.board = [[Node() for i in range(self.rows)] for i in range(self.cols)]
        return self.board

    # Print the current board
    def printBoard(self):
        res = ""
        for row in self.board:
            concat = ""
            for column in row:
                concat = concat + column.value + " "
            concat = concat.strip() + "\n"
            res = res+concat
        print(res)
        return 0

    # Generate the coordinates of the mines AFTER the first user input
    # THE FIRST ACTION ON A CELL WOULD NEVER BE A MINE!
    def setMines(self):
        num_of_mines = (self.rows * self.cols)//4
        counter = 0
        while counter <= num_of_mines:
            mine_coord = (randint(0,self.rows-1),randint(0,self.cols-1))
            print("Mine coord: ", mine_coord)
            if mine_coord in self.mine_coords:
                continue
            self.board[mine_coord[0]][mine_coord[1]].type = MType.MINE
            self.mine_coords.append(mine_coord)
            counter +=1
        for i in range(self.rows):
            for j in range(self.cols):
                self.board[i][j].setInitialValue()
        return 0

    # The first click of the game
    def firstClick(self):
        self.printBoard()
        try:
            ipt = input("What row and column? (format in row,column): ")
            row, col = int(ipt.split(",")[0]), int(ipt.split(",")[1])
            if row not in range(1,self.rows+1) or col not in range(1,self.cols+1):
                print("Values not in bound, try again")
                raise NotInBoard
            self.setMines()
        except NotInBoard:
            self.firstClick()

    # Following actions after the first-dig
    # checkAdkacent function will be created soon
    def digMine(self, r ,c):
        if self.board[r][c].type == MType.MINE:
            self.gameOver = True
        else:
            safe_mines = self.checkAdjacent(r,c)

CHALLENGE 2: Enumerated types

This part is kinda free tho ngl

from enum import Enum

class MType(Enum):
    SAFE = 1
    MINE = 2     

class MStatus(Enum):
    UNKNOWN = 1
    DUG = 2  
    FLAG = 3  

CHALLENGE 3: Recursive algorithm

Yet to be implemented

CHALLENGE 4: Yet to be implemented

Yet to be implemented

CHALLENGE 5: JSON communication

Yet to be implemented

Current Game Implementation

def main():
    MS = Minesweeper()
    rows = int(input("How many rows do you want? "))
    cols = int(input("How many columns do you want? "))

    MS.generateBoard(cols, rows)
    MS.firstClick()
    MS.printBoard()
    # Define a while lop here to loop game.
    print("List of generated mine coordinates: ", MS.mine_coords)

main()
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -
- - - - - - - - - -

Mine coord:  (3, 8)
Mine coord:  (9, 2)
Mine coord:  (4, 7)
Mine coord:  (7, 3)
Mine coord:  (9, 6)
Mine coord:  (8, 1)
Mine coord:  (5, 1)
Mine coord:  (7, 6)
Mine coord:  (5, 5)
Mine coord:  (8, 4)
Mine coord:  (7, 2)
Mine coord:  (4, 7)
Mine coord:  (8, 3)
Mine coord:  (2, 9)
Mine coord:  (0, 9)
Mine coord:  (5, 4)
Mine coord:  (7, 9)
Mine coord:  (2, 0)
Mine coord:  (0, 1)
Mine coord:  (4, 4)
Mine coord:  (4, 2)
Mine coord:  (6, 3)
Mine coord:  (0, 6)
Mine coord:  (3, 0)
Mine coord:  (9, 7)
Mine coord:  (8, 3)
Mine coord:  (4, 9)
Mine coord:  (9, 6)
Mine coord:  (3, 9)
- M - - - - M - - M
- - - - - - - - - -
M - - - - - - - - M
M - - - - - - - M M
- - M - M - - M - M
- M - - M M - - - -
- - - M - - - - - -
- - M M - - M - - M
- M - M M - - - - -
- - M - - - M M - -

List of generated mine coordinates:  [(3, 8), (9, 2), (4, 7), (7, 3), (9, 6), (8, 1), (5, 1), (7, 6), (5, 5), (8, 4), (7, 2), (8, 3), (2, 9), (0, 9), (5, 4), (7, 9), (2, 0), (0, 1), (4, 4), (4, 2), (6, 3), (0, 6), (3, 0), (9, 7), (4, 9), (3, 9)]