pygame8 扫雷游戏2

142 阅读4分钟

上一节课我们做了一扫雷游戏的界面,本节课我们将会实现扫雷的逻辑

一、响应鼠标左键事件

pygame.MOUSEBUTTONDOWN 表示鼠标事件发生, pygame.mouse.get_pressed()[0] 确认是鼠标左键被按下 pygame.mouse.get_pos() 获取到鼠标按下时的坐标值。 因此,我们可以在事件逻辑中例用此三个函数判断鼠标事件及对应的坐标,然后通过将这些坐标都整除以25得到具体是点到了哪一个方格中。 事件的代码如下:

elif event.type == pygame.MOUSEBUTTONDOWN and pygame.mouse.get_pressed()[0]:
    x, y = pygame.mouse.get_pos()
    self.cover.delete(x, y)
    self.game_map.add(x, y)

其中cover是我们之前所写的整个20*20的方格块,且每个格子的大小为25*25 game_map是我们即将实现的新类,此类中用来标明每个小格子中存放的是地雷还是表示地雷数量的数字

二、地雷算法实现类

很明显,此类中应该首先包含一个表格,此表格每行应有格子20个,然后有20行,与前面cover中表示的格子一致。然后默认值为0

class Map:
    def __init__(self, screen, cover):
        self.screen = screen
        self.cover = cover
        self.maps = []
        for i in range(20):
            item = [0] * 20
            self.maps.append(item)
        self.moves = [[-1, -1], [0, -1], [1, -1], [-1, 0], [1, 0], [-1, 1], [0, 1], [1, 1]]

item有20个元素,maps是一个20个元素的数组,每个元素就是一个item,即maps从0到19相当于20行,每行的item有20格 moves是在具体每一格时,可以上下左右算出另外8个格子的位移 然后我们就可以随机生成地雷了,先随机选一个格子,如果格子本身没有雷并且周围也没有雷,就将此格子置为X表示雷,然后再此格子周边的格子全部将地雷的数量增1 代码如下:

def _born_map(self):
    for i in range(40):
        x = randint(0, 19)
        y = randint(0, 19)
        while self.maps[x][y] != 0:  # 找到一个可放雷的
            x = randint(0, 19)
            y = randint(0, 19)
        self.maps[x][y] = 'X'
        self._connect_(x, y)
    return

def _re_calc_(self, x, y):
    for cur in self.moves:
        i = x + cur[0]
        j = y + cur[1]
        if 0 <= i < 20 and 0 <= j < 20 and self.maps[i][j] != 'X':
            self.maps[i][j] += 1

当用户点击格子时,首先将鼠标坐标转换为地图上格子的索引,然后将这些索引都存入一个列表now_show中,然后调用show函数,show函数遍历now_show,然后将now_show指向的地图内容显示出来。需要注意的是,我们需要通过pygame.font.SysFont获取到字体,然后通过font.render将具体的字转换为图像,这样才能调用screen.blit将这些字显示在格子中,代码如下:

def add(self, x, y):
    i = x // 25
    j = y // 25
    if i >= 0 and i < 20 and j >= 0 and j < 20:
        self.now_show.append([i, j])
        
def show(self):
    for cur in self.now_show:
        i = cur[0]
        j = cur[1]
        if self.maps[i][j] == 0:
            continue
        msg = str(self.maps[i][j])
        font = pygame.font.SysFont(None, 45)
        font_image = font.render(msg, True, [60, 0, 0], None)
        image_rect = font_image.get_rect()
        image_rect.x = i * 25
        image_rect.y = j * 25
        image_rect.width = 24
        image_rect.height = 24
        self.screen.blit(font_image, image_rect)

当我们点击的格子本身不是雷,周边也没有雷时,需要将周边显示出来, 逻辑如下:当发现选到的格子不是雷且不在now_show时,将其放入now_show中,然后针对其周围的8个格子做同样的操作,代码如下:

def _add_connect_(self, x, y):
    que = []
    que.append([x, y])
    while que:
        cur = que[-1]
        del que[-1]
        for k in range(8):
            i, j = cur[0] + self.moves[k][0], cur[1] + self.moves[k][1]
            if i >= 0 and i < 20 and j >= 0 and j < 20 and self.maps[i][j] != 'X' and [i, j] not in self.now_show:
                self.now_show.append([i, j])
                self.cover.delete(i*25, j * 25)
                if self.maps[i][j] == 0:
                    que.append([i, j])

其中self.cover.delete(i*25, j * 25)是为了增加点击的效果,因为cover.delete对应的格子后,相应格子会显示的更亮一点。

三、一些特效的处理

1 再画一层边框,使得格子立体化:

def frame(self):
    for i in range(25):
        pygame.draw.line(self.screen, [120, 120, 120], [0, i * 25], [500, i * 25], 1)
        pygame.draw.line(self.screen, [120, 120, 120], [i * 25, 0], [i * 25, 500], 1)

