diff --git a/src/game/engine/scenes/tests/auto_play.gd b/src/game/engine/scenes/tests/auto_play.gd index c5433e2c..026c103f 100644 --- a/src/game/engine/scenes/tests/auto_play.gd +++ b/src/game/engine/scenes/tests/auto_play.gd @@ -682,7 +682,7 @@ func _pick_research(player: RefCounted) -> void: func _score_site(pos: Vector2i, game_map: RefCounted) -> float: - ## Score a hex as a city site. Food*2 + production*1.5 + resources. + ## Score a hex as a city site. Food*2 + production*1.5 + resources + mixed-yield bonus. var tile: Resource = game_map.get_tile(pos) if tile == null: return 0.0 @@ -691,7 +691,14 @@ func _score_site(pos: Vector2i, game_map: RefCounted) -> float: return 0.0 var score: float = 0.0 var y: Dictionary = tile.get_yields(-1) - score += float(y.get("food", 0)) * 2.0 + float(y.get("production", 0)) * 1.5 + var center_food: int = int(y.get("food", 0)) + var center_prod: int = int(y.get("production", 0)) + score += float(center_food) * 2.0 + float(center_prod) * 1.5 + # Heavy penalty for food-starved center biomes (mountain/tundra/desert). + if tile.biome_id in ["mountains", "tundra", "desert"]: + score -= 5.0 + var neighbor_food: int = 0 + var neighbor_prod: int = 0 for n: Vector2i in HexUtilsScript.get_neighbors(pos): var norm: Vector2i = HexUtilsScript.normalize_position(n, game_map.width, game_map.height, game_map.wrap_mode) var nt: Resource = game_map.get_tile(norm) @@ -700,10 +707,19 @@ func _score_site(pos: Vector2i, game_map: RefCounted) -> float: if nt.biome_id in water: score += 0.5 continue - var ny: Dictionary = nt.get_yields(-1) - score += float(ny.get("food", 0)) * 0.5 + float(ny.get("production", 0)) * 0.3 - if nt.resource_id != "": + if nt.biome_id == "river": score += 2.0 + var ny: Dictionary = nt.get_yields(-1) + var nf: int = int(ny.get("food", 0)) + var np: int = int(ny.get("production", 0)) + neighbor_food += nf + neighbor_prod += np + score += float(nf) * 0.5 + float(np) * 0.3 + if nt.resource_id != "": + score += 3.0 + # Mixed-yield reward: at least one food + one prod in neighbors. + if neighbor_food >= 1 and neighbor_prod >= 1: + score += 2.0 return score @@ -737,15 +753,12 @@ func _has_settler(player: RefCounted) -> bool: return false -func _try_found_city(player: RefCounted, _game_map: RefCounted) -> void: +func _try_found_city(player: RefCounted, game_map: RefCounted) -> void: for u: Variant in player.units: if u.get("can_found_city") == true and u.is_alive(): - if _world_map != null and _world_map.has_method("_select_unit"): - _world_map._select_unit(u) - if _world_map != null and _world_map.has_method("_on_found_city_pressed"): - _world_map._on_found_city_pressed() + _decide_settler(u, player, game_map) + if not player.cities.is_empty(): _founded_city = true - print(" Founded city at %s" % u.position) return @@ -909,24 +922,7 @@ func _command_unit(unit: Variant, player: RefCounted, game_map: RefCounted) -> v _command_worker(unit, player, game_map) return if unit.get("can_found_city") == true: - # Check distance from existing cities - var too_close: bool = false - for c: Variant in player.cities: - if HexUtilsScript.hex_distance(unit.position, c.position) < 5: - too_close = true - break - # Score current tile quality - # Check tile is land (not water) - var tile: Resource = game_map.get_tile(unit.position) - var is_water: bool = tile != null and tile.biome_id in ["ocean", "coast", "deep_ocean", "lake"] - if too_close or is_water: - _explore(unit, player, game_map) - else: - if _world_map != null and _world_map.has_method("_select_unit"): - _world_map._select_unit(unit) - if _world_map != null and _world_map.has_method("_on_found_city_pressed"): - _world_map._on_found_city_pressed() - print(" Founded city at %s" % unit.position) + _decide_settler(unit, player, game_map) return var target_pos: Vector2i = _find_attack_target(player) @@ -936,6 +932,65 @@ func _command_unit(unit: Variant, player: RefCounted, game_map: RefCounted) -> v _explore(unit, player, game_map) +func _decide_settler(unit: Variant, player: RefCounted, game_map: RefCounted) -> void: + ## State-based founding: score reachable sites, move to the best; only found at a local max. + const MIN_FOUND_SCORE: float = 4.0 + const MAX_WAIT_TURNS: int = 40 + const LOOK_AHEAD_MULT: int = 3 + var water: Array = ["ocean", "coast", "deep_ocean", "lake"] + var reach: int = max(1, int(unit.max_movement) * LOOK_AHEAD_MULT) + var reachable: Dictionary = PathfinderScript.movement_range(game_map, unit.position, reach, "land") + var best_pos: Vector2i = unit.position + var best_score: float = _score_site(unit.position, game_map) + var current_score: float = best_score + for pos: Vector2i in reachable: + var tile: Resource = game_map.get_tile(pos) + if tile == null or tile.biome_id in water: + continue + var too_close: bool = false + for c: Variant in player.cities: + if HexUtilsScript.hex_distance(pos, c.position) < 5: + too_close = true + break + if too_close: + continue + var s: float = _score_site(pos, game_map) + if s > best_score: + best_score = s + best_pos = pos + print("[SITE] turn=%d current=%.1f best=%.1f chosen=%s" % [ + _turn_count, current_score, best_score, str(best_pos) + ]) + var current_valid: bool = true + var cur_tile: Resource = game_map.get_tile(unit.position) + if cur_tile == null or cur_tile.biome_id in water: + current_valid = false + for c: Variant in player.cities: + if HexUtilsScript.hex_distance(unit.position, c.position) < 5: + current_valid = false + break + if best_pos == unit.position and current_valid: + if _world_map != null and _world_map.has_method("_select_unit"): + _world_map._select_unit(unit) + if _world_map != null and _world_map.has_method("_on_found_city_pressed"): + _world_map._on_found_city_pressed() + print(" Founded city at %s (score %.1f)" % [unit.position, current_score]) + return + var waiting: bool = current_score < MIN_FOUND_SCORE and _turn_count < MAX_WAIT_TURNS + var should_wait: bool = (not current_valid) or waiting + if best_pos != unit.position and should_wait: + _move_toward(unit, best_pos, game_map) + return + if current_valid: + if _world_map != null and _world_map.has_method("_select_unit"): + _world_map._select_unit(unit) + if _world_map != null and _world_map.has_method("_on_found_city_pressed"): + _world_map._on_found_city_pressed() + print(" Founded city at %s (good-enough, score %.1f)" % [unit.position, current_score]) + else: + _explore(unit, player, game_map) + + func _command_worker(unit: Variant, player: RefCounted, game_map: RefCounted) -> void: ## Build an improvement on the current tile if possible; else walk toward a city. if _improvement_manager == null: