python没有pygame_python制作小游戏(二十五)

python制作小游戏(十九)

python制作小游戏(二十)

python制作小游戏(二十一)

python制作小游戏(二十二)

python制作小游戏(二十三)

python制作小游戏(二十四)

最近脑子不太好使,儿童节到了,也没想出可以做点啥好玩又能和节日相符的东西来。所以就在节日末尾简单带大家写个小游戏吧。看封面大家应该猜到了是啥游戏了吧,没错就是扫雷。

废话不多说,让我们愉快地开始吧~

关注微信公众号"Charles的皮卡丘",公众号内回复"game25"获取。

Python版本:3.6.4

相关模块:

pygame模块;

以及一些python自带的模块。

安装Python并添加到环境变量,pip安装需要的相关模块即可。

在终端运行如下命令即可:

python Game25.py

效果如下:

因为时间有点赶,所以这里我只简单介绍一下游戏的实现思路。

相信大家对扫雷这款windows自带的经典小游戏都不陌生,它的游戏规则很简单:

e269e45774ab5de14111b2d54296fb10.png

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

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

0e45ad7f12e65cbd88259343d27e7d72.png

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

ok,了解了游戏规则之后,我们就可以开始写代码了。首先还是先初始化一下游戏:

# 游戏初始化pygame.init()screen = pygame.display.set_mode(cfg.SCREENSIZE)pygame.display.set_caption('mine sweeper —— Charles的皮卡丘')

然后把需要用到的字体,图片,音乐啥的都导入进来:

# 导入所有图片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)

接着,我们来定义一个文字板,用于显示左上角的埋雷数量和右上角的游戏已进行时间:

'''文字板'''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

其实很简单,只需要把用字体渲染之后的文本对象绑定到屏幕上就行,然后设置一个update函数,来实时更新里面的文本内容。

然后,我们再定义一个表情按钮类:

'''表情按钮'''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

当鼠标点击到这个按钮的时,就重新开始新的游戏(无论当前的游戏状态如何,都将重新开始新的游戏):

b06358baab9efd08a3cfe8d0379fa34a.png

接下来,我们需要定义的就是下面的方格类了:

'''雷'''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

它的主要作用就是记录游戏地图中某个方格的状态(比如是不是埋了雷呀,有没有被点开呀,有没有被标记呀之类的)。

最后定义一个游戏地图类,来把游戏地图中的所有方格都整合在一起方便在游戏主循环里调用更新:

'''扫雷地图'''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

这里只解释几个可能有小伙伴看不太懂的地方:

  • 打开雷的时候我们用了递归,作用是当点击到的方格周围都没有雷的时候,系统就自动打开这个方格周围的方格,以实现有时候点击一个方格可以打开一大片方格的效果,这里的周围都特指以目标方格为中心的九宫格内的所有方格;

  • 鼠标左右键一起按在已经打开的方格上的话,如果这个方格周围的方格已经被标记为雷的数目和这个方格上显示的数字一致,就把这个方格周围未被标记为雷的方格都打开(所以如果你标记错的话,一起打开的时候会显示你游戏已经GG了)。

其他也没啥好说的了,感觉关注我的小伙伴应该都挺聪明的,自己看代码就能看懂吧。

定义完这些游戏中必要的元素类之后就在游戏主函数里实例化它们:

# 实例化游戏地图minesweeper_map = MinesweeperMap(cfg, images)position = (cfg.SCREENSIZE[0] - int(cfg.GRIDSIZE * 1.25)) // 2, (cfg.GRIDSIZE * 2 - int(cfg.GRIDSIZE * 1.25)) // 2emoji_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

然后写个游戏主循环以根据用户的操作来更新当前的游戏状态就ok啦:

# 游戏主循环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)

大功告成,完整源代码详见相关文件呗~

代码截止2020-06-01测试无误。

怕你们错过的一些内容:

听说用python来批量删除说说也挺快乐的呢~

是时候做个有趣的二维码皮一下啦~

0192ae6da8ea81d3c7079e66120226fb.gif 0192ae6da8ea81d3c7079e66120226fb.gif  cf33ebb7da989a58facfd80e36df5c52.png

微信公众号:

Charles的皮卡丘

204c89ea5705b50e882100e227267903.png

哈哈哈哈哈

长按二维码关注

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值