用python写扫雷游戏

# -*- coding: utf-8 -*-
# __/author__by:Kevin_F/__
import tkinter.messagebox
import random
import time
import pygame
import tkinter
import tkinter.messagebox
num_w = 30  # 横向格子数
num_h = 16  # 竖向格子数


#  界面初始化
def window_init():
    pygame.init()
    global window
    #  格子尺寸30*30,状态栏高度100,上下左右边界20
    window = pygame.display.set_mode((30 * num_w + 20 * 2, 100 + 30 * num_h + 2 * 40))
    pygame.display.set_caption('扫雷xp')
    window.fill((128, 138, 135))  # 背景色:冷灰
    pygame.draw.rect(window, (192, 192, 192), (0, 0, 30 * num_w + 20 * 2, 100))  # 最上面状态框

    for j in range(num_h):
        for i in range(num_w):
            #  格子坐标
            block_x = 20 + i * 30
            block_y = 100 + 20 + j * 30
            list_block.append([block_x, block_y, 0, 0])
            list_block_pos.append((block_x, block_y))
            """
            list_temp生成存放每一行中格子的坐标信息
            block_x:格子的x坐标
            block_y:格子的y坐标 (坐标信息是固定不可修改的,所以用元组表示)
            0:定义此格子是否是雷  是雷:-1  不是雷:表示周边相邻非雷格子的数量,先写入默认值为0
            0:定义格子的标记状态  0-未标记,即初始值  1-标记是雷  2-标记问号 3-左击点开
            """

            pygame.draw.rect(window, (192, 192, 192), (block_x, block_y, 30, 30), 0)  # 实心格子
            pygame.draw.rect(window, (220, 220, 220), (block_x, block_y, 30, 30), 2)  # 格子边线

    pygame.display.flip()


#  状态栏 雷剩余数
def bomb_les():
    global count_bomb
    if count_bomb < 0:
        count_bomb = 0
    #  不存在标记雷超过99的可能,因为未标记的状态只有先标记成雷-1,再右键取消标记雷+1
    pygame.draw.rect(window, (192, 192, 192), (0, 0, 150, 100), 0)
    font_bomb_num = pygame.font.SysFont('Microsoft YaHei', 60, bold=True)
    bomb_num = font_bomb_num.render(str(count_bomb), True, (255, 0, 0))
    window.blit(bomb_num, (20, 10))
    pygame.display.update()


#  状态栏 状态显示
def game_status(status):
    img_working = pygame.image.load('me.png')
    img_bad = pygame.image.load('yc.png')
    img_win = pygame.image.load('yes.png')
    if status == 'working':  # 游戏开始时的图片
        window.blit(img_working, (15 * num_w + 20 - 45, 5))
    elif status == 'loose':  # 游戏失败的图片
        window.blit(img_bad, (15 * num_w + 20 - 45, 5))
    elif status == 'win':  # 游戏胜利的图片
        window.blit(img_win, (15 * num_w + 20 - 45, 5))
    else:
        pass
    pygame.display.update()


#  游戏初始化(随机生成a颗雷,初始化每个格子的位置信息,状态信息)
def game_init():
    #  随机生成99颗地雷
    a = random.randint(10, 100)
    global list_bomb_pos
    list_bomb_pos = random.sample(list_block_pos, a)
    #  从所有格子中取不重复的99个,即得到99个位置坐标(x,y),组成的列表即是所有雷的坐标列表

    #  改写list_block中格子的第三个值 从0改成-1 即表示是雷
    for i in range(len(list_bomb_pos)):
        for j in range(len(list_block)):
            if list_block[j][0] == list_bomb_pos[i][0] and list_block[j][1] == list_bomb_pos[i][1]:
                list_block[j][2] = -1

    #  不是雷的格子,要计算出格子周边的雷数,并写入list_block
    for index in range(len(list_block)):
        # index为list_block的下标
        x = index % 30
        y = index // 30
        if list_block[index][2] == -1:
            continue
        else:
            list_beside = list_side(x, y)

            mark_num = 0  # 定义一个变量,用于存放此格子周边是雷的格子的个数
            for element in list_beside:
                if element[2] == -1:
                    mark_num += 1
            list_block[index][2] = mark_num  # 将周边雷的个数写入list_block中的第三个值

    #  初始化一个列表,用于存放不是雷的格子的坐标
    for i in list_block_pos:
        if i not in list_bomb_pos:
            list_not_bomb.append(i)


#  获得一个存放此格子周边相邻格子组成的列表(定义此函数返回值就是这个列表)
def list_side(x, y):
    if 1 <= x <= 28 and 1 <= y <= 14:
        list_side = [
            list_block[x - 1 + (y - 1) * 30],
            list_block[x + (y - 1) * 30],
            list_block[x + 1 + (y - 1) * 30],
            list_block[x - 1 + y * 30],
            list_block[x + 1 + y * 30],
            list_block[x - 1 + (y + 1) * 30],
            list_block[x + (y + 1) * 30],
            list_block[x + 1 + (y + 1) * 30]
        ]
    elif x == 0 and 1 <= y <= 14:
        list_side = [
            list_block[(y - 1) * 30],
            list_block[1 + (y - 1) * 30],
            list_block[1 + y * 30],
            list_block[(y + 1) * 30],
            list_block[1 + (y + 1) * 30]
        ]
    elif x == 29 and 1 <= y <= 14:
        list_side = [
            list_block[28 + (y - 1) * 30],
            list_block[29 + (y - 1) * 30],
            list_block[28 + y * 30],
            list_block[28 + (y + 1) * 30],
            list_block[29 + (y + 1) * 30]
        ]
    elif 1 <= x <= 28 and y == 0:
        list_side = [
            list_block[x - 1 + y * 30],
            list_block[x + 1 + y * 30],
            list_block[x - 1 + (y + 1) * 30],
            list_block[x + (y + 1) * 30],
            list_block[x + 1 + (y + 1) * 30]
        ]
    elif 1 <= x <= 28 and y == 15:
        list_side = [
            list_block[x - 1 + (y - 1) * 30],
            list_block[x + (y - 1) * 30],
            list_block[x + 1 + (y - 1) * 30],
            list_block[x - 1 + y * 30],
            list_block[x + 1 + y * 30]
        ]
    elif x == 0 and y == 0:
        list_side = [list_block[1], list_block[30], list_block[31]]
    elif x == 0 and y == 15:
        list_side = [list_block[420], list_block[421], list_block[451]]
    elif x == 29 and y == 0:
        list_side = [list_block[28], list_block[58], list_block[59]]
    else:
        list_side = [list_block[448], list_block[449], list_block[478]]

    return list_side


#  绘制鼠标点击后的格子
def draw_block(para, x, y):
    if para == 0:  # 此格子周围格子都不是雷,此格子直接画个实心方块
        pygame.draw.rect(window, (180, 180, 180), (x * 30 + 20, y * 30 + 120, 30, 30), 0)
    elif para in [1, 2, 3, 4, 5, 6, 7, 8]:  # 此格子周围有雷,显示周围雷的数量
        colors = {
            1: (0, 0, 255),
            2: (34, 139, 34),
            3: (25, 25, 112),
            4: (160, 32, 240),
            5: (94, 38, 18),
            6: (199, 97, 20),
            7: (240, 230, 140),
            8: (240, 230, 140)
        }
        num_font = pygame.font.SysFont('SimHei', 24, bold=True)
        text_num = num_font.render(str(para), True, colors[para])
        w1, h1 = text_num.get_size()
        window.blit(text_num, (x * 30 + 35 - w1 / 2, y * 30 + 135 - h1 / 2))
    elif para == 'bomb':  # 左键点击到雷的时候,地雷爆炸(变红色)
        pygame.draw.circle(window, (0, 0, 0), (x * 30 + 35, y * 30 + 135), 12)

    elif para == 'mark':  # 右键标记为雷时,画个红色实心圆表示地雷
        pygame.draw.rect(window, (192, 192, 192), (x * 30 + 20, y * 30 + 120, 30, 30), 0)  # 实心格子
        pygame.draw.rect(window, (220, 220, 220), (x * 30 + 20, y * 30 + 120, 30, 30), 2)  # 格子边线
        pygame.draw.circle(window, (255, 0, 0), (x * 30 + 35, y * 30 + 135), 11)
    elif para == '?':  # 右键标记?,格子上显示?
        num_font = pygame.font.SysFont('SimHei', 24)
        text_num = num_font.render('?', True, (255, 0, 0))
        w1, h1 = text_num.get_size()
        pygame.draw.rect(window, (192, 192, 192), (x * 30 + 20, y * 30 + 120, 30, 30), 0)  # 实心格子
        pygame.draw.rect(window, (220, 220, 220), (x * 30 + 20, y * 30 + 120, 30, 30), 2)  # 格子边线
        window.blit(text_num, (x * 30 + 35 - w1 / 2, y * 30 + 135 - h1 / 2))
    elif para == 'blank':  # 右键切换成未标记状态时,画为初始化时的格子
        pygame.draw.rect(window, (192, 192, 192), (x * 30 + 20, y * 30 + 120, 30, 30), 0)  # 实心格子
        pygame.draw.rect(window, (220, 220, 220), (x * 30 + 20, y * 30 + 120, 30, 30), 2)  # 格子边线
    else:
        pass
    pygame.display.update()


