building but still bugs
This commit is contained in:
@@ -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<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(
|
||||
game: &Game,
|
||||
player: &Player,
|
||||
_player: &Player,
|
||||
builder: &Unit,
|
||||
building_type: UnitType,
|
||||
max_range: i32,
|
||||
) -> Option<TilePosition> {
|
||||
// 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 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,
|
||||
};
|
||||
|
||||
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))
|
||||
if let Ok(true) = game.can_build_here(Some(builder), cand, building_type, false) {
|
||||
return Some(cand);
|
||||
}
|
||||
|
||||
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 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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,
|
||||
let cand2 = TilePosition {
|
||||
x: start.x + dx,
|
||||
y: start.y - dy * dy_sign,
|
||||
};
|
||||
|
||||
// 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;
|
||||
if let Ok(true) = game.can_build_here(Some(builder), cand2, building_type, false) {
|
||||
return Some(cand2);
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -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(_) => {
|
||||
|
||||
@@ -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,);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user