【SDK社区】欢迎来到python扫雷

Posted on 2021-05-14 16:43  sesen  阅读(184)  评论(0编辑  收藏  举报

python实现扫雷小游戏

  • 扫雷
    • 准备工作
    • 设计原理
    • 实施步骤
      • 1.定义一个类用来表示方块的状态
    • -2.再定义一个类用来获取坐标,周边地雷的数量,方块的状态
    • -3.随机生成地雷,递归打开周边无雷的方块
    • -4.主函数:窗口绘制,图形加载,事件处理
  • -游戏效果

扫雷

新人博主,学习python一个多月,请大家多多关照,不足之处,敬请雅正。

准备工作

下载制作扫雷游戏所需要的一些图片,如数字0-8,雷等。 链接: link.

设计原理

定义一个类block_condition用来判断方块的状态。再用个类Mine返回方块的性质:坐标,是否为雷,周边雷的数量。类mine_block用来埋地雷和点击的效果,点到雷,返回False,没点到雷就点开这个方块,如果点击的方块周边无雷,则向周围递归打开,可以实现一点就是一片的效果。判断游戏胜利的标志为:点开的方块加地雷数等于总数,失败的标志为:踩到地雷。

实施步骤

1.定义一个类用来表示方块的状态

代码如下

class Block_Condition():
    unclicked = 1  # 未点击
    clicked = 2  # 已点击
    mine = 3    # 地雷
    flag = 4    # 标记为地雷
    ask = 5   # 标记为问号
    bomb = 6    # 踩中地雷

2.再定义一个类用来获取坐标,周边地雷的数量,方块的状态

代码如下

from block_condition import *

class Mine:
    def __init__(self, x, y, value=0):
        self._x = x
        self._y = y
        self._value = 0
        self._surround_count = -1 #周边雷的数量
        self._condition = Block_Condition.unclicked #初始设置为未点击
        self.set_value(value)

    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)

    def get_surround_count(self): #获取周边雷的数量
        return self._surround_count
    def set_surround_count(self, surround_count):
        self._surround_count = surround_count
    surround_count = property(fget=get_surround_count, fset=set_surround_count)

    def get_condition(self): #方块的状态
        return self._condition
    def set_condition(self, value):
        self._condition = value
    condition = property(fget=get_condition, fset=set_condition)

3.随机生成地雷,递归打开周边无雷的方块

要想实现扫雷,每个方块都得是一个类,包含其坐标,自身的状态(是否是雷或是周边有多少地雷),所以需要定义一个二维数组,每个元素都是Mine类。而要想实现一点开就是一片的效果,则需要函数返回某个方块周边的方块,向周边打开(只打开周边没有地雷的方块),形成一点就是一片的效果。游戏可以从代码里调整方块和地雷的数量来改变难度。

import random
import time
from Mine import *
from block_condition import *
BLOCK_WIDTH=30
BLOCK_HEIGHT=20
MINE_COUNT=99 #地雷数量

list_mine=[[]]
list_mine=[[Mine(i,j) for i in range(BLOCK_WIDTH)] for j in range(BLOCK_HEIGHT)]
def get_around(x, y):
    #返回(x, y)周围一圈的点
        return [(i, j) for i in range(max(0, x - 1), min(BLOCK_WIDTH - 1, x + 1) + 1) #range是左闭右开
                for j in range(max(0, y - 1), min(BLOCK_HEIGHT - 1, y + 1) + 1) ]
