计算机软件实习项目三 A*算法迷宫(代码)


from modules.Sprites import *
from modules.mazes import *
from modules.pathfinding import *

'''主函数'''


def main(cfg):
    #初始化
    pygame.init() #初始化pygame库
    pygame.font.init()#初始化字体
    screen = pygame.display.set_mode(cfg.SCREENSIZE)#屏幕初始化为cfg文件中的屏幕规格为800px*625px
    pygame.display.set_caption('Maze')#设置窗口标题为Maze

    # 字体
    font = pygame.font.SysFont(cfg.FONT, 15)#设置字体为SegoeScript
    font_button = pygame.font.SysFont(cfg.FONT, 15)  #设置按钮字体为SegoeScript
    font_button.set_underline(True)  # 按钮字体开启下划线
    font_focus = pygame.font.SysFont(cfg.FONT, 25)  # 按钮焦点字体,放大字体
    # 三个界面
    modes = {'start': 'game_start', 'switch': 'game_switch', 'end': 'game_end'}#设置三个界面,游戏开始界面,游戏设置界面,游戏结束界面
    # 开始界面
    choice = Interface(screen, cfg, modes['start'])#设置为开始界面
    # 开始或者重新开始
    while True:#一直循环
        # 退出游戏
        if not choice:#如果不是开始界面
            pygame.quit()#关闭pygame库
            sys.exit(-1)#关闭程序
        # 设置界面 返回迷宫生成算法
        arithmetic = setting(screen, cfg)
        # 记录关卡数
        num_levels = 0

        # 关卡循环切换
        while True:#一直循环
            num_levels += 1 #关卡数加1
            clock = pygame.time.Clock()#创建游戏时钟对象
            screen = pygame.display.set_mode(cfg.SCREENSIZE)#刷新屏幕显示
            # 随机生成关卡地图
            maze_now = RandomMaze(cfg.MAZESIZE, cfg.BLOCKSIZE, cfg.BORDERSIZE, arithmetic, cfg.STARTPOINT,
                                  cfg.DESTINATION)
            #调用RandomMaze创建迷宫,cfg.MAZESIZE:迷宫大小;cfg.BLOCKSIZE:格子大小;cfg.BORDERSIZE:边框大小;arithmetic:创建迷宫按钮字符串;
            #cfg.STARTPOINT:迷宫起点,cfg.DESTINATION:迷宫终点。
            # 生成hero
            start = [cfg.STARTPOINT[1], cfg.STARTPOINT[0]]#人物起始位置为迷宫起点
            hero_now = Hero(cfg.HEROPICPATH, start, cfg.BLOCKSIZE, cfg.BORDERSIZE)
            #调用Hero创建人物,cfg.HEROPICPATH:人物图片;start:人物起始位置;cfg.BLOCKSIZE:格子大小;cfg.BORDERSIZE:边框大小
            # 统计步数
            num_steps = 0
            # 记录路径
            move_records = []
            # 寻路功能按钮:显示路径 直接通关
            path_finding = ["Do you want to cheat?","Cheat(A*)","Do you want to continue"]
            path_n = 0

            # 预先获取位置
            rect = pygame.Rect(10, 10, font.size('Level Done: %d' % num_levels)[0],
                               font.size('Level Done: %d' % num_levels)[1])
            #创建文本,在左上角10px,10px处创建关卡数据
            button = Label_ce(screen, font_button, path_finding[path_n], cfg.HIGHLIGHT,
                              (cfg.SCREENSIZE[0] - font_button.size(path_finding[0])[0], rect.centery))
            #创建作弊按钮,screen:窗口大小;font_button:按钮大小;cfg.HIGHLIGHT:字体高光;在右上角循环显示按钮文本
            # 关卡内主循环
            while True:
                dt = clock.tick(cfg.FPS)#设置游戏帧数
                screen.fill((255, 255, 255))#将黑色铺满窗口
                is_move = False #人物移动

                # 寻找路径
                if path_n ==0:
                    guide, searched = A_Star(maze_now, hero_now.coordinate, cfg.DESTINATION)
                    #调用A_Star函数使guide为最终路径,searched为A星算法查询过的路径


                # 显示移动路径
                for move in move_records:
                    pygame.draw.circle(screen, cfg.HIGHLIGHT, move, 2)
                    #通过画红色的圆点显示人物走过的路径

                # 显示路径
                if path_n != 0:#如果玩家点击了作弊按钮
                    for move in searched:
                        pygame.draw.circle(screen, cfg.LINE, move, 2)
                        # 通过画灰色的圆点显示查询过的路径
                    for move in guide:
                        pygame.draw.circle(screen, cfg.FOREGROUND, move, 2)
                        # 通过画黑色的圆点显示最终的路径

                # ----显示一些信息
                Label_co(screen, font, 'Level Done: %d' % num_levels, cfg.HIGHLIGHT, (10, 10))
                #显示玩家玩过的关卡数
                Label_co(screen, font, 'Used Steps: %s' % num_steps, cfg.HIGHLIGHT, (cfg.SCREENSIZE[0] // 4 + 10, 10))
                #显示玩家走过的步数
                Label_co(screen, font, 'Min Cost: %s' % (len(guide) - 1), cfg.HIGHLIGHT,
                         (cfg.SCREENSIZE[0] // 2 + 10, 10))
                #显示玩家最优路线
                Label_co(screen, font, 'S: your starting point    D: your destination', cfg.HIGHLIGHT,
                         (10, cfg.SCREENSIZE[1] - font.size('')[1] - 10))
                #设置起点和终点的提示文本

                # 显示作弊按钮,鼠标移动到作弊按钮,采用焦点字体格式
                if button.collidepoint(pygame.mouse.get_pos()):
                    button = Label_ce(screen, font_focus, path_finding[path_n], cfg.HIGHLIGHT,
                                      (cfg.SCREENSIZE[0] - font_button.size(path_finding[0])[0], rect.centery))
                else:
                # 反之,采用正常字体格式
                    button = Label_ce(screen, font_button, path_finding[path_n], cfg.HIGHLIGHT,
                                      (cfg.SCREENSIZE[0] - font_button.size(path_finding[0])[0], rect.centery))

                # 事件响应 ↑↓←→控制hero
                for event in pygame.event.get():
                    # 退出
                    if event.type == pygame.QUIT:
                        pygame.quit()
                        sys.exit(-1)
                    # 点击事件
                    elif event.type == pygame.MOUSEBUTTONDOWN:
                        #如果没有点击作弊按钮
                        if path_n != 2:
                            #更新作弊按钮显示
                            path_n += 1
                        else:
                            #将最终路径加入到人物移动路径中
                            move_records += guide
                            #默认胜利,将人物坐标移动到终点
                            hero_now.coordinate[0] = cfg.DESTINATION[1]
                            hero_now.coordinate[1] = cfg.DESTINATION[0]
                    # 键盘事件
                    elif event.type == pygame.KEYDOWN:
                        # 判断是否移动
                        if event.key == pygame.K_UP:
                            is_move = hero_now.move('Up', maze_now)
                        elif event.key == pygame.K_DOWN:
                            is_move = hero_now.move('Down', maze_now)
                        elif event.key == pygame.K_LEFT:
                            is_move = hero_now.move('Left', maze_now)
                        elif event.key == pygame.K_RIGHT:
                            is_move = hero_now.move('Right', maze_now)
                    #增加统计步数
                    num_steps += int(is_move)

                    # 添加移动记录
                    if is_move:
                        left, top = hero_now.coordinate[0] * cfg.BLOCKSIZE + cfg.BORDERSIZE[0], hero_now.coordinate[
                            1] * cfg.BLOCKSIZE + cfg.BORDERSIZE[1]
                        move_records.append((left + cfg.BLOCKSIZE // 2, top + cfg.BLOCKSIZE // 2))

                hero_now.draw(screen)#更新人物位置
                maze_now.draw(screen)#更新迷宫

                # ----判断游戏是否胜利
                if (hero_now.coordinate[0] == cfg.DESTINATION[1]) and (hero_now.coordinate[1] == cfg.DESTINATION[0]):
                    break
                pygame.display.update()#刷新游戏

            # --关卡切换
            choice = Interface(screen, cfg, modes['switch'])#设置为游戏设置界面
            if not choice:
                break
        choice = Interface(screen, cfg, modes['end'])#设置为游戏结束界面


if __name__ == '__main__':
    # 读入配置
    read_cfg(cfg)
    main(cfg)
import json

SCREENSIZE = (800, 625)#窗口大小
HEROPICPATH = 'resources/images/hero.png'#人物
# font
FONT = 'SegoeScript'#字体1
FONT2 = 'Consolas'#字体2
# colors
BACKGROUND = (255, 255, 255)#背景
FOREGROUND = (0, 0, 0)#前景
HIGHLIGHT = (255, 0, 0)#高光
LINE = (185, 185, 185)#下划线

FPS = 30#游戏帧率

# 迷宫相关
BLOCKSIZE = 15 #格子大小
MAZESIZE = [15, 10]  # 迷宫大小 r*c
STARTPOINT = [1, 1]#起点
DESTINATION = [15, 10]#终点
BORDERSIZE = ((SCREENSIZE[0] - MAZESIZE[1] * BLOCKSIZE) // 2, (SCREENSIZE[1] - MAZESIZE[0] * BLOCKSIZE) // 2)#边框大小

'''写入配置'''


def write_cfg():
    cfgs = {'MAZESIZE': MAZESIZE, 'STARTPOINT': STARTPOINT, 'DESTINATION': DESTINATION}
    fp = open('cfg.txt', 'w')
    json.dump(cfgs, fp)
    fp.close()


'''读出配置'''


def read_cfg():
    fp = open('cfg.txt', 'r')
    cfgs = json.load(fp)
    fp.close()

    return cfgs
import re
import sys
import pygame
from pygame.locals import *
import pygame.font
import pygame.event
import pygame.draw

#读取游戏设置
def read_cfg(cfg):
    cfgs = cfg.read_cfg()
    cfg.MAZESIZE = cfgs['MAZESIZE']
    cfg.STARTPOINT = cfgs['STARTPOINT']
    cfg.DESTINATION = cfgs['DESTINATION']


'''Label: 在指定位置(中心)显示文字'''


def Label_ce(screen, font, text, color, position):
    text_render = font.render(text, True, color)  # 文本样式
    rect = text_render.get_rect()  # 文本位置
    rect.centerx, rect.centery = position#设置位置为中部
    return screen.blit(text_render, rect)


'''Label: 在指定位置(左上角)显示文字'''


def Label_co(screen, font, text, color, position):
    text_render = font.render(text, True, color)  # 文本样式
    rect = text_render.get_rect()  # 文本位置
    rect.left, rect.top = position#设置位置为左上
    return screen.blit(text_render, rect)


'''setting: 检查输入设置文本格式是否正确'''


def check_setted(cfg):
    #设置初始化
    setted = [True, False, False, False, False]
    # 类型转换为字符串
    for key in cfg.keys():
        cfg[key] = str(cfg[key])
    # Rows是否为数字
    if cfg['Rows'].isdigit():
        # 转换为数字
        cfg['Rows'] = int(cfg['Rows'])
        # 检查输入数字是否在(0,35]之间
        if 0 < cfg['Rows'] <= 35:
            setted[1] = True
    # Cols是否为数字
    if cfg['Cols'].isdigit():
        # 转换为数字
        cfg['Cols'] = int(cfg['Cols'])
        # 检查输入数字是否在(0,50]之间
        if 0 < cfg['Cols'] <= 50:
            setted[2] = True
    # 正则表达式 匹配起点,终点输入格式
    pattern = re.compile('^\[(\d{1,})[,*,*\s]*(\d{1,})\]$')
    #将起点输入框的字符串转换为数字
    g = pattern.match(cfg['Starting point'])
    #如果g不为空,匹配起点
    if g:
        #x,y设置为g的第一个和第二个数字
        x, y = g.group(1), g.group(2)
        #写入cfg文件
        cfg['Starting point'] = [int(x), int(y)]
        #将起点位置信息进行再次测试是否正确
        if setted[1] and setted[2] and 0 < cfg['Starting point'][0] <= cfg['Rows'] and 0 < cfg['Starting point'][1] <= \
                cfg['Cols']:
            #将setted数组的第四个false设置为true
            setted[3] = True
    # 将终点输入框的字符串转换为数字
    g = pattern.match(cfg['Destination'])
    if g:#如果g不为空,匹配终点
        x, y = g.group(1), g.group(2)# x,y设置为g的第一个和第二个数字
        # 写入cfg文件
        cfg['Destination'] = [int(x), int(y)]
        # 将终点位置信息进行再次测试是否正确
        if setted[1] and setted[2] and 0 < cfg['Destination'][0] <= cfg['Rows'] and 0 < cfg['Destination'][1] <= cfg[
            'Cols']:
            # 将setted数组的第五个false设置为true
            setted[4] = True
    return setted


'''setting: 输入框显示'''


# 输入框显示设置:窗口,字体,是否为焦点输入框,输入问题,输入回答,输入提示,字体颜色,输入框位置,输入框最大宽度
def InputBox(screen, font, focus, question, ans, hint, color, position, max):
    # 焦点输入框设置字体斜体
    font.set_italic(focus)
    # 根据本身宽度、最大宽度求空格数
    width, height = font.size(question + ans + hint)
    blank = (max - position[0] - width) // 2 // font.size(' ')[0]
    left = (max - position[0] - width - blank * font.size(' ')[0]) // font.size(' ')[0]
    # 输入问题显示
    rect = Label_co(screen, font, question, color, position)
    # 输入回答显示
    x, y = rect.left + rect.width, rect.top  # 根据输入问题位置求显示位置
    font.set_underline(focus)  # 焦点输入框输入回答位置设置下划线
    rect2 = Label_co(screen, font, ' ' * blank + ans + ' ' * left, color, (x, y))
    font.set_underline(False)
    # 输入提示显示
    Label_co(screen, font, hint, color, (rect2.left + rect2.width, y))
    font.set_italic(False)
    return pygame.Rect(rect.left, rect.top, max - position[0], rect.height)


'''setting: 界面设置及其响应事件(包含按钮、输入框)'''


def setting(screen, cfg):
    read_cfg(cfg)
    # 字体样式
    font_title = pygame.font.SysFont(cfg.FONT, 80)  # 标题字体
    font = pygame.font.SysFont(cfg.FONT2, 45)  # 输入框字体
    font_warn = pygame.font.SysFont(cfg.FONT2, 20)  # 提示字体
    font_start = pygame.font.SysFont(cfg.FONT, 70)  # 按钮字体
    font_start.set_underline(True)  # 按钮字体开启下划线
    font_start_focus = pygame.font.SysFont(cfg.FONT, 85)  # 按钮焦点字体

    # 设置
    cfg_now = {'Rows': cfg.MAZESIZE[0], 'Cols': cfg.MAZESIZE[1], 'Starting point': cfg.STARTPOINT,
               'Destination': cfg.DESTINATION}
    # 设置检查
    setted = check_setted(cfg_now)

    # 设置正误相关提示
    warnings = {'Correct': 'Correct settings', 'Incorrect': 'Incorrect settings'}

    # 输入框相关参数:行数、列数、起点、终点
    focus = ['title', 'Rows', 'Cols', 'Starting point', 'Destination']  # 标题 输入问题
    hint = {'Rows': '(0<rows<=35)', 'Cols': '(0 < cols <= 50)', 'Starting point': ' ', 'Destination': ' '}  # 输入提示
    color = {'True': cfg.HIGHLIGHT, 'False': cfg.FOREGROUND}

    # 焦点输入框下标 默认第一个
    focus_now = 1

    # 按钮:
    buttons = {'Create maze': Label_ce(screen, font_start, 'Create maze', cfg.FOREGROUND,
                                (cfg.SCREENSIZE[0] // 2, cfg.SCREENSIZE[1] - font_start.size('')[1] // 2)),
              }

    #setting主循环
    while True:
        screen.fill(cfg.BACKGROUND)
        # 绘制标题
        labels = {'title': Label_co(screen, font_title, 'Setting:', cfg.FOREGROUND, (10, 0)),
                  'Rows': None, 'Cols': None, 'Starting point': None, 'Destination': None}
        pygame.draw.line(screen, cfg.LINE, (0, labels['title'].top + labels['title'].height - 20),
                         (cfg.SCREENSIZE[0], labels['title'].top + labels['title'].height - 20))  # 分割线

        # 绘制输入框(区分焦点输入框)
        for i in range(1, 5):
            labels[focus[i]] = InputBox(screen, font, i == focus_now, focus[i] + ': ', str(cfg_now[focus[i]]),
                                        hint[focus[i]], color[str(i == focus_now)],
                                        (20, labels[focus[i - 1]].top + labels[focus[i - 1]].height + 30),
                                        cfg.SCREENSIZE[0])

        # 显示提醒
        if setted.count(True) == 5:  # 设置正确
            warn = warnings['Correct']
        else:
            warn = warnings['Incorrect']
        #显示警告信息
        warning = Label_co(screen, font_warn, '· Tips: ' + warn, cfg.HIGHLIGHT,
                           (20, cfg.SCREENSIZE[1] - font_start_focus.size('')[1] - 30))
        pygame.draw.line(screen, cfg.LINE, (0, warning.top + warning.height),
                         (cfg.SCREENSIZE[0], warning.top + warning.height))  # 分割线

        # 按钮 区分设置正确与否 区分焦点
        for key in buttons.keys():
            #如果鼠标在这个按钮上同时输入正确的数据对按钮采用焦点格式
            if buttons[key].collidepoint(pygame.mouse.get_pos()) and warn == warnings['Correct']:
                buttons[key] = Label_ce(screen, font_start_focus, key, cfg.FOREGROUND,
                                        (buttons[key].centerx, buttons[key].centery));
            #反之,则采用正常格式
            else:
                buttons[key] = Label_ce(screen, font_start, key, cfg.FOREGROUND,
                                        (buttons[key].centerx, buttons[key].centery));

        # 当前焦点输入框的内容
        current_string = list(str(cfg_now[focus[focus_now]]))

        # 事件响应
        for event in pygame.event.get():
            # 退出游戏
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit(-1)
            # 鼠标点击
            elif event.type == pygame.MOUSEBUTTONDOWN:
                # 点击按钮 检查按钮及是否可按(设置是否正确)
                for key in buttons.keys():
                    if buttons[key].collidepoint(pygame.mouse.get_pos()) and warn == warnings['Correct']:
                        # 输入正确,修改cfg配置
                        cfg.MAZESIZE = [cfg_now['Rows'], cfg_now['Cols']]
                        cfg.STARTPOINT = cfg_now['Starting point']
                        cfg.DESTINATION = cfg_now['Destination']
                        cfg.BORDERSIZE = (
                            (cfg.SCREENSIZE[0] - cfg.MAZESIZE[1] * cfg.BLOCKSIZE) // 2,
                            (cfg.SCREENSIZE[1] - cfg.MAZESIZE[0] * cfg.BLOCKSIZE) // 2)
                        cfg.write_cfg()  # 将新配置写入配置文件
                        cfg.STARTPOINT = [cfg_now['Starting point'][0] - 1, cfg_now['Starting point'][1] - 1]
                        cfg.DESTINATION = [cfg_now['Destination'][0] - 1, cfg_now['Destination'][1] - 1]
                        return key
                # 点击标签
                for key in labels.keys():
                    if labels[key].collidepoint(pygame.mouse.get_pos()) and key != 'title':
                        focus_now = focus.index(key)
            # 键盘输入
            elif event.type == KEYDOWN:
                inkey = event.key
                if inkey == K_BACKSPACE:  # 撤销
                    current_string = current_string[0:-1]
                    cfg_now[focus[focus_now]] = ''.join(current_string)  # 更新字符串
                elif inkey == K_RETURN or inkey == K_RIGHT or inkey == K_DOWN:  # 下个输入框
                    if focus_now < 4:
                        focus_now = focus_now + 1
                elif inkey == K_LEFT or inkey == K_UP:  # 上个输入框
                    if focus_now > 1:
                        focus_now = focus_now - 1
                elif inkey <= 127:  # 输入字符
                    current_string.append(chr(inkey))
                    cfg_now[focus[focus_now]] = ''.join(current_string)  # 更新字符串
            else:
                pass
            # 设置更新
            setted = check_setted(cfg_now)
        # 界面更新
        pygame.display.update()


'''Interface: 其他界面设置及其响应事件(包含按钮)'''


def Interface(screen, cfg, mode='game_start', title='Maze'):
    # 字体样式
    font_title = pygame.font.SysFont(cfg.FONT, 120)  # 标题字体
    font = pygame.font.SysFont(cfg.FONT, 55)  # 按钮字体
    font.set_underline(True)  # 按钮字体开启下划线
    font_focus = pygame.font.SysFont(cfg.FONT, 100)  # 焦点按钮字体
    # 按钮样式
    #标题
    label_title = {'font': font_title, 'font_focus': font_title, 'text': title, 'color': cfg.HIGHLIGHT,
                   'position': ((cfg.SCREENSIZE[0]) // 2, cfg.SCREENSIZE[1] // 4)}
    #继续
    label_continue = {'font': font, 'font_focus': font_focus, 'text': 'Start', 'color': cfg.FOREGROUND,
                      'position': ((cfg.SCREENSIZE[0]) // 2, cfg.SCREENSIZE[1] // 2)}
    #退出
    label_quit = {'font': font, 'font_focus': font_focus, 'text': 'Quit', 'color': cfg.FOREGROUND,
                  'position': ((cfg.SCREENSIZE[0]) // 2, cfg.SCREENSIZE[1] - cfg.SCREENSIZE[1] // 3)}
    #将按钮放在同一个数组中
    label_format = {'title': label_title, 'continue': label_continue, 'quit': label_quit}
    #如果游戏窗口在设置界面
    if mode == 'game_switch':
        #更改继续按钮文本为Next
        label_continue['text'] = 'Next'
    #如果游戏窗口在结束界面
    elif mode == 'game_end':
        # 更改继续按钮文本为Restart
        label_continue['text'] = 'Restart'

    # 按钮
    buttons = {'title': None, 'continue': None, 'quit': None}

    clock = pygame.time.Clock()
    screen.fill(cfg.BACKGROUND)

    #设置按钮格式
    for key in buttons.keys():
        buttons[key] = Label_ce(screen, label_format[key]['font'], label_format[key]['text'],
                                label_format[key]['color'],
                                label_format[key]['position'])
    pygame.draw.line(screen, cfg.LINE,
                     (buttons['title'].left - 100, buttons['title'].top + buttons['title'].height - 40), (
                         buttons['title'].left + buttons['title'].width + 100,
                         buttons['title'].top + buttons['title'].height - 40))

    # ----Logo图片
    image = pygame.image.load(cfg.HEROPICPATH)
    image = pygame.transform.scale(image, (50, 50))
    rect = image.get_rect()
    rect.centerx, rect.centery = buttons['title'].centerx + buttons['title'].width // 2 + 25, buttons[
        'title'].centery + 30
    screen.blit(image, rect)

    # ----界面主循环
    while True:
        screen.fill(cfg.BACKGROUND)
        # 绘制按钮 区分焦点
        for key in buttons.keys():
            if buttons[key].collidepoint(pygame.mouse.get_pos()):#如果鼠标在按钮上则调用焦点字体格式
                buttons[key] = Label_ce(screen, label_format[key]['font_focus'], label_format[key]['text'],
                                        label_format[key]['color'], label_format[key]['position'])
            else:#否则就采用正常字体格式
                buttons[key] = Label_ce(screen, label_format[key]['font'], label_format[key]['text'],
                                        label_format[key]['color'], label_format[key]['position'])
        #在按钮下添加下划线
        pygame.draw.line(screen, cfg.LINE,
                         (buttons['title'].left - 100, buttons['title'].top + buttons['title'].height - 40), (
                             buttons['title'].left + buttons['title'].width + 100,
                             buttons['title'].top + buttons['title'].height - 40))
        # 绘制Logo
        screen.blit(image, rect)
        # 事件响应
        for event in pygame.event.get():
            #退出
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit(-1)
            #鼠标事件
            elif event.type == pygame.MOUSEBUTTONDOWN:
                #继续
                if buttons['continue'].collidepoint(pygame.mouse.get_pos()):
                    return True
                #退出
                elif buttons['quit'].collidepoint(pygame.mouse.get_pos()):
                    return False
        pygame.display.update()
        clock.tick(cfg.FPS)
class Block(object):
    #Block:格子类初始化
    def __init__(self, coordinate, block_size, border_size):
        # (col, row)
        self.coordinate = coordinate #坐标
        self.block_size = block_size #格子大小
        self.border_size = border_size #边框大小
        self.is_visited = False #是否被浏览
        # 上下左右有没有墙
        self.has_walls = [True, True, True, True]
        self.color = (0, 0, 0)

    '''画到屏幕上'''

    def draw(self, screen):
        directions = ['top', 'bottom', 'left', 'right']#方向
        for idx, direction in enumerate(directions):#遍历格子的四周
            if self.has_walls[idx]:#如果有墙
                if direction == 'top':#如果是上部,x1为左边的点,x2为右边的点
                    x1 = self.coordinate[0] * self.block_size + self.border_size[0]
                    y1 = self.coordinate[1] * self.block_size + self.border_size[1]
                    x2 = (self.coordinate[0] + 1) * self.block_size + self.border_size[0]
                    y2 = self.coordinate[1] * self.block_size + self.border_size[1]
                    pygame.draw.line(screen, self.color, (x1, y1), (x2, y2))#在(x1, y1), (x2, y2)之间画线
                elif direction == 'bottom':#如果方向在底部,x1为左边的点,x2为右边的点
                    x1 = self.coordinate[0] * self.block_size + self.border_size[0]
                    y1 = (self.coordinate[1] + 1) * self.block_size + self.border_size[1]
                    x2 = (self.coordinate[0] + 1) * self.block_size + self.border_size[0]
                    y2 = (self.coordinate[1] + 1) * self.block_size + self.border_size[1]
                    pygame.draw.line(screen, self.color, (x1, y1), (x2, y2))
                elif direction == 'left':#方向在左侧,x1为上部的点,x2为下部的点
                    x1 = self.coordinate[0] * self.block_size + self.border_size[0]
                    y1 = self.coordinate[1] * self.block_size + self.border_size[1]
                    x2 = self.coordinate[0] * self.block_size + self.border_size[0]
                    y2 = (self.coordinate[1] + 1) * self.block_size + self.border_size[1]
                    pygame.draw.line(screen, self.color, (x1, y1), (x2, y2))
                elif direction == 'right':#方向在右侧,x1为上部的点,x2为下部的点
                    x1 = (self.coordinate[0] + 1) * self.block_size + self.border_size[0]
                    y1 = self.coordinate[1] * self.block_size + self.border_size[1]
                    x2 = (self.coordinate[0] + 1) * self.block_size + self.border_size[0]
                    y2 = (self.coordinate[1] + 1) * self.block_size + self.border_size[1]
                    pygame.draw.line(screen, self.color, (x1, y1), (x2, y2))
        return True


'''随机生成迷宫类'''


class RandomMaze(object):
    # maze_size:迷宫大小;block_size:格子大小;border_size:边框大小
    def __init__(self, maze_size, block_size, border_size, arithmetic, starting_point, destination, ):
        self.block_size = block_size#格子大小
        self.border_size = border_size#边框大小
        self.maze_size = maze_size#迷宫大小
        self.starting_point = starting_point#起点
        self.destination = destination#终点
        if arithmetic == 'Create maze':#如果迷宫按钮字符串为Create maze,调用迷宫生成。
            self.blocks_list = RandomMaze.createMaze_Prim(maze_size, block_size, border_size)
        self.font = pygame.font.SysFont(cfg.FONT, 12)#迷宫字体

    '''画到屏幕上'''

    def draw(self, screen):
        for row in range(self.maze_size[0]):
            for col in range(self.maze_size[1]):
                self.blocks_list[row][col].draw(screen)#从左往右,从上到下依次画格子
        # 起点和终点标志
        Label_ce(screen, self.font, 'S', (255, 0, 0),
                 (self.starting_point[1] * self.block_size + self.border_size[0] + 8, self.starting_point[
                     0] * self.block_size + self.border_size[1] + 8))
        Label_ce(screen, self.font, 'D', (255, 0, 0),
                 (self.destination[1] * self.block_size + self.border_size[0] + 8, self.destination[
                     0] * self.block_size + self.border_size[1] + 8))



    @staticmethod
    def createMaze_Prim(maze_size, block_size, border_size):
        # 生成全是墙的迷宫,循环生成r*c格格子的列表
        blocks_list = [[Block([col, row], block_size, border_size) for col in range(maze_size[1])] for row in
                       range(maze_size[0])]
        # 将第一个单元格加入列表
        block_now = blocks_list[0][0]
        records = [block_now]
        # 当列表不空时
        while records:
            # 随机取出一个单元格
            block_now = random.choice(records)
            # 标记访问 加入迷宫
            block_now.is_visited = True
            # 从列表中移除这个单元格
            records.remove(block_now)
            # 检查相邻单元格 上下左右
            check = []
            #获取这个单元格的位置
            c, r = block_now.coordinate[0], block_now.coordinate[1]
            #获取这个单元格的上下左右单元格
            check_list = [
                {'check': r > 0, 'coordinate': (r - 1, c), 'direction': 'Up', 'index': 1},#上单元格
                {'check': r < maze_size[0] - 1, 'coordinate': (r + 1, c), 'direction': 'Down', 'index': 0},#下单元格
                {'check': c > 0, 'coordinate': (r, c - 1), 'direction': 'Left', 'index': 3},#左单元格
                {'check': c < maze_size[1] - 1, 'coordinate': (r, c + 1), 'direction': 'Right', 'index': 2}#右单元格
            ]
            for item in check_list:
                # 如果单元格合法,判断依据在于上下左右四个位置是否只有一个位置是路
                if item['check']:
                    # 如果该相邻单元格在已在迷宫中,记录该单元格
                    if blocks_list[item['coordinate'][0]][item['coordinate'][1]].is_visited:#如果这个格子被浏览过
                        check.append(item['direction'])#加入到check表中
                    else:
                        # 否则 若该单元格不在列表中,加入列表
                        if not blocks_list[item['coordinate'][0]][item['coordinate'][1]] in records:#如果这个格子不在records表中
                            records.append(blocks_list[item['coordinate'][0]][item['coordinate'][1]])#加入到records表中
            if check:#如果check表不为空
                # 从记录的相邻单元格中随机选择一个单元格,将中间的墙拆除
                move_direction = random.choice(check)
                for index, item in enumerate(check_list):#遍历这个单元格四周
                    if item['direction'] == move_direction:#如果这个单元格的某个方向为目标单元格
                        blocks_list[r][c].has_walls[index] = False#将该单元格的这个方向的墙设置为空
                        blocks_list[item['coordinate'][0]][item['coordinate'][1]].has_walls[item['index']] = False#将目标单元格的这个方向的墙设置为空
                        break
        return blocks_list
#获得格子在迷宫中的坐标位置
def get_pos(coordinate, block_size, border_size):
    l, t = coordinate[1] * block_size + border_size[0], coordinate[0] * block_size + border_size[1]
    coordinate = (l + block_size // 2, t + block_size // 2)
    return coordinate


'''查表 有返回下标 无返回-1'''

#查询坐标点是否在表内
def check(r, c, table):
    for index, item in enumerate(table):
        if item['r'] == r and item['c'] == c:
            return index#返回坐标
    return -1


'''A* search'''

#a*算法
def A_Star(maze, starting_point, destination):
    # 计算曼哈顿距离
    #r,c:目标格子的坐标;dis:已经移动花费的开销;er,ec:终点坐标
    def weight(r, c, dis, er=destination[0], ec=destination[1]):
        return dis + abs(r - er) + abs(c - ec)

    # 获取查询过的地图块
    def get_searched(block_size, border_size, table):
        #查询表
        searched = []
        for item in table:
            #获取查询过的坐标加入到查询表中
            searched.append(get_pos((item['r'], item['c']), block_size, border_size))
        return searched

    path = []  # 最终路径
    open = []  # open表 能走的路
    close = []  # close表 已经走了的路
    #上下左右
    directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
    #起点格子
    block = {'parent': -1, 'r': starting_point[1], 'c': starting_point[0], 'dis': 0,
             'f': weight(starting_point[1], starting_point[0], 0)}
    # 添加起点
    open.append(block)

    while open:
        # open表根据f值表排序 取优先级最高的结点
        open = sorted(open, key=lambda x: x['f'], reverse=True)
        #弹出f值最小的格子
        block = open.pop()

        #加入close表
        close.append(block)
        # 如果这个格子的坐标等于终点坐标说明已到达终点 根据close返回路径
        if block['r'] == destination[0] and block['c'] == destination[1]:
            #从终点往后面进行遍历
            next = len(close) - 1
            while next != -1:
                #获得遍历点的坐标
                coordinate = (close[next]['r'], close[next]['c'])
                #将遍历点加入到最终路径表中
                path.append(get_pos(coordinate, maze.block_size, maze.border_size))
                #next指向这个节点的父节点
                next = close[next]['parent']
            #返回最终路径和查询过的路径
            return path, get_searched(maze.block_size, maze.border_size, open + close)
        # 遍历当前结点邻近结点
        for i in range(0, 4):
            #建立新节点
            block_new = {'parent': check(block['r'], block['c'], close), 'r': block['r'] + directions[i][1],
                         'c': block['c'] + directions[i][0], 'dis': block['dis'] + 1,
                         'f': weight(block['r'] + directions[i][1], block['c'] + directions[i][0], block['dis'] + 1)}
            # 检查是否在close表中
            p = check(block_new['r'], block_new['c'], close)
            # 没有墙且不在close中
            if not maze.blocks_list[block['r']][block['c']].has_walls[i] and p == -1:
                # 检查是否在open表中
                idx = check(block_new['r'], block_new['c'], open)
                # 不在 添进open表中
                if idx == -1:
                    open.append(block_new)
                # 在 比较路径距离 短则更新
                else:
                    #如果open表的节点花费大于新节点的花费,则更新open节点
                    if open[idx]['dis'] > block_new['dis']:
                        open[idx] = block_new
    # open表为空 路径不存在
    return path, get_searched(maze.block_size, maze.border_size, open + close)
import pygame

'''定义hero'''

#游戏人物类
class Hero(pygame.sprite.Sprite):
    #初始化人物
    def __init__(self, imagepath, coordinate, block_size, border_size, **kwargs):
        pygame.sprite.Sprite.__init__(self)#
        self.image = pygame.image.load(imagepath)#读取人物图片
        self.image = pygame.transform.scale(self.image, (block_size, block_size))#设置人物图片大小
        self.rect = self.image.get_rect()
        self.rect.left, self.rect.top = coordinate[0] * block_size + border_size[0], coordinate[1] * block_size + \
                                        border_size[1]#设置人物初始位置
        self.coordinate = coordinate#人物坐标
        self.block_size = block_size#格子大小
        self.border_size = border_size#边框大小

    '''移动'''

    def move(self, direction, maze):
        blocks_list = maze.blocks_list
        # 上下左右对应坐标的加减
        directions = {'Up': (0, -1), 'Down': (0, 1), 'Left': (-1, 0), 'Right': (1, 0)}
        for index, key in enumerate(directions.keys()):
            if direction == key:
                #如果目标方向有墙
                if blocks_list[self.coordinate[1]][self.coordinate[0]].has_walls[index]:
                    # 不能移动
                    return False
                else:
                    #可以移动,更新人物坐标
                    self.coordinate[0] += directions[key][0]
                    self.coordinate[1] += directions[key][1]
                    return True

    '''绑定到屏幕'''

    def draw(self, screen):
        self.rect.left, self.rect.top = self.coordinate[0] * self.block_size + self.border_size[0], self.coordinate[
            1] * self.block_size + self.border_size[1]
        screen.blit(self.image, self.rect)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值