2 再点击格子时,发现是地雷,将其存储到red_point中,使得在显示雷时,格子是红色的

def add(self, x, y):
    ...
    if self.maps[i][j] == 'X':
        self.red_points["{}.{}".format(i, j)] = [i, j]

然后在显示时,先显示有雷的红色框:

def show(self):
    for k in self.red_points.values():
        pygame.draw.rect(self.screen, [255, 0, 0], [k[0] * 25, k[1] * 25, 25, 25])
    ...

四、附上全部代码:

game.py

import sys

import pygame
from cover import Cover
from map_of_game import Map


class Game:
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode([500, 500])
        self.cover = Cover(self.screen)
        self.game_map = Map(self.screen, self.cover)

    def check_event(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.MOUSEBUTTONDOWN and pygame.mouse.get_pressed()[0]:
                x, y = pygame.mouse.get_pos()
                self.cover.delete(x, y)
                self.game_map.add(x, y)

    def update_screen(self):
        self.screen.fill([255, 255, 255], [0, 0, 500, 500])
        self.cover.show()
        self.cover.frame()
        self.game_map.show()
        pygame.display.flip()

    def run_game(self):
        while True:
            self.check_event()
            self.update_screen()


if __name__ == '__main__':
    game = Game()
    game.run_game()

cover.py

import pygame


class Cover:
    def __init__(self, screen):
        self.screen = screen
        self.covers = []
        for i in range(20):
            for j in range(20):
                self.covers.append([i, j])

    def show(self):
        for cur in self.covers:
            pygame.draw.rect(self.screen, [160, 160, 160], [cur[0] * 25, cur[1] * 25, 24, 24])

    def frame(self):
        for i in range(25):
            pygame.draw.line(self.screen, [120, 120, 120], [0, i * 25], [500, i * 25], 1)
            pygame.draw.line(self.screen, [120, 120, 120], [i * 25, 0], [i * 25, 500], 1)

    def delete(self, x, y):
        x = x // 25
        y = y // 25
        if [x, y] in self.covers:
            self.covers.remove([x, y])

map_of_game.py

import pygame
from random import randint


class Map:
    def __init__(self, screen, cover):
        self.screen = screen
        self.cover = cover
        self.maps = []
        for i in range(20):
            item = [0] * 20
            self.maps.append(item)
        self.moves = [[-1, -1], [0, -1], [1, -1], [-1, 0], [1, 0], [-1, 1], [0, 1], [1, 1]]
        self.now_show = []
        self._born_map()
        self.red_points = {}  # 雷的坐标

    def _born_map(self):
        for i in range(40):
            x = randint(0, 19)
            y = randint(0, 19)
            while self.maps[x][y] != 0:  # 找到一个可放雷的
                x = randint(0, 19)
                y = randint(0, 19)
            self.maps[x][y] = 'X'
            self._re_calc_(x, y)
        return

    def _re_calc_(self, x, y):
        for cur in self.moves:
            i = x + cur[0]
            j = y + cur[1]
            if 0 <= i < 20 and 0 <= j < 20 and self.maps[i][j] != 'X':
                self.maps[i][j] += 1

    def add(self, x, y):
        i = x // 25
        j = y // 25
        if 0 <= i < 20 and 0 <= j < 20:
            self.now_show.append([i, j])
            if self.maps[i][j] == 0:
                self._add_connect_(i, j)
        if self.maps[i][j] == 'X':
            self.red_points["{}.{}".format(i, j)] = [i, j]

    def show(self):
        for k in self.red_points.values():
            pygame.draw.rect(self.screen, [255, 0, 0], [k[0] * 25, k[1] * 25, 25, 25])
        for cur in self.now_show:
            i = cur[0]
            j = cur[1]
            if self.maps[i][j] == 0:
                continue
            msg = str(self.maps[i][j])
            font = pygame.font.SysFont(None, 45)
            font_image = font.render(msg, True, [60, 0, 0], None)
            image_rect = font_image.get_rect()
            image_rect.x = i * 25
            image_rect.y = j * 25
            image_rect.width = 24
            image_rect.height = 24
            self.screen.blit(font_image, image_rect)

    def _add_connect_(self, x, y):
        que = [[x, y]]
        while que:
            cur = que[-1]
            del que[-1]
            for k in range(8):
                i, j = cur[0] + self.moves[k][0], cur[1] + self.moves[k][1]
                if 0 <= i < 20 and 0 <= j < 20 and self.maps[i][j] != 'X' and [i, j] not in self.now_show:
                    self.now_show.append([i, j])
                    self.cover.delete(i*25, j * 25)
                    if self.maps[i][j] == 0:
                        que.append([i, j])