From 810fc4a3d4a6d8c9ef2fa2d030747a245e67ed31 Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Fri, 23 Jan 2026 17:03:01 -0700 Subject: [PATCH] building but still bugs --- protossbot/src/utils/build_location_utils.rs | 172 ++++--------------- protossbot/src/utils/build_manager.rs | 44 ++++- protossbot/src/utils/worker_management.rs | 14 +- 3 files changed, 79 insertions(+), 151 deletions(-) diff --git a/protossbot/src/utils/build_location_utils.rs b/protossbot/src/utils/build_location_utils.rs index bdeb3ec..be37211 100644 --- a/protossbot/src/utils/build_location_utils.rs +++ b/protossbot/src/utils/build_location_utils.rs @@ -1,146 +1,46 @@ use rsbwapi::{Game, Player, TilePosition, Unit, UnitType}; -// Spiral iterator similar to Styx2's approach -struct Spiral { - center: TilePosition, - x: i32, - y: i32, - dx: i32, - dy: i32, - segment_length: i32, - segment_passed: i32, -} - -impl Spiral { - fn new(center: TilePosition) -> Self { - Self { - center, - x: 0, - y: 0, - dx: 0, - dy: -1, - segment_length: 1, - segment_passed: 0, - } - } -} - -impl Iterator for Spiral { - type Item = TilePosition; - - fn next(&mut self) -> Option { - let result = TilePosition { - x: self.center.x + self.x, - y: self.center.y + self.y, - }; - - // Move to next position - self.x += self.dx; - self.y += self.dy; - self.segment_passed += 1; - - if self.segment_passed == self.segment_length { - self.segment_passed = 0; - - // Turn 90 degrees clockwise - let temp = self.dx; - self.dx = -self.dy; - self.dy = temp; - - // Increase segment length every two turns - if self.dy == 0 { - self.segment_length += 1; - } - } - - Some(result) - } -} - pub fn find_build_location( game: &Game, - player: &Player, + _player: &Player, builder: &Unit, building_type: UnitType, max_range: i32, ) -> Option { - // Find the base to start spiral search from - let start_tile = if let Some(nexus) = player - .get_units() - .iter() - .find(|u| u.get_type() == UnitType::Protoss_Nexus) - { - nexus.get_tile_position() - } else { - builder.get_tile_position() - }; + if building_type.is_refinery() { + for geyser in game.get_geysers() { + let tp = geyser.get_tile_position(); + if let Ok(true) = game.can_build_here(Some(builder), tp, building_type, false) { + return Some(tp); + } + } + return None; + } - let map_width = game.map_width(); - let map_height = game.map_height(); - let max_tiles = (max_range * max_range) as usize; - - // Use spiral search like Styx2 - Spiral::new(start_tile) - .take(max_tiles.min(300)) // Limit to 300 tiles like Styx2 - .filter(|&tile| { - // Check bounds - tile.x >= 0 && tile.y >= 0 && tile.x < map_width && tile.y < map_height - }) - .find(|&tile| is_valid_build_location(game, player, building_type, tile, builder)) -} - -fn is_valid_build_location( - game: &Game, - player: &Player, - building_type: UnitType, - position: TilePosition, - builder: &Unit, -) -> bool { - // Get building dimensions - let width = building_type.tile_width(); - let height = building_type.tile_height(); - - let map_width = game.map_width(); - let map_height = game.map_height(); - - // Check if building would fit on the map - if position.x + width > map_width || position.y + height > map_height { - return false; - } - - // Use BWAPI's can_build_here like Styx2 does (it handles most validation) - if !game - .can_build_here(Some(builder), position, building_type, false) - .unwrap_or(false) - { - return false; - } - - let center = position.to_position() - + rsbwapi::Position { - x: (width * 32) / 2, - y: (height * 32) / 2, - }; - - // Check no resource containers nearby (minerals/geysers) - 128 pixel radius like Styx2 - let has_resources_nearby = game.get_all_units().iter().any(|u| { - (u.get_type().is_mineral_field() || u.get_type().is_refinery()) - && u.get_position().distance(center) < 128.0 - }); - - if has_resources_nearby { - return false; - } - - // Check no resource depots nearby - 128 pixel radius like Styx2 - let has_depot_nearby = player - .get_units() - .iter() - .any(|u| u.get_type().is_resource_depot() && u.get_position().distance(center) < 128.0); - - if has_depot_nearby { - return false; - } - - true + let start = builder.get_tile_position(); + for radius in 0..=max_range { + for dx in -radius..=radius { + let dy = radius - dx.abs(); + for &dy_sign in &[-1, 1] { + let cand = TilePosition { + x: start.x + dx, + y: start.y + dy * dy_sign, + }; + if let Ok(true) = game.can_build_here(Some(builder), cand, building_type, false) { + return Some(cand); + } + if dy == 0 { + continue; + } + let cand2 = TilePosition { + x: start.x + dx, + y: start.y - dy * dy_sign, + }; + if let Ok(true) = game.can_build_here(Some(builder), cand2, building_type, false) { + return Some(cand2); + } + } + } + } + None } diff --git a/protossbot/src/utils/build_manager.rs b/protossbot/src/utils/build_manager.rs index 71b981c..2e9c614 100644 --- a/protossbot/src/utils/build_manager.rs +++ b/protossbot/src/utils/build_manager.rs @@ -12,8 +12,17 @@ pub fn on_frame(game: &Game, player: &Player, state: &mut GameState) { try_start_next_build(game, player, state); } -pub fn on_building_create(_unit: &Unit, _state: &mut GameState) { - // No intended command tracking needed. +pub fn on_building_create(unit: &Unit, state: &mut GameState) { + // When a building is created, remove the corresponding build history entry so the next + // build can be started without waiting for the current one to finish. + if let Some(pos) = state.unit_build_history.iter().position(|entry| { + entry + .unit_type + .map(|ut| ut == unit.get_type()) + .unwrap_or(false) + }) { + state.unit_build_history.remove(pos); + } } /// Called when a non‑building unit (e.g., a trained unit) is created. @@ -49,11 +58,23 @@ fn try_start_next_build(game: &Game, player: &Player, state: &mut GameState) { } fn should_start_next_build(_game: &Game, player: &Player, state: &mut GameState) -> bool { - // Only start a new build if there are no ongoing constructions or training actions. - !has_ongoing_constructions(state, player) + // Do not start a new build if there is a pending build (assigned but not yet constructing/training). + if pending_build_exists(state, player) { + return false; + } + // Ensure there is a builder available for the next thing to build. + if let Some(unit_type) = get_next_thing_to_build(_game, player, state) { + find_builder_for_unit(player, unit_type, state).is_some() + } else { + false + } } fn has_ongoing_constructions(state: &GameState, player: &Player) -> bool { + // Consider a construction ongoing if there is a build history entry with an assigned unit that + // is currently constructing or training. This covers the period after a build command is issued + // but before the unit starts the actual constructing state, preventing multiple workers from + // being assigned to the same build command. state.unit_build_history.iter().any(|entry| { if let Some(unit_id) = entry.assigned_unit_id { if let Some(unit) = player.get_units().iter().find(|u| u.get_id() == unit_id) { @@ -64,6 +85,18 @@ fn has_ongoing_constructions(state: &GameState, player: &Player) -> bool { }) } +// Returns true if there is a build history entry with an assigned builder that has not yet started constructing or training. +fn pending_build_exists(state: &GameState, player: &Player) -> bool { + state.unit_build_history.iter().any(|entry| { + if let Some(unit_id) = entry.assigned_unit_id { + if let Some(unit) = player.get_units().iter().find(|u| u.get_id() == unit_id) { + return !(unit.is_constructing() || unit.is_training()); + } + } + false + }) +} + fn get_status_for_stage_items( _game: &Game, player: &Player, @@ -185,11 +218,10 @@ fn assign_builder_to_construct( build_location_utils::find_build_location(game, player, builder, unit_type, 42); if let Some(pos) = build_location { println!( - "Attempting to build {} at {:?} with worker {} (currently at {:?})", + "Attempting to build {} at {:?} with worker {}", unit_type.name(), pos, builder_id, - builder.get_position() ); match builder.build(unit_type, pos) { Ok(_) => { diff --git a/protossbot/src/utils/worker_management.rs b/protossbot/src/utils/worker_management.rs index ee4d431..6a91d23 100644 --- a/protossbot/src/utils/worker_management.rs +++ b/protossbot/src/utils/worker_management.rs @@ -16,10 +16,10 @@ pub fn assign_idle_workers_to_minerals(game: &Game, player: &Player, state: &mut // Assign idle workers that are not already assigned in the build history for worker in workers { // Skip if this worker is already recorded as assigned in any ongoing build history entry - let already_assigned = state - .unit_build_history - .iter() - .any(|entry| entry.assigned_unit_id == Some(worker.get_id())); + let already_assigned = state.unit_build_history.iter().any(|entry| { + entry.assigned_unit_id == Some(worker.get_id()) + && entry.unit_type.map(|ut| ut.is_building()).unwrap_or(false) + }); if already_assigned { continue; } @@ -51,11 +51,7 @@ fn assign_worker_to_mineral(game: &Game, worker: &Unit, state: &mut GameState) { // Assign worker to gather minerals (ignore intended command tracking). if worker.gather(&mineral).is_ok() { - println!( - "Assigned worker {} to mine from mineral at {:?}", - worker_id, - mineral.get_position() - ); + println!("Assigned worker {} to mine from mineral", worker_id,); } }