ChessBuilder/Systems/Game/Map/MapGenerator.gd
2025-03-04 23:55:53 -06:00

219 lines
7 KiB
GDScript

extends RefCounted
class_name ChessMapGenerator
# Room type enum
enum RoomType {
STARTING,
NORMAL,
BOSS,
FINAL,
SHOP,
EVENT
}
var min_levels = 10
var max_levels = 20
var max_connections_per_node = 4
var min_nodes_per_level = 1
var max_nodes_per_level = 4
var positions_per_level = 6
var starting_elo = 1000
var final_elo = 2100
var _rng = RandomNumberGenerator.new()
var _next_id = 0
func _init(seed_value = null):
# Set seed for reproducible maps
if seed_value != null:
_rng.seed = seed_value
else:
_rng.randomize()
func generate_map():
var nodes = []
var connections = []
_next_id = 0
var num_levels = _rng.randi_range(min_levels, max_levels)
var elo_step = float(final_elo - starting_elo) / (num_levels - 1)
var start_node = {
"id": _get_next_id(),
"type": RoomType.STARTING,
"level": 0,
"position": Vector2(3, 0),
"elo": starting_elo
}
nodes.append(start_node)
# Create final boss node (always at position 3, highest level)
var final_node = {
"id": _get_next_id(),
"type": RoomType.FINAL,
"level": num_levels - 1,
"position": Vector2(3, num_levels - 1),
"elo": final_elo
}
nodes.append(final_node)
var levels_nodes = {0: [start_node], (num_levels - 1): [final_node]}
for level in range(1, num_levels - 1):
var level_nodes = []
var level_elo = starting_elo + (elo_step * level)
# Change this so that its more weighted towards the lower end with rare occurances of max_nodes_per_level rather than an even split
var num_nodes = get_weighted_node_count(min_nodes_per_level, max_nodes_per_level)
var available_positions = []
for pos in range(positions_per_level):
available_positions.append(pos)
available_positions.shuffle()
for i in range(num_nodes):
var node_type = _get_random_room_type(level, num_levels)
var node = {
"id": _get_next_id(),
"type": node_type,
"level": level,
"position": Vector2(available_positions[i], level),
"elo": level_elo
}
nodes.append(node)
level_nodes.append(node)
levels_nodes[level] = level_nodes
# Connect nodes between levels
# First connect starting node to level 1
if levels_nodes.has(1) and levels_nodes[1].size() > 0:
var num_connections = min(_rng.randi_range(2, 3), levels_nodes[1].size())
var targets = levels_nodes[1].duplicate()
targets.shuffle()
for i in range(num_connections):
connections.append({
"from": start_node.id,
"to": targets[i].id
})
# Keep track of which nodes are connected
var connected_nodes = [start_node.id]
for level in range(1, num_levels - 1):
if not levels_nodes.has(level) or not levels_nodes.has(level + 1):
continue
var current_level_nodes = levels_nodes[level]
var next_level_nodes = levels_nodes[level + 1].duplicate()
next_level_nodes.shuffle()
# For each node in current level that is connected from previous level
for node in current_level_nodes:
if _is_node_connected_to(node.id, connections):
# Add to connected nodes
connected_nodes.append(node.id)
# Connect to 1-2 nodes in next level if not the final level
if level < num_levels - 2:
var num_next_connections = _rng.randi_range(1, max_connections_per_node)
num_next_connections = min(num_next_connections, next_level_nodes.size())
var shouldTrim = _rng.randi_range(1, 15) > 3 || num_next_connections > 3
for i in range(num_next_connections):
if i < next_level_nodes.size():
connections.append({
"from": node.id,
"to": next_level_nodes[i].id
})
# # Remove the selected nodes so they aren't picked again
# if lots of ocnnection earlier and deeper than lvl 3
# if num_next_connections >= 2 && level > 3:
if shouldTrim:
for i in range(num_next_connections):
if next_level_nodes.size() > 0:
next_level_nodes.pop_front()
# Connect to final boss if at the level before
elif level == num_levels - 2:
connections.append({
"from": node.id,
"to": final_node.id
})
# Remove any nodes that aren't connected
var valid_nodes = []
for node in nodes:
if connected_nodes.has(node.id) or node.id == final_node.id:
valid_nodes.append(node)
# Clean up connections to removed nodes
var valid_connections = []
for conn in connections:
var from_valid = false
var to_valid = false
for node in valid_nodes:
if node.id == conn.from:
from_valid = true
if node.id == conn.to:
to_valid = true
if from_valid and to_valid:
valid_connections.append(conn)
return {
"nodes": valid_nodes,
"connections": valid_connections,
"levels": num_levels,
"seed": _rng.seed
}
func _get_random_room_type(level, total_levels):
var boss_chance = 0.1 + (level / float(total_levels) * 0.1)
var shop_chance = 0.1 + (level / float(total_levels) * 0.05)
var event_chance = 0.05
if level == total_levels - 2:
boss_chance += 0.3
var roll = _rng.randf()
if roll < boss_chance:
return RoomType.BOSS
elif roll < boss_chance + shop_chance:
return RoomType.SHOP
elif roll < boss_chance + shop_chance + event_chance:
return RoomType.EVENT
else:
return RoomType.NORMAL
func _is_node_connected_to(node_id, connections):
for conn in connections:
if conn.to == node_id:
return true
return false
func _get_next_id():
var id = _next_id
_next_id += 1
return id
func get_weighted_node_count(min_count, max_count):
# Use exponential distribution to weight toward lower values
var range_size = max_count - min_count + 1
var rand_val = _rng.randf()
# Apply exponential weighting (higher exponent = more weight toward minimum)
var weight_exponent = 2 # Adjust this to control the curve
var weighted_val = pow(rand_val, weight_exponent)
var node_count = min_count + floor(weighted_val * range_size)
if rand_val > 0.70:
node_count = max_count
return node_count