Skip to content

Module quickpython.examples.minesweeper

/games/minesweeper.py

Copyright (c) 2019 ShineyDev

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

View Source
"""

/games/minesweeper.py

    Copyright (c) 2019 ShineyDev

    Licensed under the Apache License, Version 2.0 (the "License");

    you may not use this file except in compliance with the License.

    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software

    distributed under the License is distributed on an "AS IS" BASIS,

    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

    See the License for the specific language governing permissions and

    limitations under the License.

"""

__authors__ = [("shineydev", "contact@shiney.dev")]

__maintainers__ = [("shineydev", "contact@shiney.dev")]

__version_info__ = (1, 0, 0, "final", 0)

__version__ = "{0}.{1}.{2}{3}{4}".format(

    *[str(n)[0] if (i == 3) else str(n) for (i, n) in enumerate(__version_info__)]

)

import os

import random

import re

import string

import time

import colorama

import pyfiglet

GRID_SIZES = {"easy": 10, "intermediate": 18, "hard": 20}

MINE_COUNTS = {"easy": 10, "intermediate": 50, "hard": 100}

COLORS = [

    colorama.Fore.LIGHTYELLOW_EX,

    colorama.Fore.YELLOW,

    colorama.Fore.LIGHTMAGENTA_EX,

    colorama.Fore.MAGENTA,

    colorama.Fore.LIGHTRED_EX,

    colorama.Fore.RED,

    colorama.Fore.LIGHTCYAN_EX,

    colorama.Fore.CYAN,

]

class Grid:

    def __init__(self, size: int, mine_count: int):

        """

        initializes a `Grid` object

        """

        self.size = size

        self.mine_count = mine_count

        self.known = [[" " for (i) in range(self.size)] for (j) in range(self.size)]

    def get_neighbors(self, cell: tuple) -> set:

        """

        gets a set of surrounding cells from `cell`

        arguments:

            cell     :: tuple :: the cell to surround

        returns:

           neighbors :: set   :: a set of surrounding cells

        """

        row_index, column_index = cell

        neighbors = set()

        for i in range(-1, 2):

            for j in range(-1, 2):

                if (i == 0) and (j == 0):

                    # this is `cell`

                    continue

                elif (-1 < (row_index + i) < self.size) and (-1 < (column_index + j) < self.size):

                    neighbors.add((row_index + i, column_index + j))

        return neighbors

    def get_random_cell(self) -> tuple:

        """

        gets a random (row, column) tuple

        returns:

            :: tuple :: a random (row, column) tuple

        """

        return (random.randint(0, self.size - 1), random.randint(0, self.size - 1))

    def generate_mines(self, start_cell: tuple):

        """

        generates a set of mines excluding `start_cell`

        arguments:

            start_cell :: tuple :: the player's first selected cell

        """

        self.mines = set()

        neighbors = self.get_neighbors(start_cell)

        while len(self.mines) != self.mine_count:

            cell = self.get_random_cell()

            if (cell == start_cell) or (cell in neighbors):

                continue

            self.mines.add(cell)

        for (row, column) in self.mines:

            self.hidden[row][column] = "X"

    def generate_numbers(self):

        """

        generates a number for each cell based on surrounding mines

        """

        for (row_index, row) in enumerate(self.hidden):

            for (column_index, cell) in enumerate(row):

                if cell != "X":

                    values = [

                        self.hidden[row][column]

                        for (row, column) in self.get_neighbors((row_index, column_index))

                    ]

                    number = values.count("X")

                    if number == 0:

                        self.hidden[row_index][column_index] = "0"

                    else:

                        self.hidden[row_index][column_index] = "{0}{1}{2}".format(

                            COLORS[number - 1], number, colorama.Fore.RESET

                        )

    def show(self, grid: list) -> str:

        """

        generates a string containing a readable form of the minesweeper grid

        arguments:

            grid        :: list :: the minesweeper grid to generate

        returns:

            grid_string :: str  :: the readable form of `grid`

        """

        horizontal = "   " + ("-" * (4 * len(grid))) + "-"

        top_label = "     "

        for character in string.ascii_uppercase[: len(grid)]:

            top_label += "{0}   ".format(character)

        grid_string = "{0}\n{1}".format(top_label, horizontal)

        for (i, j) in enumerate(grid, 1):

            row = "\n{0:>2} |".format(i)

            for k in j:

                row += " {0} |".format(k)

            grid_string += "{0}\n{1}".format(row, horizontal)

        return grid_string

    def show_cell(self, cell: tuple):

        """

        sets `cell` in `self.known` as `self.hidden` and iterates through neighbors if `self.known` == "0"

        arguments:

            cell :: tuple :: the cell to show

        """

        row_index, column_index = cell

        if self.known[row_index][column_index] != " ":

            return

        self.known[row_index][column_index] = self.hidden[row_index][column_index]

        if self.known[row_index][column_index] == "0":

            for (row_index, column_index) in self.get_neighbors((row_index, column_index)):

                if self.known[row_index][column_index] != "F":

                    self.show_cell((row_index, column_index))

    def start(self, start_cell: tuple):

        """

        generates the minesweeper grid

        arguments:

            start_cell :: tuple :: passed into `self.generate_mines` and `self.show_cell`

        """

        self.hidden = [["0" for (i) in range(self.size)] for (j) in range(self.size)]

        self.generate_mines(start_cell)

        self.generate_numbers()

        self.show_cell(start_cell)

