ygdra/tools/generator.gd

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