class Mine_Block:
    def __init__(self):
        self._block = list_mine

        # 埋下地雷的种子
        for i in random.sample(range(BLOCK_WIDTH * BLOCK_HEIGHT), MINE_COUNT):
            self._block[i // BLOCK_WIDTH][i % BLOCK_WIDTH].value = 1
    def getblock(self, x, y):
        return self._block[y][x]
    def open_block(self, x, y):
        # 真不幸,这是颗地雷
        if self._block[y][x].value:
            self._block[y][x].condition = Block_Condition.bomb
            return False

        self._block[y][x].condition = Block_Condition.clicked #没踩雷

        around = get_around(x, y)

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

        if sum1 == 0: #周边无雷,向旁边递归,一点打开一片
            for i, j in around:
                if self._block[j][i].surround_count == -1:
                    self.open_block(i, j)

        return True


4.主函数:窗口绘制,图形加载,事件处理

先做一些准备工作,引入一些库,其中sys库用sys.exit()来结束程序,time库则是用来加载时间,pygame库用来绘制窗口,加载图片,处理事件等,是我们这次扫雷游戏的核心库函数。

import sys
import time
import pygame
from pygame.locals import *
from mine_block import *
from Mine import *
import block_condition 
BLOCK_WIDTH = 30 #横向30块
BLOCK_HEIGHT = 20 #纵向20块
SIZE=30 #一个小方块的边长
SCREEN_WIDTH = BLOCK_WIDTH * SIZE # 游戏屏幕的宽
SCREEN_HEIGHT = (BLOCK_HEIGHT + 2) * SIZE # 游戏屏幕的高

ready=0
start=1
win=2
lose=3
game_dict=[ready, start, win, lose]#四个标志用来判断游戏的进行与否
#ready:准备就绪  start:游戏开始  win:胜利  lose:失败

下面是主函数,设置屏幕标题,加载图片(加载图片都是同样的代码,此处我就放一个)其他图片以同样的方式加载就行

def main():
    pygame.init()
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) #设置窗口大小
    pygame.display.set_caption('欢迎来到艾欧尼亚->扫雷') #设置标题

    png0 = pygame.image.load('./number_mine/0.png').convert() #png支持高级别无损耗压缩
    png0 = pygame.transform.smoothscale(png0, (SIZE, SIZE)) #将曲面缩放到指定大小,使得图片一样大

下面是加载笑脸,笑脸用来判断游戏的输赢状态,点击笑脸可以重新开始游戏

face_size = int(SIZE * 1.5)#设置笑脸的大小
png_face_lose = pygame.image.load('./face/3.png').convert()
png_face_lose = pygame.transform.smoothscale(png_face_lose, (face_size, face_size))
png_face_normal = pygame.image.load('./face/1.png').convert()
png_face_normal = pygame.transform.smoothscale(png_face_normal, (face_size, face_size))
png_face_win = pygame.image.load('./face/4.png').convert()
png_face_win = pygame.transform.smoothscale(png_face_win, (face_size, face_size))
face_pos_x = (SCREEN_WIDTH - face_size) // 2 
face_pos_y = (SIZE * 2 - face_size) // 2 #笑脸的坐标


此处用一个字典对应雷的数量以及相应的图片

