Commit 340be132 authored by tgw3ff's avatar tgw3ff
Browse files

Room Detection and Spanning

The isolated rooms of the map are now detected and a modified Kruskal's
algorithm is used to create a tree spanning all of said rooms.

This algorithm will be modified so that the smallest rooms will not have so
many branches off of them in the tree that they cannot contain all of the holes
that will be created because of those branches.

Cell and Room classes will be created to make the code easier to understand as
well as to support hole placement, biome placement, landmark spawning, and
enemy spawning.
parent 2596498a
51813
56360
C:\Users\Username\Desktop\Godot_v3.2.3-stable_mono_win64\Godot_v3.2.3-stable_mono_win64.exe
extends Area2D
# Declare member variables here. Examples:
# var a = 2
# var b = "text"
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass
func line(v1, v2):
draw_line(v1, v2, Color(255, 255, 255), 4)
update()
[gd_scene load_steps=2 format=2]
[ext_resource path="res://src/Area2D.gd" type="Script" id=1]
[node name="Area2D" type="Area2D"]
script = ExtResource( 1 )
extends Line2D
# Declare member variables here. Examples:
# var a = 2
# var b = "text"
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass
[gd_scene load_steps=2 format=2]
[ext_resource path="res://src/Line2D.gd" type="Script" id=1]
[node name="Line2D" type="Line2D"]
default_color = Color( 0.0666667, 0.211765, 0.368627, 1 )
script = ExtResource( 1 )
var centers = []
var minimum_tree = []
# Declare member variables here
# Enumerator that stores which integers represent which type of tile
# The pairings also equate to the corresponding tile group's index in the
......@@ -26,44 +29,55 @@ var invalidArrangments = {
[EMPTY,WALL ,EMPTY] ] : [true, false, false],
[ [EMPTY,EMPTY,EMPTY],
[EMPTY,WALL ,EMPTY],
[WALL ,WALL ,EMPTY] ] : [true, true, true],
[WALL ,WALL ,EMPTY] ] : [true, true, false],
[ [EMPTY,EMPTY,EMPTY],
[EMPTY,WALL ,WALL ],
[WALL ,WALL ,EMPTY] ] : [true, true, true],
[WALL ,WALL ,EMPTY] ] : [true, true, false],
[ [WALL ,EMPTY,EMPTY],
[EMPTY,WALL ,WALL ],
[WALL ,WALL ,EMPTY] ] : [true, true, true],
[WALL ,WALL ,EMPTY] ] : [true, true, false],
[ [EMPTY,WALL ,EMPTY],
[EMPTY,WALL ,WALL ],
[WALL ,WALL ,EMPTY] ] : [true, true, true],
[WALL ,WALL ,EMPTY] ] : [true, true, false],
[ [EMPTY,WALL ,WALL ],
[EMPTY,WALL ,WALL ],
[WALL ,EMPTY,EMPTY] ] : [true, true, true],
[WALL ,EMPTY,EMPTY] ] : [true, true, false],
[ [WALL ,WALL ,EMPTY],
[EMPTY,WALL ,EMPTY],
[EMPTY,EMPTY,WALL ] ] : [true, true, true],
[EMPTY,EMPTY,WALL ] ] : [true, true, false],
[ [WALL ,EMPTY,EMPTY],
[WALL ,WALL ,WALL ],
[EMPTY,WALL ,WALL ] ] : [true, true, true],
[EMPTY,WALL ,WALL ] ] : [true, true, false],
[ [EMPTY,WALL ,EMPTY],
[WALL ,WALL ,WALL ],
[WALL ,WALL ,WALL ] ] : [true, true, true],
[WALL ,WALL ,WALL ] ] : [true, true, false],
[ [WALL ,WALL ,EMPTY],
[WALL ,WALL ,EMPTY],
[EMPTY,WALL ,WALL ] ] : [true, true, true],
[EMPTY,WALL ,WALL ] ] : [true, true, false],
[ [WALL ,WALL ,EMPTY],
[EMPTY,WALL ,EMPTY],
[WALL ,WALL ,WALL ] ] : [true, true, true],
[WALL ,WALL ,WALL ] ] : [true, true, false],
[ [WALL ,WALL ,EMPTY],
[EMPTY,WALL ,EMPTY],
[EMPTY,WALL ,WALL ] ] : [true, true, true]
[EMPTY,WALL ,WALL ] ] : [true, true, false],
[ [EMPTY,EMPTY,EMPTY],
[EMPTY,WALL ,EMPTY],
[EMPTY,WALL ,EMPTY] ] : [true, true, false],
[ # This gets rid of single tile sized, isolated rooms as they are too
# small to contain anything
[WALL ,WALL ,WALL ],
[WALL ,EMPTY,WALL ],
[WALL ,WALL ,WALL ] ] : [false, false, false]
}
# The sub matrix to replace invalid arrangements with
const FILLED = [ [WALL ,WALL ,WALL ],
[WALL ,WALL ,WALL ],
[WALL ,WALL ,WALL ] ]
# This variable is used to store a 2D array that represents the level map
# Stores a 2D array that represents the level map
# When referencing an index in the grid, use [y][x] format
var grid = []
# Stores the empty coordinates on the map and which "room" they are each a part of
var rooms = {}
func _init():
......@@ -153,18 +167,18 @@ func rotate_matrix(matrix=[[]], n=1):
for h in p_height:
matrix_p[w].append(EMPTY)
# Unfortunately, GDscript does not have switch case statements :/
# Fill new matrix with original rotated...
if n % 4 == 0: # 0 degrees
matrix_p = matrix
elif n % 4 == 1: # 90 degrees clockwise
for i in m_height:
for j in m_width:
matrix_p[j][m_height - 1 - i] = matrix[i][j]
elif n % 4 == 2: # 180 degrees (simulated by mirroring horizontally then vertically
matrix_p = mirror_matrix(mirror_matrix(matrix, false), true)
elif n % 4 == 3: # 260 degrees (utilizing nested recursion)
matrix_p = rotate_matrix(rotate_matrix(matrix, 2), 1)
match n % 4:
0: # 0 degrees
matrix_p = matrix
1: # 90 degrees clockwise
for i in m_height:
for j in m_width:
matrix_p[j][m_height - 1 - i] = matrix[i][j]
2: # 180 degrees (simulated by mirroring horizontally then vertically
matrix_p = mirror_matrix(mirror_matrix(matrix, false), true)
3: # 260 degrees (utilizing nested recursion)
matrix_p = rotate_matrix(rotate_matrix(matrix, 2), 1)
return matrix_p
......@@ -219,7 +233,8 @@ func replace_invalids(width, height):
if focus == key:
valid = false
replace_3x3_grid(x, y, FILLED)
elif invalidArrangments[key][0]: # Otherwise check if it matches a rotation of key?
# Otherwise check if it matches a rotation of key?
elif invalidArrangments[key][0]:
for n in range(1, 4):
var rotated_key = rotate_matrix(key, n)
if focus == rotated_key:
......@@ -229,24 +244,165 @@ func replace_invalids(width, height):
elif focus == mirror_matrix(rotated_key, false) && (invalidArrangments[key][1] || invalidArrangments[key][2]):
valid = false
replace_3x3_grid(x, y, FILLED)
elif invalidArrangments[key][1]: # horizontally mirrored key?
# horizontally mirrored key?
elif invalidArrangments[key][1]:
if focus == mirror_matrix(key, false):
valid = false
replace_3x3_grid(x, y, FILLED)
elif invalidArrangments[key][2]: # vertically mirrored key?
# vertically mirrored key?
elif invalidArrangments[key][2]:
if focus == mirror_matrix(key, true):
valid = true
replace_3x3_grid(x, y, FILLED)
# Returns whether or not all of the coordinates in the rooms dictionary have been assigned a room
func assign_rooms_completed():
if null in rooms.values():
return false
else:
return true
# Assigns rooms to each of the coordinate keys in the rooms dictionary
func assign_rooms(width, height):
var complete = false
var queue = []
var curr_room = 0
# Add each coordinate that does not house a wall as a key to the rooms dictionary
for y in range(0, height):
for x in range(0, width):
if grid[y][x] != WALL:
rooms[Vector2(x, y)] = null
# While there are still empty coords that have not been assigned rooms...
while !complete:
# Assign rooms to every empty coord
for key in rooms:
complete = true
if rooms[key] == null:
complete = false
queue.append(key)
# While the queue isn't empty...
while !queue.empty():
# Assign a room to the first coord in the queue
var curr_key = queue.pop_front()
rooms[curr_key] = curr_room
# Insert the current coordinate's 4 neighbors into the queue
# (if they are not already in the queue, are empty, and
# have not already been assigned a room)
for y_mod in range(-1, 2):
for x_mod in range(-1, 2):
if abs(y_mod) != abs(x_mod):
var neighbor = Vector2(curr_key.x + x_mod, curr_key.y + y_mod)
if !queue.has(neighbor) && rooms.has(neighbor) && rooms[neighbor] == null:
queue.append(neighbor)
# All coordinates of the current room have been found, so look
# for coords that belong to the next room
curr_room += 1
# Returns an array of the centers of each room in Vector2 format
func get_centers():
var values = []
var centers = []
# Get all of the unique values, or rooms, in the rooms dictionary
for key in rooms:
if !(rooms[key] in values):
values.append(rooms[key])
while !values.empty():
var curr_value = values.pop_front()
var curr_value_count = 0
var x_avg = 0
var y_avg = 0
for key in rooms:
if rooms[key] == curr_value:
curr_value_count += 1
x_avg += key.x
y_avg += key.y
centers.append([Vector2(x_avg / curr_value_count, y_avg / curr_value_count), curr_value_count])
return centers
# A modified Kruskal's algorithm to find close to the minimum spanning tree
# between all room centers while also trying to ensuring that the larger rooms
# have the most branching off points.
func get_minimum_spanning_tree():
var tree = []
var m_s_tree = []
var visited = []
for p1 in centers:
for p2 in centers:
if p1 != p2:
var distance = p1[0].distance_to(p2[0])
if tree.empty():
tree.append([p1[0], p2[0], distance])
else:
var index = -1
var duplicate = false
for i in range(0, tree.size()):
if index == -1 && distance < tree[i][2]:
index = i
if tree[i][0] == p2[0] && tree[i][1] == p1[0]:
duplicate = true
if !duplicate:
if index == -1:
tree.push_back([p1[0], p2[0], distance])
else:
tree.insert(index, [p1[0], p2[0], distance])
while visited.size() != centers.size():
if m_s_tree.empty():
var biggest_room
var curr_max = 0
for center in centers:
if center[1] > curr_max:
curr_max = center[1]
biggest_room = center[0]
visited.append(biggest_room)
for line in tree:
if visited.has(line[0]) != visited.has(line[1]):
if !visited.has(line[0]):
visited.append(line[0])
else:
visited.append(line[1])
m_s_tree.append(line)
return m_s_tree
# Generates a new map
func generate_map(fill_percent, width, height):
rooms.clear()
initialize_grid(fill_percent, width, height)
for i in range(0, 5):
for i in range(0, 3):
smooth_grid(width, height)
replace_invalids(width, height)
assign_rooms(width, height)
centers = get_centers()
minimum_tree = get_minimum_spanning_tree()
# Returns the grid member variable
......
extends TileMap
const MAPGEN = preload("res://src/MapGenerator.gd")
var map_gen = MAPGEN.new()
const DRAWER = preload("res://src/Line2D.gd")
var drawer = DRAWER.new()
var lines = []
# Declare member variables here
export(int, 100) var fill_percent = 45
export var width = 50
export var height = 40
var gen = true
# Custom cell-clear command that doesn't mess up the setting of atlas tiles
......@@ -200,7 +202,11 @@ func update_tile(x, y):
# Generate a new map and update the level map on screen accordingly
func update_map():
gen = false
if !lines.empty():
for line in lines:
if line != null:
line.free()
map_gen.generate_map(fill_percent, width, height)
for x in width:
......@@ -208,7 +214,15 @@ func update_map():
update_tile(x, y)
update_dirty_quadrants()
gen = true
for line in map_gen.minimum_tree:
lines.append(DRAWER.new())
lines.back().default_color = Color(255, 255, 255)
lines.back().width = 1
lines.back().add_point(Vector2(line[0][0] * 16, line[0][1] * 16))
lines.back().add_point(Vector2(line[1][0] * 16, line[1][1] * 16))
add_child(lines.back())
# Called when the node enters the scene tree for the first time.
......@@ -222,5 +236,4 @@ func _unhandled_input(event):
update_map()
func _process(delta):
if gen:
update_map()
pass
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment