初始化
This commit is contained in:
commit
f877c2a678
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
__pycache__/
|
BIN
assets/coin.png
Normal file
BIN
assets/coin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
assets/load.png
Normal file
BIN
assets/load.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
assets/save.png
Normal file
BIN
assets/save.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
BIN
assets/start_button.png
Normal file
BIN
assets/start_button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
BIN
assets/trap.png
Normal file
BIN
assets/trap.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
BIN
assets/wall.png
Normal file
BIN
assets/wall.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
120
draw.py
Normal file
120
draw.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import pygame
|
||||||
|
import os
|
||||||
|
|
||||||
|
class Button:
|
||||||
|
def __init__(self, rect: pygame.Rect, texture):
|
||||||
|
self.rect = rect
|
||||||
|
self.texture = texture
|
||||||
|
self.hovered = False
|
||||||
|
self.pressed = False
|
||||||
|
|
||||||
|
def handle_event(self, event):
|
||||||
|
if event.type == pygame.MOUSEMOTION:
|
||||||
|
self.hovered = self.rect.collidepoint(event.pos)
|
||||||
|
elif event.type == pygame.MOUSEBUTTONDOWN:
|
||||||
|
if self.hovered and event.button == 1:
|
||||||
|
self.pressed = True
|
||||||
|
elif event.type == pygame.MOUSEBUTTONUP:
|
||||||
|
if self.pressed and self.hovered and event.button == 1:
|
||||||
|
self.pressed = False
|
||||||
|
return True # Button was clicked
|
||||||
|
self.pressed = False
|
||||||
|
return False
|
||||||
|
|
||||||
|
def draw(self, screen):
|
||||||
|
screen.blit(self.texture, self.rect)
|
||||||
|
|
||||||
|
class Toast:
|
||||||
|
def __init__(self, text, screen_width=None, screen_height=None, duration=2, font=None, color=(255, 255, 255), bg_color=(0, 0, 0), pos=None, font_size=28):
|
||||||
|
self.text = text
|
||||||
|
self.duration = duration # seconds
|
||||||
|
self.start_time = None
|
||||||
|
# Use a font that supports Chinese characters, or use the default system font
|
||||||
|
if font is None:
|
||||||
|
# Try to use a system font that supports UTF-8
|
||||||
|
try:
|
||||||
|
self.font = pygame.font.SysFont("microsoftyahei,simsun,simhei", font_size)
|
||||||
|
except:
|
||||||
|
# Fall back to default font if specific fonts are not available
|
||||||
|
self.font = pygame.font.Font(None, font_size)
|
||||||
|
else:
|
||||||
|
self.font = font
|
||||||
|
self.color = color
|
||||||
|
self.bg_color = bg_color
|
||||||
|
self.pos = pos # (x, y) or None for center
|
||||||
|
self.visible = False
|
||||||
|
self.surface = None
|
||||||
|
self.rect = None
|
||||||
|
self.screen_width = screen_width
|
||||||
|
self.screen_height = screen_height
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
self.start_time = pygame.time.get_ticks()
|
||||||
|
self.visible = True
|
||||||
|
txt_surface = self.font.render(self.text, True, self.color)
|
||||||
|
padding = 16
|
||||||
|
self.surface = pygame.Surface((txt_surface.get_width() + padding, txt_surface.get_height() + padding), pygame.SRCALPHA)
|
||||||
|
self.surface.fill(self.bg_color)
|
||||||
|
self.surface.blit(txt_surface, (padding // 2, padding // 2))
|
||||||
|
self.rect = self.surface.get_rect()
|
||||||
|
if self.pos:
|
||||||
|
self.rect.topleft = self.pos
|
||||||
|
elif self.screen_width and self.screen_height:
|
||||||
|
# Center on screen using provided dimensions
|
||||||
|
self.rect.center = (self.screen_width // 2, self.screen_height // 2)
|
||||||
|
|
||||||
|
def draw(self, screen):
|
||||||
|
if not self.visible:
|
||||||
|
return
|
||||||
|
elapsed = (pygame.time.get_ticks() - self.start_time) / 1000.0
|
||||||
|
if elapsed > self.duration:
|
||||||
|
self.visible = False
|
||||||
|
return
|
||||||
|
if self.pos is None and (self.screen_width is None or self.screen_height is None):
|
||||||
|
# Center on screen if dimensions weren't provided at initialization
|
||||||
|
screen_rect = screen.get_rect()
|
||||||
|
self.rect.center = screen_rect.center
|
||||||
|
screen.blit(self.surface, self.rect)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class InputBox:
|
||||||
|
def __init__(self, rect: pygame.Rect = pygame.Rect(100, 100, 140, 32)) -> None:
|
||||||
|
"""
|
||||||
|
rect,传入矩形实体,传达输入框的位置和大小
|
||||||
|
"""
|
||||||
|
self.boxBody: pygame.Rect = rect
|
||||||
|
self.color_inactive = pygame.Color('lightskyblue3') # 未被选中的颜色
|
||||||
|
self.color_active = pygame.Color('dodgerblue2') # 被选中的颜色
|
||||||
|
self.color = self.color_inactive # 当前颜色,初始为未激活颜色
|
||||||
|
self.active = False
|
||||||
|
self.text = '' # 输入的内容
|
||||||
|
self.done = False
|
||||||
|
self.font = pygame.font.Font(None, 32)
|
||||||
|
|
||||||
|
def dealEvent(self, event: pygame.event.Event):
|
||||||
|
if(event.type == pygame.MOUSEBUTTONDOWN):
|
||||||
|
if(self.boxBody.collidepoint(event.pos)): # 若按下鼠标且位置在文本框
|
||||||
|
self.active = not self.active
|
||||||
|
else:
|
||||||
|
self.active = False
|
||||||
|
self.color = self.color_active if(
|
||||||
|
self.active) else self.color_inactive
|
||||||
|
if(event.type == pygame.KEYDOWN): # 键盘输入响应
|
||||||
|
if(self.active):
|
||||||
|
if(event.key == pygame.K_RETURN):
|
||||||
|
print(self.text)
|
||||||
|
# self.text=''
|
||||||
|
elif(event.key == pygame.K_BACKSPACE):
|
||||||
|
self.text = self.text[:-1]
|
||||||
|
else:
|
||||||
|
self.text += event.unicode
|
||||||
|
|
||||||
|
def draw(self, screen: pygame.surface.Surface):
|
||||||
|
txtSurface = self.font.render(
|
||||||
|
self.text, True, self.color) # 文字转换为图片
|
||||||
|
width = max(200, txtSurface.get_width()+10) # 当文字过长时,延长文本框
|
||||||
|
self.boxBody.w = width
|
||||||
|
screen.blit(txtSurface, (self.boxBody.x+5, self.boxBody.y+5))
|
||||||
|
pygame.draw.rect(screen, self.color, self.boxBody, 2)
|
||||||
|
|
95
main.py
Normal file
95
main.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import pygame
|
||||||
|
from pygame import Surface
|
||||||
|
from pygame.constants import QUIT
|
||||||
|
from maze import Maze
|
||||||
|
|
||||||
|
from draw import Button, Toast
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
UI_HEIGHT = 800
|
||||||
|
UI_WIDTH = 1100
|
||||||
|
|
||||||
|
MAZE_SIZE = 800
|
||||||
|
WALL_SIZE = 50
|
||||||
|
FPS = 120
|
||||||
|
|
||||||
|
screen: Surface = None # 窗口实例
|
||||||
|
clock = None # 时钟实例
|
||||||
|
|
||||||
|
textFont = None # 字体
|
||||||
|
|
||||||
|
|
||||||
|
def pygameInit(title: str = "pygame"):
|
||||||
|
"""初始化 pygame"""
|
||||||
|
pygame.init()
|
||||||
|
pygame.mixer.init() # 声音初始化
|
||||||
|
pygame.display.set_caption(title)
|
||||||
|
global screen, clock, textFont # 修改全局变量
|
||||||
|
screen = pygame.display.set_mode((UI_WIDTH, UI_HEIGHT))
|
||||||
|
clock = pygame.time.Clock()
|
||||||
|
# Initialize font with UTF-8 support
|
||||||
|
pygame.font.init()
|
||||||
|
textFont = pygame.font.Font("syht.otf", 18)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pygameInit("maze")
|
||||||
|
maze = Maze(wall_size=WALL_SIZE, maze_size=MAZE_SIZE, file_name="maze.csv")
|
||||||
|
image_wall = pygame.image.load("assets/wall.png").convert_alpha()
|
||||||
|
image_wall = pygame.transform.scale(image_wall, (WALL_SIZE, WALL_SIZE)) # 例如缩放到50x50像素
|
||||||
|
|
||||||
|
image_coin = pygame.image.load("assets/coin.png").convert_alpha()
|
||||||
|
image_coin = pygame.transform.scale(image_coin, (WALL_SIZE, WALL_SIZE)) # 例如缩放到50x50像素
|
||||||
|
|
||||||
|
image_trap = pygame.image.load("assets/trap.png").convert_alpha()
|
||||||
|
image_trap = pygame.transform.scale(image_trap, (WALL_SIZE, WALL_SIZE)) # 例如缩放到50x50像素
|
||||||
|
|
||||||
|
button_start_texture = pygame.image.load("assets/start_button.png").convert_alpha()
|
||||||
|
button_start_texture = pygame.transform.scale(button_start_texture, (200, 100))
|
||||||
|
button_start = Button(pygame.rect.Rect(MAZE_SIZE + ((UI_WIDTH - MAZE_SIZE) / 2 - 100), 0, 200, 100), button_start_texture)
|
||||||
|
|
||||||
|
button_save_texture = pygame.image.load("assets/save.png").convert_alpha()
|
||||||
|
button_save_texture = pygame.transform.scale(button_save_texture, (80, 80))
|
||||||
|
button_save = Button(pygame.rect.Rect(MAZE_SIZE + ((UI_WIDTH - MAZE_SIZE) / 2 - 100), 110, 80, 80), button_save_texture)
|
||||||
|
|
||||||
|
|
||||||
|
# 没有生成迷宫就保存的提示框
|
||||||
|
mes1 = Toast("没有生成迷宫,无法保存", UI_WIDTH, UI_HEIGHT, font=textFont)
|
||||||
|
mes2 = Toast("迷宫已保存", UI_WIDTH, UI_HEIGHT, font=textFont)
|
||||||
|
|
||||||
|
|
||||||
|
running = True
|
||||||
|
while running:
|
||||||
|
clock.tick(FPS) # 限制帧数
|
||||||
|
screen.fill((255, 255, 255)) # 铺底
|
||||||
|
for event in pygame.event.get():
|
||||||
|
button_start.handle_event(event=event)
|
||||||
|
button_save.handle_event(event=event)
|
||||||
|
|
||||||
|
if button_start.pressed == True:
|
||||||
|
maze.generate()
|
||||||
|
|
||||||
|
if button_save.pressed == True:
|
||||||
|
if len(maze.grid) == 0:
|
||||||
|
mes1.show()
|
||||||
|
else:
|
||||||
|
maze.export_to_csv("maze.csv")
|
||||||
|
mes2.text = "迷宫已保存至maze.csv"
|
||||||
|
mes2.show()
|
||||||
|
|
||||||
|
if event.type == QUIT:
|
||||||
|
running = False
|
||||||
|
|
||||||
|
|
||||||
|
maze.draw(screen=screen, wall_texture=image_wall, coin_texture=image_coin, trap_texture=image_trap)
|
||||||
|
button_start.draw(screen=screen)
|
||||||
|
button_save.draw(screen=screen)
|
||||||
|
|
||||||
|
mes1.draw(screen=screen)
|
||||||
|
mes2.draw(screen=screen)
|
||||||
|
pygame.display.flip()
|
||||||
|
pygame.quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
16
maze.csv
Normal file
16
maze.csv
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||||
|
1,0,l28,1,0,0,0,0,0,l14,l22,1,0,0,0,1
|
||||||
|
1,0,0,1,1,1,1,1,1,0,1,1,0,1,0,1
|
||||||
|
1,0,0,1,0,0,g28,t10,0,0,0,1,0,1,t14,1
|
||||||
|
1,0,g28,1,0,1,1,1,1,1,1,1,0,1,1,1
|
||||||
|
1,g27,0,1,0,1,0,t14,0,0,0,1,0,0,0,1
|
||||||
|
1,0,0,1,0,1,1,1,l11,1,1,1,0,1,1,1
|
||||||
|
1,0,0,b95,0,0,0,0,0,0,0,0,0,0,l17,1
|
||||||
|
1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1
|
||||||
|
1,0,0,1,0,1,g20,0,0,0,1,0,l17,0,0,1
|
||||||
|
1,0,e,1,0,1,0,1,1,0,1,1,g26,1,1,1
|
||||||
|
1,0,0,1,0,0,0,1,0,0,0,0,g13,0,0,1
|
||||||
|
1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1
|
||||||
|
1,s,0,1,g27,0,0,1,0,0,0,0,t16,0,0,1
|
||||||
|
1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1
|
||||||
|
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
|
79
maze.py
Normal file
79
maze.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import pygame
|
||||||
|
from maze_generator import MazeGenerator
|
||||||
|
from tanxin import *
|
||||||
|
import time
|
||||||
|
|
||||||
|
class Maze:
|
||||||
|
def __init__(self, wall_size, maze_size, file_name):
|
||||||
|
self.wall_size = wall_size
|
||||||
|
self.maze_size = maze_size
|
||||||
|
self.size = int(maze_size / wall_size)
|
||||||
|
self.file_name = file_name
|
||||||
|
|
||||||
|
|
||||||
|
self.grid = []
|
||||||
|
self.generater = MazeGenerator(self.size, self.file_name)
|
||||||
|
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
seed = int(time.time() * 1000) % (2**32)
|
||||||
|
self.generater.generate(seed=seed)
|
||||||
|
# player = GreedyPlayer(generater.maze)
|
||||||
|
# player.find_path()
|
||||||
|
self.grid = self.generater.maze
|
||||||
|
|
||||||
|
def export_to_csv(self, filename):
|
||||||
|
self.generater.export_to_csv(filename=filename)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def draw(self, screen, wall_texture, coin_texture, trap_texture):
|
||||||
|
tile_size = wall_texture.get_width()
|
||||||
|
|
||||||
|
if len(self.grid) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
for y in range(self.size):
|
||||||
|
for x in range(self.size):
|
||||||
|
if self.grid[y][x] == '1':
|
||||||
|
screen.blit(wall_texture, (x * tile_size, y * tile_size))
|
||||||
|
if self.grid[y][x].startswith('g'):
|
||||||
|
screen.blit(coin_texture, (x * tile_size, y * tile_size))
|
||||||
|
if self.grid[y][x].startswith('t'):
|
||||||
|
screen.blit(trap_texture, (x * tile_size, y * tile_size))
|
||||||
|
if self.grid[y][x].startswith('|') or self.grid[y][x].startswith('-'):
|
||||||
|
font = pygame.font.SysFont(None, tile_size // 2)
|
||||||
|
num = 12
|
||||||
|
center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2)
|
||||||
|
radius = tile_size // 3
|
||||||
|
pygame.draw.circle(screen, (255, 215, 0), center, radius)
|
||||||
|
if num:
|
||||||
|
text = font.render(str(num), True, (0, 0, 0))
|
||||||
|
text_rect = text.get_rect(center=center)
|
||||||
|
screen.blit(text, text_rect)
|
||||||
|
|
||||||
|
if self.grid[y][x].startswith('s'):
|
||||||
|
font = pygame.font.SysFont(None, tile_size // 2)
|
||||||
|
text = "s"
|
||||||
|
center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2)
|
||||||
|
radius = tile_size // 3
|
||||||
|
pygame.draw.circle(screen, (255, 215, 0), center, radius)
|
||||||
|
if text:
|
||||||
|
text = font.render(text, True, (0, 0, 0))
|
||||||
|
text_rect = text.get_rect(center=center)
|
||||||
|
screen.blit(text, text_rect)
|
||||||
|
|
||||||
|
if self.grid[y][x].startswith('e'):
|
||||||
|
font = pygame.font.SysFont(None, tile_size // 2)
|
||||||
|
text = "e"
|
||||||
|
center = (x * tile_size + tile_size // 2, y * tile_size + tile_size // 2)
|
||||||
|
radius = tile_size // 3
|
||||||
|
pygame.draw.circle(screen, (255, 215, 0), center, radius)
|
||||||
|
if text:
|
||||||
|
text = font.render(text, True, (0, 0, 0))
|
||||||
|
text_rect = text.get_rect(center=center)
|
||||||
|
screen.blit(text, text_rect)
|
||||||
|
|
||||||
|
|
||||||
|
pygame.draw.line(screen, (0, 0, 0), (self.maze_size, 0), (self.maze_size, self.maze_size), 5)
|
||||||
|
|
256
maze_generator.py
Normal file
256
maze_generator.py
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
import random
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class MazeGenerator:
|
||||||
|
def __init__(self, size, filename, name="Default Maze"):
|
||||||
|
# 迷宫基础元素
|
||||||
|
self.ROUTE = '0'
|
||||||
|
self.WALL = '1'
|
||||||
|
# 特殊元素
|
||||||
|
self.BOSS = 'b'
|
||||||
|
self.START = 's'
|
||||||
|
self.END = 'e'
|
||||||
|
self.TRAP = 't'
|
||||||
|
self.MECHANISM = 'l'
|
||||||
|
self.GOLD = 'g'
|
||||||
|
|
||||||
|
self.size = size
|
||||||
|
self.maze = []
|
||||||
|
self.filename = filename
|
||||||
|
self.name = name # 迷宫名称
|
||||||
|
self.special_elements = [] # 存储特殊元素的位置和值
|
||||||
|
|
||||||
|
def initialize_maze(self):
|
||||||
|
"""初始化迷宫,四周设置为墙"""
|
||||||
|
self.maze = [[self.ROUTE for _ in range(self.size)] for _ in range(self.size)]
|
||||||
|
for i in range(self.size):
|
||||||
|
self.maze[0][i] = self.WALL
|
||||||
|
self.maze[i][0] = self.WALL
|
||||||
|
self.maze[self.size - 1][i] = self.WALL
|
||||||
|
self.maze[i][self.size - 1] = self.WALL
|
||||||
|
|
||||||
|
def create_maze(self, x1, y1, x2, y2):
|
||||||
|
"""递归分割法生成迷宫"""
|
||||||
|
if x2 - x1 < 2 or y2 - y1 < 2:
|
||||||
|
return
|
||||||
|
|
||||||
|
x = x1 + 1 + random.randint(0, (x2 - x1 - 2))
|
||||||
|
y = y1 + 1 + random.randint(0, (y2 - y1 - 2))
|
||||||
|
|
||||||
|
# 画墙
|
||||||
|
for i in range(x1, x2 + 1):
|
||||||
|
self.maze[i][y] = self.WALL
|
||||||
|
for i in range(y1, y2 + 1):
|
||||||
|
self.maze[x][i] = self.WALL
|
||||||
|
|
||||||
|
# 递归分割四个区域
|
||||||
|
self.create_maze(x1, y1, x - 1, y - 1)
|
||||||
|
self.create_maze(x + 1, y + 1, x2, y2)
|
||||||
|
self.create_maze(x + 1, y1, x2, y - 1)
|
||||||
|
self.create_maze(x1, y + 1, x - 1, y2)
|
||||||
|
|
||||||
|
# 随机打通三面墙
|
||||||
|
r = [0, 0, 0, 0]
|
||||||
|
r[random.randint(0, 3)] = 1
|
||||||
|
for i in range(4):
|
||||||
|
if r[i] == 0:
|
||||||
|
rx, ry = x, y
|
||||||
|
if i == 0: # 上方
|
||||||
|
while True:
|
||||||
|
rx = x1 + random.randint(0, (x - x1 - 1))
|
||||||
|
wall_count = sum([
|
||||||
|
(int)(self.maze[rx - 1][ry]), (int)(self.maze[rx + 1][ry]),
|
||||||
|
(int)(self.maze[rx][ry - 1]), (int)(self.maze[rx][ry + 1])
|
||||||
|
])
|
||||||
|
if wall_count <= 2 * (int)(self.WALL):
|
||||||
|
break
|
||||||
|
elif i == 1: # 右侧
|
||||||
|
while True:
|
||||||
|
ry = y + 1 + random.randint(0, (y2 - y - 1))
|
||||||
|
wall_count = sum([
|
||||||
|
(int)(self.maze[rx - 1][ry]), (int)(self.maze[rx + 1][ry]),
|
||||||
|
(int)(self.maze[rx][ry - 1]), (int)(self.maze[rx][ry + 1])
|
||||||
|
])
|
||||||
|
if wall_count <= 2 * (int)(self.WALL):
|
||||||
|
break
|
||||||
|
elif i == 2: # 下方
|
||||||
|
while True:
|
||||||
|
rx = x + 1 + random.randint(0, (x2 - x - 1))
|
||||||
|
wall_count = sum([
|
||||||
|
(int)(self.maze[rx - 1][ry]), (int)(self.maze[rx + 1][ry]),
|
||||||
|
(int)(self.maze[rx][ry - 1]), (int)(self.maze[rx][ry + 1])
|
||||||
|
])
|
||||||
|
if wall_count <= 2 * (int)(self.WALL):
|
||||||
|
break
|
||||||
|
elif i == 3: # 左侧
|
||||||
|
while True:
|
||||||
|
ry = y1 + random.randint(0, (y - y1 - 1))
|
||||||
|
wall_count = sum([
|
||||||
|
(int)(self.maze[rx - 1][ry]), (int)(self.maze[rx + 1][ry]),
|
||||||
|
(int)(self.maze[rx][ry - 1]), (int)(self.maze[rx][ry + 1])
|
||||||
|
])
|
||||||
|
if wall_count <= 2 * (int)(self.WALL):
|
||||||
|
break
|
||||||
|
self.maze[rx][ry] = self.ROUTE
|
||||||
|
|
||||||
|
def set_random_exits(self):
|
||||||
|
"""随机设置迷宫入口和出口"""
|
||||||
|
available = self.get_available_cells()
|
||||||
|
if len(available) < 2:
|
||||||
|
raise ValueError("迷宫空间不足,无法设置随机出入口")
|
||||||
|
start, end = random.sample(available, 2)
|
||||||
|
self.maze[start[0]][start[1]] = self.START
|
||||||
|
self.maze[end[0]][end[1]] = self.END
|
||||||
|
self.special_elements.extend([(start[0], start[1], self.START), (end[0], end[1], self.END)])
|
||||||
|
|
||||||
|
def get_available_cells(self):
|
||||||
|
"""获取所有可通行单元格"""
|
||||||
|
cells = []
|
||||||
|
for i in range(1, self.size - 1):
|
||||||
|
for j in range(1, self.size - 1):
|
||||||
|
if self.maze[i][j] == self.ROUTE:
|
||||||
|
cells.append((i, j))
|
||||||
|
return cells
|
||||||
|
|
||||||
|
def place_special_elements(self, boss_count=1, traps_range=(3, 8),
|
||||||
|
mechanisms_range=(2, 6), skill_traps=5,gold_range=(3,8)):
|
||||||
|
"""放置特殊元素(支持技能触发陷阱)"""
|
||||||
|
available = self.get_available_cells()
|
||||||
|
random.shuffle(available)
|
||||||
|
|
||||||
|
# 计算所需单元格数量
|
||||||
|
required = 2 + boss_count + random.randint(*traps_range) + random.randint(*mechanisms_range) + skill_traps + random.randint(*gold_range)
|
||||||
|
if len(available) < required:
|
||||||
|
raise ValueError(f"空间不足,需要{required}个单元格,实际可用{len(available)}")
|
||||||
|
|
||||||
|
# 放置出入口
|
||||||
|
|
||||||
|
start, end = available.pop(), available.pop()
|
||||||
|
self.special_elements.extend([(start[0], start[1], self.START), (end[0], end[1], self.END)])
|
||||||
|
self.maze[start[0]][start[1]] = self.START
|
||||||
|
self.maze[end[0]][end[1]] = self.END
|
||||||
|
|
||||||
|
# 放置BOSS
|
||||||
|
for _ in range(boss_count):
|
||||||
|
pos = available.pop()
|
||||||
|
val = random.randint(50, 100)
|
||||||
|
self.special_elements.append((pos[0], pos[1], f"{self.BOSS}{val}"))
|
||||||
|
self.maze[pos[0]][pos[1]] = f"{self.BOSS}{val}"
|
||||||
|
|
||||||
|
# 放置普通陷阱
|
||||||
|
traps = random.randint(*traps_range)
|
||||||
|
for _ in range(traps):
|
||||||
|
pos = available.pop()
|
||||||
|
val = random.randint(5, 20)
|
||||||
|
self.special_elements.append((pos[0], pos[1], f"{self.TRAP}{val}"))
|
||||||
|
self.maze[pos[0]][pos[1]] = f"{self.TRAP}{val}"
|
||||||
|
|
||||||
|
# 放置机关
|
||||||
|
mechanisms = random.randint(*mechanisms_range)
|
||||||
|
for _ in range(mechanisms):
|
||||||
|
pos = available.pop()
|
||||||
|
val = random.randint(10, 30)
|
||||||
|
self.special_elements.append((pos[0], pos[1], f"{self.MECHANISM}{val}"))
|
||||||
|
self.maze[pos[0]][pos[1]] = f"{self.MECHANISM}{val}"
|
||||||
|
# 放置金币
|
||||||
|
mechanisms = random.randint(*gold_range)
|
||||||
|
for _ in range(mechanisms):
|
||||||
|
pos = available.pop()
|
||||||
|
val = random.randint(10, 30)
|
||||||
|
self.special_elements.append((pos[0], pos[1], f"{self.GOLD}{val}"))
|
||||||
|
self.maze[pos[0]][pos[1]] = f"{self.GOLD}{val}"
|
||||||
|
|
||||||
|
def generate(self, seed=None, boss_count=1, traps_range=(3, 8),
|
||||||
|
mechanisms_range=(2, 6), skill_traps=5):
|
||||||
|
"""生成迷宫主方法"""
|
||||||
|
random.seed(seed or random.randint(0, 1000))
|
||||||
|
self.initialize_maze()
|
||||||
|
self.create_maze(1, 1, self.size - 2, self.size - 2)
|
||||||
|
self.place_special_elements(boss_count, traps_range, mechanisms_range, skill_traps)
|
||||||
|
print(f"成功生成迷宫: {self.name}")
|
||||||
|
|
||||||
|
def export_to_csv(self, filename):
|
||||||
|
"""导出迷宫到CSV文件"""
|
||||||
|
try:
|
||||||
|
with open(filename, 'w', newline='') as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
for row in self.maze:
|
||||||
|
writer.writerow(row)
|
||||||
|
print(f"迷宫已导出至: {os.path.abspath(filename)}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"导出失败: {str(e)}")
|
||||||
|
|
||||||
|
def read_from_csv(self):
|
||||||
|
"""从CSV读取迷宫数据"""
|
||||||
|
try:
|
||||||
|
with open(self.filename, 'r', newline='') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
self.maze = [list(map(self._parse_element, row)) for row in reader]
|
||||||
|
self.size = len(self.maze)
|
||||||
|
print(f"成功从{os.path.abspath(self.filename)}读取迷宫")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"读取失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _parse_element(self, elem):
|
||||||
|
"""解析CSV中的元素类型"""
|
||||||
|
if elem == 's': return self.START
|
||||||
|
if elem == 'e': return self.END
|
||||||
|
if elem.startswith('b'): return self.BOSS
|
||||||
|
if elem.startswith('t'): return self.TRAP
|
||||||
|
if elem.startswith('l'): return self.MECHANISM
|
||||||
|
if elem.startswith('g'): return self.GOLD
|
||||||
|
|
||||||
|
return int(elem)
|
||||||
|
|
||||||
|
def print_maze(self):
|
||||||
|
"""打印迷宫到控制台(带元素标识)"""
|
||||||
|
symbols = {
|
||||||
|
self.WALL: '■', self.ROUTE: '·', self.START: 'S',
|
||||||
|
self.END: 'E', self.BOSS: 'B', self.TRAP: 'T',
|
||||||
|
self.GOLD: 'G','|':'|','-':'—',
|
||||||
|
}
|
||||||
|
for row in self.maze:
|
||||||
|
display = []
|
||||||
|
for cell in row:
|
||||||
|
symbol = symbols.get(((str)(cell))[0], ((str)(cell))[0])
|
||||||
|
display.append(str(symbol))
|
||||||
|
print(' '.join(display))
|
||||||
|
def read_csv(self):
|
||||||
|
l = []
|
||||||
|
with open(f'{self.filename}', 'r', newline='') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
for row in reader:
|
||||||
|
l.append(row)
|
||||||
|
return l
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# 示例1: 生成带技能陷阱的迷宫
|
||||||
|
generator = MazeGenerator(
|
||||||
|
size=20,
|
||||||
|
filename="dungeon_maze.csv",
|
||||||
|
name="龙脊峡谷迷宫"
|
||||||
|
)
|
||||||
|
generator.generate(
|
||||||
|
seed=666,
|
||||||
|
boss_count=2,
|
||||||
|
traps_range=(5, 10),
|
||||||
|
mechanisms_range=(3, 7),
|
||||||
|
skill_traps=8
|
||||||
|
)
|
||||||
|
generator.print_maze()
|
||||||
|
generator.export_to_csv()
|
||||||
|
|
||||||
|
|
||||||
|
reader = MazeGenerator(size=1, filename="dungeon_maze.csv")
|
||||||
|
if reader.read_from_csv():
|
||||||
|
print("\n读取的迷宫:")
|
||||||
|
reader.print_maze()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
210
tanxin.py
Normal file
210
tanxin.py
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
import math
|
||||||
|
from maze import *
|
||||||
|
import math
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
class GreedyPlayer:
|
||||||
|
def __init__(self, map_data, start=None, end=None):
|
||||||
|
"""初始化GreedyPlayer对象"""
|
||||||
|
self.map_data = map_data
|
||||||
|
self.rows = len(map_data)
|
||||||
|
self.cols = len(map_data[0]) if self.rows > 0 else 0
|
||||||
|
self.start = start
|
||||||
|
self.end = end
|
||||||
|
self.path = []
|
||||||
|
self.total_reward = 0
|
||||||
|
self.visited = set()
|
||||||
|
self.marked_map = []
|
||||||
|
|
||||||
|
# 如果未指定起点和终点,自动查找
|
||||||
|
if not self.start or not self.end:
|
||||||
|
self._find_start_end()
|
||||||
|
|
||||||
|
def _find_start_end(self):
|
||||||
|
"""自动查找地图中的起点(s)和终点(e)"""
|
||||||
|
for y in range(self.rows):
|
||||||
|
for x in range(self.cols):
|
||||||
|
if self.map_data[y][x] == 's' or self.map_data[y][x] == 'S':
|
||||||
|
self.start = (x, y)
|
||||||
|
elif self.map_data[y][x] == 'e' or self.map_data[y][x] == 'E':
|
||||||
|
self.end = (x, y)
|
||||||
|
print(f"起点: {self.start}, 终点: {self.end}")
|
||||||
|
|
||||||
|
def get_visible_cells(self, x, y, visibility=1):
|
||||||
|
"""获取以(x,y)为中心的上下左右四个方向的单元格信息"""
|
||||||
|
visible = {}
|
||||||
|
# 只考虑上下左右四个方向(dx或dy为±1,另一个为0)
|
||||||
|
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
|
||||||
|
for dx, dy in directions:
|
||||||
|
nx, ny = x + dx, y + dy
|
||||||
|
if 0 <= nx < self.cols and 0 <= ny < self.rows:
|
||||||
|
cell = self.map_data[ny][nx]
|
||||||
|
distance = 1 # 上下左右移动距离为1
|
||||||
|
visible[(nx, ny)] = (cell, distance)
|
||||||
|
return visible
|
||||||
|
|
||||||
|
def evaluate_cell(self, cell, distance):
|
||||||
|
"""评估单元格的价值,返回奖励/路径的比值"""
|
||||||
|
if cell == 's' or cell == 'e':
|
||||||
|
return 0 # 起点和终点不参与资源评估
|
||||||
|
|
||||||
|
if cell.startswith('t'):
|
||||||
|
try:
|
||||||
|
value = -int(cell[1:]) # t表示损失,转为负值
|
||||||
|
return value / distance
|
||||||
|
except ValueError:
|
||||||
|
return 0
|
||||||
|
elif cell.startswith('g'):
|
||||||
|
try:
|
||||||
|
value = int(cell[1:]) # g表示收益,转为正值
|
||||||
|
return value / distance
|
||||||
|
except ValueError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return 0 # 0、l、b等不产生资源价值
|
||||||
|
|
||||||
|
def find_path(self):
|
||||||
|
"""基于贪心策略的路径规划(只能上下左右移动)"""
|
||||||
|
if not self.start or not self.end:
|
||||||
|
raise ValueError("地图中未找到起点或终点")
|
||||||
|
|
||||||
|
current = self.start
|
||||||
|
self.path = [current]
|
||||||
|
self.visited = {current}
|
||||||
|
self.total_reward = 0
|
||||||
|
|
||||||
|
while current != self.end:
|
||||||
|
x, y = current
|
||||||
|
visible = self.get_visible_cells(x, y)
|
||||||
|
|
||||||
|
best_cell = None
|
||||||
|
best_value = -float('inf')
|
||||||
|
|
||||||
|
for (nx, ny), (cell, distance) in visible.items():
|
||||||
|
# 跳过已访问的位置
|
||||||
|
if (nx, ny) in self.visited:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 只允许在0、t、g、l、b上行走
|
||||||
|
if cell not in ['0'] and not cell.startswith(('t', 'g', 'l', 'b')):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 评估单元格价值
|
||||||
|
value = self.evaluate_cell(cell, distance)
|
||||||
|
|
||||||
|
# 终点具有最高优先级
|
||||||
|
if cell == 'e':
|
||||||
|
value = float('inf')
|
||||||
|
|
||||||
|
# 选择贪心值最大的单元格
|
||||||
|
if value > best_value:
|
||||||
|
best_value = value
|
||||||
|
best_cell = (nx, ny)
|
||||||
|
|
||||||
|
# 无法找到可行路径
|
||||||
|
if best_cell is None:
|
||||||
|
print("无法找到通往终点的路径!")
|
||||||
|
break
|
||||||
|
|
||||||
|
# 更新当前位置和路径
|
||||||
|
current = best_cell
|
||||||
|
self.path.append(current)
|
||||||
|
self.visited.add(current)
|
||||||
|
|
||||||
|
# 更新总收益(跳过起点和终点)
|
||||||
|
if len(self.path) > 1 and len(self.path) < len(self.path) + 1:
|
||||||
|
cell = self.map_data[current[1]][current[0]]
|
||||||
|
if cell.startswith('t'):
|
||||||
|
self.total_reward -= int(cell[1:])
|
||||||
|
elif cell.startswith('g'):
|
||||||
|
self.total_reward += int(cell[1:])
|
||||||
|
self.add_path_to_map()
|
||||||
|
return self.path
|
||||||
|
|
||||||
|
def add_path_to_map(self):
|
||||||
|
"""在地图上标记路径,上下移动用|,左右移动用-"""
|
||||||
|
if not self.path:
|
||||||
|
print("没有路径可标记")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 创建地图副本,避免修改原始地图
|
||||||
|
marked_map = [row.copy() for row in self.map_data]
|
||||||
|
|
||||||
|
# 标记路径点
|
||||||
|
for i, (x, y) in enumerate(self.path):
|
||||||
|
if marked_map[y][x] == 's':
|
||||||
|
marked_map[y][x] = 'S' # 标记起点
|
||||||
|
elif marked_map[y][x] == 'e':
|
||||||
|
marked_map[y][x] = 'E' # 标记终点
|
||||||
|
else:
|
||||||
|
marked_map[y][x] = '*' # 标记路径点
|
||||||
|
|
||||||
|
# 标记路径线(上下左右)
|
||||||
|
for i in range(len(self.path) - 1):
|
||||||
|
x1, y1 = self.path[i]
|
||||||
|
x2, y2 = self.path[i + 1]
|
||||||
|
|
||||||
|
# 左右移动
|
||||||
|
if x1 != x2 and y1 == y2:
|
||||||
|
start, end = (x1, x2) if x1 < x2 else (x2, x1)
|
||||||
|
for x in range(start, end + 1):
|
||||||
|
if marked_map[y1][x] not in ['S', 'E']:
|
||||||
|
marked_map[y1][x] = '-'
|
||||||
|
|
||||||
|
# 上下移动
|
||||||
|
elif y1 != y2 and x1 == x2:
|
||||||
|
start, end = (y1, y2) if y1 < y2 else (y2, y1)
|
||||||
|
for y in range(start, end + 1):
|
||||||
|
if marked_map[y][x1] not in ['S', 'E']:
|
||||||
|
marked_map[y][x1] = '|'
|
||||||
|
|
||||||
|
# 保存标记后的地图
|
||||||
|
self.marked_map = marked_map
|
||||||
|
return marked_map
|
||||||
|
|
||||||
|
def get_path(self):
|
||||||
|
"""返回找到的路径"""
|
||||||
|
return self.path
|
||||||
|
|
||||||
|
def get_total_reward(self):
|
||||||
|
"""返回总收益"""
|
||||||
|
return self.total_reward
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 使用示例
|
||||||
|
def main():
|
||||||
|
obj = MazeGenerator(20,'demo.csv',name="龙脊峡谷迷宫")
|
||||||
|
obj.generate(
|
||||||
|
seed=123,
|
||||||
|
boss_count=2,
|
||||||
|
traps_range=(5, 10),
|
||||||
|
mechanisms_range=(3, 7),
|
||||||
|
skill_traps=8
|
||||||
|
)
|
||||||
|
obj.export_to_csv()
|
||||||
|
|
||||||
|
map_data = obj.read_csv()
|
||||||
|
see = MazeGenerator(1,filename ='demo.csv')
|
||||||
|
see.read_from_csv()
|
||||||
|
see.print_maze()
|
||||||
|
player = GreedyPlayer(map_data)
|
||||||
|
player.find_path()
|
||||||
|
see.maze = player.marked_map
|
||||||
|
see.print_maze()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user