png_dict = {0: png0, 1: png1, 2: png2, 3: png3, 4: png4, 5: png5, 6: png6, 7: png7, 8: png8} #依据雷的数目加载相应图片
bgcolor = (240, 200, 200) #背景色
block = Mine_Block()
game_dict1=game_dict[0] #准备就绪
start_time = None #开始时间
used_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()#鼠标点击的位置,b1对应左键,b3对应右键
            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_dict1=game_dict[0] #重置数据,开始新一局游戏
                        start_time = time.time()
                        used_time = 0
                        for i in list_mine:
                            for j in i:
                                pos = (j.x * SIZE, (j.y + 2) * SIZE)
                                j.surround_count = -1 #把周边雷的数目回归到原始状态
                                j.condition = Block_Condition.unclicked #所有方块回归为未点击状态
                                screen.blit(png_blank, pos)
                        pygame.display.flip()
                        for i in random.sample(range(BLOCK_WIDTH * BLOCK_HEIGHT), BLOCK_WIDTH * BLOCK_HEIGHT):
                            list_mine[i // BLOCK_WIDTH][i % BLOCK_WIDTH].value = 0 #把上一轮雷的定义全部清除                        
                        block=Mine_Block() #重新定义雷
                        continue
                    else:
                        continue
                    
                if game_dict1 == game_dict[0]: #状态为准备就绪
                    game_dict1 = game_dict[1] #开始
                    start_time = time.time() #开始计时

                if game_dict1 == game_dict[1]:
                    mine = block.getblock(x, y)
                    if b1 and not b3: #按鼠标左键
                        if mine.condition == Block_Condition.unclicked: #未点击的会点开
                            if not block.open_block(x, y): #中奖了啊,一颗雷
                                game_dict1 = game_dict[3] #游戏状态为失败
                    elif not b1 and b3: #按鼠标右键
                        if mine.condition == Block_Condition.unclicked: #如果未点击,则标记为旗帜(雷)
                            mine.condition = Block_Condition.flag
                        elif mine.condition == Block_Condition.flag: #如果之前的标记是旗帜,则改标记为问号
                            mine.condition = Block_Condition.ask
                        elif mine.condition == Block_Condition.ask: #如果之前的标记是问号,则改为未点击的状态
                            mine.condition = Block_Condition.unclicked

        flag_count = 0 #旗帜为0
        opened_count = 0 #点开的方块为0

        for row in block._block:
            for mine in row:
                pos = (mine.x * SIZE, (mine.y + 2) * SIZE)
                if mine.condition == Block_Condition.clicked: #点开了加载图片(数字),点开的方块加一
                    screen.blit(png_dict[mine.surround_count], pos)
                    opened_count += 1
                elif mine.condition == Block_Condition.bomb: #状态是炸弹
                    screen.blit(png_blood, pos)
                elif mine.condition == Block_Condition.flag: #是旗帜
                    screen.blit(png_flag, pos)
                    flag_count += 1
                elif mine.condition == Block_Condition.ask: #是问号
                    screen.blit(png_ask, pos)
                elif game_dict1 == game_dict[3] and mine.value:
                    screen.blit(png_mine, pos) #失败了显示所有地雷
                elif mine.condition == Block_Condition.unclicked: #是未打开
                    screen.blit(png_blank, pos)
        if event.type == MOUSEMOTION:#用特定的图像显示鼠标的位置
                mouse_x, mouse_y = event.pos
                x = mouse_x // SIZE
                y = mouse_y // SIZE - 2
                mine = block.getblock(x, y)
                pos = (mine.x * SIZE, (mine.y + 2) * SIZE)
                if y < 0:
                    continue
                else:
                    if mine.condition == Block_Condition.unclicked:
                        screen.blit(png_dict[0],pos)

                    
        font1 = pygame.font.Font(None, SIZE * 2)
        width, height = font1.size('100')
        text1=font1.render('%d'%(MINE_COUNT-flag_count),True,(255,0,0))
        screen.blit(text1,(30,(SIZE*2-height)//2-2)) #打印剩余雷的数量
        
        if game_dict1 == game_dict[1]:
            used_time = int(time.time() - start_time)
        text2=font1.render('%d'%used_time,True,(255,0,0))
        screen.blit(text2,(SCREEN_WIDTH-width-30,(SIZE*2-height)//2-2)) #打印已用时间

        if opened_count + MINE_COUNT == BLOCK_WIDTH * BLOCK_HEIGHT: #点开方块的加上地雷数等于总数
            game_dict1 = game_dict[2] #则视为胜利
        if game_dict1 == game_dict[2]: #游戏的状态为胜利
            screen.blit(png_face_win, (face_pos_x, face_pos_y))
        elif game_dict1 == game_dict[3]: #游戏的状态为失败
            screen.blit(png_face_lose, (face_pos_x, face_pos_y))
        else:
            screen.blit(png_face_normal, (face_pos_x, face_pos_y))

        pygame.display.update()

if __name__ == '__main__':
    main()


游戏效果

1.游戏开始画面
在这里插入图片描述
2.鼠标显示
在这里插入图片描述
3.游戏失败画面(有可能第一次点击就会点到雷哦)
在这里插入图片描述
4.游戏胜利画面
在这里插入图片描述
有兴趣的小伙伴可以自己写一写,欢迎前来交流~后续可能会添加新的功能,希望大家多多支持!

本文转自SDK社区:https://www.sdk.cn/

Copyright © 2024 sesen
Powered by .NET 9.0 on Kubernetes