This code is based on python3.6 and pygame1.9.4.

This time, we will imitate to do a XP minesweeper, feel XP style than win7 on much better.

Forgive me, minesweeper is almost never won, I secretly changed the number of mines from 99 to 50 in the test to win…

Here’s my implementation logic.

First of all, how do you represent mines and non-mines? Well, the first idea was to create a two-dimensional array for the entire region, 0 for non-mines, 1 for mines. Later, I thought it was not right, and there were marks for mines, marks for question marks, and numbers indicating the number of surrounding mines. Many states, simply make a class

class BlockStatus(Enum):
    normal = 1  # not click
    opened = 2  # has click
    mine = 3    # mine
    flag = 4    # Mark as mine
    ask = 5   # marked with a question mark
    bomb = 6    Step on a landmine
    hint = 7    # be double-click around
    double = 8  # is being double-clicked by the left and right mouse button


class Mine:
    def __init__(self, x, y, value=0):
        self._x = x
        self._y = y
        self._value = 0
        self._around_mine_count = - 1
        self._status = BlockStatus.normal
        self.set_value(value)

    def __repr__(self):
        return str(self._value)
        # return f'({self._x},{self._y})={self._value}, status={self.status}'

    def get_x(self):
        return self._x

    def set_x(self, x):
        self._x = x

    x = property(fget=get_x, fset=set_x)

    def get_y(self):
        return self._y

    def set_y(self, y):
        self._y = y

    y = property(fget=get_y, fset=set_y)

    def get_value(self):
        return self._value

    def set_value(self, value):
        if value:
            self._value = 1
        else:
            self._value = 0

    value = property(fget=get_value, fset=set_value, doc='0: non-mine 1: mine ')

    def get_around_mine_count(self):
        return self._around_mine_count

    def set_around_mine_count(self, around_mine_count):
        self._around_mine_count = around_mine_count

    around_mine_count = property(fget=get_around_mine_count, fset=set_around_mine_count, doc='Number of mines around')

    def get_status(self):
        return self._status

    def set_status(self, value):
        self._status = value

    status = property(fget=get_status, fset=set_status, doc='BlockStatus')
Copy the code

It’s very easy to lay a mine, just pick 99 numbers at random and go from top to bottom.

class MineBlock:
    def __init__(self):
        self._block = [[Mine(i, j) for i in range(BLOCK_WIDTH)] for j in range(BLOCK_HEIGHT)]

        # mine
        for i in random.sample(range(BLOCK_WIDTH * BLOCK_HEIGHT), MINE_COUNT):
            self._block[i // BLOCK_WIDTH][i % BLOCK_WIDTH].value = 1
Copy the code

When we click on a grid, as long as we find the corresponding Mine according to the coordinates of the click and see what its value is, we can know whether we have stepped on the thunder.

If you don’t step on a lightning, you need to count the number of lightning in eight surrounding locations to display the corresponding number.

If there is lightning around, then display the number, this is easy, but if there is no lightning around, then display an area, until there is lightning, as shown in the picture below, I only click on the middle, there is a large area

This calculation is also easy, just use recursion, if the number of nearby lightning is 0, recursively calculate the number of nearby lightning in 8 locations, until the number of lightning is not 0.

class MineBlock:   def open_mine(self, x, y):
        # Stepped on thunder
        if self._block[y][x].value:
            self._block[y][x].status = BlockStatus.bomb
            return False

        Change state to Opened
        self._block[y][x].status = BlockStatus.opened

        around = _get_around(x, y)

        _sum = 0
        for i, j in around:
            if self._block[j][i].value:
                _sum += 1
        self._block[y][x].around_mine_count = _sum

        # If there is no thunder around, then the recursion of the surrounding 8 unmatched dots is performed
        # This can be achieved at one point a large open effect
        if _sum == 0:
            for i, j in around:
                if self._block[j][i].around_mine_count == - 1:
                    self.open_mine(i, j)

        return True


def _get_around(x, y):
    "" return the coordinates of the points around (x, y) ""
    # Notice that the end of range is the open range, so we have to add 1
    return [(i, j) for i in range(max(0, x - 1), min(BLOCK_WIDTH - 1, x + 1) + 1)
            for j in range(max(0, y - 1), min(BLOCK_HEIGHT - 1, y + 1) + 1) ifi ! = xorj ! = y]Copy the code

Then there is another trouble, we often mouse button down at the same time, if the ray is all marked, it will open all the surrounding grid, if there is a wrong mark, sorry, GAME OVER.

If not, there is an effect that shows a circle of unopened and unmarked squares

class MineBlock:   def double_mouse_button_down(self, x, y):
        if self._block[y][x].around_mine_count == 0:
            return True

        self._block[y][x].status = BlockStatus.double

        around = _get_around(x, y)

        sumflag = 0     # Number of mines marked around
        for i, j in _get_around(x, y):
            if self._block[j][i].status == BlockStatus.flag:
                sumflag += 1
        # All surrounding mines have been marked
        result = True
        if sumflag == self._block[y][x].around_mine_count:
            for i, j in around:
                if self._block[j][i].status == BlockStatus.normal:
                    if not self.open_mine(i, j):
                        result = False
        else:
            for i, j in around:
                if self._block[j][i].status == BlockStatus.normal:
                    self._block[j][i].status = BlockStatus.hint
        return result

    def double_mouse_button_up(self, x, y):
        self._block[y][x].status = BlockStatus.opened
        for i, j in _get_around(x, y):
            if self._block[j][i].status == BlockStatus.hint:
                self._block[j][i].status = BlockStatus.normal
Copy the code

So much for the main logic of mine clearance, which leaves a miscellany of events.


Related blog recommendations:

  • Python: Game: Snake
  • Python: Games: Write a “minesweeper” exactly like on XP
  • Python: Game: 300 lines of code to implement Tetris
  • Python: Game: Gobang vs man

Scan the code to pay attention to my personal public number, reply “minesweeper” to obtain the source code.