class Minesweeper:

    def __init__(self, difficulty: str):

        """

        initializes a `Minesweeper` object

        """

        self.grid_size = GRID_SIZES[difficulty]

        self.mine_count = MINE_COUNTS[difficulty]

    def game(self):

        """

        starts the game

        """

        self.grid = Grid(self.grid_size, self.mine_count)

        self.message = ""

        coordinates = ""

        while not self.valid_coordinates(coordinates) or (coordinates.endswith("F")):

            cls()

            print()

            print(self.grid.show(self.grid.known))

            print()

            coordinates = input("coordinates;\n> ").strip().upper()

            if not self.valid_coordinates(coordinates):

                self.message = "invalid coordinates"

                continue

        row, column, flag = self.parse_coordinates(coordinates)

        self.grid.start((row, column))

        flags = set()

        while not flags == self.grid.mines:

            cls()

            print()

            print(self.grid.show(self.grid.known))

            print()

            if self.message:

                print(self.message)

                print()

                self.message = ""

            else:

                print(

                    "{0} mine{1} left".format(

                        len(self.grid.mines) - len(flags),

                        "s" if (len(self.grid.mines) - len(flags) != 1) else "",

                    )

                )

                print()

            coordinates = input("coordinates;\n> ").strip().upper()

            if coordinates.startswith("EVAL("):

                # this entire block of code was me messing with the game and making cheats bc i got bored :))

                # commands are;

                # eval(va-`column``row`) -> sets self.message to the value at `column`, `row`

                # eval(win)              -> does some freaky looking printing to generate mines and win the game

                pattern_string = r"^EVAL\(VA-(?P<column>[A-{0}])(?P<row>{1})\)$".format(

                    string.ascii_uppercase[self.grid_size - 1],

                    "|".join([str(i) for i in range(1, self.grid_size + 1)][::-1]),

                )

                pattern = re.compile(pattern_string)

                match = re.match(pattern, coordinates)

                if match:

                    row = int(match.group("row")) - 1

                    column = string.ascii_uppercase.index(match.group("column"))

                    self.message = self.grid.hidden[row][column]

                    continue

                pattern_string = r"^EVAL\(WIN\)$"

                pattern = re.compile(pattern_string)

                match = re.match(pattern, coordinates)

                if match:

                    for i in range(self.grid_size):

                        for j in range(self.grid_size):

                            if self.grid.known[i][j] != " ":

                                continue

                            if self.grid.hidden[i][j] != "X":

                                continue

                            flags.add((i, j))

                            self.grid.known[i][j] = "F"

                            cls()

                            print()

                            print(self.grid.show(self.grid.known))

                            print()

                            print(

                                "{0} mine{1} left".format(

                                    len(self.grid.mines) - len(flags),

                                    "s" if (len(self.grid.mines) - len(flags) != 1) else "",

                                )

                            )

                    continue

            if not self.valid_coordinates(coordinates):

                self.message = "invalid coordinates"

                continue

            row, column, flag = self.parse_coordinates(coordinates)

            if flag:

                if self.grid.known[row][column] == " ":

                    self.grid.known[row][column] = "F"

                    flags.add((row, column))

                elif self.grid.known[row][column] == "F":

                    self.grid.known[row][column] = " "

                    flags.remove((row, column))

                else:

                    self.message = "cannot put a flag there"

            elif (row, column) in flags:

                self.message = "there is a flag there"

            elif self.grid.hidden[row][column] == "X":

                cls()

                print()

                print(self.grid.show(self.grid.hidden))

                print()

                print("you lose")

                return

            elif self.grid.known[row][column] == " ":

                self.grid.show_cell((row, column))

            else:

                self.message = "that cell is already shown"

        cls()

        print()

        print(self.grid.show(self.grid.hidden))

        print()

        print("you win")

    def parse_coordinates(self, coordinates: str) -> tuple:

        """

        generates a (row : int, column : int, flag : bool) tuple from coordinates

        arguments:

            coordinates :: str   :: the coordinates convert

        returns:

                        :: tuple :: a (row : int, column : int, flag : bool) tuple

        """

        pattern = re.compile(

            r"^(?P<column>[A-{0}])(?P<row>{1})(?P<flag>F?)$".format(

                string.ascii_uppercase[self.grid_size - 1],

                "|".join([str(i) for i in range(1, self.grid_size + 1)][::-1]),

            )

        )

        match = re.match(pattern, coordinates)

        row = int(match.group("row")) - 1

        column = string.ascii_uppercase.index(match.group("column"))

        flag = match.group("flag") == "F"

        return (row, column, flag)

    def start(self):

        """

        calls `self.game` in a 'would you like to play again?' loop

        """

        choice = "y"

        while choice.startswith("y"):

            cls()

            print(pyfiglet.figlet_format("Minesweeper"))

            print()

            input("enter to play\nctrl + c to quit to main menu\n\n")

            self.game()

            choice = input("\nwould you like to play again?\n> ").strip()

    def valid_coordinates(self, coordinates: str) -> bool:

        """

        determines whether `coordinates` are valid

        arguments:

            coordinates :: str  :: the coordinates to check

        returns:

                        :: bool :: whether `coordinates` are valid

        """

        pattern = re.compile(

            r"^(?P<column>[A-{0}])(?P<row>{1})(?P<flag>F?)$".format(

                string.ascii_uppercase[self.grid_size - 1],

                "|".join([str(i) for i in range(1, self.grid_size + 1)][::-1]),

            )

        )

        match = re.match(pattern, coordinates)

        if match:

            return True

        return False

if __name__ == "__main__":

    difficulty = None

    while difficulty not in {"easy", "intermediate", "hard"}:

        cls()

        print()

        difficulty = input("difficulty;\n> ").strip()

    game = Minesweeper(difficulty)

    game.start()

Variables

COLORS
GRID_SIZES
MINE_COUNTS

Classes

Grid

class Grid(
    size: int,
    mine_count: int
)
View Source
class Grid:

    def __init__(self, size: int, mine_count: int):

        """

        initializes a `Grid` object

        """

        self.size = size

        self.mine_count = mine_count

        self.known = [[" " for (i) in range(self.size)] for (j) in range(self.size)]

    def get_neighbors(self, cell: tuple) -> set:

        """

        gets a set of surrounding cells from `cell`

        arguments:

            cell     :: tuple :: the cell to surround

        returns:

           neighbors :: set   :: a set of surrounding cells

        """

        row_index, column_index = cell

        neighbors = set()

        for i in range(-1, 2):

            for j in range(-1, 2):

                if (i == 0) and (j == 0):

                    # this is `cell`

                    continue

                elif (-1 < (row_index + i) < self.size) and (-1 < (column_index + j) < self.size):

                    neighbors.add((row_index + i, column_index + j))

        return neighbors

    def get_random_cell(self) -> tuple:

        """

        gets a random (row, column) tuple

        returns:

            :: tuple :: a random (row, column) tuple

        """

        return (random.randint(0, self.size - 1), random.randint(0, self.size - 1))

    def generate_mines(self, start_cell: tuple):

        """

        generates a set of mines excluding `start_cell`

        arguments:

            start_cell :: tuple :: the player's first selected cell

        """

        self.mines = set()

        neighbors = self.get_neighbors(start_cell)

        while len(self.mines) != self.mine_count:

            cell = self.get_random_cell()

            if (cell == start_cell) or (cell in neighbors):

                continue

            self.mines.add(cell)

        for (row, column) in self.mines:

            self.hidden[row][column] = "X"

    def generate_numbers(self):

        """

        generates a number for each cell based on surrounding mines

        """

        for (row_index, row) in enumerate(self.hidden):

            for (column_index, cell) in enumerate(row):

                if cell != "X":

                    values = [

                        self.hidden[row][column]

                        for (row, column) in self.get_neighbors((row_index, column_index))

                    ]

                    number = values.count("X")

                    if number == 0:

                        self.hidden[row_index][column_index] = "0"

                    else:

                        self.hidden[row_index][column_index] = "{0}{1}{2}".format(

                            COLORS[number - 1], number, colorama.Fore.RESET

                        )

    def show(self, grid: list) -> str:

        """

        generates a string containing a readable form of the minesweeper grid

        arguments:

            grid        :: list :: the minesweeper grid to generate

        returns:

            grid_string :: str  :: the readable form of `grid`

        """

        horizontal = "   " + ("-" * (4 * len(grid))) + "-"

        top_label = "     "

        for character in string.ascii_uppercase[: len(grid)]:

            top_label += "{0}   ".format(character)

        grid_string = "{0}\n{1}".format(top_label, horizontal)

        for (i, j) in enumerate(grid, 1):

            row = "\n{0:>2} |".format(i)

            for k in j:

                row += " {0} |".format(k)

            grid_string += "{0}\n{1}".format(row, horizontal)

        return grid_string

    def show_cell(self, cell: tuple):

        """

        sets `cell` in `self.known` as `self.hidden` and iterates through neighbors if `self.known` == "0"

        arguments:

            cell :: tuple :: the cell to show

        """

        row_index, column_index = cell

        if self.known[row_index][column_index] != " ":

            return

        self.known[row_index][column_index] = self.hidden[row_index][column_index]

        if self.known[row_index][column_index] == "0":

            for (row_index, column_index) in self.get_neighbors((row_index, column_index)):

                if self.known[row_index][column_index] != "F":

                    self.show_cell((row_index, column_index))

    def start(self, start_cell: tuple):

        """

        generates the minesweeper grid

        arguments:

            start_cell :: tuple :: passed into `self.generate_mines` and `self.show_cell`

        """

        self.hidden = [["0" for (i) in range(self.size)] for (j) in range(self.size)]

        self.generate_mines(start_cell)

        self.generate_numbers()

        self.show_cell(start_cell)

