terran pivot
This commit is contained in:
@@ -24,23 +24,20 @@ impl BuildStage {
|
||||
pub fn get_build_stages() -> Vec<BuildStage> {
|
||||
vec![
|
||||
BuildStage::new("Start")
|
||||
.with_unit(UnitType::Protoss_Probe, 10)
|
||||
.with_unit(UnitType::Protoss_Pylon, 1),
|
||||
|
||||
.with_unit(UnitType::Terran_SCV, 10)
|
||||
.with_unit(UnitType::Terran_Supply_Depot, 1),
|
||||
BuildStage::new("Basic Production")
|
||||
.with_unit(UnitType::Protoss_Probe, 12)
|
||||
.with_unit(UnitType::Protoss_Pylon, 2)
|
||||
.with_unit(UnitType::Protoss_Gateway, 1)
|
||||
.with_unit(UnitType::Protoss_Forge, 1),
|
||||
|
||||
|
||||
// Stage 2: Defense cannons
|
||||
BuildStage::new("Defense Cannons")
|
||||
.with_unit(UnitType::Protoss_Probe, 16)
|
||||
.with_unit(UnitType::Protoss_Pylon, 3)
|
||||
.with_unit(UnitType::Protoss_Nexus, 1)
|
||||
.with_unit(UnitType::Protoss_Gateway, 1)
|
||||
.with_unit(UnitType::Protoss_Forge, 1)
|
||||
.with_unit(UnitType::Protoss_Photon_Cannon, 4),
|
||||
.with_unit(UnitType::Terran_SCV, 12)
|
||||
.with_unit(UnitType::Terran_Supply_Depot, 2)
|
||||
.with_unit(UnitType::Terran_Barracks, 1)
|
||||
.with_unit(UnitType::Terran_Refinery, 1),
|
||||
// Stage 2: Defense bunker
|
||||
BuildStage::new("Defense Bunker")
|
||||
.with_unit(UnitType::Terran_SCV, 16)
|
||||
.with_unit(UnitType::Terran_Supply_Depot, 3)
|
||||
.with_unit(UnitType::Terran_Command_Center, 1)
|
||||
.with_unit(UnitType::Terran_Barracks, 1)
|
||||
.with_unit(UnitType::Terran_Refinery, 1)
|
||||
.with_unit(UnitType::Terran_Bunker, 2),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -43,5 +43,6 @@ pub struct BuildHistoryEntry {
|
||||
pub unit_type: Option<UnitType>,
|
||||
pub upgrade_type: Option<UpgradeType>,
|
||||
pub assigned_unit_id: Option<usize>,
|
||||
pub tile_position: Option<rsbwapi::TilePosition>,
|
||||
// pub status: BuildStatus,
|
||||
}
|
||||
|
||||
@@ -1,48 +1,146 @@
|
||||
use rsbwapi::{Game, 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(
|
||||
game: &Game,
|
||||
player: &Player,
|
||||
builder: &Unit,
|
||||
building_type: UnitType,
|
||||
max_range: i32,
|
||||
) -> Option<TilePosition> {
|
||||
let start_tile = builder.get_tile_position();
|
||||
let map_width = game.map_width();
|
||||
let map_height = game.map_height();
|
||||
|
||||
for distance in 0..max_range {
|
||||
for dx in -distance..=distance {
|
||||
for dy in -distance..=distance {
|
||||
if dx.abs() != distance && dy.abs() != distance {
|
||||
continue;
|
||||
}
|
||||
|
||||
let tile = TilePosition {
|
||||
x: start_tile.x + dx,
|
||||
y: start_tile.y + dy,
|
||||
// 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 tile.x < 0 || tile.y < 0 || tile.x >= map_width || tile.y >= map_height {
|
||||
continue;
|
||||
}
|
||||
let map_width = game.map_width();
|
||||
let map_height = game.map_height();
|
||||
let max_tiles = (max_range * max_range) as usize;
|
||||
|
||||
if is_valid_build_location(game, building_type, tile, builder) {
|
||||
return Some(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
// 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 {
|
||||
game
|
||||
.can_build_here(builder, position, building_type, false)
|
||||
.unwrap_or(false)
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -69,11 +69,15 @@ fn try_start_next_build(game: &Game, player: &Player, state: &mut GameState) {
|
||||
|
||||
let builder_id = builder.get_id();
|
||||
|
||||
if assign_builder_to_construct(game, &builder, unit_type, state) {
|
||||
if let Some((success, tile_pos)) =
|
||||
assign_builder_to_construct(game, player, &builder, unit_type, state)
|
||||
{
|
||||
if success {
|
||||
let entry = BuildHistoryEntry {
|
||||
unit_type: Some(unit_type),
|
||||
upgrade_type: None,
|
||||
assigned_unit_id: Some(builder_id),
|
||||
tile_position: tile_pos,
|
||||
};
|
||||
|
||||
state.unit_build_history.push(entry);
|
||||
@@ -87,6 +91,7 @@ fn try_start_next_build(game: &Game, player: &Player, state: &mut GameState) {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_status_for_stage_items(
|
||||
_game: &Game,
|
||||
@@ -174,7 +179,7 @@ fn get_next_thing_to_build(game: &Game, player: &Player, state: &GameState) -> O
|
||||
.max_by_key(|unit_type| unit_type.mineral_price() + unit_type.gas_price())
|
||||
}
|
||||
|
||||
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_total = player.supply_total();
|
||||
|
||||
@@ -189,15 +194,9 @@ fn check_need_more_supply(game: &Game, player: &Player, state: &GameState) -> Op
|
||||
let pylon_type = UnitType::Protoss_Pylon;
|
||||
|
||||
if can_afford_unit(player, pylon_type) {
|
||||
if let Some(builder) = find_builder_for_unit(player, pylon_type, state) {
|
||||
let build_location =
|
||||
build_location_utils::find_build_location(game, &builder, pylon_type, 25);
|
||||
if build_location.is_some() {
|
||||
return Some(pylon_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
@@ -224,14 +223,16 @@ fn find_builder_for_unit(
|
||||
|
||||
fn assign_builder_to_construct(
|
||||
game: &Game,
|
||||
player: &Player,
|
||||
builder: &rsbwapi::Unit,
|
||||
unit_type: UnitType,
|
||||
state: &mut GameState,
|
||||
) -> bool {
|
||||
) -> Option<(bool, Option<rsbwapi::TilePosition>)> {
|
||||
let builder_id = builder.get_id();
|
||||
|
||||
if unit_type.is_building() {
|
||||
let build_location = build_location_utils::find_build_location(game, builder, unit_type, 25);
|
||||
let build_location =
|
||||
build_location_utils::find_build_location(game, player, builder, unit_type, 42);
|
||||
|
||||
if let Some(pos) = build_location {
|
||||
println!(
|
||||
@@ -251,11 +252,11 @@ fn assign_builder_to_construct(
|
||||
target_unit: None,
|
||||
};
|
||||
state.intended_commands.insert(builder_id, intended_cmd);
|
||||
true
|
||||
Some((true, Some(pos)))
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Build command FAILED for {}: {:?}", unit_type.name(), e);
|
||||
false
|
||||
Some((false, None))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -264,7 +265,7 @@ fn assign_builder_to_construct(
|
||||
unit_type.name(),
|
||||
builder.get_id()
|
||||
);
|
||||
false
|
||||
Some((false, None))
|
||||
}
|
||||
} else {
|
||||
match builder.train(unit_type) {
|
||||
@@ -275,11 +276,11 @@ fn assign_builder_to_construct(
|
||||
target_unit: None,
|
||||
};
|
||||
state.intended_commands.insert(builder_id, intended_cmd);
|
||||
true
|
||||
Some((true, None))
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Train command FAILED for {}: {:?}", unit_type.name(), e);
|
||||
false
|
||||
Some((false, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,4 +376,125 @@ pub fn print_debug_build_status(game: &Game, player: &Player, state: &GameState)
|
||||
y += 10;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw boxes for pending building assignments
|
||||
let has_pending = 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
|
||||
}
|
||||
});
|
||||
|
||||
for entry in &state.unit_build_history {
|
||||
if let (Some(unit_type), Some(tile_pos), Some(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) {
|
||||
// Convert tile position to pixel position
|
||||
let pixel_x = tile_pos.x * 32;
|
||||
let pixel_y = tile_pos.y * 32;
|
||||
|
||||
// Get building dimensions in pixels
|
||||
let width = unit_type.tile_width() * 32;
|
||||
let height = unit_type.tile_height() * 32;
|
||||
|
||||
// Draw the building outline box
|
||||
use rsbwapi::{Color, Position};
|
||||
let top_left = Position {
|
||||
x: pixel_x,
|
||||
y: pixel_y,
|
||||
};
|
||||
let top_right = Position {
|
||||
x: pixel_x + width,
|
||||
y: pixel_y,
|
||||
};
|
||||
let bottom_left = Position {
|
||||
x: pixel_x,
|
||||
y: pixel_y + height,
|
||||
};
|
||||
let bottom_right = Position {
|
||||
x: pixel_x + width,
|
||||
y: pixel_y + height,
|
||||
};
|
||||
|
||||
// Draw yellow box for pending buildings
|
||||
let color = Color::Yellow;
|
||||
game.draw_line_map(top_left, top_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_left, top_left, color);
|
||||
|
||||
// Draw building name at the center
|
||||
let center = Position {
|
||||
x: pixel_x + width / 2,
|
||||
y: pixel_y + height / 2,
|
||||
};
|
||||
game.draw_text_map(center, &format!("Pending: {}", unit_type.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw buildable locations around nexus if there are pending assignments
|
||||
if has_pending {
|
||||
use rsbwapi::{Color, Position, TilePosition};
|
||||
|
||||
// Find the nexus
|
||||
if let Some(nexus) = player
|
||||
.get_units()
|
||||
.iter()
|
||||
.find(|u| u.get_type() == UnitType::Protoss_Nexus)
|
||||
{
|
||||
let nexus_tile = nexus.get_tile_position();
|
||||
let radius = 30;
|
||||
|
||||
// Iterate through tiles in radius around nexus
|
||||
for dx in -radius..=radius {
|
||||
for dy in -radius..=radius {
|
||||
let tile_x = nexus_tile.x + dx;
|
||||
let tile_y = nexus_tile.y + dy;
|
||||
|
||||
// Check bounds
|
||||
if tile_x < 0 || tile_y < 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let tile_pos = TilePosition {
|
||||
x: tile_x,
|
||||
y: tile_y,
|
||||
};
|
||||
|
||||
// Check if within circular radius
|
||||
let dist_sq = (dx * dx + dy * dy) as f32;
|
||||
if dist_sq > (radius * radius) as f32 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check buildability
|
||||
let is_buildable = game.is_buildable(tile_pos);
|
||||
if !is_buildable {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if powered (check if a 1x1 tile has power)
|
||||
let has_power = game.has_power(tile_pos, (1, 1));
|
||||
|
||||
// Draw small box at tile position
|
||||
let pixel_x = tile_x * 32;
|
||||
let pixel_y = tile_y * 32;
|
||||
let center = Position {
|
||||
x: pixel_x + 16,
|
||||
y: pixel_y + 16,
|
||||
};
|
||||
|
||||
// Green for buildable+powered, Blue for just buildable
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ map: maps/BroodWar/(4)CircuitBreaker.scx
|
||||
# map: maps/(2)Boxer.scm
|
||||
|
||||
# player_race: Zerg
|
||||
# player_race: Terran
|
||||
player_race: Protoss
|
||||
player_race: Terran
|
||||
# player_race: Protoss
|
||||
# player_race: Random
|
||||
|
||||
# enemy_count: 1
|
||||
|
||||
Reference in New Issue
Block a user