Pygame小游戏:玩扫雷就在瞎点的,不止你一个人。

前言

《扫雷》是一款大众类的益智小游戏,于1992年发行。

游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个

雷即全盘皆输。

它是许多人接触到的第一款游戏,大概也是广大办公族和无网学生无聊时消遣的最佳游戏,是不是

每次见到戴上墨镜的小人很有成就感?在那些还没有网(被切断网)的岁月,扫雷曾陪伴无数人度

过了他们的童年。你的最佳纪录是多少?小编在工作间隙随手刷了一局30秒(囧?)。

那么?怎么才能通关呢?这,有生之年靠自己猜可能做不到了,BUT我们可以靠一靠我们的Python自动扫雷的嘛!不要慌呢~我们正式开始吧!

《自动扫雷》

环境配置:

Python3、 Pycharm 、Pygame以及部分自带的模块。

第三方库的安装:pip  install pygame


效果展示:

游戏开始:

 自动扫雷:

游戏结束:

代码演示:

1)主程序

代码比较多哈,每行都有注释大家自己看,不懂的可以找我交流、一起学习嘛!

# -*- coding: utf-8 -*-  
import pygame
from pygame.locals import *
import numpy as np
import random
import sys
import time
import copy


# 屏幕大小
Screen_Size = (1200, 600)
# 行数
Rows = 20
# 列数
Colums = 40
# 雷的数量
numOfMines = 80
# 胜率
VictoryRate=0

class Sweep(object):
    """docstring for Sweep"""

    def __init__(self):
        # 初始化一个页面
        self.Screen = pygame.display.set_mode(Screen_Size)
        # 字体
        self.myfont = pygame.font.SysFont('幼圆', 25)
        # 格子大小
        self.gwide = int(Screen_Size[0] / Colums)
        self.gheight = int(Screen_Size[1] / Rows)
        self.board = np.zeros((Rows, Colums))
        # 存储下一步可选的位置
        self.NBS = []
        # NBS辅助容器,用于判断NBS有没变化
        self.NBSTool = []
        # 判断是否进行概率选择扫雷
        self.GO = False
        # 遍历存储容器
        self.container = []
        # 标注地雷存储容器
        self.mineContainer = []
        # 实际地雷位置存储容器
        self.Mines = []
        # 数字存储容器
        self.numbers = []
        # 加载图片
        self.LoadImg()
        # 画格子
        self.DrawGrid()
        # 埋雷
        self.HideMines()

    def LoadImg(self):
        # 加载地雷图片
        self.mine = pygame.image.load('image/mine.jpg').convert_alpha()
        self.mine = pygame.transform.scale(
            self.mine, (self.gwide, self.gheight))
        # 加载旗子图片
        self.flag = pygame.image.load('image/flag.jpg').convert_alpha()
        self.flag = pygame.transform.scale(
            self.flag, (self.gwide, self.gheight))
        # 加载地雷爆炸图片
        self.boom = pygame.image.load('image/boom.png').convert_alpha()
        self.boom = pygame.transform.scale(
            self.boom, (self.gwide, self.gheight))
        # 加载数字图片
        self.num1 = pygame.image.load('image/1.png')
        self.num1 = pygame.transform.scale(
            self.num1, (self.gwide, self.gheight))
        self.num2 = pygame.image.load('image/2.png')
        self.num2 = pygame.transform.scale(
            self.num2, (self.gwide, self.gheight))
        self.num3 = pygame.image.load('image/3.png')
        self.num3 = pygame.transform.scale(
            self.num3, (self.gwide, self.gheight))
        self.num4 = pygame.image.load('image/4.png')
        self.num4 = pygame.transform.scale(
            self.num4, (self.gwide, self.gheight))
        self.num5 = pygame.image.load('image/5.png')
        self.num5 = pygame.transform.scale(
            self.num5, (self.gwide, self.gheight))
        self.num6 = pygame.image.load('image/6.png')
        self.num6 = pygame.transform.scale(
            self.num6, (self.gwide, self.gheight))
        self.num7 = pygame.image.load('image/7.png')
        self.num7 = pygame.transform.scale(
            self.num7, (self.gwide, self.gheight))
        self.num8 = pygame.image.load('image/8.png')
        self.num8 = pygame.transform.scale(
            self.num8, (self.gwide, self.gheight))
        # 加载访问过后设置背景图
        self.back = pygame.image.load('image/back.jpg')
        self.back = pygame.transform.scale(
            self.back, (self.gwide, self.gheight))
        # 加载游戏失败背景
        self.gameOver=pygame.image.load('image/gameover.jpg')
        self.gameOver=pygame.transform.scale(self.gameOver,Screen_Size)
        # 加载游戏胜利背景
        self.victoryOver=pygame.image.load('image/victory.jpg')
        self.victoryOver=pygame.transform.scale(self.victoryOver,Screen_Size)

    def HideMines(self):
        """埋雷"""
        for i in range(numOfMines):
            while True:
                y = random.randint(0, Colums - 1)
                x = random.randint(0, Rows - 1)
                if self.board[x][y] == 0:
                    self.board[x][y] = -1
                    self.Mines.append((x, y))
                    break

    def ShowAllMines(self):
        """
        显示所有地雷的位置
        """
        for i in range(Rows):
            for j in range(Colums):
                if self.board[i][j] == -1:
                    self.Screen.blit(
                        self.mine, (self.gwide * j, self.gheight * i))

    def DrawGrid(self):
        """
        绘制背景界面
        """
        self.Screen.fill((191, 251, 255))
        # 画横线
        for i in range(1, Rows):
            pygame.draw.line(
                self.Screen, (0, 0, 0), (0, self.gheight * i), (Screen_Size[0], self.gheight * i))
        # 画竖线
        for i in range(1, Colums):
            pygame.draw.line(self.Screen, (0, 0, 0),
                             (self.gwide * i, 0), (self.gwide * i, Screen_Size[1]))

    def NumOfPos(self, pos):
        """
        返回一个点周围的地雷数
        pos为地图坐标
        同时在二维数组对应位置设置地雷数
        """
        n = 0
        y, x = pos[0], pos[1]
        if x - 1 >= 0:
            if self.board[x - 1][y] == -1:
                n += 1
            if y - 1 >= 0 and self.board[x - 1][y - 1] == -1:
                n += 1
            if y + 1 <= Colums - 1 and self.board[x - 1][y + 1] == -1:
                n += 1
        if x + 1 <= Rows - 1:
            if self.board[x + 1][y] == -1:
                n += 1
            if y - 1 >= 0 and self.board[x + 1][y - 1] == -1:
                n += 1
            if y + 1 <= Colums - 1 and self.board[x + 1][y + 1] == -1:
                n += 1
        if y - 1 >= 0 and self.board[x][y - 1] == -1:
            n += 1
        if y + 1 <= Colums - 1 and self.board[x][y + 1] == -1:
            n += 1
        # self.board[x][y] = n
        return n

    def SetNumOfPos(self, pos):
        """
        设置一个安全点周围地雷的数量
        pos是地图坐标
        """
        n = self.NumOfPos(pos)
        if n == 0:
            self.Screen.blit(
                self.back, (self.gwide * pos[0], self.gheight * pos[1]))
        if n == 1:
            self.Screen.blit(
                self.num1, (self.gwide * pos[0], self.gheight * pos[1]))
        if n == 2:
            self.Screen.blit(
                self.num2, (self.gwide * pos[0], self.gheight * pos[1]))
        if n == 3:
            self.Screen.blit(
                self.num3, (self.gwide * pos[0], self.gheight * pos[1]))
        if n == 4:
            self.Screen.blit(
                self.num4, (self.gwide * pos[0], self.gheight * pos[1]))
        if n == 5:
            self.Screen.blit(
                self.num5, (self.gwide * pos[0], self.gheight * pos[1]))
        if n == 6:
            self.Screen.blit(
                self.num6, (self.gwide * pos[0], self.gheight * pos[1]))
        if n == 7:
            self.Screen.blit(
                self.num7, (self.gwide * pos[0], self.gheight * pos[1]))
        if n == 8:
            self.Screen.blit(
                self.num8, (self.gwide * pos[0], self.gheight * pos[1]))
        return n

    def NeighborsOf(self, pos):
        """
        获取一个点的邻居坐标
        pos为二维数组的坐标
        """
        x, y = pos[0], pos[1]
        neibors = []
        if x - 1 >= 0:
            if y - 1 >= 0:
                neibors.append((x - 1, y - 1))
            if y + 1 <= Colums - 1:
                neibors.append((x - 1, y + 1))
            neibors.append((x - 1, y))
        if x + 1 <= Rows - 1:
            if y - 1 >= 0:
                neibors.append((x + 1, y - 1))
            if y + 1 <= Colums - 1:
                neibors.append((x + 1, y + 1))
            neibors.append((x + 1, y))
        if y - 1 >= 0:
            neibors.append((x, y - 1))
        if y + 1 <= Colums - 1:
            neibors.append((x, y + 1))
        return neibors

    def Boom(self, pos):
        """
        pos为二维数组位置
        """
        self.Screen.blit(
            self.boom, (self.gwide * pos[1], self.gheight * pos[0]))
        # pygame.display.update()

    def Ergodic(self, pos):
        """
        从一个位置向四周发散直到遇到有雷的位置结束,是一个递归函数
        pos是二维数组的坐标
        将二维数组pos坐标和周围的地雷数存入容器self.container中
        """
        x, y = pos[0], pos[1]
        # 如果该位置周围地雷数不为0,停止
        if self.NumOfPos((y, x)) > 0 and self.board[x][y] != -1:
            self.numbers.append(pos)
            return
        # 将二维数组pos坐标和周围的地雷数存入容器self.container中
        if self.board[x][y] != -1:
            self.container.append(pos)
        # 向上遍历
        if x - 1 >= 0 and (x - 1, y) not in self.container:
            self.Ergodic((x - 1, y))
        # 向下遍历
        if x + 1 <= Rows - 1 and (x + 1, y) not in self.container:
            self.Ergodic((x + 1, y))
        # 想左遍历
        if y - 1 >= 0 and (x, y - 1) not in self.container:
            self.Ergodic((x, y - 1))
        # 箱右遍历
        if y + 1 <= Colums - 1 and (x, y + 1) not in self.container:
            self.Ergodic((x, y + 1))

    def DrawContainer(self):
        # self.ShowAllMines()
        for pos in self.container:
            x, y = pos[0], pos[1]
            self.SetNumOfPos((y, x))

    def DrawNumbers(self):
        for pos in self.numbers:
            self.SetNumOfPos((pos[1], pos[0]))

    def DrawFlags(self):
        for pos in self.mineContainer:
            self.Screen.blit(
                self.flag, (pos[1] * self.gwide, pos[0] * self.gheight))

    def Removed(self):
        n = 0
        for pos in self.mineContainer:
            if pos in self.Mines:
                n += 1
        return n

    def AutoPlay(self):
        # 二维数组随机位置
        x = random.randint(0, Rows - 1)
        y = random.randint(0, Colums - 1)
        print("第一步:",self.board[x][y])
        if self.board[x][y]==-1:
            self.Boom((x,y))
            pygame.display.update()
            self.GameOver()
        print(x, y)
        while True:
            for ev in pygame.event.get():
                if ev.type == pygame.QUIT:
                    sys.exit(0)
            if self.board[x][y] == -1:
                self.Boom((x, y))
                time.sleep(3)
                sys.exit(0)
            # 画格子
            self.DrawGrid()
            # 向四周发散遍历
            self.Ergodic((x, y))
            # 画已经遍历过且周围没雷的位置(用白色背景)
            self.DrawContainer()
            # 画已经遍历过但周围有雷的位置(用数字)
            self.DrawNumbers()
            # 找出下一步可能走的所有位置
            self.NextSteps()
            # 画已经标注过的地雷的位置(用旗子)
            self.DrawFlags()
            # 找出一定是地雷的位置并标注(用旗子)
            self.SetFlags()
            # 找出一定没雷的位置标注(数字或空白)
            self.NoMines()
            # NBS为0时,概率选择
            self.ChooseWithBigProbability()
            # 刷新
            pygame.display.update()
            # 打印被标注的地雷数
            print("被标注的地雷数:", len(self.mineContainer))
            print("被排除的地雷数:", self.Removed())
            if self.Removed()==numOfMines:
                time.sleep(3)
                self.Victory()

    def NextSteps(self):
        """
        找出下一步扫雷的可选择位置
        算法思想:
            找出每个已被标明周围地雷数的位置的八邻域诶被访问过
            的位置,这些位置就是下一步可能走的位置
        """
        self.NBS.clear()
        for pos in self.numbers:
            for n in self.NeighborsOf(pos):
                if n not in self.NBS + self.container + self.numbers:
                    self.NBS.append(n)
        if self.NBSTool == self.NBS:
            print(self.GO)
            self.GO = True
        self.NBSTool = copy.deepcopy(self.NBS)
        # print(self.NBS)

    def SetFlags(self):
        """
        找出一定有雷的位置并用旗子标注
        算法思想:
            对每个已经标明过地雷数的位置,找出该位置八邻域还没有访问过的位置,
            找出还没发现的地雷数目,如果该位置周围还没发现的地雷数大于等于可
            走的位置数目,那么该位置剩余八邻域可走位置必定全为地雷,标注该位置
            为地雷,并放入self.mineContainer容器
        """
        for pos in self.numbers:
            s = list(set(self.NeighborsOf(pos)) -
                     set(self.container) - set(self.numbers) - set(self.mineContainer))
            s1 = list(set(self.NeighborsOf(pos)) -
                      set(self.container) - set(self.numbers))
            # if self.NumOfPos((pos[1], pos[0])) == 1 and len(s) == 1:
            if self.NumOfPos((pos[1], pos[0])) - len(set(s1) & set(self.mineContainer)) >= len(s):
                if self.board[pos[0]][pos[1]] == -1:
                    self.Boom(pos)
                    time.sleep(3)
                    sys.exit(0)
                for pos1 in s:
                    self.mineContainer.append(pos1)
                    self.Screen.blit(
                        self.flag, ((self.gwide * pos1[1], self.gheight * pos1[0])))
                    # time.sleep(1)
                    # pygame.display.update()

    def NoMines(self):
        """
        找出能确定没有地雷的位置
        算法思想:
            对于每个已被标明周围地雷数的位置,找出该位置八邻域内剩下
            未访问过的位置,查看当前位置周围的地雷是否被全部标记了,
            如果已经全部被标记,那么该位置八邻域内剩下未访问过的位置
            ,如果位置周围的地雷数为0,那么用Ergodic()函数遍历,如果
            位置周围地雷数不为0,那么标明该位置的地雷数
        """
        for pos in self.numbers:
            s = list(set(self.NeighborsOf(pos)) -
                     set(self.container) - set(self.numbers) - set(self.mineContainer))
            if self.NumOfPos((pos[1], pos[0])) == len(set(self.NeighborsOf(pos)) & set(self.mineContainer)):
                for pos1 in s:
                    if self.board[pos1[0]][pos1[1]] == -1:
                        self.mineContainer.append(pos)
                        continue
                    if self.NumOfPos((pos1[1], pos1[0])) == 0:
                        self.container.append(pos1)
                        self.Ergodic(pos1)
                        self.Screen.blit(
                            self.back, (pos1[1] * self.gwide, pos1[0] * self.gheight))
                        continue
                    if self.NumOfPos((pos1[1], pos1[0])) > 0:
                        self.SetNumOfPos((pos1[1], pos1[0]))
                        self.numbers.append(pos1)
                    # self.DrawContainer()
                    # pygame.display.update()

    def ChooseWithBigProbability(self):
        """
        概率选择函数
        算法思想:
            当遇到无法肯定有雷或无雷的情况时,找出还没有被访问过的位置
            找出这些位置八邻域内已被标明地雷数的位置的地雷数的和,并用
            和除以8,将这些位置和其对应的概率存入容器并按概率从大到小的
            顺序排序,如果概率最大的位置的概率大于等于0.75,那么将该位置
            标注为地雷,如果概率小于0.75,那么找出还未访问过且不在下一步
            计划的位置容器的位置,随机选择一个位置,如果踩到雷游戏结束,
            如果该位置周围没有地雷,则调用Ergodic()遍历,如果周围有地雷
            那么标注该位置的地雷数
        """
        if self.GO == True:
            print("进入概率选择环境")
            noSeen = []
            # 待排雷数量
            leftmines=numOfMines-self.Removed()
            # 找出还未遍历过的位置
            for i in range(Rows):
                for j in range(Colums):
                    if (i, j) not in self.container + self.mineContainer + self.numbers:
                        s = list(set(self.NeighborsOf((i, j)))
                                 & set(self.numbers))
                        numerator = 0
                        for s1 in s:
                            numerator += self.NumOfPos((s1[1], s1[0]))
                        # 计算概率
                        noSeen.append([(i, j), numerator / 8])
            sorted(noSeen, key=lambda proba: proba[1], reverse=True)
            if noSeen != []:
                if noSeen[0][1] >= 0.75:
                    pos = noSeen[0][0]
                    self.mineContainer.append(pos)
                else:
                    nos = [p[0] for p in noSeen]
                    s = list(set(nos) - set(self.NBS))
                    index = 0
                    pos = (-1, -1)
                    if len(s) > 1:
                        index = random.randint(0, len(s) - 1)
                        pos = s[index]
                    elif len(s) == 1:
                        pos = s[0]
                    elif s == []:
                        pos = noSeen[0][0]
                    if self.board[pos[0]][pos[1]] == -1:
                        self.Boom(pos)
                        time.sleep(2)
                        self.GameOver()
                    elif self.NumOfPos((pos[1], pos[0])) > 0:
                        self.numbers.append(pos)
                    elif self.NumOfPos((pos[1], pos[0])) == 0:
                        self.Ergodic(pos)

            noSeen.clear()
            self.GO = False

    def GameOver(self):
        for pos in self.Mines:
            self.Boom(pos)
            pygame.display.update()
            time.sleep(0.1)
        self.Screen.blit(self.gameOver,(0,0))
        VictoryRate=content="排雷率:%d"%((self.Removed()/numOfMines)*100)
        text=self.myfont.render(content+'%',True,(0,0,0),(255,255,255))
        self.Screen.blit(text,(500,150))
        pygame.display.update()
        time.sleep(5)
        sys.exit(0)

    def Victory(self):
        # if self.Removed()==numOfMines:
        VictoryRate=100
        self.Screen.blit(self.victoryOver,(0,0))
        pygame.display.update()
        time.sleep(5)
        sys.exit(0)

    def Show(self):
        self.DrawGrid()
        self.ShowAllMines()
        pygame.display.update()
        time.sleep(5)
        self.DrawGrid()
        pygame.display.update()
        time.sleep(3)
        while True:
            for ev in pygame.event.get():
                if ev.type == pygame.QUIT:
                    sys.exit(0)
                self.AutoPlay()
                pygame.display.update()


if __name__ == '__main__':
    pygame.display.init()
    pygame.font.init()
    app = Sweep()
    app.Show()

结尾

话说——不靠AI扫雷,大家自己手动玩儿通关的有嘛?

自动扫雷的、还有一款扫雷,这2款源码都可以找我拿去自己玩下的哈!

免费获取源码项目:滴滴我即可啦!

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>