迷宫游戏开发

迷宫游戏开发


一、实现目标

1.随机生成一个迷宫,并且求解迷宫。
2.要求游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控制,并在行走路径上留下痕迹;系统走迷宫路径要求基于A*算法实现,输出走迷宫的最优路径并显示。
3.设计交互友好的游戏图形界面。

二、实现方式

1.使用python和threading模块。我们进行程序开发的时候,肯定避免不了要处理并发的情况。一般并发的手段有采用多进程和多线程。但线程比进程更轻量化,系统开销一般也更低,所以大家更倾向于用多线程的方式处理并发的情况。Python 提供多线程编程的方式。threading 模块中最核心的内容是 Thread 这个类。我们要创建 Thread 对象,然后让它们运行,每个 Thread 对象代表一个线程,在每个线程中我们可以让程序处理不同的任务,这就是多线程编程。
参考:Python多线程编程(一):threading 模块 Thread 类的用法详解

2.使用随机Prim算法。
原始版本的随机Prim算法是维护一个墙的列表。
首先随机选择一个迷宫单元,设置为已访问,然后把它的所有邻墙放入列表。
当列表里还有墙时,重复下面循环
a)从列表里随机选择一面墙。
b)如果这面墙相邻的两个迷宫单元只有一个被访问过,先把这面墙设置为打通,并从列表里删除这面墙,然后把未访问的迷宫单元设为已访问,并把这个迷宫单元的邻墙添加到列表。
c)如果这面墙相邻的两个单元单元都已经被访问过,那就从列表里删除这面墙。
但是在实现时要同时记录墙和迷宫单元的信息,会比较复杂,所以使用改进版本,只维护一个迷宫单元的列表。地图信息和之前的递推回溯算法是一样的。
在迷宫生成算法中会用到这个表示方式(2 * x+1, 2 * y+1)。同时迷宫的长度和宽度必须为奇数。
算法主循环,重复下面步骤2直到检查列表为空:
①随机选择一个迷宫单元作为起点,加入检查列表并标记为已访问
②当检查列表非空时,随机从列表中取出一个迷宫单元,进行循环
a)如果当前迷宫单元有未被访问过的相邻迷宫单元
b)随机选择一个未访问的相邻迷宫单元
c)去掉当前迷宫单元与相邻迷宫单元之间的墙
d)标记相邻迷宫单元为已访问,并将它加入检查列表
e)否则,当前迷宫单元没有未访问的相邻迷宫单元
f)则从检查列表删除当前迷宫单元
参考:Python 四大迷宫生成算法实现(2): 随机Prim算法

三、主要函数

1.随机Prim算法

代码如下(示例):

from random import randint, choice
# 单元格类型
#0为路,1为墙
class CellType:
    ROAD = 0
    WALL = 1
# 墙的方向
class Direction:
    LEFT = 0,
    UP = 1,
    RIGHT = 2,
    DOWN = 3,
# 迷宫地图
class Maze:
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.maze = [[0 for x in range(self.width)] for y in range(self.height)]
    def reset_maze(self, value):
        for y in range(self.height):
            for x in range(self.width):
                self.set_maze(x, y, value)
    def set_maze(self, x, y, value):
        self.maze[y][x] = CellType.ROAD if value == CellType.ROAD else CellType.WALL
    def visited(self, x, y):
        return self.maze[y][x] != 1
def check_neighbors(maze, x, y, width, height, checklist):
    directions = []
    if x > 0:
        if not maze.visited(2 * (x - 1) + 1, 2 * y + 1):
            directions.append(Direction.LEFT)
    if y > 0:
        if not maze.visited(2 * x + 1, 2 * (y - 1) + 1):
            directions.append(Direction.UP)
    if x < width - 1:
        if not maze.visited(2 * (x + 1) + 1, 2 * y + 1):
            directions.append(Direction.RIGHT)
    if y < height - 1:
        if not maze.visited(2 * x + 1, 2 * (y + 1) + 1):
            directions.append(Direction.DOWN)
    if len(directions):
        direction = choice(directions)
        if direction == Direction.LEFT:
            maze.set_maze(2 * (x - 1) + 1, 2 * y + 1, CellType.ROAD)
            maze.set_maze(2 * x, 2 * y + 1, CellType.ROAD)
            checklist.append((x - 1, y))
        elif direction == Direction.UP:
            maze.set_maze(2 * x + 1, 2 * (y - 1) + 1, CellType.ROAD)
            maze.set_maze(2 * x + 1, 2 * y, CellType.ROAD)
            checklist.append((x, y - 1))
        elif direction == Direction.RIGHT:
            maze.set_maze(2 * (x + 1) + 1, 2 * y + 1, CellType.ROAD)
            maze.set_maze(2 * x + 2, 2 * y + 1, CellType.ROAD)
            checklist.append((x + 1, y))
        elif direction == Direction.DOWN:
            maze.set_maze(2 * x + 1, 2 * (y + 1) + 1, CellType.ROAD)
            maze.set_maze(2 * x + 1, 2 * y + 2, CellType.ROAD)
            checklist.append((x, y + 1))
        return True
    return False
def random_prime(map, width, height):
    start_x, start_y = (randint(0, width - 1), randint(0, height - 1))
    map.set_maze(2 * start_x + 1, 2 * start_y + 1, CellType.ROAD)
    checklist = [(start_x, start_y)]
    while len(checklist):
        entry = choice(checklist)
        if not check_neighbors(map, entry[0], entry[1], width, height, checklist):
            checklist.remove(entry)
def do_random_prime(map):
    map.reset_maze(CellType.WALL)
    random_prime(map, (map.width - 1) // 2, (map.height - 1) // 2)
def set_entrance_exit(maze):
    entrance = []
    for i in range(maze.height):
        if maze.maze[i][1] == 0:
            maze.set_maze(0, i, 0)
            entrance = [0, i]
            break
    exit = []
    for i in range(maze.height - 1, 0, -1):
        if maze.maze[i][maze.width - 2] == 0:
            maze.set_maze(maze.width - 1, i, 0)
            exit = [maze.width - 1, i]
            break
    return entrance, exit
def generate_maze(width=21, height=21):
    # 初始化迷宫
    maze = Maze(width, height)
    # 生成地图
    do_random_prime(maze)
    # 选择起点和终点
    entrance, exit = set_entrance_exit(maze)
    # 返回地图
    return maze.maze, entrance, exit

2.迷宫入口

代码如下(示例):

if __name__ == '__main__':
    # 生成迷宫与入口
    size = random_maze_size()
    maze, entrance, exit = generate_maze(size, size)
    solve_thread = threading.Thread(target=solve_maze, args=(maze, entrance, exit, draw_maze))
    solve_thread.start()
    while True:
        CLOCK.tick(FPS)
        for event in pygame.event.get():
            # 检查是否关闭窗口
            if event.type == pygame.QUIT:
                if solve_thread is not None and solve_thread.is_alive():
                    stop_thread(solve_thread)
                    solve_thread = None
                exit(0)
            elif event.type == pygame.MOUSEBUTTONDOWN:
                mouse_pos = pygame.mouse.get_pos()
                dispatcher_click(mouse_pos)


3.迷宫地图

do_random_prime 函数 先调用reset_maze函数将地图都设置为墙,迷宫单元所在位置为墙表示未访问。有个注意点是地图的长宽和迷宫单元的位置取值范围的对应关系。
假如地图的宽度是21,长度是21,对应的迷宫单元的位置取值范围是 x(0,10), y(0,10), 因为迷宫单元(x,y)对应到地图上的位置是(2 * x+1, 2 * y+1)。
random_prime 函数就是上面算法主循环的实现。

代码如下(示例):

def random_prime(map, width, height):
    start_x, start_y = (randint(0, width - 1), randint(0, height - 1))
    map.set_maze(2 * start_x + 1, 2 * start_y + 1, CellType.ROAD)
    checklist = [(start_x, start_y)]
    while len(checklist):
        entry = choice(checklist)
        if not check_neighbors(map, entry[0], entry[1], width, height, checklist):
            checklist.remove(entry)
def do_random_prime(map):
    map.reset_maze(CellType.WALL)
    random_prime(map, (map.width - 1) // 2, (map.height - 1) // 2)
def generate_maze(width=21, height=21):
    # 初始化迷宫,注意长度和宽度为奇数
    maze = Maze(width, height)
    # 生成地图
    do_random_prime(maze)
    # 选择起点和终点
    entrance, exit = set_entrance_exit(maze)
    # 返回地图
    return maze.maze, entrance, exit

4.迷宫绘制

代码如下(示例):

WHITE = (255, 255, 255)   #道路
BLACK = (0, 0, 0)         #障碍
RED = (255, 0, 0)         #走过的路
GREEN = (0, 255, 0)       #出口
YELLOW = (255, 255, 0)    #最终的通路 

获取玩家上下左右四个相邻位置的值和位置。

# 单元格类型
#0为路,1为墙,2是走过的路,3是死胡同,不在迷宫里或是已经走过又回退回来,说明此路不通
class CellType:
    ROAD = 0
    WALL = 1
    WALKED = 2
    DEAD = 3
# 墙的方向
class Direction:
    LEFT = 0,
    UP = 1,
    RIGHT = 2,
    DOWN = 3,
def valid(maze, x, y):
    if x < 0 or y < 0:
        return False
    if x >= len(maze) or y >= len(maze):
        return False
    val = maze[y][x]
    if val == CellType.WALL or val == CellType.DEAD:
        return False
    return val, x, y
def neighbors(maze, pos):
    x, y = pos
    t, r, d, l = valid(maze, x, y - 1), valid(maze, x + 1, y), valid(maze, x, y + 1), valid(maze, x - 1, y)
    return t, r, d, l
def mark_walked(maze, pos):
    maze[pos[1]][pos[0]] = CellType.WALKED
def mark_dead(maze, pos):
    maze[pos[1]][pos[0]] = CellType.DEAD

对获取到的相邻四个位置进行处理,返回最推荐走的位置与值。

def suggest_pos(cells):
    arr = []
     # 遍历相邻4个位置
    for cell in cells:
     # 如果不是False,说明是路,可能是没走过的路,也可能是走过的路,添加到数组中
        if cell:
            arr.append(cell[0])
     # 如果是False,说明是墙或者死胡同,不再考虑
        else:
            arr.append(CellType.DEAD)
     # 优先推荐没走过的路,根据我们CellType中的定义,没走过的路值最小,其次是走过的路
    return cells[arr.index(min(arr))]

对推荐的位置进行处理。
a)如果是没走过的路,直接走,并把当前的位置标记为已走过
b)如果是走过的路,也走,并把当前的位置标记为死胡同,此路不通
c)如果没有推荐的路,游戏结束,迷宫有问题,无解
d)走 这个动作,就用到了递归,继续调用solve_maze方法,位置参数改为推e)荐的下一步的位置
f)重复以上步骤直到当前位置与出口位置重叠,迷宫走完,游戏结束

 next_pos = suggest_pos((t, r, d, l))
    if next_pos:
        if next_pos[0] == CellType.WALKED:
            mark_dead(maze, pos)
        else:
            mark_walked(maze, pos)
        callback(maze, next_pos)
        return solve_maze(maze, (next_pos[1], next_pos[2]), end, callback)
    else:
        mark_dead(maze, pos)
        callback(maze, next_pos)
        return False

5.强行关闭线程

因为在迷宫中一直走找不到出口而使程序无法结束,这时候就需要一个强行关闭程序来中止。
参考:python强制关闭的一种办法

代码如下(示例):

import ctypes  #用于调用动态链接库函数的功能模块
import inspect


def _async_raise(tid, exctype):
    """raises the exception, performs cleanup if needed"""  #强行关闭线程
    tid = ctypes.c_long(tid)
    if not inspect.isclass(exctype):
        exctype = type(exctype)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")


def stop_thread(thread):
    _async_raise(thread.ident, SystemExit)


四、示意图展示

随机生成的两个地图并自动寻找出口。
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值