better management

This commit is contained in:
2026-01-23 15:31:38 -07:00
parent 0dfdb6a6df
commit 997cbdb48e

View File

@@ -9,7 +9,6 @@ pub fn on_frame(game: &Game, player: &Player, state: &mut GameState) {
cleanup_stale_commands(player, state); cleanup_stale_commands(player, state);
check_and_advance_stage(player, state); check_and_advance_stage(player, state);
state.stage_item_status = get_status_for_stage_items(game, player, state); state.stage_item_status = get_status_for_stage_items(game, player, state);
try_start_next_build(game, player, state); try_start_next_build(game, player, state);
} }
@@ -21,7 +20,6 @@ pub fn on_building_create(unit: &Unit, state: &mut GameState) {
.find(|e| e.unit_type == Some(unit.get_type())) .find(|e| e.unit_type == Some(unit.get_type()))
{ {
if let Some(probe_id) = entry.assigned_unit_id { if let Some(probe_id) = entry.assigned_unit_id {
// Remove the probe's intended command (PlaceBuilding order)
state.intended_commands.remove(&probe_id); state.intended_commands.remove(&probe_id);
println!( println!(
"Building {} started. Removed assignment for probe {}", "Building {} started. Removed assignment for probe {}",
@@ -34,41 +32,33 @@ pub fn on_building_create(unit: &Unit, state: &mut GameState) {
fn cleanup_stale_commands(player: &Player, state: &mut GameState) { fn cleanup_stale_commands(player: &Player, state: &mut GameState) {
let unit_ids: Vec<usize> = player.get_units().iter().map(|u| u.get_id()).collect(); let unit_ids: Vec<usize> = player.get_units().iter().map(|u| u.get_id()).collect();
state.intended_commands.retain(|unit_id, cmd| { state.intended_commands.retain(|unit_id, cmd| {
// Remove if unit no longer exists
if !unit_ids.contains(unit_id) { if !unit_ids.contains(unit_id) {
return false; return false;
} }
// Find the unit
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) {
// For PlaceBuilding orders, check if unit is actually constructing or idle
if cmd.order == Order::PlaceBuilding { if cmd.order == Order::PlaceBuilding {
// Keep command only if unit is moving to build location or constructing
return unit.is_constructing() || unit.get_order() == Order::PlaceBuilding; return unit.is_constructing() || unit.get_order() == Order::PlaceBuilding;
} }
// For Train orders, check if the building is training
if cmd.order == Order::Train { if cmd.order == Order::Train {
return unit.is_training(); return unit.is_training();
} }
} }
false false
}); });
} }
fn try_start_next_build(game: &Game, player: &Player, state: &mut GameState) { fn try_start_next_build(game: &Game, player: &Player, state: &mut GameState) {
if !should_start_next_build(game, player, state) {
return;
}
let Some(unit_type) = get_next_thing_to_build(game, player, state) else { let Some(unit_type) = get_next_thing_to_build(game, player, state) else {
return; return;
}; };
let Some(builder) = find_builder_for_unit(player, unit_type, state) else { let Some(builder) = find_builder_for_unit(player, unit_type, state) else {
return; return;
}; };
let builder_id = builder.get_id(); let builder_id = builder.get_id();
if let Some((success, tile_pos)) = if let Some((success, tile_pos)) =
assign_builder_to_construct(game, player, &builder, unit_type, state) assign_builder_to_construct(game, player, &builder, unit_type, state)
{ {
@@ -79,9 +69,7 @@ fn try_start_next_build(game: &Game, player: &Player, state: &mut GameState) {
assigned_unit_id: Some(builder_id), assigned_unit_id: Some(builder_id),
tile_position: tile_pos, tile_position: tile_pos,
}; };
state.unit_build_history.push(entry); state.unit_build_history.push(entry);
let current_stage = &state.build_stages[state.current_stage_index]; let current_stage = &state.build_stages[state.current_stage_index];
println!( println!(
"Started building {} with unit {} (Stage: {})", "Started building {} with unit {} (Stage: {})",
@@ -93,21 +81,43 @@ 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 {
!has_pending_assignment(state)
}
fn has_pending_assignment(state: &GameState) -> bool {
state.unit_build_history.iter().any(|entry| {
if let Some(unit_id) = entry.assigned_unit_id {
state.intended_commands.contains_key(&unit_id)
} else {
false
}
})
}
fn has_ongoing_constructions(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,
state: &GameState, state: &GameState,
) -> std::collections::HashMap<String, String> { ) -> std::collections::HashMap<String, String> {
let mut status_map = std::collections::HashMap::new(); let mut status_map = std::collections::HashMap::new();
let Some(current_stage) = state.build_stages.get(state.current_stage_index) else { let Some(current_stage) = state.build_stages.get(state.current_stage_index) else {
return status_map; return status_map;
}; };
for (unit_type, &desired_count) in &current_stage.desired_counts { for (unit_type, &desired_count) in &current_stage.desired_counts {
let unit_name = unit_type.name().to_string(); let unit_name = unit_type.name().to_string();
let current_count = count_units_of_type(player, state, *unit_type); let current_count = count_units_of_type(player, state, *unit_type);
if current_count >= desired_count { if current_count >= desired_count {
status_map.insert( status_map.insert(
unit_name, unit_name,
@@ -115,7 +125,6 @@ fn get_status_for_stage_items(
); );
continue; continue;
} }
if !can_afford_unit(player, *unit_type) { if !can_afford_unit(player, *unit_type) {
let minerals_short = unit_type.mineral_price() - player.minerals(); let minerals_short = unit_type.mineral_price() - player.minerals();
let gas_short = unit_type.gas_price() - player.gas(); let gas_short = unit_type.gas_price() - player.gas();
@@ -131,7 +140,6 @@ fn get_status_for_stage_items(
); );
continue; continue;
} }
if unit_type.is_building() { if unit_type.is_building() {
if find_builder_for_unit(player, *unit_type, state).is_none() { if find_builder_for_unit(player, *unit_type, state).is_none() {
status_map.insert( status_map.insert(
@@ -141,39 +149,31 @@ fn get_status_for_stage_items(
continue; continue;
} }
} }
status_map.insert( status_map.insert(
unit_name, unit_name,
format!("Ready to build ({}/{})", current_count, desired_count), format!("Ready to build ({}/{})", current_count, desired_count),
); );
} }
status_map status_map
} }
fn get_next_thing_to_build(game: &Game, player: &Player, state: &GameState) -> Option<UnitType> { fn get_next_thing_to_build(game: &Game, player: &Player, state: &GameState) -> Option<UnitType> {
let current_stage = state.build_stages.get(state.current_stage_index)?; let current_stage = state.build_stages.get(state.current_stage_index)?;
if let Some(pylon) = check_need_more_supply(game, player, state) { if let Some(pylon) = check_need_more_supply(game, player, state) {
return Some(pylon); return Some(pylon);
} }
let status_map = get_status_for_stage_items(game, player, state); let status_map = get_status_for_stage_items(game, player, state);
let mut candidates = Vec::new(); let mut candidates = Vec::new();
for (unit_type, &desired_count) in &current_stage.desired_counts { for (unit_type, &desired_count) in &current_stage.desired_counts {
let current_count = count_units_of_type(player, state, *unit_type); let current_count = count_units_of_type(player, state, *unit_type);
if current_count >= desired_count { if current_count >= desired_count {
continue; continue;
} }
let status = status_map.get(&unit_type.name().to_string()); let status = status_map.get(&unit_type.name().to_string());
if status.is_some() && status.unwrap().starts_with("Ready to build") { if status.is_some() && status.unwrap().starts_with("Ready to build") {
candidates.push(*unit_type); candidates.push(*unit_type);
} }
} }
candidates candidates
.into_iter() .into_iter()
.max_by_key(|unit_type| unit_type.mineral_price() + unit_type.gas_price()) .max_by_key(|unit_type| unit_type.mineral_price() + unit_type.gas_price())
@@ -182,22 +182,17 @@ fn get_next_thing_to_build(game: &Game, player: &Player, state: &GameState) -> O
fn check_need_more_supply(_game: &Game, player: &Player, _state: &GameState) -> Option<UnitType> { fn check_need_more_supply(_game: &Game, player: &Player, _state: &GameState) -> Option<UnitType> {
let supply_used = player.supply_used(); let supply_used = player.supply_used();
let supply_total = player.supply_total(); let supply_total = player.supply_total();
if supply_total == 0 { if supply_total == 0 {
return None; return None;
} }
let supply_remaining = supply_total - supply_used; let supply_remaining = supply_total - supply_used;
let threshold = ((supply_total as f32) * 0.15).ceil() as i32; let threshold = ((supply_total as f32) * 0.15).ceil() as i32;
if supply_remaining <= threshold && supply_total < 400 { if supply_remaining <= threshold && supply_total < 400 {
let pylon_type = UnitType::Protoss_Pylon; let pylon_type = UnitType::Protoss_Pylon;
if can_afford_unit(player, pylon_type) { if can_afford_unit(player, pylon_type) {
return Some(pylon_type); return Some(pylon_type);
} }
} }
None None
} }
@@ -207,7 +202,6 @@ fn find_builder_for_unit(
state: &GameState, state: &GameState,
) -> Option<rsbwapi::Unit> { ) -> Option<rsbwapi::Unit> {
let builder_type = unit_type.what_builds().0; let builder_type = unit_type.what_builds().0;
player player
.get_units() .get_units()
.iter() .iter()
@@ -229,11 +223,9 @@ fn assign_builder_to_construct(
state: &mut GameState, state: &mut GameState,
) -> Option<(bool, Option<rsbwapi::TilePosition>)> { ) -> Option<(bool, Option<rsbwapi::TilePosition>)> {
let builder_id = builder.get_id(); let builder_id = builder.get_id();
if unit_type.is_building() { if unit_type.is_building() {
let build_location = let build_location =
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 {} (currently at {:?})",
@@ -242,7 +234,6 @@ fn assign_builder_to_construct(
builder_id, builder_id,
builder.get_position() builder.get_position()
); );
match builder.build(unit_type, pos) { match builder.build(unit_type, pos) {
Ok(_) => { Ok(_) => {
println!("Build command succeeded for {}", unit_type.name()); println!("Build command succeeded for {}", unit_type.name());
@@ -287,19 +278,16 @@ fn assign_builder_to_construct(
} }
fn count_units_of_type(player: &Player, _state: &GameState, unit_type: UnitType) -> i32 { fn count_units_of_type(player: &Player, _state: &GameState, unit_type: UnitType) -> i32 {
let existing = player player
.get_units() .get_units()
.iter() .iter()
.filter(|u| u.get_type() == unit_type) .filter(|u| u.get_type() == unit_type)
.count() as i32; .count() as i32
existing
} }
fn can_afford_unit(player: &Player, unit_type: UnitType) -> bool { fn can_afford_unit(player: &Player, unit_type: UnitType) -> bool {
let minerals = player.minerals(); let minerals = player.minerals();
let gas = player.gas(); let gas = player.gas();
minerals >= unit_type.mineral_price() && gas >= unit_type.gas_price() minerals >= unit_type.mineral_price() && gas >= unit_type.gas_price()
} }
@@ -307,7 +295,6 @@ fn check_and_advance_stage(player: &Player, state: &mut GameState) {
let Some(current_stage) = state.build_stages.get(state.current_stage_index) else { let Some(current_stage) = state.build_stages.get(state.current_stage_index) else {
return; return;
}; };
let stage_complete = current_stage let stage_complete = current_stage
.desired_counts .desired_counts
.iter() .iter()
@@ -315,10 +302,8 @@ fn check_and_advance_stage(player: &Player, state: &mut GameState) {
let current_count = count_units_of_type(player, state, *unit_type); let current_count = count_units_of_type(player, state, *unit_type);
current_count >= desired_count current_count >= desired_count
}); });
if stage_complete { if stage_complete {
let next_stage_index = state.current_stage_index + 1; let next_stage_index = state.current_stage_index + 1;
if next_stage_index < state.build_stages.len() { if next_stage_index < state.build_stages.len() {
println!( println!(
"Stage '{}' complete! Advancing to stage {}", "Stage '{}' complete! Advancing to stage {}",
@@ -332,7 +317,6 @@ fn check_and_advance_stage(player: &Player, state: &mut GameState) {
pub fn print_debug_build_status(game: &Game, player: &Player, state: &GameState) { pub fn print_debug_build_status(game: &Game, player: &Player, state: &GameState) {
let mut y = 10; let mut y = 10;
let x = 3; let x = 3;
if let Some(current_stage) = state.build_stages.get(state.current_stage_index) { if let Some(current_stage) = state.build_stages.get(state.current_stage_index) {
let next_build = get_next_thing_to_build(game, player, state); let next_build = get_next_thing_to_build(game, player, state);
let next_build_str = if let Some(unit_type) = next_build { let next_build_str = if let Some(unit_type) = next_build {
@@ -349,7 +333,6 @@ pub fn print_debug_build_status(game: &Game, player: &Player, state: &GameState)
}; };
game.draw_text_screen((x, y), &next_build_str); game.draw_text_screen((x, y), &next_build_str);
y += 10; y += 10;
if let Some(last_entry) = state.unit_build_history.last() { if let Some(last_entry) = state.unit_build_history.last() {
let unit_name = if let Some(unit_type) = last_entry.unit_type { let unit_name = if let Some(unit_type) = last_entry.unit_type {
unit_type.name() unit_type.name()
@@ -363,10 +346,8 @@ pub fn print_debug_build_status(game: &Game, player: &Player, state: &GameState)
game.draw_text_screen((x, y), "Last Built: None"); game.draw_text_screen((x, y), "Last Built: None");
} }
y += 10; y += 10;
game.draw_text_screen((x, y), "Stage Progress:"); game.draw_text_screen((x, y), "Stage Progress:");
y += 10; y += 10;
for (unit_type, &desired_count) in &current_stage.desired_counts { for (unit_type, &desired_count) in &current_stage.desired_counts {
let current_count = count_units_of_type(player, state, *unit_type); let current_count = count_units_of_type(player, state, *unit_type);
game.draw_text_screen( game.draw_text_screen(
@@ -376,8 +357,6 @@ pub fn print_debug_build_status(game: &Game, player: &Player, state: &GameState)
y += 10; y += 10;
} }
} }
// Draw boxes for pending building assignments
let has_pending = state.unit_build_history.iter().any(|entry| { let has_pending = 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 {
state.intended_commands.contains_key(&unit_id) state.intended_commands.contains_key(&unit_id)
@@ -385,22 +364,15 @@ pub fn print_debug_build_status(game: &Game, player: &Player, state: &GameState)
false false
} }
}); });
for entry in &state.unit_build_history { for entry in &state.unit_build_history {
if let (Some(unit_type), Some(tile_pos), Some(unit_id)) = if let (Some(unit_type), Some(tile_pos), Some(unit_id)) =
(entry.unit_type, entry.tile_position, entry.assigned_unit_id) (entry.unit_type, entry.tile_position, entry.assigned_unit_id)
{ {
// Check if the assignment is still active (unit hasn't started building yet)
if state.intended_commands.contains_key(&unit_id) { if state.intended_commands.contains_key(&unit_id) {
// Convert tile position to pixel position
let pixel_x = tile_pos.x * 32; let pixel_x = tile_pos.x * 32;
let pixel_y = tile_pos.y * 32; let pixel_y = tile_pos.y * 32;
// Get building dimensions in pixels
let width = unit_type.tile_width() * 32; let width = unit_type.tile_width() * 32;
let height = unit_type.tile_height() * 32; let height = unit_type.tile_height() * 32;
// Draw the building outline box
use rsbwapi::{Color, Position}; use rsbwapi::{Color, Position};
let top_left = Position { let top_left = Position {
x: pixel_x, x: pixel_x,
@@ -418,15 +390,11 @@ pub fn print_debug_build_status(game: &Game, player: &Player, state: &GameState)
x: pixel_x + width, x: pixel_x + width,
y: pixel_y + height, y: pixel_y + height,
}; };
// Draw yellow box for pending buildings
let color = Color::Yellow; let color = Color::Yellow;
game.draw_line_map(top_left, top_right, color); game.draw_line_map(top_left, top_right, color);
game.draw_line_map(top_right, bottom_right, color); game.draw_line_map(top_right, bottom_right, color);
game.draw_line_map(bottom_right, bottom_left, color); game.draw_line_map(bottom_right, bottom_left, color);
game.draw_line_map(bottom_left, top_left, color); game.draw_line_map(bottom_left, top_left, color);
// Draw building name at the center
let center = Position { let center = Position {
x: pixel_x + width / 2, x: pixel_x + width / 2,
y: pixel_y + height / 2, y: pixel_y + height / 2,
@@ -435,12 +403,8 @@ pub fn print_debug_build_status(game: &Game, player: &Player, state: &GameState)
} }
} }
} }
// Draw buildable locations around nexus if there are pending assignments
if has_pending { if has_pending {
use rsbwapi::{Color, Position, TilePosition}; use rsbwapi::{Color, Position, TilePosition};
// Find the nexus
if let Some(nexus) = player if let Some(nexus) = player
.get_units() .get_units()
.iter() .iter()
@@ -448,50 +412,33 @@ pub fn print_debug_build_status(game: &Game, player: &Player, state: &GameState)
{ {
let nexus_tile = nexus.get_tile_position(); let nexus_tile = nexus.get_tile_position();
let radius = 30; let radius = 30;
// Iterate through tiles in radius around nexus
for dx in -radius..=radius { for dx in -radius..=radius {
for dy in -radius..=radius { for dy in -radius..=radius {
let tile_x = nexus_tile.x + dx; let tile_x = nexus_tile.x + dx;
let tile_y = nexus_tile.y + dy; let tile_y = nexus_tile.y + dy;
// Check bounds
if tile_x < 0 || tile_y < 0 { if tile_x < 0 || tile_y < 0 {
continue; continue;
} }
let tile_pos = TilePosition { let tile_pos = TilePosition {
x: tile_x, x: tile_x,
y: tile_y, y: tile_y,
}; };
// Check if within circular radius
let dist_sq = (dx * dx + dy * dy) as f32; let dist_sq = (dx * dx + dy * dy) as f32;
if dist_sq > (radius * radius) as f32 { if dist_sq > (radius * radius) as f32 {
continue; continue;
} }
// Check buildability
let is_buildable = game.is_buildable(tile_pos); let is_buildable = game.is_buildable(tile_pos);
if !is_buildable { if !is_buildable {
continue; continue;
} }
// Check if powered (check if a 1x1 tile has power)
let has_power = game.has_power(tile_pos, (1, 1)); let has_power = game.has_power(tile_pos, (1, 1));
// Draw small box at tile position
let pixel_x = tile_x * 32; let pixel_x = tile_x * 32;
let pixel_y = tile_y * 32; let pixel_y = tile_y * 32;
let center = Position { let center = Position {
x: pixel_x + 16, x: pixel_x + 16,
y: pixel_y + 16, y: pixel_y + 16,
}; };
// Green for buildable+powered, Blue for just buildable
let color = if has_power { Color::Green } else { Color::Blue }; let color = if has_power { Color::Green } else { Color::Blue };
// Draw small filled circle/box to indicate buildable location
game.draw_circle_map(center, 3, color, true); game.draw_circle_map(center, 3, color, true);
} }
} }