Methods

generate_mines
def generate_mines(
    self,
    start_cell: tuple
)

generates a set of mines excluding start_cell

arguments: start_cell :: tuple :: the player's first selected cell

View Source
    def generate_mines(self, start_cell: tuple):

        """

        generates a set of mines excluding `start_cell`

        arguments:

            start_cell :: tuple :: the player's first selected cell

        """

        self.mines = set()

        neighbors = self.get_neighbors(start_cell)

        while len(self.mines) != self.mine_count:

            cell = self.get_random_cell()

            if (cell == start_cell) or (cell in neighbors):

                continue

            self.mines.add(cell)

        for (row, column) in self.mines:

            self.hidden[row][column] = "X"
generate_numbers
def generate_numbers(
    self
)

generates a number for each cell based on surrounding mines

View Source
    def generate_numbers(self):

        """

        generates a number for each cell based on surrounding mines

        """

        for (row_index, row) in enumerate(self.hidden):

            for (column_index, cell) in enumerate(row):

                if cell != "X":

                    values = [

                        self.hidden[row][column]

                        for (row, column) in self.get_neighbors((row_index, column_index))

                    ]

                    number = values.count("X")

                    if number == 0:

                        self.hidden[row_index][column_index] = "0"

                    else:

                        self.hidden[row_index][column_index] = "{0}{1}{2}".format(

                            COLORS[number - 1], number, colorama.Fore.RESET

                        )
