2020.7.23

  1. 参考:用Python和Pygame写游戏-从入门到精通(1)Python 游戏:扫雷用pygame.image.load加载时像素化和不完整的图像Pygame详解(四):event 模块参考游戏下载:Windows 7 Games for Windows 10 and 8图片素材 剩下7.8我用ps按样式画了差不多的。虽然所有图标组合起来后我被自己的界面丑到了- -…但是无所谓了主要想练习怎么实现其它推荐:扫雷网
  2. 运行环境
    Win10,anaconda+python 3.8.3
  3. 代码
# -*- coding: utf-8 -*-

from enum import Enum
import random
import pygame
from pygame.locals import *
from sys import exit
import datetime
import time

class STATE(Enum):
    NORMAL = 1  # 未点击
    NUMBERED = 2# 有数字(周围有雷)
    BLANK = 3   # 空白的(周围无雷)
    FLAG = 4    # 标记为地雷
    QUESTION = 5# 标记为问号

class GameMinesweeper(object):
    def __init__(self):
        self.BLOCK_SIZE = 20
        self.ROW, self.COL = 16, 30
        self.NUM_MINE = 99
        self.num_opened = 0

        self.block_state = dict()
        self.has_mine = dict()
        for i in range(self.ROW):
            for j in range(self.COL):
                self.block_state[(i, j)] = STATE.NORMAL
                self.has_mine[(i, j)] = False
        
        pos_list = list(range(self.ROW * self.COL))
        random.shuffle(pos_list)
        for pos in pos_list[0:self.NUM_MINE]:
            i, j = pos // self.COL, pos % self.COL
            self.has_mine[(i, j)] = True
    

    def CalNumMine(self, screen, x, y, i, j): # 计算周围8个格中的雷数
        sum_mine = 0
        need_check = []
        DI, DJ = [-1, -1, -1, 0, 0, 1, 1, 1], [-1, 0, 1, -1, 1, -1, 0, 1]
        for k in range(8):
            newi, newj = i + DI[k], j + DJ[k]
            if newi >= 0 and newi < self.ROW and newj >= 0 and newj < self.COL:
                if self.has_mine[(newi, newj)]:
                    sum_mine += 1
                if self.block_state[(newi, newj)] == STATE.NORMAL:
                    need_check.append((newi, newj))
        
        self.num_opened += 1
        if sum_mine:
            self.block_state[(i, j)] = STATE.NUMBERED
            screen.blit(self.img_block, (x, y))
            exec("screen.blit(self.img{}, (x, y))".format(sum_mine))
        else:
            self.block_state[(i, j)] = STATE.BLANK
            screen.blit(self.img_block_hover, (x, y))
            for newi, newj in need_check:
                newx, newy = newj * self.BLOCK_SIZE, newi * self.BLOCK_SIZE
                if self.block_state[(newi, newj)] == STATE.NORMAL: # 因为是递归调用,所以不一定此时此刻在need_check里的仍然是NORMAL,可能在之前的调用中被打开了
                    self.CalNumMine(screen, newx, newy, newi, newj)


    def HandleDoubleClick(self, screen, x, y, i, j):
        num_flag = 0
        num_mine = 0
        block_mine = []
        block_unopened = []
        DI, DJ = [-1, -1, -1, 0, 0, 1, 1, 1], [-1, 0, 1, -1, 1, -1, 0, 1]
        for k in range(8):
            newi, newj = i + DI[k], j + DJ[k]
            if newi >= 0 and newi < self.ROW and newj >= 0 and newj < self.COL:
                if self.block_state[(newi, newj)] == STATE.FLAG:
                    num_flag += 1
                if self.has_mine[(newi, newj)]:
                    num_mine += 1
                    block_mine.append((newi, newj))
                if self.block_state[(newi, newj)] == STATE.NORMAL:
                    block_unopened.append((newi, newj))

        if num_flag == num_mine:
            for newi, newj in block_mine:
                if self.block_state[(newi, newj)] != STATE.FLAG: # 玩家标错,游戏结束
                    newx, newy = newj * self.BLOCK_SIZE, newi * self.BLOCK_SIZE
                    screen.blit(self.img_bomb, (newx, newy))
                    pygame.display.update()
                    return True
            for newi, newj in block_unopened: # 打开周围未打开的格子
                newx, newy = newj * self.BLOCK_SIZE, newi * self.BLOCK_SIZE
                self.CalNumMine(screen, newx, newy, newi, newj)
        else: # 玩家标多或者标少了,显示周围未打开的格子(因为pygame不能移除blit后的图像,所以暂时想不出更好的提示方法)
            for newi, newj in block_unopened:
                newx, newy = newj * self.BLOCK_SIZE, newi * self.BLOCK_SIZE
                screen.blit(self.img_block_hover, (newx, newy))
            pygame.display.update()
            time.sleep(0.2)
            for newi, newj in block_unopened:
                newx, newy = newj * self.BLOCK_SIZE, newi * self.BLOCK_SIZE
                screen.blit(self.img_block, (newx, newy))

        return False


    def HandleLeftClick(self, screen, x, y, i, j):
        if self.has_mine[(i, j)]:
            screen.blit(self.img_bomb, (x, y))
            pygame.display.update()
            return True
        elif self.block_state[(i, j)] == STATE.NORMAL or self.block_state[(i, j)] == STATE.QUESTION:
            self.CalNumMine(screen, x, y, i, j)
            pygame.display.update()
            return False
    

    def Play(self):
        # 界面初始化
        screen = pygame.display.set_mode((self.COL * self.BLOCK_SIZE, (self.ROW + 2) * self.BLOCK_SIZE))
        pygame.display.set_caption("扫雷")
        font = pygame.font.SysFont("timesroman", 35) #第一个参数是字体,第二个参数是大小。可以用pygame.font.get_fonts()获取可用的所有字体
        font_file = font.render("0", False, (255, 0, 0))
        screen.blit(font_file, (self.COL // 4 * self.BLOCK_SIZE, self.ROW * self.BLOCK_SIZE)) # 显示时间
        font_file = font.render(str(self.NUM_MINE), False, (255, 0, 0))
        screen.blit(font_file, (self.COL // 4 * 3 * self.BLOCK_SIZE, self.ROW * self.BLOCK_SIZE)) # 显示已标记地雷数

        for i in range(1, 9):
            exec("self.img{} = pygame.image.load('pictures/{}.png').convert_alpha()".format(i, i))
            exec("self.img{} = pygame.transform.smoothscale(self.img{}, (self.BLOCK_SIZE // 4 * 3, self.BLOCK_SIZE // 4 * 3))".format(i, i))
        for i in ["block", "block_hover", "flag", "question", "bomb", "restart"]:
            exec("self.img_{} = pygame.image.load('pictures/{}.png').convert_alpha()".format(i, i))
            exec("self.img_{} = pygame.transform.smoothscale(self.img_{}, (self.BLOCK_SIZE, self.BLOCK_SIZE))".format(i, i))
        for i in range(self.ROW):
            for j in range(self.COL):
                screen.blit(self.img_block, (j * self.BLOCK_SIZE, i * self.BLOCK_SIZE)) # blit参数 
        screen.blit(self.img_restart, (self.COL // 2 * self.BLOCK_SIZE, int((self.ROW + 0.5) * self.BLOCK_SIZE)))
        pygame.display.update()
        
        # 游戏主循环
        is_start = False
        starttime = 0
        nowtime = "0"
        num_flag = 0
        prex, prey, prei, prej = 0, 0, 0, 0
        while True:
            event = pygame.event.poll()
            if event.type == QUIT:
                return False

            # 显示经过的时间
            if is_start:
                endtime = datetime.datetime.now()
                font_file = font.render(nowtime, False, (0, 0, 0)) #渲染成一个surface对象。第一个是显示的内容,第二个是否消除锯齿,第三个是RGB颜色
                screen.blit(font_file, (self.COL // 4 * self.BLOCK_SIZE, (self.ROW) * self.BLOCK_SIZE))
                nowtime = str((endtime - starttime).seconds)
                font_file = font.render(nowtime, False, (255, 0, 0))
                screen.blit(font_file, (self.COL // 4 * self.BLOCK_SIZE, (self.ROW) * self.BLOCK_SIZE))
                pygame.display.update() 

            x, y = pygame.mouse.get_pos()
            i, j = y // self.BLOCK_SIZE, x // self.BLOCK_SIZE
            x, y = j * self.BLOCK_SIZE, i * self.BLOCK_SIZE # 调整为所在块左上角的坐标
            # 展示鼠标在每一个块上悬停的效果
            if prex != x or prey != y: 
                if i >= 0 and i < self.ROW and j >= 0 and j < self.COL and self.block_state[(i, j)] == STATE.NORMAL:
                    screen.blit(self.img_block_hover, (x, y))
                if prei >= 0 and prei < self.ROW and prej >= 0 and prej < self.COL and self.block_state[(prei, prej)] == STATE.NORMAL:
                    screen.blit(self.img_block, (prex, prey))
                prex, prey, prei, prej = x, y, i, j 
                pygame.display.update() 
            
            # 如果有鼠标点击事件
            if event.type == MOUSEBUTTONDOWN:
                if event.button == 1: # 左键按下
                    # 玩家选择重新开始
                    if event.pos[0] >= self.COL // 2 * self.BLOCK_SIZE and event.pos[0] < self.COL // 2 * self.BLOCK_SIZE + self.BLOCK_SIZE and \
                        event.pos[1] >= (self.ROW + 0.5) * self.BLOCK_SIZE and event.pos[1] < int((self.ROW + 0.5) * self.BLOCK_SIZE) + self.BLOCK_SIZE:
                        return True
                    # 玩家单击某个块
                    elif i >= 0 and i < self.ROW and j >= 0 and j < self.COL:
                        if not is_start: # 点击某个块后游戏才开始计时
                            is_start = True
                            starttime = datetime.datetime.now()

                        # 玩家单击某个已标数字的块
                        if self.block_state[(i,j)] == STATE.NUMBERED:
                            time.sleep(0.3)
                            if pygame.event.peek(MOUSEBUTTONDOWN): # 玩家双击某个已标数字的块
                                if self.HandleDoubleClick(screen, x, y, i, j): # 玩家标雷错误则返回真
                                    break
                        # 玩家单击某个未标数字的块
                        elif self.HandleLeftClick(screen, x, y, i, j): # 玩家踩雷则返回真
                            break
                        
                        if self.num_opened == self.ROW * self.COL - self.NUM_MINE: # 获胜判断
                            break
                    
                elif event.button == 3: # 右键按下
                    if i >= 0 and i < self.ROW and j >= 0 and j < self.COL:
                        # 玩家单击某个已打开的块
                        if self.block_state[(i,j)] == STATE.NUMBERED:
                            while not pygame.event.peek(MOUSEBUTTONUP): # 在右键放开之前
                                if pygame.event.peek(MOUSEBUTTONDOWN):
                                    next_event = pygame.event.get(MOUSEBUTTONDOWN)
                                    if next_event[0].button == 1: # 玩家同时对某个已标数字的块按下左右键
                                        if self.HandleDoubleClick(screen, x, y, i, j): # 玩家标雷错误则返回真
                                            break
                                        if self.num_opened == self.ROW * self.COL - self.NUM_MINE: # 获胜判断
                                            break
                        # 玩家单击某个未打开的块
                        elif self.block_state[(i, j)] == STATE.NORMAL: # 玩家想标旗子
                            font_file = font.render(str(self.NUM_MINE - num_flag), False, (0, 0, 0))
                            screen.blit(font_file, (self.COL // 4 * 3 * self.BLOCK_SIZE, (self.ROW) * self.BLOCK_SIZE))
                            num_flag += 1
                            font_file = font.render(str(self.NUM_MINE - num_flag), False, (255, 0, 0))
                            screen.blit(font_file, (self.COL // 4 * 3 * self.BLOCK_SIZE, (self.ROW) * self.BLOCK_SIZE))
                            
                            self.block_state[(i, j)] = STATE.FLAG
                            screen.blit(self.img_block, (x, y))
                            screen.blit(self.img_flag, (x, y))
                        elif self.block_state[(i, j)] == STATE.FLAG: # 玩家想标问号
                            font_file = font.render(str(self.NUM_MINE - num_flag), False, (0, 0, 0))
                            screen.blit(font_file, (self.COL // 4 * 3 * self.BLOCK_SIZE, (self.ROW) * self.BLOCK_SIZE))
                            num_flag -= 1
                            font_file = font.render(str(self.NUM_MINE - num_flag), False, (255, 0, 0))
                            screen.blit(font_file, (self.COL // 4 * 3 * self.BLOCK_SIZE, (self.ROW) * self.BLOCK_SIZE))

                            self.block_state[(i, j)] = STATE.QUESTION
                            screen.blit(self.img_block, (x, y))
                            screen.blit(self.img_question, (x, y))
                        elif self.block_state[(i, j)] == STATE.QUESTION: # 玩家取消标记
                            self.block_state[(i, j)] = STATE.NORMAL
                            screen.blit(self.img_block, (x, y))
                pygame.display.update()
        
        if self.num_opened == self.ROW * self.COL - self.NUM_MINE: # 获胜判断
            font_file = font.render("! WIN !", False, (255, 0, 0))
            screen.blit(font_file, ((self.COL - 3) // 2 * self.BLOCK_SIZE, (self.ROW - 1) // 2 * self.BLOCK_SIZE))
            pygame.display.update()
        while True: # 玩家获胜或者失败后要么退出,要么重新开始
            event = pygame.event.wait()
            if event.type == QUIT:
                return False
            if event.type == MOUSEBUTTONDOWN and event.button == 1 and \
                event.pos[0] >= self.COL // 2 * self.BLOCK_SIZE and event.pos[0] < self.COL // 2 * self.BLOCK_SIZE + self.BLOCK_SIZE and \
                event.pos[1] >= (self.ROW + 0.5) * self.BLOCK_SIZE and event.pos[1] < (self.ROW + 0.5) * self.BLOCK_SIZE + self.BLOCK_SIZE:
                return True
                
        
pygame.init()
while True:
    game = GameMinesweeper()
    if not game.Play(): # 重新开始返回真,退出返回假
        break