test(scenes): ✅ Update test cases to verify mixed-yield bonuses and biome penalties in auto-play scoring
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
8c1456b24c
commit
ff591b1fec
1 changed files with 84 additions and 29 deletions
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue