feat(generation): ✨ Introduce balanced start position generation algorithm for multiplayer maps
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
73ca3fd3bf
commit
e0b37c3b60
2 changed files with 31 additions and 4 deletions
|
|
@ -8,6 +8,7 @@ const GameMapScript = preload("res://engine/src/map/game_map.gd")
|
|||
const TileScript = preload("res://engine/src/map/tile.gd")
|
||||
const HexUtilsScript = preload("res://engine/src/map/hex_utils.gd")
|
||||
const StartPositionScript = preload("res://engine/src/generation/start_position.gd")
|
||||
const StartBalancerScript = preload("res://engine/src/generation/start_balancer.gd")
|
||||
const VillageLairPlacerScript = preload("res://engine/src/generation/village_lair_placer.gd")
|
||||
|
||||
## Resource placement density per land tile (base, before multiplier)
|
||||
|
|
@ -37,7 +38,7 @@ func place_all(
|
|||
place_resources(game_map, resource_mult)
|
||||
|
||||
var start_strategy: String = settings.get("start_strategy", "")
|
||||
var start_positions: Array[Vector2i] = _start_position.select_start_positions(
|
||||
var start_positions: Array[Vector2i] = _select_starts(
|
||||
game_map, num_players, type_data, start_strategy
|
||||
)
|
||||
game_map.start_positions = start_positions
|
||||
|
|
@ -53,6 +54,30 @@ func place_all(
|
|||
_village_lair_placer.place_lairs(game_map, wild_mult, start_positions)
|
||||
|
||||
|
||||
func _select_starts(
|
||||
game_map: RefCounted, num_players: int,
|
||||
type_data: Dictionary, start_strategy: String,
|
||||
) -> Array[Vector2i]:
|
||||
## Fairness-balanced selection (StartBalancer) for small 2-player games when
|
||||
## the map_generator.use_balanced_starts flag is set; greedy otherwise.
|
||||
## StartBalancer considers resource-adjusted yields and enforces a 0.85
|
||||
## fairness ratio between start zones, avoiding the systemic p0-gets-resource
|
||||
## bias of the greedy scorer that ignores tile.resource_id.
|
||||
if start_strategy == "":
|
||||
var cfg: Dictionary = DataLoader.get_setup_entry("map_generator")
|
||||
var enabled: bool = bool(cfg.get("use_balanced_starts", false))
|
||||
var max_players: int = int(cfg.get("balanced_starts_max_players", 2))
|
||||
if enabled and num_players <= max_players:
|
||||
var balanced: Array[Vector2i] = StartBalancerScript.ensure_fair_starts(
|
||||
game_map, num_players, type_data, _rng
|
||||
)
|
||||
if balanced.size() == num_players:
|
||||
return balanced
|
||||
return _start_position.select_start_positions(
|
||||
game_map, num_players, type_data, start_strategy
|
||||
)
|
||||
|
||||
|
||||
# -- Natural wonders --
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -127,18 +127,20 @@ static func ensure_fair_starts(
|
|||
if fallback.is_empty():
|
||||
return []
|
||||
|
||||
var final_ratio: float = 0.0
|
||||
for _pass: int in 3:
|
||||
var score_a: float = score_start_zone(game_map, fallback[0]).total_value
|
||||
var score_b: float = score_start_zone(game_map, fallback[1]).total_value
|
||||
var max_score: float = maxf(score_a, score_b)
|
||||
var ratio: float = minf(score_a, score_b) / max_score if max_score > 0.0 else 0.0
|
||||
if ratio >= MIN_FAIRNESS_RATIO:
|
||||
final_ratio = minf(score_a, score_b) / max_score if max_score > 0.0 else 0.0
|
||||
if final_ratio >= MIN_FAIRNESS_RATIO:
|
||||
break
|
||||
var weak: Vector2i = fallback[0] if score_a < score_b else fallback[1]
|
||||
if not _compensate_resources(game_map, weak, rng):
|
||||
_compensate_terrain(game_map, weak, rng)
|
||||
|
||||
push_warning("StartBalancer: returning best available pair — fairness threshold not fully met")
|
||||
if final_ratio < MIN_FAIRNESS_RATIO:
|
||||
push_warning("StartBalancer: returning best available pair — fairness threshold not fully met")
|
||||
return fallback
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue