use goap::prelude::*;
fn main() {
let initial_state = State::new()
.set("health", 30) .set("armor", 0) .set("ammo", 5) .set("at_medical", false)
.set("at_armory", false)
.set("at_trading", false)
.set("has_credits", 100) .build();
let goal = Goal::new("prepare_for_boss")
.requires("health", 100) .requires("armor", 50) .requires("ammo", 50) .build();
let goto_medical = Action::new("goto_medical")
.cost(1.0)
.sets("at_medical", true)
.sets("at_armory", false)
.sets("at_trading", false)
.build();
let goto_armory = Action::new("goto_armory")
.cost(1.0)
.sets("at_armory", true)
.sets("at_medical", false)
.sets("at_trading", false)
.build();
let goto_trading = Action::new("goto_trading")
.cost(1.0)
.sets("at_trading", true)
.sets("at_medical", false)
.sets("at_armory", false)
.build();
let heal = Action::new("use_health_station")
.cost(2.0)
.requires("at_medical", true)
.requires("has_credits", 20)
.sets("health", 100)
.subtracts("has_credits", 20)
.build();
let buy_armor = Action::new("buy_armor")
.cost(2.0)
.requires("at_armory", true)
.requires("has_credits", 30)
.sets("armor", 50)
.subtracts("has_credits", 30)
.build();
let buy_ammo = Action::new("buy_ammo")
.cost(1.5)
.requires("at_trading", true)
.requires("has_credits", 25)
.sets("ammo", 50)
.subtracts("has_credits", 25)
.build();
let actions = vec![
goto_medical,
goto_armory,
goto_trading,
heal,
buy_armor,
buy_ammo,
];
let initial_state_copy = initial_state.clone();
let planner = Planner::new();
let plan_result = planner.plan(initial_state, &goal, &actions);
assert!(
plan_result.is_ok(),
"Expected to find a valid plan for combat preparation"
);
let plan = plan_result.unwrap();
println!(
"\nCombat Strategy Plan found with cost {cost}",
cost = plan.cost
);
println!("Starting with 100 credits");
for action in &plan.actions {
println!(
"- {name} (cost: {cost})",
name = action.name,
cost = action.cost
);
}
assert!(
actions.len() >= 5,
"Expected at least 5 actions in the plan"
);
let action_names: Vec<_> = plan.actions.iter().map(|a| a.name.as_str()).collect();
assert!(
action_names.contains(&"goto_medical"),
"Plan should include going to medical"
);
assert!(
action_names.contains(&"use_health_station"),
"Plan should include healing"
);
assert!(
action_names.contains(&"goto_armory"),
"Plan should include going to armory"
);
assert!(
action_names.contains(&"buy_armor"),
"Plan should include buying armor"
);
assert!(
action_names.contains(&"goto_trading"),
"Plan should include going to trading post"
);
assert!(
action_names.contains(&"buy_ammo"),
"Plan should include buying ammo"
);
assert!(
plan.cost >= 7.5,
"Total cost should be at least 7.5 (movement + purchases)"
);
let mut current_state = initial_state_copy.clone();
let mut total_credits = 100;
for action in &plan.actions {
current_state = action.apply_effect(¤t_state);
if let Some(credits) = current_state.get::<i64>("has_credits") {
total_credits = credits;
}
}
assert!(
current_state.satisfies(&goal.desired_state),
"Final state should meet all goals"
);
assert!(
total_credits >= 0,
"Should not spend more credits than available"
);
println!("\nFinal state verification:");
println!("Remaining credits: {total_credits}");
if let Some(health) = current_state.get::<i64>("health") {
println!("Final health: {health}");
}
if let Some(armor) = current_state.get::<i64>("armor") {
println!("Final armor: {armor}");
}
if let Some(ammo) = current_state.get::<i64>("ammo") {
println!("Final ammo: {ammo}");
}
}