119 lines
4.4 KiB
GDScript
119 lines
4.4 KiB
GDScript
class_name Generator
|
|
extends Reference
|
|
|
|
# Maze carver position
|
|
class MazeCarverPosition:
|
|
var x: int = 0
|
|
var y: int = 0
|
|
var available_directions: Array = [Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST]
|
|
|
|
func _init(pos_x: int, pos_y: int):
|
|
x = pos_x
|
|
y = pos_y
|
|
available_directions.shuffle()
|
|
|
|
func has_next_direction():
|
|
return not available_directions.empty()
|
|
|
|
func next_direction():
|
|
return available_directions.pop_back()
|
|
|
|
# Generate a new map
|
|
# Each map is a array indicating the features of the map
|
|
func generate_map(width: int, height: int):
|
|
var maze = null
|
|
# A correct maze needs at least two events to place the entrance and exit
|
|
var events = []
|
|
while events.size() < 2:
|
|
maze = Maze.new(width, height)
|
|
var start_x = randi() % width
|
|
var start_y = randi() % height
|
|
carve_maze(maze, start_x, start_y, true)
|
|
add_events(maze)
|
|
events = maze.events
|
|
# Shuffle the event list
|
|
maze.events.shuffle()
|
|
# Set the entrance and exit
|
|
set_entrance(maze)
|
|
set_exit(maze)
|
|
# Set the chests
|
|
set_chests(maze)
|
|
return maze
|
|
|
|
# Carve a maze (iterative)
|
|
func carve_maze(maze: Maze, start_x: int, start_y: int, loop: bool):
|
|
# Starting position
|
|
var start_pos: MazeCarverPosition = MazeCarverPosition.new(start_x, start_y)
|
|
# Position pile
|
|
var carver = [start_pos]
|
|
# While there is a pos in carver
|
|
while not carver.empty():
|
|
var pos: MazeCarverPosition = carver.back()
|
|
# Set the current cell floor kind to PATH
|
|
maze.at(pos.x, pos.y).floor_kind = Cell.Floor.PATH
|
|
# If there are available directions, pick the first, and continue from there
|
|
if pos.has_next_direction():
|
|
# Get the next direction
|
|
var dir = pos.next_direction()
|
|
# Position of next cell
|
|
var nx = pos.x + Direction.offset_x(dir)
|
|
var ny = pos.y + Direction.offset_y(dir)
|
|
# if the next cell is inside the maze, and has an unknown floor kind,
|
|
# remove the wall between the cells, and continue from next cell.
|
|
if maze.is_inside(nx, ny) and (maze.at(nx, ny).floor_kind == Cell.Floor.UNKNOWN):
|
|
maze.at(pos.x, pos.y).set_edge(dir, Cell.Edge.NONE)
|
|
maze.at(nx, ny).set_edge(Direction.opposite(dir), Cell.Edge.NONE)
|
|
# Push the new position into the carver
|
|
var new_pos: MazeCarverPosition = MazeCarverPosition.new(nx, ny)
|
|
carver.push_back(new_pos)
|
|
# if the next cell is inside the maze and has a path floor kind,
|
|
# if the loop is true and at a 1/20 chance, remove the wall,
|
|
# but don't continue on the next cell
|
|
elif maze.is_inside(nx, ny) and (maze.at(nx, ny).floor_kind == Cell.Floor.PATH) and loop and (randi() % 20 < 1):
|
|
maze.at(pos.x, pos.y).set_edge(dir, Cell.Edge.NONE)
|
|
maze.at(nx, ny).set_edge(Direction.opposite(dir), Cell.Edge.NONE)
|
|
else:
|
|
# do nothing
|
|
pass
|
|
else:
|
|
# No new available position : pop pos from carver
|
|
carver.pop_back()
|
|
|
|
# Add events at the deadends
|
|
func add_events(maze: Maze):
|
|
for j in range(maze.height):
|
|
for i in range(maze.width):
|
|
if maze.at(i, j).is_deadend() and (maze.at(i, j).feature == Cell.Feature.NONE):
|
|
maze.at(i, j).feature = Cell.Feature.EVENT
|
|
maze.events.push_back(Vector2(i, j))
|
|
|
|
# Choose an event to set as the entrance
|
|
func set_entrance(maze: Maze):
|
|
var pos: Vector2 = maze.events.pop_back()
|
|
maze.at(pos.x, pos.y).feature = Cell.Feature.ENTRANCE
|
|
maze.entrance = pos
|
|
|
|
# Set the exit on the event that is farthest from the entrance
|
|
func set_exit(maze: Maze):
|
|
var distance = 0
|
|
var exit = maze.entrance
|
|
for e in maze.events:
|
|
var d = maze.entrance.distance_squared_to(e)
|
|
if d > distance:
|
|
distance = d
|
|
exit = e
|
|
maze.at(exit.x, exit.y).feature = Cell.Feature.EXIT
|
|
maze.exit = exit
|
|
|
|
# Set chests on a few events
|
|
func set_chests(maze: Maze):
|
|
var max_nb_chests = sqrt(maze.width * maze.height)
|
|
var count = 0
|
|
for e in maze.events:
|
|
if maze.at(e.x, e.y).feature == Cell.Feature.EVENT:
|
|
count += 1
|
|
maze.at(e.x, e.y).feature = Cell.Feature.CHEST
|
|
if count >= max_nb_chests:
|
|
break
|
|
maze.nb_chests = count
|