Python:游戏:扫雷

作者: 丹枫无迹 | 来源:发表于2019-03-07 19:55 被阅读43次

    本文代码基于 python3.6 和 pygame1.9.4。

    这次,我们来模仿做一个 XP 上的扫雷,感觉 XP 上的样式比 win7 上的好看多了。

    原谅我手残,扫雷基本就没赢过,测试的时候我是偷偷的把雷的数量从99改到50才赢了。。。

    下面将一下我的实现逻辑。

    首先,如何表示雷和非雷,一开始想的是,建立一个二维数组表示整个区域,0表示非地雷,1表示地雷。后来一想不对,还有标记为地雷,标记为问号,还有表示周边雷数的数字,好多状态,干脆就做个类吧

    class BlockStatus(Enum):
        normal = 1  # 未点击
        opened = 2  # 已点击
        mine = 3    # 地雷
        flag = 4    # 标记为地雷
        ask = 5   # 标记为问号
        bomb = 6    # 踩中地雷
        hint = 7    # 被双击的周围
        double = 8  # 正被鼠标左右键双击
    
    
    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:非地雷 1:雷')
    
        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='四周地雷数量')
    
        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')
    

    布雷就很简单了,随机取99个数,从上往下顺序排就是了。

    class MineBlock:
        def __init__(self):
            self._block = [[Mine(i, j) for i in range(BLOCK_WIDTH)] for j in range(BLOCK_HEIGHT)]
    
            # 埋雷
            for i in random.sample(range(BLOCK_WIDTH * BLOCK_HEIGHT), MINE_COUNT):
                self._block[i // BLOCK_WIDTH][i % BLOCK_WIDTH].value = 1
    

    我们点击一个格子的时候,只要根据点击的坐标,找到对应的 Mine,看它的值是多少,就知道有没有踩中雷了。

    如果没踩中雷的话,要计算周边8个位置中有几个雷,以便显示对应的数字。

    如果周边有雷,那么显示数字,这个简单,可是如果周边没有雷,那就要显示一片区域,直到有雷出现,如下图,我只点了当中一下,就出现了那么大一片区域

    这个计算其实也容易,只要用递归就可以了,如果计算出周围的雷数为0,则递归计算周边8个位置的四周雷数,直到雷数不为0。

    class MineBlock:
      def open_mine(self, x, y):
            # 踩到雷了
            if self._block[y][x].value:
                self._block[y][x].status = BlockStatus.bomb
                return False
    
            # 先把状态改为 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
    
            # 如果周围没有雷,那么将周围8个未中未点开的递归算一遍
            # 这就能实现一点出现一大片打开的效果了
            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):
        """返回(x, y)周围的点的坐标"""
        # 这里注意,range 末尾是开区间,所以要加 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) if i != x or j != y]
    

    接下来还有一个麻烦的地方,我们经常鼠标左右键同时按下,如果雷被全部标记,则会一下子打开周围所有的格子,如果其中有标记错的,那么不好意思,GAME OVER。

    如果没有全标记完,会有一个效果显示周围一圈未被打开和标记的格子

    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     # 周围被标记的雷数量
            for i, j in _get_around(x, y):
                if self._block[j][i].status == BlockStatus.flag:
                    sumflag += 1
            # 周边的雷已经全部被标记
            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
    

    扫雷的主要逻辑就这么多,剩下来的就是一些杂七杂八的事件了。


    相关博文推荐:

    Python:游戏:贪吃蛇

    Python:游戏:300行代码实现俄罗斯方块

    Python:游戏:五子棋之人机对战


    扫码关注我的个人公众号,回复 “扫雷” 获取源码。


    扫码关注我的个人公众号

    相关文章

      网友评论

        本文标题:Python:游戏:扫雷

        本文链接:https://www.haomeiwen.com/subject/faecpqtx.html