【Python-pygame实战】Python 制作经典扫雷游戏(附源码)

这次我们基于 pygame 来做一个扫雷

先看截图,仿照 XP 上的扫雷做的

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

首先,如何表示雷和非雷,一开始想的是,建立一个二维数组表示整个区域,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

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

import sys
import time
from enum import Enum
import pygame
from pygame.locals import \*
from mineblock import \*

# 游戏屏幕的宽
SCREEN\_WIDTH = BLOCK\_WIDTH \* SIZE
# 游戏屏幕的高
SCREEN\_HEIGHT = (BLOCK\_HEIGHT + 2) \* SIZE

class GameStatus(Enum):
    readied \= 1,
    started \= 2,
    over \= 3,
    win \= 4

def print\_text(screen, font, x, y, text, fcolor=(255, 255, 255)):
    imgText \= font.render(text, True, fcolor)
    screen.blit(imgText, (x, y))

def main():
    pygame.init()
    screen \= pygame.display.set\_mode((SCREEN\_WIDTH, SCREEN\_HEIGHT))
    pygame.display.set\_caption('扫雷')

    font1 \= pygame.font.Font('resources/a.TTF', SIZE \* 2)  # 得分的字体
    fwidth, fheight = font1.size('999')
    red \= (200, 40, 40)

    # 加载资源图片,因为资源文件大小不一,所以做了统一的缩放处理
    img0 = pygame.image.load('resources/0.bmp').convert()
    img0 \= pygame.transform.smoothscale(img0, (SIZE, SIZE))
    img1 \= pygame.image.load('resources/1.bmp').convert()
    img1 \= pygame.transform.smoothscale(img1, (SIZE, SIZE))
    img2 \= pygame.image.load('resources/2.bmp').convert()
    img2 \= pygame.transform.smoothscale(img2, (SIZE, SIZE))
    img3 \= pygame.image.load('resources/3.bmp').convert()
    img3 \= pygame.transform.smoothscale(img3, (SIZE, SIZE))
    img4 \= pygame.image.load('resources/4.bmp').convert()
    img4 \= pygame.transform.smoothscale(img4, (SIZE, SIZE))
    img5 \= pygame.image.load('resources/5.bmp').convert()
    img5 \= pygame.transform.smoothscale(img5, (SIZE, SIZE))
    img6 \= pygame.image.load('resources/6.bmp').convert()
    img6 \= pygame.transform.smoothscale(img6, (SIZE, SIZE))
    img7 \= pygame.image.load('resources/7.bmp').convert()
    img7 \= pygame.transform.smoothscale(img7, (SIZE, SIZE))
    img8 \= pygame.image.load('resources/8.bmp').convert()
    img8 \= pygame.transform.smoothscale(img8, (SIZE, SIZE))
    img\_blank \= pygame.image.load('resources/blank.bmp').convert()
    img\_blank \= pygame.transform.smoothscale(img\_blank, (SIZE, SIZE))
    img\_flag \= pygame.image.load('resources/flag.bmp').convert()
    img\_flag \= pygame.transform.smoothscale(img\_flag, (SIZE, SIZE))
    img\_ask \= pygame.image.load('resources/ask.bmp').convert()
    img\_ask \= pygame.transform.smoothscale(img\_ask, (SIZE, SIZE))
    img\_mine \= pygame.image.load('resources/mine.bmp').convert()
    img\_mine \= pygame.transform.smoothscale(img\_mine, (SIZE, SIZE))
    img\_blood \= pygame.image.load('resources/blood.bmp').convert()
    img\_blood \= pygame.transform.smoothscale(img\_blood, (SIZE, SIZE))
    img\_error \= pygame.image.load('resources/error.bmp').convert()
    img\_error \= pygame.transform.smoothscale(img\_error, (SIZE, SIZE))
    face\_size \= int(SIZE \* 1.25)
    img\_face\_fail \= pygame.image.load('resources/face\_fail.bmp').convert()
    img\_face\_fail \= pygame.transform.smoothscale(img\_face\_fail, (face\_size, face\_size))
    img\_face\_normal \= pygame.image.load('resources/face\_normal.bmp').convert()
    img\_face\_normal \= pygame.transform.smoothscale(img\_face\_normal, (face\_size, face\_size))
    img\_face\_success \= pygame.image.load('resources/face\_success.bmp').convert()
    img\_face\_success \= pygame.transform.smoothscale(img\_face\_success, (face\_size, face\_size))
    face\_pos\_x \= (SCREEN\_WIDTH - face\_size) // 2
    face\_pos\_y \= (SIZE \* 2 - face\_size) // 2

    img\_dict \= {
        0: img0,
        1: img1,
        2: img2,
        3: img3,
        4: img4,
        5: img5,
        6: img6,
        7: img7,
        8: img8
    }

    bgcolor \= (225, 225, 225)   # 背景色
    block \= MineBlock()
    game\_status \= GameStatus.readied
    start\_time \= None   # 开始时间
    elapsed\_time = 0    # 耗时

    while True:
        # 填充背景色
        screen.fill(bgcolor)

        for event in pygame.event.get():
            if event.type == QUIT:
                sys.exit()
            elif event.type == MOUSEBUTTONDOWN:
                mouse\_x, mouse\_y \= event.pos
                x \= mouse\_x // SIZE
                y \= mouse\_y // SIZE - 2
                b1, b2, b3 \= pygame.mouse.get\_pressed()
                if game\_status == GameStatus.started:
                    # 鼠标左右键同时按下,如果已经标记了所有雷,则打开周围一圈
                    # 如果还未标记完所有雷,则有一个周围一圈被同时按下的效果
                    if b1 and b3:
                        mine \= block.getmine(x, y)
                        if mine.status == BlockStatus.opened:
                            if not block.double\_mouse\_button\_down(x, y):
                                game\_status \= GameStatus.over
            elif event.type == MOUSEBUTTONUP:
                if y < 0:
                    if face\_pos\_x <= mouse\_x <= face\_pos\_x + face\_size \\
                            and face\_pos\_y <= mouse\_y <= face\_pos\_y + face\_size:
                        game\_status \= GameStatus.readied
                        block \= MineBlock()
                        start\_time \= time.time()
                        elapsed\_time \= 0
                        continue

                if game\_status == GameStatus.readied:
                    game\_status \= GameStatus.started
                    start\_time \= time.time()
                    elapsed\_time \= 0

                if game\_status == GameStatus.started:
                    mine \= block.getmine(x, y)
                    if b1 and not b3:       # 按鼠标左键
                        if mine.status == BlockStatus.normal:
                            if not block.open\_mine(x, y):
                                game\_status \= GameStatus.over
                    elif not b1 and b3:     # 按鼠标右键
                        if mine.status == BlockStatus.normal:
                            mine.status \= BlockStatus.flag
                        elif mine.status == BlockStatus.flag:
                            mine.status \= BlockStatus.ask
                        elif mine.status == BlockStatus.ask:
                            mine.status \= BlockStatus.normal
                    elif b1 and b3:
                        if mine.status == BlockStatus.double:
                            block.double\_mouse\_button\_up(x, y)

        flag\_count \= 0
        opened\_count \= 0

        for row in block.block:
            for mine in row:
                pos \= (mine.x \* SIZE, (mine.y + 2) \* SIZE)
                if mine.status == BlockStatus.opened:
                    screen.blit(img\_dict\[mine.around\_mine\_count\], pos)
                    opened\_count += 1
                elif mine.status == BlockStatus.double:
                    screen.blit(img\_dict\[mine.around\_mine\_count\], pos)
                elif mine.status == BlockStatus.bomb:
                    screen.blit(img\_blood, pos)
                elif mine.status == BlockStatus.flag:
                    screen.blit(img\_flag, pos)
                    flag\_count += 1
                elif mine.status == BlockStatus.ask:
                    screen.blit(img\_ask, pos)
                elif mine.status == BlockStatus.hint:
                    screen.blit(img0, pos)
                elif game\_status == GameStatus.over and mine.value:
                    screen.blit(img\_mine, pos)
                elif mine.value == 0 and mine.status == BlockStatus.flag:
                    screen.blit(img\_error, pos)
                elif mine.status == BlockStatus.normal:
                    screen.blit(img\_blank, pos)

        print\_text(screen, font1, 30, (SIZE \* 2 - fheight) // 2 - 2, '%02d' % (MINE\_COUNT - flag\_count), red)
        if game\_status == GameStatus.started:
            elapsed\_time \= int(time.time() - start\_time)
        print\_text(screen, font1, SCREEN\_WIDTH \- fwidth - 30, (SIZE \* 2 - fheight) // 2 - 2, '%03d' % elapsed\_time, red)

        if flag\_count + opened\_count == BLOCK\_WIDTH \* BLOCK\_HEIGHT:
            game\_status \= GameStatus.win

        if game\_status == GameStatus.over:
            screen.blit(img\_face\_fail, (face\_pos\_x, face\_pos\_y))
        elif game\_status == GameStatus.win:
            screen.blit(img\_face\_success, (face\_pos\_x, face\_pos\_y))
        else:
            screen.blit(img\_face\_normal, (face\_pos\_x, face\_pos\_y))

        pygame.display.update()

if \_\_name\_\_ == '\_\_main\_\_':
    main()

需要游戏素材,和完整代码,可在点击蓝色链接获取,备注:超级玛丽 即可获取,长期有效。
如有侵权,请联系删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值