diff --git a/protossbot/Cargo.toml b/protossbot/Cargo.toml index 8914766..ccb3be9 100644 --- a/protossbot/Cargo.toml +++ b/protossbot/Cargo.toml @@ -14,6 +14,11 @@ tokio-tungstenite = "0.24" futures-util = "0.3" tower-http = { version = "0.6", features = ["fs", "cors"] } rand = "0.8" +leptos = { version = "0.6", features = ["csr"] } +leptos_axum = { version = "0.6" } +leptos_meta = { version = "0.6" } +leptos_router = { version = "0.6" } +protoss-bot-web = { path = "web" } [profile.release] opt-level = 3 diff --git a/protossbot/src/bot.rs b/protossbot/src/bot.rs index 60ef448..ab76704 100644 --- a/protossbot/src/bot.rs +++ b/protossbot/src/bot.rs @@ -1,6 +1,9 @@ +use crate::{ + state::game_state::GameState, + utils::{build_manager, worker_management}, +}; use rsbwapi::*; use std::sync::{Arc, Mutex}; -use crate::{state::game_state::GameState, utils::{build_manager, worker_management}}; fn draw_unit_ids(game: &Game) { for unit in game.get_all_units() { @@ -47,6 +50,17 @@ impl AiModule for ProtosBot { return; } println!("unit created: {:?}", unit.get_type()); + + // Check if the created unit is a building + if !unit.get_type().is_building() { + return; + } + + let Ok(mut locked_state) = self.game_state.lock() else { + return; + }; + + build_manager::on_building_create(&unit, &mut locked_state); } fn on_unit_morph(&mut self, _game: &Game, _unit: Unit) {} diff --git a/protossbot/src/main.rs b/protossbot/src/main.rs index 10ca8ca..3499397 100644 --- a/protossbot/src/main.rs +++ b/protossbot/src/main.rs @@ -3,14 +3,41 @@ mod state; mod utils; use bot::ProtosBot; -use std::sync::{Arc, Mutex}; use state::game_state::GameState; - +use std::sync::{Arc, Mutex}; fn main() { println!("Starting RustBot..."); let game_state = Arc::new(Mutex::new(GameState::default())); - rsbwapi::start(move |_game| ProtosBot::new(game_state.clone() )); + // // Start the webserver in a separate thread + // std::thread::spawn(|| { + // let rt = tokio::runtime::Runtime::new().unwrap(); + // rt.block_on(start_webserver()); + // }); + + rsbwapi::start(move |_game| ProtosBot::new(game_state.clone())); +} + +async fn start_webserver() { + use axum::Router; + use leptos::*; + use leptos_axum::{generate_route_list, LeptosRoutes}; + use protoss_bot_web::App; + + let conf = get_configuration(None).await.unwrap(); + let leptos_options = conf.leptos_options; + let addr = leptos_options.site_addr; + let routes = generate_route_list(App); + + let app = Router::new() + .leptos_routes(&leptos_options, routes, App) + .with_state(leptos_options); + + println!("Web server listening on http://{}", &addr); + let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); + axum::serve(listener, app.into_make_service()) + .await + .unwrap(); } diff --git a/protossbot/src/utils/build_manager.rs b/protossbot/src/utils/build_manager.rs index dd1626a..f3f0815 100644 --- a/protossbot/src/utils/build_manager.rs +++ b/protossbot/src/utils/build_manager.rs @@ -1,4 +1,4 @@ -use rsbwapi::{Game, Order, Player, UnitType}; +use rsbwapi::{Game, Order, Player, Unit, UnitType}; use crate::{ state::game_state::{BuildHistoryEntry, GameState, IntendedCommand}, @@ -10,6 +10,27 @@ 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) { + // Find the build history entry associated with this building type + // and remove the probe's assignment + if let Some(entry) = state + .unit_build_history + .iter() + .rev() + .find(|e| e.unit_type == Some(unit.get_type())) + { + if let Some(probe_id) = entry.assigned_unit_id { + // Remove the probe's intended command (PlaceBuilding order) + state.intended_commands.remove(&probe_id); + println!( + "Building {} started. Removed assignment for probe {}", + unit.get_type().name(), + probe_id + ); + } + } +} + fn try_start_next_build(game: &Game, player: &Player, state: &mut GameState) { let Some(unit_type) = get_next_thing_to_build(game, player, state) else { return; @@ -62,7 +83,8 @@ fn get_next_thing_to_build(game: &Game, player: &Player, state: &GameState) -> O if unit_type.is_building() { let builder = find_builder_for_unit(player, *unit_type)?; - let build_location = build_location_utils::find_build_location(game, &builder, *unit_type, 20); + let build_location = + build_location_utils::find_build_location(game, &builder, *unit_type, 20); if build_location.is_none() { continue; } @@ -71,28 +93,29 @@ fn get_next_thing_to_build(game: &Game, player: &Player, state: &GameState) -> O candidates.push(*unit_type); } - candidates.into_iter().max_by_key(|unit_type| { - unit_type.mineral_price() + unit_type.gas_price() - }) + candidates + .into_iter() + .max_by_key(|unit_type| unit_type.mineral_price() + unit_type.gas_price()) } fn check_need_more_supply(game: &Game, player: &Player) -> Option { let supply_used = player.supply_used(); let supply_total = player.supply_total(); - + if supply_total == 0 { return None; } let supply_remaining = supply_total - supply_used; let threshold = ((supply_total as f32) * 0.15).ceil() as i32; - + if supply_remaining <= threshold && supply_total < 400 { let pylon_type = UnitType::Protoss_Pylon; - + if can_afford_unit(player, pylon_type) { if let Some(builder) = find_builder_for_unit(player, pylon_type) { - let build_location = build_location_utils::find_build_location(game, &builder, pylon_type, 20); + let build_location = + build_location_utils::find_build_location(game, &builder, pylon_type, 20); if build_location.is_some() { return Some(pylon_type); } @@ -115,9 +138,14 @@ fn find_builder_for_unit(player: &Player, unit_type: UnitType) -> Option bool { +fn assign_builder_to_construct( + game: &Game, + builder: &rsbwapi::Unit, + unit_type: UnitType, + state: &mut GameState, +) -> bool { let builder_id = builder.get_id(); - + if unit_type.is_building() { let build_location = build_location_utils::find_build_location(game, builder, unit_type, 20); @@ -129,7 +157,7 @@ fn assign_builder_to_construct(game: &Game, builder: &rsbwapi::Unit, unit_type: builder_id, builder.get_position() ); - + match builder.build(unit_type, pos) { Ok(_) => { println!("Build command succeeded for {}", unit_type.name()); @@ -180,8 +208,6 @@ fn count_units_of_type(player: &Player, _state: &GameState, unit_type: UnitType) .filter(|u| u.get_type() == unit_type) .count() as i32; - - existing } diff --git a/protossbot/src/utils/worker_management.rs b/protossbot/src/utils/worker_management.rs index 2be4fa1..e474221 100644 --- a/protossbot/src/utils/worker_management.rs +++ b/protossbot/src/utils/worker_management.rs @@ -10,33 +10,12 @@ pub fn assign_idle_workers_to_minerals(game: &Game, player: &Player, state: &mut .cloned() .collect(); - // First, clean up workers that finished building - reassign_finished_builders(game, &workers, state); - - // Then assign idle workers to mining + // Assign idle workers to mining for worker in workers { assign_worker_to_mineral(game, &worker, state); } } -fn reassign_finished_builders(_game: &Game, workers: &[Unit], state: &mut GameState) { - for worker in workers { - let worker_id = worker.get_id(); - - if let Some(cmd) = state.intended_commands.get(&worker_id) { - if cmd.order == Order::PlaceBuilding { - let current_order = worker.get_order(); - if worker.is_idle() && current_order != Order::PlaceBuilding && current_order != Order::ConstructingBuilding { - println!("Worker {} with order {:?} finished building, reassigning to minerals", worker_id, current_order); - state.intended_commands.remove(&worker_id); - } - } else if cmd.order == Order::Train && worker.is_idle() && !worker.is_training() { - state.intended_commands.remove(&worker_id); - } - } - } -} - fn assign_worker_to_mineral(game: &Game, worker: &Unit, state: &mut GameState) { let worker_id = worker.get_id();