/*
Hurry Curry! - a game about cooking
Copyright 2024 metamuffin
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, version 3 of the License only.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
use anyhow::{anyhow, Result};
use hurrycurry_protocol::{ItemIndex, Recipe, TileIndex};
use hurrycurry_server::data::DataIndex;
use std::collections::HashSet;
#[tokio::main]
async fn main() -> Result<()> {
let mut index = DataIndex::default();
index.reload()?;
println!("digraph {{");
let rn = std::env::args()
.nth(1)
.ok_or(anyhow!("first arg should be recipe set name"))?;
let (data, sdata, _) = index.generate(format!("5star-{rn}")).await?;
struct Node {
inputs: Vec,
outputs: Vec,
tool_item: Option,
tool_tile: Option,
instant: bool,
demand: bool,
}
let map_items = sdata
.initial_map
.iter()
.flat_map(|(_, (_, i))| *i)
.collect::>();
let mut nodes = Vec::new();
for r in &data.recipes {
nodes.push(Node {
inputs: r.inputs(),
outputs: r.outputs(),
tool_item: None,
tool_tile: r.tile(),
instant: matches!(r, Recipe::Instant { .. }),
demand: false,
})
}
for d in &data.demands {
nodes.push(Node {
demand: true,
instant: false,
inputs: vec![d.input],
outputs: d.output.into_iter().collect(),
tool_item: None,
tool_tile: None,
})
}
loop {
let node_count_before = nodes.len();
let mut has_fdeps = vec![false; data.item_names.len()];
for n in &nodes {
for ItemIndex(i) in &n.inputs {
has_fdeps[*i] = true;
}
}
// Remove demand outputs
for n in &mut nodes {
n.outputs.retain(|_item| !n.demand);
}
// Remove outputs that are not depended on
for n in &mut nodes {
n.outputs.retain(|item| n.demand || has_fdeps[item.0])
}
// Remove outputs that exist on the map, like pots and plates
for n in &mut nodes {
n.outputs.retain(|item| !map_items.contains(item))
}
// Convert map item inputs to tools
for n in &mut nodes {
n.inputs.retain(|i| {
if map_items.contains(i) {
n.tool_item = Some(*i);
false
} else {
true
}
})
}
// Remove outputless recipes
nodes.retain(|n| n.demand || !n.outputs.is_empty());
if nodes.len() == node_count_before {
break;
}
}
let mut items = HashSet::::new();
for n in &nodes {
items.extend(&n.inputs);
items.extend(&n.outputs);
}
for ItemIndex(i) in items {
println!("i{i} [label=\"{}\"]", data.item_name(ItemIndex(i)))
}
for (ni, node) in nodes.iter().enumerate() {
let color = if node.demand {
"#c4422b"
} else if node.instant {
"#5452d8"
} else {
"#47c42b"
};
println!(
"r{ni} [label=\"{}\" shape=box color={color:?} fillcolor={color:?} style=filled]",
if let Some(tool) = node.tool_tile {
data.tile_name(tool)
} else if let Some(tool) = node.tool_item {
data.item_name(tool)
} else if node.instant {
"Combine"
} else if node.demand {
"Demand"
} else {
"Passive"
}
);
for ItemIndex(input) in &node.inputs {
println!("i{input} -> r{ni}")
}
for ItemIndex(output) in &node.outputs {
println!("r{ni} -> i{output}")
}
}
println!("}}");
Ok(())
}