use goap::prelude::*;
fn main() {
let initial_state = State::new()
.set("tank_available", true)
.set("healer_available", false)
.set("dps_available", true)
.set("tank_health", 50)
.set("tank_armor", 30)
.set("dps_damage", 40)
.set("party_size", 2)
.set("tank_poisoned", true)
.set("dps_cursed", true)
.set("gold", 400)
.set("healing_potions", 1)
.set("antidote_potions", 0)
.set("remove_curse_scrolls", 0)
.build();
let goal = Goal::new("prepare_dungeon_party")
.requires("healer_available", true)
.requires("tank_health", 100)
.requires("tank_armor", 50)
.requires("tank_poisoned", false)
.requires("dps_cursed", false)
.requires("dps_damage", 60)
.requires("party_size", 3)
.build();
let recruit_healer = Action::new("recruit_healer")
.cost(3.0)
.requires("gold", 100)
.sets("healer_available", true)
.adds("party_size", 1)
.subtracts("gold", 100)
.build();
let buy_healing = Action::new("buy_healing_potion")
.cost(1.0)
.requires("gold", 50)
.adds("healing_potions", 1)
.subtracts("gold", 50)
.build();
let buy_antidote = Action::new("buy_antidote")
.cost(1.0)
.requires("gold", 30)
.adds("antidote_potions", 1)
.subtracts("gold", 30)
.build();
let buy_scroll = Action::new("buy_remove_curse_scroll")
.cost(1.0)
.requires("gold", 40)
.adds("remove_curse_scrolls", 1)
.subtracts("gold", 40)
.build();
let heal_tank = Action::new("heal_tank")
.cost(1.0)
.requires("healing_potions", 1)
.sets("tank_health", 100)
.subtracts("healing_potions", 1)
.build();
let cure_poison = Action::new("cure_tank_poison")
.cost(1.0)
.requires("antidote_potions", 1)
.sets("tank_poisoned", false)
.subtracts("antidote_potions", 1)
.build();
let remove_curse = Action::new("remove_dps_curse")
.cost(1.0)
.requires("remove_curse_scrolls", 1)
.sets("dps_cursed", false)
.subtracts("remove_curse_scrolls", 1)
.build();
let upgrade_armor = Action::new("upgrade_tank_armor")
.cost(2.0)
.requires("gold", 80)
.sets("tank_armor", 50)
.subtracts("gold", 80)
.build();
let upgrade_weapon = Action::new("upgrade_dps_weapon")
.cost(2.0)
.requires("gold", 90)
.sets("dps_damage", 60)
.subtracts("gold", 90)
.build();
let actions = vec![
recruit_healer,
buy_healing,
buy_antidote,
buy_scroll,
heal_tank,
cure_poison,
remove_curse,
upgrade_armor,
upgrade_weapon,
];
let planner = Planner::new();
let plan_result = planner.plan(initial_state.clone(), &goal, &actions);
assert!(
plan_result.is_ok(),
"Expected to find a valid plan for party management"
);
let plan = plan_result.unwrap();
println!(
"\nParty Management Plan found with cost {cost}",
cost = plan.cost
);
for action in &plan.actions {
println!(
"- {name} (cost: {cost})",
name = action.name,
cost = action.cost
);
}
let action_names: Vec<_> = plan.actions.iter().map(|a| a.name.as_str()).collect();
assert!(
action_names.contains(&"recruit_healer"),
"Plan should include recruiting a healer"
);
assert!(
action_names.contains(&"heal_tank"),
"Plan should include healing the tank"
);
assert!(
action_names.contains(&"buy_antidote"),
"Plan should include buying antidote"
);
assert!(
action_names.contains(&"cure_tank_poison"),
"Plan should include curing tank's poison"
);
assert!(
action_names.contains(&"buy_remove_curse_scroll"),
"Plan should include buying curse removal"
);
assert!(
action_names.contains(&"remove_dps_curse"),
"Plan should include removing DPS curse"
);
assert!(
action_names.contains(&"upgrade_tank_armor"),
"Plan should include upgrading tank's armor"
);
assert!(
action_names.contains(&"upgrade_dps_weapon"),
"Plan should include upgrading DPS weapon"
);
let mut current_state = initial_state.clone();
let remaining_gold = 400;
println!("\nSimulating plan execution:");
for action in &plan.actions {
current_state = action.apply_effect(¤t_state);
let name = &action.name;
println!("After {name}: ");
println!(" Gold: {remaining_gold}");
if let Some(party_size) = current_state.get::<i64>("party_size") {
println!(" Party Size: {party_size}");
}
if let Some(tank_health) = current_state.get::<i64>("tank_health") {
println!(" Tank Health: {tank_health}");
}
if let Some(tank_armor) = current_state.get::<i64>("tank_armor") {
println!(" Tank Armor: {tank_armor}");
}
if let Some(dps_damage) = current_state.get::<i64>("dps_damage") {
println!(" DPS Damage: {dps_damage}");
}
}
assert!(
current_state.satisfies(&goal.desired_state),
"Final state should meet all goals"
);
assert!(remaining_gold >= 0, "Should not overspend gold");
if let Some(has_healer) = current_state.get::<bool>("healer_available") {
assert!(has_healer, "Should have recruited a healer");
}
if let Some(party_size) = current_state.get::<i64>("party_size") {
assert!(party_size == 3, "Party size should be 3");
}
if let Some(tank_poisoned) = current_state.get::<bool>("tank_poisoned") {
assert!(!tank_poisoned, "Tank should not be poisoned");
}
if let Some(dps_cursed) = current_state.get::<bool>("dps_cursed") {
assert!(!dps_cursed, "DPS should not be cursed");
}
println!("\nFinal party state verification:");
let party_size = current_state.get::<i64>("party_size");
println!("Party Size: {party_size:?}");
let tank_health = current_state.get::<i64>("tank_health");
println!("Tank Health: {tank_health:?}");
let tank_armor = current_state.get::<i64>("tank_armor");
println!("Tank Armor: {tank_armor:?}");
let tank_poisoned = current_state.get::<bool>("tank_poisoned");
println!("Tank Poisoned: {tank_poisoned:?}");
let dps_damage = current_state.get::<i64>("dps_damage");
println!("DPS Damage: {dps_damage:?}");
let dps_cursed = current_state.get::<bool>("dps_cursed");
println!("DPS Cursed: {dps_cursed:?}");
let healer_available = current_state.get::<bool>("healer_available");
println!("Healer Available: {healer_available:?}");
println!("Remaining Gold: {remaining_gold}");
}