#  扫雷区鼠标左击事件
def mouse_click_left(mouse_x, mouse_y):
    global list_block, is_loose, count_click
    pos_x = (mouse_x - 20) // 30
    pos_y = (mouse_y - 120) // 30
    index = pos_x + pos_y * 30  # list_block的下标index

    #  触发了此位置的左击事件,要先把此位置的左击事件表达出来

    if list_block[index][3] == 3:  # 如果这个格子已经被左击点开过,则不能重复点击
        pass
    else:
        #  先判断点击是否是雷 ,如果是雷,游戏失败
        if list_block[index][2] == -1:  # 鼠标左击 是雷  游戏失败
            is_loose = True
            for x, y in list_bomb_pos:
                draw_block('bomb', (x - 20) // 30, (y - 120) // 30)
            #  把当前点击的这个雷标成红色
            pygame.draw.rect(window, (255, 0, 0), (20 + pos_x * 30, 120 + pos_y * 30, 30, 30), 0)
            draw_block('bomb', pos_x, pos_y)
            game_status('loose')
            #  游戏已经失败,这个时候再点击扫雷区,无效
            #  计时停止
        else:
            #  不是雷的情况下,再判断这个格子是否是周边雷数为0 ,如果为0 ,则要继续触发左击点开该格子周围8个格子
            #  除了画出当前位置的左击事件,还需要判断当前周边雷数是否为0
            #  如果为0,自动触发此格子周边8个相邻格子的左击事件(因为0 代表周边8个格子都不是雷,直接自动左击显示出这8个格子每一个的周边雷数)
            #  如果这8个格子中还有周边雷数为0的,则递归调用函数
            if list_block[index][2] == 0:
                draw_block(list_block[index][2], pos_x, pos_y)
                list_block[index][3] = 3

                list_beside = list_side(pos_x, pos_y)
                for i in list_beside:
                    x = (i[0] - 20) // 30
                    y = (i[1] - 120) // 30
                    if list_block[x + 30 * y][3] == 0:  # 判断此位置的标记状态(list_block里的最后一个参数),0-未标记
                        mouse_click_left(x * 30 + 20, y * 30 + 120)  # 调用函数
                        draw_block(list_block[x + 30 * y][2], x, y)  # 画出左击后的图形
                        list_block[x + 30 * y][3] = 3  # 调用函数,即说明发生左击(即使是电脑自动触发,而非人为操作),更改标记状态为3


            else:  # 该格子周边雷数不为0,直接显示该格子的周边雷数
                draw_block(list_block[index][2], pos_x, pos_y)
                list_block[index][3] = 3

    count_click = 0
    for i in list_block:
        if i[3] == 3:
            count_click += 1
    #  每次触发鼠标左击事件后,都重新统计已经左击点开的格子个数
    #  为了后面判断如果点开的格子个数=非雷的格子总数,则游戏胜利


#  扫雷区鼠标右击事件
def mouse_click_right(mouse_x, mouse_y):
    global count_bomb
    pos_x = (mouse_x - 20) // 30
    pos_y = (mouse_y - 120) // 30
    index = pos_x + pos_y * 30  # list_block的下标index
    status = list_block[index][3]

    #  游戏结束,不能再右击
    if is_loose:
        import tkinter.messagebox
        tkinter.messagebox.showinfo('扫雷xp','你死了')
        # -*- conding:utf-8 -*-

        while True:
            # 检查音乐流播放,有返回True,没有返回False

            # 如果没有音乐流则选择播放
            if pygame.mixer.music.get_busy() == False:
                pygame.mixer.music.play()
    else:
        #  右击,格子显示状态在0-未标记 1-标记雷 2-标记? 三种状态之间循环切换
        if status == 0:  # 如果此格子当前处于未标记状态
            draw_block('mark', pos_x, pos_y)
            list_block[index][3] = 1
            count_bomb -= 1
        elif status == 1:
            draw_block('?', pos_x, pos_y)
            list_block[index][3] = 2
            count_bomb += 1
        elif status == 2:
            draw_block('blank', pos_x, pos_y)
            list_block[index][3] = 0
        else:
            #  已经左击点开的位置 不能在右击(即status=3的情况)
            pass


#  扫雷区鼠标中键事件
def mouse_click_mid(mouse_x, mouse_y):
    #  鼠标中键该格子,表示扫描该格子周边相邻格子
    #  该格子只有被左击点开(list_block[index][3]==3),且该格子周边雷数(list_block[index][2])=标记雷的总个数
    #  中键触发自动左键点开周边非雷的格子(之前没有左击点开过 ),如果之前左击点开过,则跳过

    pos_x = (mouse_x - 20) // 30
    pos_y = (mouse_y - 120) // 30
    index = pos_x + pos_y * 30  # list_block的下标index
    status = list_block[index][3]

    list_beside = list_side(pos_x, pos_y)
    mark_bomb_sum = 0
    for item in list_beside:
        if item[3] == 1:  # item[3]即相当于list_block[index][3]  如果该值为1 表示标记是雷
            mark_bomb_sum += 1

    if list_block[index][3] == 3 and list_block[index][2] == mark_bomb_sum and list_block[index][2] != 0:
        #  该格子只有被左击点开(list_block[index][3]==3),且该格子周边雷数(list_block[index][2])=标记雷的总个数,且不等于0
        for item in list_beside:
            #  中键触发自动左键点开周边非雷的格子(之前没有左击点开过 ),如果之前左击点开过,则跳过
            if item[3] == 3 or item[3] == 1 or item[3] == 2:
                #  状态是3,表示左击点开过,状态为1 ,表示标记是雷,状态为2,表示疑问,这三种情况都不能触发鼠标左击事件
                continue
            else:
                mouse_click_left(item[0], item[1])


def main():
    global is_loose, list_block, list_block_pos, list_bomb_pos, list_not_bomb, count_click, count_bomb, num_not_bomb, t_start
    list_block_pos = []  # 用于存放每个格子的位置坐标信息(x,y)
    list_block = []  # 用于存放每个格子的坐标和状态信息,即每个元素=[x,y,0,0]
    list_bomb_pos = []  # 用于存放雷的元素的坐标
    list_not_bomb = []  # 用于存放不是雷的格子的坐标
    count_click = 0  # 用于记录左击点开的格子数量

    window_init()
    game_status('working')
    game_init()

    is_loose = False
    num_not_bomb = len(list_block_pos) - len(list_bomb_pos)
    count_bomb = len(list_bomb_pos)

    while True:


        bomb_les()

        if 0 <= count_click < num_not_bomb and not is_loose:
            if count_click == 0:
                t_start = int(time.time())
                #  在while True循环内,如果count_click=0,表示没有鼠标左击,此时不计时
                #  此时,因为while True一直循环,所以会一直刷新t_start的值,不断获取当前时间

            #  当count_click从1开始,即一旦发生左击点击,上面if条件 count_click==0不成立,则不会再刷新t_start的值
            #  t_start的值固定在count_click=1前上一次循环的值,即左击点击之前的最后的一次循环的值,从而实现左击开始计时
            t_show = int(time.time()) - t_start
            pygame.draw.rect(window, (192, 192, 192), (720, 0, 200, 100), 0)
            font_time = pygame.font.SysFont('Microsoft YaHei', 60, bold=True)
            text_time = font_time.render(str(t_show), True, (255, 0, 0))
            window.blit(text_time, (920 - text_time.get_size()[0], 10))  # 时间靠右显示
            pygame.display.update()
        else:  # 如果游戏成功(count_click = num_not_bomb) 或者游戏失败(is_loose=True) 都停止时间显示
            # !/usr/bin/python
            # -*- coding: UTF-8 -*-

            # Python2.x 导入方法

            # Python3.x 导入方法
            # from tkinter import *
            pass
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                exit()
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if is_loose:
                    game_status('loose')
                    continue
                elif count_click < num_not_bomb:
                    mouse_x, mouse_y = pygame.mouse.get_pos()
                    click = pygame.mouse.get_pressed()
                    if 20 <= mouse_x <= 920 and 120 <= mouse_y <= 600:
                        if click == (True, False, False):  # 鼠标左击
                            mouse_click_left(mouse_x, mouse_y)
                        elif click == (False, True, False):  # 鼠标中键
                            mouse_click_mid(mouse_x, mouse_y)
                        elif click == (False, False, True):  # 鼠标右键
                            mouse_click_right(mouse_x, mouse_y)
                else:  # 当count_click == num_not_bomb,则说明所有非雷的格子全部被点开,游戏胜利
                    game_status('win')
            elif event.type == pygame.MOUSEBUTTONUP:
                mouse_x1, mouse_y1 = pygame.mouse.get_pos()
                if 0 <= mouse_x1 - (15 * num_w + 20 - 45) <= 90 and 10 <= mouse_y1 <= 100:
                    #  鼠标在状态图标位置左击点击时,表示游戏开始
                    return main()  # 游戏开始或者游戏重新开始
if __name__ == '__main__':
    import tkinter
    import PySimpleGUI as sg

    count = range(100)
    for i, item in enumerate(count):
        sg.one_line_progress_meter('扫雷xp', i + 1, len(count), '扫雷xp进度')

        # 假设这代码部分需要0.05s
        time.sleep(0.000001)



    main()

有图和pygame就行

图片:

运行截图:

猜你喜欢

转载自blog.csdn.net/yydsdeni/article/details/132548587