terran pivot
This commit is contained in:
@@ -24,23 +24,20 @@ impl BuildStage {
|
|||||||
pub fn get_build_stages() -> Vec<BuildStage> {
|
pub fn get_build_stages() -> Vec<BuildStage> {
|
||||||
vec![
|
vec![
|
||||||
BuildStage::new("Start")
|
BuildStage::new("Start")
|
||||||
.with_unit(UnitType::Protoss_Probe, 10)
|
.with_unit(UnitType::Terran_SCV, 10)
|
||||||
.with_unit(UnitType::Protoss_Pylon, 1),
|
.with_unit(UnitType::Terran_Supply_Depot, 1),
|
||||||
|
|
||||||
BuildStage::new("Basic Production")
|
BuildStage::new("Basic Production")
|
||||||
.with_unit(UnitType::Protoss_Probe, 12)
|
.with_unit(UnitType::Terran_SCV, 12)
|
||||||
.with_unit(UnitType::Protoss_Pylon, 2)
|
.with_unit(UnitType::Terran_Supply_Depot, 2)
|
||||||
.with_unit(UnitType::Protoss_Gateway, 1)
|
.with_unit(UnitType::Terran_Barracks, 1)
|
||||||
.with_unit(UnitType::Protoss_Forge, 1),
|
.with_unit(UnitType::Terran_Refinery, 1),
|
||||||
|
// Stage 2: Defense bunker
|
||||||
|
BuildStage::new("Defense Bunker")
|
||||||
// Stage 2: Defense cannons
|
.with_unit(UnitType::Terran_SCV, 16)
|
||||||
BuildStage::new("Defense Cannons")
|
.with_unit(UnitType::Terran_Supply_Depot, 3)
|
||||||
.with_unit(UnitType::Protoss_Probe, 16)
|
.with_unit(UnitType::Terran_Command_Center, 1)
|
||||||
.with_unit(UnitType::Protoss_Pylon, 3)
|
.with_unit(UnitType::Terran_Barracks, 1)
|
||||||
.with_unit(UnitType::Protoss_Nexus, 1)
|
.with_unit(UnitType::Terran_Refinery, 1)
|
||||||
.with_unit(UnitType::Protoss_Gateway, 1)
|
.with_unit(UnitType::Terran_Bunker, 2),
|
||||||
.with_unit(UnitType::Protoss_Forge, 1)
|
|
||||||
.with_unit(UnitType::Protoss_Photon_Cannon, 4),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,5 +43,6 @@ pub struct BuildHistoryEntry {
|
|||||||
pub unit_type: Option<UnitType>,
|
pub unit_type: Option<UnitType>,
|
||||||
pub upgrade_type: Option<UpgradeType>,
|
pub upgrade_type: Option<UpgradeType>,
|
||||||
pub assigned_unit_id: Option<usize>,
|
pub assigned_unit_id: Option<usize>,
|
||||||
|
pub tile_position: Option<rsbwapi::TilePosition>,
|
||||||
// pub status: BuildStatus,
|
// 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(
|
pub fn find_build_location(
|
||||||
game: &Game,
|
game: &Game,
|
||||||
|
player: &Player,
|
||||||
builder: &Unit,
|
builder: &Unit,
|
||||||
building_type: UnitType,
|
building_type: UnitType,
|
||||||
max_range: i32,
|
max_range: i32,
|
||||||
) -> Option<TilePosition> {
|
) -> Option<TilePosition> {
|
||||||
let start_tile = builder.get_tile_position();
|
// Find the base to start spiral search from
|
||||||
let map_width = game.map_width();
|
let start_tile = if let Some(nexus) = player
|
||||||
let map_height = game.map_height();
|
.get_units()
|
||||||
|
.iter()
|
||||||
for distance in 0..max_range {
|
.find(|u| u.get_type() == UnitType::Protoss_Nexus)
|
||||||
for dx in -distance..=distance {
|
{
|
||||||
for dy in -distance..=distance {
|
nexus.get_tile_position()
|
||||||
if dx.abs() != distance && dy.abs() != distance {
|
} else {
|
||||||
continue;
|
builder.get_tile_position()
|
||||||
}
|
|
||||||
|
|
||||||
let tile = TilePosition {
|
|
||||||
x: start_tile.x + dx,
|
|
||||||
y: start_tile.y + dy,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if tile.x < 0 || tile.y < 0 || tile.x >= map_width || tile.y >= map_height {
|
let map_width = game.map_width();
|
||||||
continue;
|
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) {
|
// Use spiral search like Styx2
|
||||||
return Some(tile);
|
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
|
||||||
|
})
|
||||||
None
|
.find(|&tile| is_valid_build_location(game, player, building_type, tile, builder))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_valid_build_location(
|
fn is_valid_build_location(
|
||||||
game: &Game,
|
game: &Game,
|
||||||
|
player: &Player,
|
||||||
building_type: UnitType,
|
building_type: UnitType,
|
||||||
position: TilePosition,
|
position: TilePosition,
|
||||||
builder: &Unit,
|
builder: &Unit,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
game
|
// Get building dimensions
|
||||||
.can_build_here(builder, position, building_type, false)
|
let width = building_type.tile_width();
|
||||||
.unwrap_or(false)
|
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();
|
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 {
|
let entry = BuildHistoryEntry {
|
||||||
unit_type: Some(unit_type),
|
unit_type: Some(unit_type),
|
||||||
upgrade_type: None,
|
upgrade_type: None,
|
||||||
assigned_unit_id: Some(builder_id),
|
assigned_unit_id: Some(builder_id),
|
||||||
|
tile_position: tile_pos,
|
||||||
};
|
};
|
||||||
|
|
||||||
state.unit_build_history.push(entry);
|
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(
|
fn get_status_for_stage_items(
|
||||||
_game: &Game,
|
_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())
|
.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_used = player.supply_used();
|
||||||
let supply_total = player.supply_total();
|
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;
|
let pylon_type = UnitType::Protoss_Pylon;
|
||||||
|
|
||||||
if can_afford_unit(player, pylon_type) {
|
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);
|
return Some(pylon_type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -224,14 +223,16 @@ fn find_builder_for_unit(
|
|||||||
|
|
||||||
fn assign_builder_to_construct(
|
fn assign_builder_to_construct(
|
||||||
game: &Game,
|
game: &Game,
|
||||||
|
player: &Player,
|
||||||
builder: &rsbwapi::Unit,
|
builder: &rsbwapi::Unit,
|
||||||
unit_type: UnitType,
|
unit_type: UnitType,
|
||||||
state: &mut GameState,
|
state: &mut GameState,
|
||||||
) -> bool {
|
) -> 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 = 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 {
|
if let Some(pos) = build_location {
|
||||||
println!(
|
println!(
|
||||||
@@ -251,11 +252,11 @@ fn assign_builder_to_construct(
|
|||||||
target_unit: None,
|
target_unit: None,
|
||||||
};
|
};
|
||||||
state.intended_commands.insert(builder_id, intended_cmd);
|
state.intended_commands.insert(builder_id, intended_cmd);
|
||||||
true
|
Some((true, Some(pos)))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Build command FAILED for {}: {:?}", unit_type.name(), e);
|
println!("Build command FAILED for {}: {:?}", unit_type.name(), e);
|
||||||
false
|
Some((false, None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -264,7 +265,7 @@ fn assign_builder_to_construct(
|
|||||||
unit_type.name(),
|
unit_type.name(),
|
||||||
builder.get_id()
|
builder.get_id()
|
||||||
);
|
);
|
||||||
false
|
Some((false, None))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match builder.train(unit_type) {
|
match builder.train(unit_type) {
|
||||||
@@ -275,11 +276,11 @@ fn assign_builder_to_construct(
|
|||||||
target_unit: None,
|
target_unit: None,
|
||||||
};
|
};
|
||||||
state.intended_commands.insert(builder_id, intended_cmd);
|
state.intended_commands.insert(builder_id, intended_cmd);
|
||||||
true
|
Some((true, None))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Train command FAILED for {}: {:?}", unit_type.name(), 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;
|
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
|
# map: maps/(2)Boxer.scm
|
||||||
|
|
||||||
# player_race: Zerg
|
# player_race: Zerg
|
||||||
# player_race: Terran
|
player_race: Terran
|
||||||
player_race: Protoss
|
# player_race: Protoss
|
||||||
# player_race: Random
|
# player_race: Random
|
||||||
|
|
||||||
# enemy_count: 1
|
# enemy_count: 1
|
||||||
|
|||||||
Reference in New Issue
Block a user