test(scenes): Add assertions for gold_per_turn and total_pop metrics in auto_play.gd

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
autocommit 2026-04-13 14:17:03 -07:00
parent 1bd0e5249b
commit b00424ebe6

View file

@ -6,6 +6,7 @@ extends Node
const HexUtilsScript = preload("res://engine/src/map/hex_utils.gd")
const PathfinderScript = preload("res://engine/src/map/pathfinder.gd")
const BuildableHelperScript = preload("res://engine/scenes/city/city_buildable_helper.gd")
var _active: bool = false
var _frame: int = 0
@ -194,27 +195,37 @@ func _play_turn() -> void:
if _turn_count <= 5 or _turn_count % 10 == 0:
var happiness: int = player.get("happiness") if player.get("happiness") != null else -99
var gold: int = player.get("gold") if player.get("gold") != null else 0
var gpt: int = player.get("gold_per_turn") if player.get("gold_per_turn") != null else 0
var techs: int = player.researched_techs.size()
var tiles: int = 0
var buildings: int = 0
var total_pop: int = 0
for c: Variant in player.cities:
tiles += c.owned_tiles.size()
buildings += c.buildings.size()
var golden: bool = player.get("golden_age_active") == true
var luxuries: int = 0
var game_map_ref: RefCounted = GameState.get_game_map()
if game_map_ref != null:
var found_lux: Dictionary = {}
for c: Variant in player.cities:
for tp: Vector2i in c.owned_tiles:
var tl: Resource = game_map_ref.get_tile(tp)
if tl != null and tl.resource_id != "" and tl.resource_id not in found_lux:
found_lux[tl.resource_id] = true
luxuries = found_lux.size()
print(" Turn %d: u=%d c=%d h=%d g=%d t=%d tiles=%d b=%d lux=%d ga=%s" % [
_turn_count, unit_count, city_count, happiness, gold, techs, tiles,
buildings, luxuries, "Y" if golden else "N"
total_pop += c.population
var military_count: int = 0
for u: Variant in player.units:
if u.is_alive() and u.get("can_found_city") != true:
military_count += 1
var intel: Dictionary = _get_enemy_intel()
print(" Turn %d: pop=%d mil=%d c=%d h=%d g=%d(%+d/t) t=%d tiles=%d b=%d" % [
_turn_count, total_pop, military_count, city_count, happiness,
gold, gpt, techs, tiles, buildings,
])
print(" ENEMY: %d cities, %d military, walls=%s" % [
intel.get("cities", 0), intel.get("military", 0),
"Y" if intel.get("has_walls", false) else "N",
])
# Per-city detail
for c: Variant in player.cities:
var tile_json: String = BuildableHelperScript.build_tile_yields_json(c, game_map)
var cy: Dictionary = c.get_yields(tile_json)
var food_surplus: float = float(cy.get("food", 0)) - float(c.population) * 2.0
print(" [%s] pop=%d food=%+.1f prod=%.0f tiles=%d bld=%d" % [
c.city_name, c.population, food_surplus,
float(cy.get("production", 0)), c.owned_tiles.size(), c.buildings.size(),
])
# 0. Pick research if idle
if player.researching.is_empty():
@ -248,7 +259,7 @@ func _play_turn() -> void:
for c: Variant in player.cities:
_manage_production(c)
# 3. Strategy: build up army, then attack
# 3. Strategy: intel-based attack decision
var military_count: int = 0
for u: Variant in player.units:
if u.is_alive() and u.get("can_found_city") != true:
@ -258,7 +269,12 @@ func _play_turn() -> void:
var city_pos: Vector2i = player.cities[0].position if not player.cities.is_empty() else Vector2i.ZERO
if military_count >= 2:
var intel: Dictionary = _get_enemy_intel()
var enemy_mil: int = intel.get("military", 0)
var advantage: float = float(military_count) / maxf(1.0, float(enemy_mil))
# Attack when we have 1.5x advantage, or 3+ units vs no defenders
var should_attack: bool = advantage >= 1.5 or (military_count >= 3 and enemy_mil == 0)
if should_attack:
# ATTACK PHASE: lock onto one target and march until it's destroyed
if _locked_target == Vector2i(-1, -1):
_locked_target = _find_attack_target(player)
@ -310,22 +326,21 @@ func _play_turn() -> void:
u.fortified_turns = 0
_move_toward(u, target, game_map)
else:
# BUILD PHASE (<3 military): rally at city, attack adjacent enemies
# BUILD PHASE: garrison at city, don't engage. Only scouts explore.
for u: Variant in units_snapshot:
if not u.is_alive() or u.movement_remaining <= 0:
continue
if u.get("can_found_city") == true:
continue
if u.type_id == "dwarf_scout" and _turn_count <= 15:
if u.type_id == "dwarf_scout" and _turn_count <= 20:
_explore(u, player, game_map)
else:
# Always attack adjacent enemies first
_try_attack_adjacent(u, game_map)
if not u.is_alive() or u.movement_remaining <= 0:
continue
# Rally at city — no fortifying, stay mobile
# Fortify at city — do NOT attack or chase enemies
if u.position != city_pos:
_move_toward(u, city_pos, game_map)
elif not u.is_fortified:
u.is_fortified = true
u.fortified_turns = 1
func _pick_research(player: RefCounted) -> void:
@ -381,6 +396,29 @@ func _score_site(pos: Vector2i, game_map: RefCounted) -> float:
return score
func _get_enemy_intel() -> Dictionary:
## Scan all enemy players and return aggregate intel.
var player: RefCounted = GameState.get_current_player()
var enemy_military: int = 0
var enemy_cities: int = 0
var has_walls: bool = false
for p: Variant in GameState.players:
if p.index == player.index:
continue
enemy_cities += p.cities.size()
for c: Variant in p.cities:
if c.has_building("walls") or c.has_building("castle"):
has_walls = true
for u: Variant in p.units:
if u.is_alive() and u.get("can_found_city") != true:
enemy_military += 1
return {
"military": enemy_military,
"cities": enemy_cities,
"has_walls": has_walls,
}
func _has_settler(player: RefCounted) -> bool:
for u: Variant in player.units:
if u.get("can_found_city") == true and u.is_alive():