import random
import heapq
import os
import time
import json
import math
import numpy as np
from datetime import datetime
class InvalidMapError(Exception):
"""自定义异常,用于表示地图设置无效"""
pass
# -------------------------- 柏林噪声核心代码 --------------------------
def generate_permutation(seed=None):
"""生成带种子的置换表,长度为256"""
permutation = list(range(256))
random.seed(seed)
for i in range(255, 0, -1):
j = random.randint(0, i)
permutation[i], permutation[j] = permutation[j], permutation[i]
return permutation
def fade(t):
return t * t * t * (t * (t * 6 - 15) + 10)
def lerp(t, a, b):
return a + t * (b - a)
def grad(hash_val, x, y):
h = hash_val & 3
if h == 0:
return x + y
elif h == 1:
return -x + y
elif h == 2:
return x - y
else:
return -x - y
def perlin_noise_2d(x, y, permutation):
p = permutation * 2
X = math.floor(x) & 255
Y = math.floor(y) & 255
x -= math.floor(x)
y -= math.floor(y)
u = fade(x)
v = fade(y)
A = p[X] + Y
B = p[X + 1] + Y
n0 = grad(p[A], x, y)
n1 = grad(p[B], x - 1, y)
nx0 = lerp(u, n0, n1)
n2 = grad(p[A + 1], x, y - 1)
n3 = grad(p[B + 1], x - 1, y - 1)
nx1 = lerp(u, n2, n3)
return lerp(v, nx0, nx1)
def octave_perlin_2d(x, y, permutation, octaves=4, persistence=0.5, lacunarity=2.0):
total = 0
frequency = 1
amplitude = 1
max_value = 0
for _ in range(octaves):
total += perlin_noise_2d(x * frequency, y * frequency, permutation) * amplitude
max_value += amplitude
amplitude *= persistence
frequency *= lacunarity
return total / max_value
# -------------------------- 迷宫地图类 --------------------------
class CharacterMap:
def __init__(self, width, a, height, sections, obstacles=None, special_obstacles=None, goal=None, player_position=None, use_file_settings=False, auto_save=False, noise_seed=123):
print("正在生成地图,设置数据. . .")
self.width = width
self.height = a
self.section_width = sections
self.section_height = height
self.view_center = [self.section_width // 2, self.section_height // 2]
self.map = [['.' for _ in range(width)] for _ in range(a)]
self.player_position = player_position if player_position else [random.randint(0, self.view_center[0]), random.randint(0, self.view_center[1])]
self.goal = goal if goal else [random.randint(0, width - 1), random.randint(0, a - 1)]
while self.goal == self.player_position:
self.goal = [random.randint(0, width - 1), random.randint(0, a - 1)]
self.steps = 0
self.obstacles = obstacles if obstacles else []
self.special_obstacles = special_obstacles if special_obstacles else []
self.path = []
self.auto_save = auto_save
self.noise_seed = noise_seed # 柏林噪声种子
self.permutation = generate_permutation(noise_seed) # 生成置换表
if not use_file_settings:
while not self.generate_obstacles():
pass
else:
if not self.a_star(tuple(self.player_position), tuple(self.goal)):
print("%--警告:从文件加载的地图无法到达终点!--%")
try:
self.update_map()
except:
print('此文件更新错误')
raise InvalidMapError("地图设置无效")
print("完成")
time.sleep(0.5)
self.update_map()
def generate_obstacles(self):
"""使用柏林噪声生成自然分布的障碍物"""
self.map = [['.' for _ in range(self.width)] for _ in range(self.height)]
self.obstacles.clear()
self.special_obstacles.clear()
# ========== 已调好的参数 ==========
noise_scale = 15.0 # 控制纹理粗细
octaves = 3 # 减少细节,避免过密
obstacle_threshold = 0.5 # 普通障碍物阈值
special_threshold = 0.7 # 特殊障碍物阈值
# 遍历地图生成障碍物
for y in range(self.height):
for x in range(self.width):
noise_val = octave_perlin_2d(
x / noise_scale,
y / noise_scale,
self.permutation,
octaves=octaves
)
noise_val = (noise_val + 1) / 2 # 归一化到0-1
if [x, y] == self.player_position or [x, y] == self.goal:
continue
if noise_val < obstacle_threshold:
self.obstacles.append([x, y])
elif noise_val > special_threshold:
self.special_obstacles.append([x, y])
# ========== 新增的障碍物保底机制 ==========
min_obstacle_count = int(self.width * self.height * 0.50)
if len(self.obstacles) < min_obstacle_count:
while len(self.obstacles) < min_obstacle_count:
x = random.randint(0, self.width-1)
y = random.randint(0, self.height-1)
if [x,y] not in self.obstacles and [x,y] != self.player_position and [x,y] != self.goal:
self.obstacles.append([x,y])
# 检查路径是否可行
path = self.a_star(tuple(self.player_position), tuple(self.goal))
return path is not None
def zdyd(self):
if self.path == []:
self.show_shortest_path()
szx = len(self.path)
szx_pd = 0
for x, y in self.path:
print('自动寻路中...')
os.system('clear')
if szx_pd == szx - 1:
self.path = []
self.map[self.player_position[1]][self.player_position[0]] = '♛'
self.update_map()
break
self.player_position = [x, y]
self.map = [['.' for _ in range(self.width)] for _ in range(self.height)]
for x, y in self.obstacles:
self.map[y][x] = '#'
for x, y in self.special_obstacles:
self.map[y][x] = 'ᐃ'
self.map[self.player_position[1]][self.player_position[0]] = '●'
self.map[self.goal[1]][self.goal[0]] = '♞'
self.print_map()
self.steps += 1
szx_pd += 1
time.sleep(0.2)
print('完成')
def update_map(self):
self.map = [['.' for _ in range(self.width)] for _ in range(self.height)]
for x, y in self.obstacles:
self.map[y][x] = '#'
for x, y in self.special_obstacles:
self.map[y][x] = 'ᐃ'
for x, y in self.path:
self.map[y][x] = '■'
self.map[self.player_position[1]][self.player_position[0]] = '♛'
self.map[self.goal[1]][self.goal[0]] = '♞'
def print_map(self):
start_x = max(0, self.player_position[0] - self.view_center[0])
end_x = start_x + self.section_width
if end_x > self.width:
end_x = self.width
start_x = self.width - self.section_width
start_y = max(0, self.player_position[1] - self.view_center[1])
end_y = start_y + self.section_height
if end_y > self.height:
end_y = self.height
start_y = self.height - self.section_height
top_border = '╔' + '═' * int((self.section_width) * 2) + '╗'
print(top_border)
for row in self.map[start_y:end_y]:
print('║ ' + ' '.join(row[start_x:end_x]) + '║')
bottom_border = '╚' + '═' * int((self.section_width) * 2) + '╝'
print(bottom_border)
print(f"步数:{self.steps}")
print()
def move(self, directionx):
x, y = self.player_position
new_x, new_y = x, y
for direction in directionx:
if direction == '4':
new_x -= 1
elif direction == '8':
new_y += 1
elif direction == '6':
new_x += 1
elif direction == '2':
new_y -= 1
elif direction == '1':
new_x -= 1
new_y -= 1
elif direction == '3':
new_x += 1
new_y -= 1
elif direction == '7':
new_x -= 1
new_y += 1
elif direction == '9':
new_x += 1
new_y += 1
else:
print("无效的方向!")
return
if not (0 <= new_x < self.width and 0 <= new_y < self.height):
print("你不能走出地图边界!")
return
if [new_x, new_y] in self.obstacles:
print("前面有障碍物,不能通过!")
return
if [new_x, new_y] in self.special_obstacles:
print("碰到特殊障碍物,你被弹回了!")
self.player_position = [random.randint(0, self.view_center[0]), random.randint(0, self.view_center[1])]
self.update_map()
self.steps += 1
return
self.player_position = [new_x, new_y]
self.steps += 1
if self.player_position == self.goal:
print("恭喜你到达终点!游戏结束!")
print(f"总步数:{self.steps}")
return self.steps
self.random_move_goal()
self.path = []
self.update_map()
self.print_map()
time.sleep(0.1)
os.system('clear')
def random_move_goal(self):
directions = [
(-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1)
]
possible_positions = []
for dx, dy in directions:
new_goal_x = self.goal[0] + dx
new_goal_y = self.goal[1] + dy
if 0 <= new_goal_x < self.width and 0 <= new_goal_y < self.height:
if [new_goal_x, new_goal_y] not in self.obstacles and \
[new_goal_x, new_goal_y] not in self.special_obstacles and \
[new_goal_x, new_goal_y] != self.player_position:
possible_positions.append([new_goal_x, new_goal_y])
if possible_positions:
self.goal = random.choice(possible_positions)
def run(self):
print("欢迎来到迷宫地图!")
print("使用数字控制方向:")
print("1 - 左上,2 - 上,3 - 右上,4 - 左,6 - 右,7 - 左下,8 - 下,9 - 右下")
print("输入'0'退出游戏。")
print("输入'5'获取当前位置到终点的最短路径。")
print("输入'@'自动寻路到终点。")
while True:
self.print_map()
direction = input("请输入方向:").strip().lower()
os.system('clear')
if direction == '0':
print("退出游戏。")
return -1
elif direction == '5':
self.show_shortest_path()
elif direction == '@':
self.zdyd()
else:
bs = self.move(direction)
if bs is not None:
if self.auto_save:
self.save_map_data()
return bs
def save_map_data(self):
current_time = datetime.now().strftime("%Y%m%d_%H%M%S")
file_name = f"{current_time}.json"
file_path = f"/storage/emulated/0/Download/python.wj/gwzm_wj/{file_name}"
os.makedirs(os.path.dirname(file_path), exist_ok=True)
data = {
"width": self.width,
"a": self.height,
"height": self.section_height,
"sections": self.section_width,
"obstacles": self.obstacles,
"special_obstacles": self.special_obstacles,
"player_position": self.player_position,
"goal": self.goal,
"noise_seed": self.noise_seed # 保存噪声种子
}
with open(file_path, 'w') as file:
json.dump(data, file, indent=4)
print(f"地图数据已保存到 {file_path}")
def show_shortest_path(self):
start = tuple(self.player_position)
goal = tuple(self.goal)
self.path = self.a_star(start, goal)
if self.path:
self.update_map()
self.print_map()
print("已显示从当前位置到终点的最短路径。")
else:
self.path = []
self.update_map()
self.print_map()
print("无法到达终点!")
def a_star(self, start, goal):
open_set = []
heapq.heappush(open_set, (0, start))
came_from = {}
g_score = {start: 0}
f_score = {start: self.heuristic(start, goal)}
while open_set:
# 弹出f_score最小的节点
current_f, current = heapq.heappop(open_set)
# 避免重复处理已最优的节点
if current in f_score and current_f > f_score[current]:
continue
# 到达终点,重构路径
if current == goal:
return self.reconstruct_path(came_from, current)
# 遍历所有邻居并校验合法性
for neighbor in self.get_neighbors(current):
# 跳过障碍物和特殊障碍物
if list(neighbor) in self.obstacles or list(neighbor) in self.special_obstacles:
continue
# 计算移动代价:斜向√2≈1.414,正交1
dx = abs(neighbor[0] - current[0])
dy = abs(neighbor[1] - current[1])
move_cost = 1.414 if dx == 1 and dy == 1 else 1
# 计算从起点到邻居的总代价
tentative_g_score = g_score[current] + move_cost
# 仅当新路径更优时,才更新节点信息
if neighbor not in g_score or tentative_g_score < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = tentative_g_score
f_score[neighbor] = tentative_g_score + self.heuristic(neighbor, goal)
heapq.heappush(open_set, (f_score[neighbor], neighbor))
# 无路径时返回空列表
return []
def heuristic(self, a, b):
# 切比雪夫距离:适配8方向移动,更精准
dx = abs(a[0] - b[0])
dy = abs(a[1] - b[1])
return max(dx, dy)
def get_neighbors(self, position):
x, y = position
# 8方向邻居
directions = [(-1,-1), (-1,0), (-1,1),
(0,-1), (0,1),
(1,-1), (1,0), (1,1)]
valid_neighbors = []
for dx, dy in directions:
nx = x + dx
ny = y + dy
# 严格校验边界
if 0 <= nx < self.width and 0 <= ny < self.height:
valid_neighbors.append((nx, ny))
return valid_neighbors
def reconstruct_path(self, came_from, current):
path = []
# 从终点回溯到起点
while current in came_from:
path.append(list(current))
current = came_from[current]
# 添加起点
path.append(list(current))
# 反转路径,变为 起点→终点 顺序
path.reverse()
# 去重(防止回溯时的重复节点)
path = [list(item) for item in list(dict.fromkeys(tuple(p) for p in path))]
return path
# -------------------------- 原有辅助函数 --------------------------
def 初始设置():
settings = {
"width": {"default": 60, "prompt": "地图总宽,默认60>>>", "validator": lambda x: int(x) > 0},
"a": {"default": 60, "prompt": "地图总高,默认60>>>", "validator": lambda x: int(x) > 0},
"sections": {"default": 19, "prompt": "可视图宽,此项不建议更改,默认19>>>", "validator": lambda x: int(x) <= settings["width"]["value"]},
"height": {"default": 15, "prompt": "可视图高,此项不建议更改,默认15>>>", "validator": lambda x: int(x) <= settings["a"]["value"]}
}
for key, config in settings.items():
while True:
os.system('clear')
print(" 欢迎\n让我们来做一些基础设置,回车使用默认")
value = input(config["prompt"])
if value == "":
value = str(config["default"])
if config["validator"](value):
settings[key]["value"] = int(value)
break
else:
print("注意,此设置不符合要求")
time.sleep(0.5)
return settings["width"]["value"], settings["a"]["value"], settings["height"]["value"], settings["sections"]["value"]
def 从文件加载设置(file_path):
try:
with open(file_path, 'r') as file:
data = json.load(file)
width = data.get('width', 60)
a = data.get('a', 60)
if int(width) < 0 or int(a) < 0:
print("地图总不为负数")
return
height = data.get('height', 15)
sections = data.get('sections', 19)
if int(height) > width or int(sections) > a:
print("可视图大小大于地图")
return
obstacles = data.get('obstacles', [])
special_obstacles = data.get('special_obstacles', [])
player_position = data.get('player_position', [random.randint(0, sections // 2), random.randint(0, height // 2)])
goal = data.get('goal', [random.randint(0, width - 1), random.randint(0, a - 1)])
noise_seed = data.get('noise_seed', 123)
return width, a, height, sections, obstacles, special_obstacles, goal, player_position, noise_seed
except FileNotFoundError:
return -1
except json.JSONDecodeError:
return -2
except:
return -3
def yrun(bcmr, auto_save):
if bcmr:
print("已使用默认设置")
width, a, height, sections, obstacles, special_obstacles, goal, player_position, noise_seed = 60, 60, 15, 19, [], [], [random.randint(0, 59), random.randint(0, 59)], [random.randint(0, 9), random.randint(0, 7)], 123
use_file_settings = False
else:
use_file_settings = False
width, a, height, sections = 初始设置()
os.system('clear')
noise_seed = random.randint(0, 9999) # 随机生成噪声种子
print(f"加载完成: \n宽度={width}\n高度={a}\n可视图高={height}\n可视图宽={sections}\n噪声种子={noise_seed}")
os.system('clear')
times, bs = zrun(width, a, height, sections, obstacles=None, special_obstacles=None, goal=None, player_position=None, use_file_settings=use_file_settings, auto_save=auto_save, noise_seed=noise_seed)
return times, bs
def zrun(width, a, height, sections, obstacles, special_obstacles, goal, player_position, use_file_settings, auto_save, noise_seed=123):
try:
timea = time.time()
game_map = CharacterMap(width, a, height, sections, obstacles, special_obstacles, goal, player_position, use_file_settings, auto_save, noise_seed)
bs = game_map.run()
timeb = time.time() - timea
except InvalidMapError as e:
print("错误", e)
return None, None
return timeb, bs
def 设置(bcmr, auto_save):
while True:
print('''
╔══════════════════···
║1.保持地图默认设置:{}
║2.自动保存地图:{}
║回车.保存并退出
╚══════════════════···'''.format(bcmr, auto_save))
srsz = input('请选择>>>')
if srsz == "1":
os.system('clear')
bcmr = not bcmr
elif srsz == "2":
os.system('clear')
auto_save = not auto_save
elif srsz == "":
os.system('clear')
break
else:
os.system('clear')
print("输入错误")
return bcmr, auto_save
def kgx(bs):
if bs > -1:
kgx = len(str(bs))
if kgx == 1:
kgy = 2
elif kgx == 2:
kgy = 1
elif kgx == 3:
kgy = 0
kg = " " * kgy
return kg
return " "
def 选择文件界面():
print('''
╔══════════════════════╗
║ 1.手动输入路径 ║
╠══════════════════════╣
''')
file_path = "/storage/emulated/0/Download/python.wj/gwzm_wj/"
if not os.path.exists(file_path):
os.makedirs(file_path)
files = os.listdir(file_path)
json_files = [f for f in files if f.endswith('.json')]
for idx, file_name in enumerate(json_files, start=2):
print(f"║ {idx}. {file_name}{' ' * (30 - len(file_name))}║")
print('''
╚══════════════════════╝
''')
return json_files
def xrun():
bs = -1
kg = " "
bcmr = False
auto_save = False
times = -1
os.system('clear')
while True:
print('''
╔══════════════════╗
║ 1.开始游戏 ║
║ 2.进入设置 ║
║ 3.从文件加载地图 ║
╠══════════════════╣
║上一局步数:{}{} ║
║上一局时间:{}s
╚══════════════════╝'''.format(bs, kg, round(times, 3)))
xzxz = input("\n请输入你的选择>>>")
if xzxz == "1":
os.system('clear')
times, bs = yrun(bcmr, auto_save)
kg = kgx(bs)
elif xzxz == "2":
os.system('clear')
bcmr, auto_save = 设置(bcmr, auto_save)
elif xzxz == "3":
os.system('clear')
json_files = 选择文件界面()
choice = input("请选择一个选项>>>")
if choice == "1":
file_name = input("请输入文件路径>>>")
file_path = file_name
if not file_name.endswith('.json'):
file_path += ".json"
else:
try:
choice = int(choice) - 2
if 0 <= choice < len(json_files):
file_name = json_files[choice]
file_path = os.path.join("/storage/emulated/0/Download/python.wj/gwzm_wj/", file_name)
else:
print("无效的选项")
continue
except ValueError:
print("无效的选项")
continue
try:
path_sc = 从文件加载设置(file_path)
if path_sc == -3:
print("输入错误")
continue
elif path_sc == -1:
print("文件未找到")
continue
elif path_sc == -2:
print("文件格式错误")
continue
width, a, height, sections, obstacles, special_obstacles, goal, player_position, noise_seed = path_sc
print(f"设置完成: \n宽度={width}\n高度={a}\n可视图高={height}\n可视图宽={sections}\n噪声种子={noise_seed}")
times, bs = zrun(width, a, height, sections, obstacles, special_obstacles, goal, player_position, True, auto_save, noise_seed)
except Exception as e:
print(f"加载失败: {e}")
times = None
if times is None:
times, bs = -1, -1
kg = kgx(bs)
if kg is None:
kg = " "
else:
os.system('clear')
print("输入错误")
# 开始运行
xrun()