python小游戏 扫雷

扫雷游戏

游戏介绍:

游戏界面左上角的数字代表所有方格中埋有雷的数目,右上角是一个计时器。你要做的就是根据提示找出方格中所有的雷。

那么提示是啥呢?很简单,游戏刚开始的时候你需要随便点一个方格,就像这样:

上面的数字代表以该数字为中心的九宫格内埋有的雷的数目。所以如果你人品不好,一开始就点到雷的话,这局游戏就直接GG了。

设计思路:

创建一个二维数组来表示游戏地图,数组中的每个元素代表一个方格,初始状态都是未揭露的状态。

随机在地图上埋下一定数量的地雷,可以通过随机生成坐标来实现。

遍历地图,对于每个非地雷方格,统计其周围8个方格中地雷的数量,并在该方格上标记该数量。

当玩家点击一个方格时,根据其状态进行不同的处理:

如果点击的是地雷方格,则游戏失败,显示所有地雷的位置。

如果点击的是已揭露的方格,则无需处理。

如果点击的是未揭露的方格:

如果该方格周围没有地雷,则递归地揭露周围的方格。

如果该方格周围有地雷,则揭露该方格,并显示周围地雷的数量。

当所有非地雷方格都被揭露时,游戏胜利。

在游戏界面上显示剩余地雷的数量,并添加计时器。

提供游戏开始、重新开始、退出等功能。

使用图形界面库(如Pygame)来实现游戏界面交互。

代码实现:

一.扫雷地图的实现:

import random
from .mine import Mine
class MinesweeperMap():
    def __init__(self, cfg, images, **kwargs):
        self.cfg = cfg
        # 雷型矩阵
        self.mines_matrix = []
        for j in range(cfg.GAME_MATRIX_SIZE[1]):
            mines_line = []
            for i in range(cfg.GAME_MATRIX_SIZE[0]):
                position = i * cfg.GRIDSIZE + cfg.BORDERSIZE, (j + 2) * cfg.GRIDSIZE
                mines_line.append(Mine(images=images, position=position))
            self.mines_matrix.append(mines_line)
        # 随机埋雷
        for i in random.sample(range(cfg.GAME_MATRIX_SIZE[0]*cfg.GAME_MATRIX_SIZE[1]), cfg.NUM_MINES):
            self.mines_matrix[i//cfg.GAME_MATRIX_SIZE[0]][i%cfg.GAME_MATRIX_SIZE[0]].burymine()
        count = 0
        for item in self.mines_matrix:
            for i in item:
                count += int(i.is_mine_flag)
        # 游戏当前的状态
        self.status_code = -1
        # 记录鼠标按下时的位置和按的键
        self.mouse_pos = None
        self.mouse_pressed = None
    '''画出当前的游戏状态图'''
    def draw(self, screen):
        for row in self.mines_matrix:
            for item in row: item.draw(screen)
    '''设置当前的游戏状态'''
    def setstatus(self, status_code):
        # 0: 正在进行游戏, 1: 游戏结束, -1: 游戏还没开始
        self.status_code = status_code
    '''根据玩家的鼠标操作情况更新当前的游戏状态地图'''
    def update(self, mouse_pressed=None, mouse_pos=None, type_='down'):
        assert type_ in ['down', 'up']
        # 记录鼠标按下时的位置和按的键
        if type_ == 'down' and mouse_pos is not None and mouse_pressed is not None:
            self.mouse_pos = mouse_pos
            self.mouse_pressed = mouse_pressed
        # 鼠标点击的范围不在游戏地图内, 无响应
        if self.mouse_pos[0] < self.cfg.BORDERSIZE or self.mouse_pos[0] > self.cfg.SCREENSIZE[0] - self.cfg.BORDERSIZE or \
           self.mouse_pos[1] < self.cfg.GRIDSIZE * 2 or self.mouse_pos[1] > self.cfg.SCREENSIZE[1] - self.cfg.BORDERSIZE:
            return
        # 鼠标点击在游戏地图内, 代表开始游戏(即可以开始计时了)
        if self.status_code == -1:
            self.status_code = 0
        # 如果不是正在游戏中, 按鼠标是没有用的
        if self.status_code != 0:
            return
        # 鼠标位置转矩阵索引
        coord_x = (self.mouse_pos[0] - self.cfg.BORDERSIZE) // self.cfg.GRIDSIZE
        coord_y = self.mouse_pos[1] // self.cfg.GRIDSIZE - 2
        mine_clicked = self.mines_matrix[coord_y][coord_x]
        # 鼠标按下
        if type_ == 'down':
            # --鼠标左右键同时按下
            if self.mouse_pressed[0] and self.mouse_pressed[2]:
                if mine_clicked.opened and mine_clicked.num_mines_around > 0:
                    mine_clicked.setstatus(status_code=4)
                    num_flags = 0
                    coords_around = self.getaround(coord_y, coord_x)
                    for (j, i) in coords_around:
                        if self.mines_matrix[j][i].status_code == 2:
                            num_flags += 1
                    if num_flags == mine_clicked.num_mines_around:
                        for (j, i) in coords_around:
                            if self.mines_matrix[j][i].status_code == 0:
                                self.openmine(i, j)
                    else:
                        for (j, i) in coords_around:
                            if self.mines_matrix[j][i].status_code == 0:
                                self.mines_matrix[j][i].setstatus(status_code=5)
        # 鼠标释放
        else:
            # --鼠标左键
            if self.mouse_pressed[0] and not self.mouse_pressed[2]:
                if not (mine_clicked.status_code == 2 or mine_clicked.status_code == 3):
                    if self.openmine(coord_x, coord_y):
                        self.setstatus(status_code=1)
            # --鼠标右键
            elif self.mouse_pressed[2] and not self.mouse_pressed[0]:
                if mine_clicked.status_code == 0:
                    mine_clicked.setstatus(status_code=2)
                elif mine_clicked.status_code == 2:
                    mine_clicked.setstatus(status_code=3)
                elif mine_clicked.status_code == 3:
                    mine_clicked.setstatus(status_code=0)
            # --鼠标左右键同时按下
            elif self.mouse_pressed[0] and self.mouse_pressed[2]:
                mine_clicked.setstatus(status_code=1)
                coords_around = self.getaround(coord_y, coord_x)
                for (j, i) in coords_around:
                    if self.mines_matrix[j][i].status_code == 5:
                        self.mines_matrix[j][i].setstatus(status_code=0)
    '''打开雷'''
    def openmine(self, x, y):
        mine_clicked = self.mines_matrix[y][x]
        if mine_clicked.is_mine_flag:
            for row in self.mines_matrix:
                for item in row:
                    if not item.is_mine_flag and item.status_code == 2:
                        item.setstatus(status_code=7)
                    elif item.is_mine_flag and item.status_code == 0:
                        item.setstatus(status_code=1)
            mine_clicked.setstatus(status_code=6)
            return True
        mine_clicked.setstatus(status_code=1)
        coords_around = self.getaround(y, x)
        num_mines = 0
        for (j, i) in coords_around:
            num_mines += int(self.mines_matrix[j][i].is_mine_flag)
        mine_clicked.setnumminesaround(num_mines)
        if num_mines == 0:
            for (j, i) in coords_around:
                if self.mines_matrix[j][i].num_mines_around == -1:
                    self.openmine(i, j)
        return False
    '''获得坐标点的周围坐标点'''
    def getaround(self, row, col):
        coords = []
        for j in range(max(0, row-1), min(row+1, self.cfg.GAME_MATRIX_SIZE[1]-1)+1):
            for i in range(max(0, col-1), min(col+1, self.cfg.GAME_MATRIX_SIZE[0]-1)+1):
                if j == row and i == col:
                    continue
                coords.append((j, i))
        return coords
    '''是否正在游戏中'''
    @property
    def gaming(self):
        return self.status_code == 0
    '''被标记为雷的雷数目'''
    @property
    def flags(self):
        num_flags = 0
        for row in self.mines_matrix:
            for item in row: num_flags += int(item.status_code == 2)
        return num_flags
    '''已经打开的雷的数目'''
    @property
    def openeds(self):
        num_openeds = 0
        for row in self.mines_matrix:
            for item in row: num_openeds += int(item.opened)
        return num_openeds

二.图像表情的设置

import pygame

'''表情按钮'''
class EmojiButton(pygame.sprite.Sprite):
        def __init__(self, images, position, status_code=0, **kwargs):
        pygame.sprite.Sprite.__init__(self)
        # 导入图片
        self.images = images
        self.image = self.images['face_normal']
        self.rect = self.image.get_rect()
        self.rect.left, self.rect.top = position
        # 表情按钮的当前状态
        self.status_code = status_code
        '''画到屏幕上'''
        def draw(self, screen):
        # 状态码为0, 代表正常的表情
        if self.status_code == 0:
            self.image = self.images['face_normal']
        # 状态码为1, 代表失败的表情
        elif self.status_code == 1:
            self.image = self.images['face_fail']
        # 状态码为2, 代表成功的表情
        elif self.status_code == 2:
            self.image = self.images['face_success']
        # 绑定图片到屏幕
        screen.blit(self.image, self.rect)
    '''设置当前的按钮的状态'''
    def setstatus(self, status_code):
        self.status_code = status_code
    '''文字板'''
    class TextBoard(pygame.sprite.Sprite):
    def __init__(self, text, font, position, color, **kwargs):
        pygame.sprite.Sprite.__init__(self)
        self.text = text
        self.font = font
        self.position = position
        self.color = color
    def draw(self, screen):
        text_render = self.font.render(self.text, True, self.color)
        screen.blit(text_render, self.position)
    def update(self, text):
        self.text = text

三.地雷的设置

import pygame
''''''
class Mine(pygame.sprite.Sprite):
    def __init__(self, images, position, status_code=0, **kwargs):
        pygame.sprite.Sprite.__init__(self)
        # 导入图片
        self.images = images
        self.image = self.images['blank']
        self.rect = self.image.get_rect()
        self.rect.left, self.rect.top = position
        # 雷当前的状态
        self.status_code = status_code
        # 真雷还是假雷(默认是假雷)
        self.is_mine_flag = False
        # 周围雷的数目
        self.num_mines_around = -1
    '''设置当前的状态码'''
    def setstatus(self, status_code):
        self.status_code = status_code
    '''埋雷'''
    def burymine(self):
        self.is_mine_flag = True
    '''设置周围雷的数目'''
    def setnumminesaround(self, num_mines_around):
        self.num_mines_around = num_mines_around
    '''画到屏幕上'''
    def draw(self, screen):
        # 状态码为0, 代表该雷未被点击
        if self.status_code == 0:
            self.image = self.images['blank']
        # 状态码为1, 代表该雷已被点开
        elif self.status_code == 1:
            self.image = self.images['mine'] if self.is_mine_flag else self.images[str(self.num_mines_around)]
        # 状态码为2, 代表该雷被玩家标记为雷
        elif self.status_code == 2:
            self.image = self.images['flag']
        # 状态码为3, 代表该雷被玩家标记为问号
        elif self.status_code == 3:
            self.image = self.images['ask']
        # 状态码为4, 代表该雷正在被鼠标左右键双击
        elif self.status_code == 4:
            assert not self.is_mine_flag
            self.image = self.images[str(self.num_mines_around)]
        # 状态码为5, 代表该雷在被鼠标左右键双击的雷的周围
        elif self.status_code == 5:
            self.image = self.images['0']
        # 状态码为6, 代表该雷被踩中
        elif self.status_code == 6:
            assert self.is_mine_flag
            self.image = self.images['blood']
        # 状态码为7, 代表该雷被误标
        elif self.status_code == 7:
            assert not self.is_mine_flag
            self.image = self.images['error']
        # 绑定图片到屏幕
        screen.blit(self.image, self.rect)
    @property
    def opened(self):
        return self.status_code == 1

四.图片的途径

import sys
from _typeshed import BytesPath, StrPath
from genericpath import (
    commonprefix as commonprefix,
    exists as exists,
    getatime as getatime,
    getctime as getctime,
    getmtime as getmtime,
    getsize as getsize,
    isdir as isdir,
    isfile as isfile,
    samefile as samefile,
    sameopenfile as sameopenfile,
    samestat as samestat,
)
from os import PathLike

# Re-export common definitions from posixpath to reduce duplication
from posixpath import (
    abspath as abspath,
    basename as basename,
    commonpath as commonpath,
    curdir as curdir,
    defpath as defpath,
    devnull as devnull,
    dirname as dirname,
    expanduser as expanduser,
    expandvars as expandvars,
    extsep as extsep,
    isabs as isabs,
    islink as islink,
    ismount as ismount,
    lexists as lexists,
    normcase as normcase,
    normpath as normpath,
    pardir as pardir,
    pathsep as pathsep,
    relpath as relpath,
    sep as sep,
    split as split,
    splitdrive as splitdrive,
    splitext as splitext,
    supports_unicode_filenames as supports_unicode_filenames,
)
from typing import AnyStr, overload
from typing_extensions import LiteralString

__all__ = [
    "normcase",
    "isabs",
    "join",
    "splitdrive",
    "split",
    "splitext",
    "basename",
    "dirname",
    "commonprefix",
    "getsize",
    "getmtime",
    "getatime",
    "getctime",
    "islink",
    "exists",
    "lexists",
    "isdir",
    "isfile",
    "ismount",
    "expanduser",
    "expandvars",
    "normpath",
    "abspath",
    "curdir",
    "pardir",
    "sep",
    "pathsep",
    "defpath",
    "altsep",
    "extsep",
    "devnull",
    "realpath",
    "supports_unicode_filenames",
    "relpath",
    "samefile",
    "sameopenfile",
    "samestat",
    "commonpath",
]

altsep: LiteralString

# First parameter is not actually pos-only,
# but must be defined as pos-only in the stub or cross-platform code doesn't type-check,
# as the parameter name is different in posixpath.join()
@overload
def join(__path: LiteralString, *paths: LiteralString) -> LiteralString: ...
@overload
def join(__path: StrPath, *paths: StrPath) -> str: ...
@overload
def join(__path: BytesPath, *paths: BytesPath) -> bytes: ...

if sys.platform == "win32":
    if sys.version_info >= (3, 10):
        @overload
        def realpath(path: PathLike[AnyStr], *, strict: bool = ...) -> AnyStr: ...
        @overload
        def realpath(path: AnyStr, *, strict: bool = ...) -> AnyStr: ...
    else:
        @overload
        def realpath(path: PathLike[AnyStr]) -> AnyStr: ...
        @overload
        def realpath(path: AnyStr) -> AnyStr: ...

else:
    realpath = abspath

五.游戏运行的实现

import cfg
import sys
import time
import pygame
from modules import *


'''主函数'''
def main():
    # 游戏初始化
    pygame.init()
    screen = pygame.display.set_mode(cfg.SCREENSIZE)
    pygame.display.set_caption('小组作业')
    # 导入所有图片
    images = {}
    for key, value in cfg.IMAGE_PATHS.items():
        if key in ['face_fail', 'face_normal', 'face_success']:
            image = pygame.image.load(value)
            images[key] = pygame.transform.smoothscale(image, (int(cfg.GRIDSIZE*1.25), int(cfg.GRIDSIZE*1.25)))
        else:
            image = pygame.image.load(value).convert()
            images[key] = pygame.transform.smoothscale(image, (cfg.GRIDSIZE, cfg.GRIDSIZE))
    # 载入字体
    font = pygame.font.Font(cfg.FONT_PATH, cfg.FONT_SIZE)
    # 导入并播放背景音乐
    pygame.mixer.music.load(cfg.BGM_PATH)
    pygame.mixer.music.play(-1)
    # 实例化游戏地图
    minesweeper_map = MinesweeperMap(cfg, images)
    position = (cfg.SCREENSIZE[0] - int(cfg.GRIDSIZE * 1.25)) // 2, (cfg.GRIDSIZE * 2 - int(cfg.GRIDSIZE * 1.25)) // 2
    emoji_button = EmojiButton(images, position=position)
    fontsize = font.size(str(cfg.NUM_MINES))
    remaining_mine_board = TextBoard(str(cfg.NUM_MINES), font, (30, (cfg.GRIDSIZE*2-fontsize[1])//2-2), cfg.RED)
    fontsize = font.size('000')
    time_board = TextBoard('000', font, (cfg.SCREENSIZE[0]-30-fontsize[0], (cfg.GRIDSIZE*2-fontsize[1])//2-2), cfg.RED)
    time_board.is_start = False
    # 游戏主循环
    clock = pygame.time.Clock()
    while True:
        screen.fill(cfg.BACKGROUND_COLOR)
        # --按键检测
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.MOUSEBUTTONDOWN:
                mouse_pos = event.pos
                mouse_pressed = pygame.mouse.get_pressed()
                minesweeper_map.update(mouse_pressed=mouse_pressed, mouse_pos=mouse_pos, type_='down')
            elif event.type == pygame.MOUSEBUTTONUP:
                minesweeper_map.update(type_='up')
                if emoji_button.rect.collidepoint(pygame.mouse.get_pos()):
                    minesweeper_map = MinesweeperMap(cfg, images)
                    time_board.update('000')
                    time_board.is_start = False
                    remaining_mine_board.update(str(cfg.NUM_MINES))
                    emoji_button.setstatus(status_code=0)
        # --更新时间显示
        if minesweeper_map.gaming:
            if not time_board.is_start:
                start_time = time.time()
                time_board.is_start = True
            time_board.update(str(int(time.time() - start_time)).zfill(3))
        # --更新剩余雷的数目显示
        remianing_mines = max(cfg.NUM_MINES - minesweeper_map.flags, 0)
        remaining_mine_board.update(str(remianing_mines).zfill(2))
        # --更新表情
        if minesweeper_map.status_code == 1:
            emoji_button.setstatus(status_code=1)
        if minesweeper_map.openeds + minesweeper_map.flags == cfg.GAME_MATRIX_SIZE[0] * cfg.GAME_MATRIX_SIZE[1]:
            minesweeper_map.status_code = 1
            emoji_button.setstatus(status_code=2)
        # --显示当前的游戏状态地图
        minesweeper_map.draw(screen)
        emoji_button.draw(screen)
        remaining_mine_board.draw(screen)
        time_board.draw(screen)
        # --更新屏幕
        pygame.display.update()
        clock.tick(cfg.FPS)


'''run'''
if __name__ == '__main__':
    main()

游戏设计总结:

实验总结

通过本实验,我学到了如何使用Python语言来设计一个简单的扫雷游戏。在实现过程中,我掌握了Python语言的基础知识,包括数据类型、变量、循环、条件语句、函数等。同时,我还学到了如何使用Python中的GUI库来设计游戏界面,如何使用随机数来生成地图,如何使用计时器和计分器来记录游戏时间和得分,以及如何使用条件语句来判断游戏是否结束。

通过本实验,我还发现了一些需要改进的地方,如游戏界面的美化、游戏规则的完善、游戏难度的设置等。这些都是我在今后的学习中需要进一步探索和改进的方向。

本实验的设计和实现,不仅提高了我的Python编程能力和实践能力,还让我更深入地了解了计算机科学中的经典问题。在今后的学习中,我将继续深入学习Python语言和计算机科学的相关知识,不断提高自己的编程能力和实践能力。

  • 56
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值