diff --git a/src/game/engine/scenes/tests/auto_play.gd b/src/game/engine/scenes/tests/auto_play.gd index d68e707e..62c312d3 100644 --- a/src/game/engine/scenes/tests/auto_play.gd +++ b/src/game/engine/scenes/tests/auto_play.gd @@ -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():