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