get_neighbors
def get_neighbors(
    self,
    cell: tuple
) -> set

gets a set of surrounding cells from cell

arguments: cell :: tuple :: the cell to surround

returns: neighbors :: set :: a set of surrounding cells

View Source
    def get_neighbors(self, cell: tuple) -> set:

        """

        gets a set of surrounding cells from `cell`

        arguments:

            cell     :: tuple :: the cell to surround

        returns:

           neighbors :: set   :: a set of surrounding cells

        """

        row_index, column_index = cell

        neighbors = set()

        for i in range(-1, 2):

            for j in range(-1, 2):

                if (i == 0) and (j == 0):

                    # this is `cell`

                    continue

                elif (-1 < (row_index + i) < self.size) and (-1 < (column_index + j) < self.size):

                    neighbors.add((row_index + i, column_index + j))

        return neighbors
get_random_cell
def get_random_cell(
    self
) -> tuple

gets a random (row, column) tuple

returns: :: tuple :: a random (row, column) tuple

View Source
    def get_random_cell(self) -> tuple:

        """

        gets a random (row, column) tuple

        returns:

            :: tuple :: a random (row, column) tuple

        """

        return (random.randint(0, self.size - 1), random.randint(0, self.size - 1))
show
def show(
    self,
    grid: list
) -> str

generates a string containing a readable form of the minesweeper grid

arguments: grid :: list :: the minesweeper grid to generate

returns: grid_string :: str :: the readable form of grid

View Source
    def show(self, grid: list) -> str:

        """

        generates a string containing a readable form of the minesweeper grid

        arguments:

            grid        :: list :: the minesweeper grid to generate

        returns:

            grid_string :: str  :: the readable form of `grid`

        """

        horizontal = "   " + ("-" * (4 * len(grid))) + "-"

        top_label = "     "

        for character in string.ascii_uppercase[: len(grid)]:

            top_label += "{0}   ".format(character)

        grid_string = "{0}\n{1}".format(top_label, horizontal)

        for (i, j) in enumerate(grid, 1):

            row = "\n{0:>2} |".format(i)

            for k in j:

                row += " {0} |".format(k)

            grid_string += "{0}\n{1}".format(row, horizontal)

        return grid_string
show_cell
def show_cell(
    self,
    cell: tuple
)

sets cell in self.known as self.hidden and iterates through neighbors if self.known == "0"

arguments: cell :: tuple :: the cell to show

View Source
    def show_cell(self, cell: tuple):

        """

        sets `cell` in `self.known` as `self.hidden` and iterates through neighbors if `self.known` == "0"

        arguments:

            cell :: tuple :: the cell to show

        """

        row_index, column_index = cell

        if self.known[row_index][column_index] != " ":

            return

        self.known[row_index][column_index] = self.hidden[row_index][column_index]

        if self.known[row_index][column_index] == "0":

            for (row_index, column_index) in self.get_neighbors((row_index, column_index)):

                if self.known[row_index][column_index] != "F":

                    self.show_cell((row_index, column_index))
start
def start(
    self,
    start_cell: tuple
)

generates the minesweeper grid

arguments: start_cell :: tuple :: passed into self.generate_mines and self.show_cell

View Source
    def start(self, start_cell: tuple):

        """

        generates the minesweeper grid

        arguments:

            start_cell :: tuple :: passed into `self.generate_mines` and `self.show_cell`

        """

        self.hidden = [["0" for (i) in range(self.size)] for (j) in range(self.size)]

        self.generate_mines(start_cell)

        self.generate_numbers()

        self.show_cell(start_cell)

Minesweeper

class Minesweeper(
    difficulty: str
)
View Source
class Minesweeper:

    def __init__(self, difficulty: str):

        """

        initializes a `Minesweeper` object

        """

        self.grid_size = GRID_SIZES[difficulty]

        self.mine_count = MINE_COUNTS[difficulty]

    def game(self):

        """

        starts the game

        """

        self.grid = Grid(self.grid_size, self.mine_count)

        self.message = ""

        coordinates = ""

        while not self.valid_coordinates(coordinates) or (coordinates.endswith("F")):

            cls()

            print()

            print(self.grid.show(self.grid.known))

            print()

            coordinates = input("coordinates;\n> ").strip().upper()

            if not self.valid_coordinates(coordinates):

                self.message = "invalid coordinates"

                continue

        row, column, flag = self.parse_coordinates(coordinates)

        self.grid.start((row, column))

        flags = set()

        while not flags == self.grid.mines:

            cls()

            print()

            print(self.grid.show(self.grid.known))

            print()

            if self.message:

                print(self.message)

                print()

                self.message = ""

            else:

                print(

                    "{0} mine{1} left".format(

                        len(self.grid.mines) - len(flags),

                        "s" if (len(self.grid.mines) - len(flags) != 1) else "",

                    )

                )

                print()

            coordinates = input("coordinates;\n> ").strip().upper()

            if coordinates.startswith("EVAL("):

                # this entire block of code was me messing with the game and making cheats bc i got bored :))

                # commands are;

                # eval(va-`column``row`) -> sets self.message to the value at `column`, `row`

                # eval(win)              -> does some freaky looking printing to generate mines and win the game

                pattern_string = r"^EVAL\(VA-(?P<column>[A-{0}])(?P<row>{1})\)$".format(

                    string.ascii_uppercase[self.grid_size - 1],

                    "|".join([str(i) for i in range(1, self.grid_size + 1)][::-1]),

                )

                pattern = re.compile(pattern_string)

                match = re.match(pattern, coordinates)

                if match:

                    row = int(match.group("row")) - 1

                    column = string.ascii_uppercase.index(match.group("column"))

                    self.message = self.grid.hidden[row][column]

                    continue

                pattern_string = r"^EVAL\(WIN\)$"

                pattern = re.compile(pattern_string)

                match = re.match(pattern, coordinates)

                if match:

                    for i in range(self.grid_size):

                        for j in range(self.grid_size):

                            if self.grid.known[i][j] != " ":

                                continue

                            if self.grid.hidden[i][j] != "X":

                                continue

                            flags.add((i, j))

                            self.grid.known[i][j] = "F"

                            cls()

                            print()

                            print(self.grid.show(self.grid.known))

                            print()

                            print(

                                "{0} mine{1} left".format(

                                    len(self.grid.mines) - len(flags),

                                    "s" if (len(self.grid.mines) - len(flags) != 1) else "",

                                )

                            )

                    continue

            if not self.valid_coordinates(coordinates):

                self.message = "invalid coordinates"

                continue

            row, column, flag = self.parse_coordinates(coordinates)

            if flag:

                if self.grid.known[row][column] == " ":

                    self.grid.known[row][column] = "F"

                    flags.add((row, column))

                elif self.grid.known[row][column] == "F":

                    self.grid.known[row][column] = " "

                    flags.remove((row, column))

                else:

                    self.message = "cannot put a flag there"

            elif (row, column) in flags:

                self.message = "there is a flag there"

            elif self.grid.hidden[row][column] == "X":

                cls()

                print()

                print(self.grid.show(self.grid.hidden))

                print()

                print("you lose")

                return

            elif self.grid.known[row][column] == " ":

                self.grid.show_cell((row, column))

            else:

                self.message = "that cell is already shown"

        cls()

        print()

        print(self.grid.show(self.grid.hidden))

        print()

        print("you win")

    def parse_coordinates(self, coordinates: str) -> tuple:

        """

        generates a (row : int, column : int, flag : bool) tuple from coordinates

        arguments:

            coordinates :: str   :: the coordinates convert

        returns:

                        :: tuple :: a (row : int, column : int, flag : bool) tuple

        """

        pattern = re.compile(

            r"^(?P<column>[A-{0}])(?P<row>{1})(?P<flag>F?)$".format(

                string.ascii_uppercase[self.grid_size - 1],

                "|".join([str(i) for i in range(1, self.grid_size + 1)][::-1]),

            )

        )

        match = re.match(pattern, coordinates)

        row = int(match.group("row")) - 1

        column = string.ascii_uppercase.index(match.group("column"))

        flag = match.group("flag") == "F"

        return (row, column, flag)

    def start(self):

        """

        calls `self.game` in a 'would you like to play again?' loop

        """

        choice = "y"

        while choice.startswith("y"):

            cls()

            print(pyfiglet.figlet_format("Minesweeper"))

            print()

            input("enter to play\nctrl + c to quit to main menu\n\n")

            self.game()

            choice = input("\nwould you like to play again?\n> ").strip()

    def valid_coordinates(self, coordinates: str) -> bool:

        """

        determines whether `coordinates` are valid

        arguments:

            coordinates :: str  :: the coordinates to check

        returns:

                        :: bool :: whether `coordinates` are valid

        """

        pattern = re.compile(

            r"^(?P<column>[A-{0}])(?P<row>{1})(?P<flag>F?)$".format(

                string.ascii_uppercase[self.grid_size - 1],

                "|".join([str(i) for i in range(1, self.grid_size + 1)][::-1]),

            )

        )

        match = re.match(pattern, coordinates)

        if match:

            return True

        return False

Methods

game
def game(
    self
)

starts the game

View Source
    def game(self):

        """

        starts the game

        """

        self.grid = Grid(self.grid_size, self.mine_count)

        self.message = ""

        coordinates = ""

        while not self.valid_coordinates(coordinates) or (coordinates.endswith("F")):

            cls()

            print()

            print(self.grid.show(self.grid.known))

            print()

            coordinates = input("coordinates;\n> ").strip().upper()

            if not self.valid_coordinates(coordinates):

                self.message = "invalid coordinates"

                continue

        row, column, flag = self.parse_coordinates(coordinates)

        self.grid.start((row, column))

        flags = set()

        while not flags == self.grid.mines:

            cls()

            print()

            print(self.grid.show(self.grid.known))

            print()

            if self.message:

                print(self.message)

                print()

                self.message = ""

            else:

                print(

                    "{0} mine{1} left".format(

                        len(self.grid.mines) - len(flags),

                        "s" if (len(self.grid.mines) - len(flags) != 1) else "",

                    )

                )

                print()

            coordinates = input("coordinates;\n> ").strip().upper()

            if coordinates.startswith("EVAL("):

                # this entire block of code was me messing with the game and making cheats bc i got bored :))

                # commands are;

                # eval(va-`column``row`) -> sets self.message to the value at `column`, `row`

                # eval(win)              -> does some freaky looking printing to generate mines and win the game

                pattern_string = r"^EVAL\(VA-(?P<column>[A-{0}])(?P<row>{1})\)$".format(

                    string.ascii_uppercase[self.grid_size - 1],

                    "|".join([str(i) for i in range(1, self.grid_size + 1)][::-1]),

                )

                pattern = re.compile(pattern_string)

                match = re.match(pattern, coordinates)

                if match:

                    row = int(match.group("row")) - 1

                    column = string.ascii_uppercase.index(match.group("column"))

                    self.message = self.grid.hidden[row][column]

                    continue

                pattern_string = r"^EVAL\(WIN\)$"

                pattern = re.compile(pattern_string)

                match = re.match(pattern, coordinates)

                if match:

                    for i in range(self.grid_size):

                        for j in range(self.grid_size):

                            if self.grid.known[i][j] != " ":

                                continue

                            if self.grid.hidden[i][j] != "X":

                                continue

                            flags.add((i, j))

                            self.grid.known[i][j] = "F"

                            cls()

                            print()

                            print(self.grid.show(self.grid.known))

                            print()

                            print(

                                "{0} mine{1} left".format(

                                    len(self.grid.mines) - len(flags),

                                    "s" if (len(self.grid.mines) - len(flags) != 1) else "",

                                )

                            )

                    continue

            if not self.valid_coordinates(coordinates):

                self.message = "invalid coordinates"

                continue

            row, column, flag = self.parse_coordinates(coordinates)

            if flag:

                if self.grid.known[row][column] == " ":

                    self.grid.known[row][column] = "F"

                    flags.add((row, column))

                elif self.grid.known[row][column] == "F":

                    self.grid.known[row][column] = " "

                    flags.remove((row, column))

                else:

                    self.message = "cannot put a flag there"

            elif (row, column) in flags:

                self.message = "there is a flag there"

            elif self.grid.hidden[row][column] == "X":

                cls()

                print()

                print(self.grid.show(self.grid.hidden))

                print()

                print("you lose")

                return

            elif self.grid.known[row][column] == " ":

                self.grid.show_cell((row, column))

            else:

                self.message = "that cell is already shown"

        cls()

        print()

        print(self.grid.show(self.grid.hidden))

        print()

        print("you win")
parse_coordinates
def parse_coordinates(
    self,
    coordinates: str
) -> tuple

generates a (row : int, column : int, flag : bool) tuple from coordinates

arguments: coordinates :: str :: the coordinates convert

returns: :: tuple :: a (row : int, column : int, flag : bool) tuple

View Source
    def parse_coordinates(self, coordinates: str) -> tuple:

        """

        generates a (row : int, column : int, flag : bool) tuple from coordinates

        arguments:

            coordinates :: str   :: the coordinates convert

        returns:

                        :: tuple :: a (row : int, column : int, flag : bool) tuple

        """

        pattern = re.compile(

            r"^(?P<column>[A-{0}])(?P<row>{1})(?P<flag>F?)$".format(

                string.ascii_uppercase[self.grid_size - 1],

                "|".join([str(i) for i in range(1, self.grid_size + 1)][::-1]),

            )

        )

        match = re.match(pattern, coordinates)

        row = int(match.group("row")) - 1

        column = string.ascii_uppercase.index(match.group("column"))

        flag = match.group("flag") == "F"

        return (row, column, flag)
start
def start(
    self
)

calls self.game in a 'would you like to play again?' loop

View Source
    def start(self):

        """

        calls `self.game` in a 'would you like to play again?' loop

        """

        choice = "y"

        while choice.startswith("y"):

            cls()

            print(pyfiglet.figlet_format("Minesweeper"))

            print()

            input("enter to play\nctrl + c to quit to main menu\n\n")

            self.game()

            choice = input("\nwould you like to play again?\n> ").strip()
valid_coordinates
def valid_coordinates(
    self,
    coordinates: str
) -> bool

determines whether coordinates are valid

arguments: coordinates :: str :: the coordinates to check

returns: :: bool :: whether coordinates are valid

View Source
    def valid_coordinates(self, coordinates: str) -> bool:

        """

        determines whether `coordinates` are valid

        arguments:

            coordinates :: str  :: the coordinates to check

        returns:

                        :: bool :: whether `coordinates` are valid

        """

        pattern = re.compile(

            r"^(?P<column>[A-{0}])(?P<row>{1})(?P<flag>F?)$".format(

                string.ascii_uppercase[self.grid_size - 1],

                "|".join([str(i) for i in range(1, self.grid_size + 1)][::-1]),

            )

        )

        match = re.match(pattern, coordinates)

        if match:

            return True

        return False