building but still bugs

This commit is contained in:
2026-01-23 17:03:01 -07:00
parent 9956c930cd
commit 810fc4a3d4
3 changed files with 79 additions and 151 deletions

View File

@@ -1,146 +1,46 @@
use rsbwapi::{Game, Player, TilePosition, Unit, UnitType}; 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<Self::Item> {
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( pub fn find_build_location(
game: &Game, game: &Game,
player: &Player, _player: &Player,
builder: &Unit, builder: &Unit,
building_type: UnitType, building_type: UnitType,
max_range: i32, max_range: i32,
) -> Option<TilePosition> { ) -> Option<TilePosition> {
// Find the base to start spiral search from if building_type.is_refinery() {
let start_tile = if let Some(nexus) = player for geyser in game.get_geysers() {
.get_units() let tp = geyser.get_tile_position();
.iter() if let Ok(true) = game.can_build_here(Some(builder), tp, building_type, false) {
.find(|u| u.get_type() == UnitType::Protoss_Nexus) return Some(tp);
{ }
nexus.get_tile_position() }
} else { return None;
builder.get_tile_position() }
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) {
let map_width = game.map_width(); return Some(cand);
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;
} }
if dy == 0 {
// Use BWAPI's can_build_here like Styx2 does (it handles most validation) continue;
if !game
.can_build_here(Some(builder), position, building_type, false)
.unwrap_or(false)
{
return false;
} }
let cand2 = TilePosition {
let center = position.to_position() x: start.x + dx,
+ rsbwapi::Position { y: start.y - dy * dy_sign,
x: (width * 32) / 2,
y: (height * 32) / 2,
}; };
if let Ok(true) = game.can_build_here(Some(builder), cand2, building_type, false) {
// Check no resource containers nearby (minerals/geysers) - 128 pixel radius like Styx2 return Some(cand2);
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 }
None
} }

View File

@@ -12,8 +12,17 @@ pub fn on_frame(game: &Game, player: &Player, state: &mut GameState) {
try_start_next_build(game, player, state); try_start_next_build(game, player, state);
} }
pub fn on_building_create(_unit: &Unit, _state: &mut GameState) { pub fn on_building_create(unit: &Unit, state: &mut GameState) {
// No intended command tracking needed. // 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 nonbuilding unit (e.g., a trained unit) is created. /// Called when a nonbuilding 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 { 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. // Do not start a new build if there is a pending build (assigned but not yet constructing/training).
!has_ongoing_constructions(state, player) 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 { 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| { state.unit_build_history.iter().any(|entry| {
if let Some(unit_id) = entry.assigned_unit_id { if let Some(unit_id) = entry.assigned_unit_id {
if let Some(unit) = player.get_units().iter().find(|u| u.get_id() == 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( fn get_status_for_stage_items(
_game: &Game, _game: &Game,
player: &Player, player: &Player,
@@ -185,11 +218,10 @@ fn assign_builder_to_construct(
build_location_utils::find_build_location(game, player, builder, unit_type, 42); build_location_utils::find_build_location(game, player, builder, unit_type, 42);
if let Some(pos) = build_location { if let Some(pos) = build_location {
println!( println!(
"Attempting to build {} at {:?} with worker {} (currently at {:?})", "Attempting to build {} at {:?} with worker {}",
unit_type.name(), unit_type.name(),
pos, pos,
builder_id, builder_id,
builder.get_position()
); );
match builder.build(unit_type, pos) { match builder.build(unit_type, pos) {
Ok(_) => { Ok(_) => {

View File

@@ -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 // Assign idle workers that are not already assigned in the build history
for worker in workers { for worker in workers {
// Skip if this worker is already recorded as assigned in any ongoing build history entry // Skip if this worker is already recorded as assigned in any ongoing build history entry
let already_assigned = state let already_assigned = state.unit_build_history.iter().any(|entry| {
.unit_build_history entry.assigned_unit_id == Some(worker.get_id())
.iter() && entry.unit_type.map(|ut| ut.is_building()).unwrap_or(false)
.any(|entry| entry.assigned_unit_id == Some(worker.get_id())); });
if already_assigned { if already_assigned {
continue; 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). // Assign worker to gather minerals (ignore intended command tracking).
if worker.gather(&mineral).is_ok() { if worker.gather(&mineral).is_ok() {
println!( println!("Assigned worker {} to mine from mineral", worker_id,);
"Assigned worker {} to mine from mineral at {:?}",
worker_id,
mineral.get_position()
);
} }
} }