aboutsummaryrefslogtreecommitdiff
path: root/server/tools
diff options
context:
space:
mode:
Diffstat (limited to 'server/tools')
-rw-r--r--server/tools/Cargo.toml14
-rw-r--r--server/tools/src/book.rs41
-rw-r--r--server/tools/src/diagram_layout.rs75
-rw-r--r--server/tools/src/graph.rs76
-rw-r--r--server/tools/src/graph_summary.rs149
-rw-r--r--server/tools/src/main.rs46
-rw-r--r--server/tools/src/recipe_diagram.rs112
7 files changed, 513 insertions, 0 deletions
diff --git a/server/tools/Cargo.toml b/server/tools/Cargo.toml
new file mode 100644
index 00000000..1a6d6aa3
--- /dev/null
+++ b/server/tools/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "hurrycurry-tools"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+anyhow = "1.0.100"
+log = "0.4.28"
+env_logger = "0.11.8"
+clap = { version = "4.5.47", features = ["derive"] }
+hurrycurry-protocol = { path = "../protocol" }
+hurrycurry-server = { path = ".." }
+serde_json = "1.0.145"
+serde = { version = "1.0.225", features = ["derive"] }
diff --git a/server/tools/src/book.rs b/server/tools/src/book.rs
new file mode 100644
index 00000000..6c871274
--- /dev/null
+++ b/server/tools/src/book.rs
@@ -0,0 +1,41 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2025 Hurry Curry! Contributors
+
+ 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 <https://www.gnu.org/licenses/>.
+
+*/
+
+use crate::{diagram_layout::diagram_layout, recipe_diagram::recipe_diagram};
+use anyhow::Result;
+use hurrycurry_protocol::{
+ ItemIndex, Message,
+ book::{Book, BookPage},
+};
+
+pub fn book() -> Result<()> {
+ let mut diagram = recipe_diagram(&["plate:cheese-leek-soup".to_owned()])?;
+ diagram_layout(&mut diagram)?;
+
+ let mut pages = Vec::new();
+
+ pages.push(BookPage::Recipe {
+ description: Message::Item(ItemIndex(0)),
+ instruction: Message::Item(ItemIndex(0)),
+ diagram,
+ });
+
+ let book = Book { pages };
+ println!("{}", serde_json::to_string_pretty(&book).unwrap());
+ Ok(())
+}
diff --git a/server/tools/src/diagram_layout.rs b/server/tools/src/diagram_layout.rs
new file mode 100644
index 00000000..e6ae2d76
--- /dev/null
+++ b/server/tools/src/diagram_layout.rs
@@ -0,0 +1,75 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2025 Hurry Curry! Contributors
+
+ 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 <https://www.gnu.org/licenses/>.
+
+*/
+
+use anyhow::{Result, anyhow};
+use hurrycurry_protocol::{book::Diagram, glam::Vec2};
+use serde::Deserialize;
+use std::{
+ collections::BTreeMap,
+ fmt::Write as W2,
+ io::Write,
+ process::{Command, Stdio},
+};
+
+pub struct Layout {
+ pub nodes: BTreeMap<String, Vec2>,
+ pub edges: Vec<(usize, usize)>,
+}
+
+pub fn diagram_layout(diagram: &mut Diagram) -> Result<()> {
+ let mut child = Command::new("dot")
+ .arg("-Tjson")
+ .arg("-Kdot")
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn()?;
+
+ let mut out = String::new();
+ writeln!(out, "digraph {{")?;
+ for edge in &diagram.edges {
+ writeln!(out, "k{} -> k{}", edge.src, edge.dst)?;
+ }
+ writeln!(out, "}}")?;
+
+ child.stdin.as_mut().unwrap().write_all(out.as_bytes())?;
+ let output = child.wait_with_output()?;
+
+ #[derive(Deserialize)]
+ struct Out {
+ objects: Vec<Object>,
+ }
+ #[derive(Deserialize)]
+ struct Object {
+ name: String,
+ pos: String,
+ }
+ let graph: Out = serde_json::from_slice(&output.stdout)?;
+ for o in graph.objects {
+ let pos = o.pos.split_once(",").ok_or(anyhow!("malformed position"))?;
+ let pos = Vec2::new(pos.0.parse()?, pos.1.parse()?);
+
+ let index = o
+ .name
+ .strip_prefix("k")
+ .ok_or(anyhow!("invalid node name"))?
+ .parse::<usize>()?;
+ diagram.nodes[index].position = pos
+ }
+
+ Ok(())
+}
diff --git a/server/tools/src/graph.rs b/server/tools/src/graph.rs
new file mode 100644
index 00000000..53f70d99
--- /dev/null
+++ b/server/tools/src/graph.rs
@@ -0,0 +1,76 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2025 Hurry Curry! Contributors
+
+ 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 <https://www.gnu.org/licenses/>.
+
+*/
+use anyhow::Result;
+use hurrycurry_protocol::{Demand, ItemIndex, Recipe, RecipeIndex};
+use hurrycurry_server::data::DataIndex;
+
+pub(crate) fn graph() -> Result<()> {
+ let mut index = DataIndex::default();
+ index.reload()?;
+
+ println!("digraph {{");
+
+ let (data, _, _) = index.generate("5star")?;
+ for i in 0..data.item_names.len() {
+ println!("i{i} [label=\"{}\"]", data.item_name(ItemIndex(i)))
+ }
+ for (RecipeIndex(ri), recipe) in data.recipes() {
+ let (kind, color) = match recipe {
+ Recipe::Passive { .. } => ("Passive", "#b5c42b"),
+ Recipe::Active { .. } => ("Active", "#47c42b"),
+ Recipe::Instant { .. } => ("Instant", "#5452d8"),
+ };
+ println!(
+ "r{ri} [label=\"{kind}\\non {}\" shape=box color={color:?} fillcolor={color:?} style=filled]",
+ if let Some(tile) = recipe.tile() {
+ data.tile_name(tile)
+ } else {
+ "anything"
+ }
+ );
+ for ItemIndex(input) in recipe.inputs() {
+ println!("i{input} -> r{ri}")
+ }
+ for ItemIndex(output) in recipe.outputs() {
+ println!("r{ri} -> i{output}")
+ }
+ }
+
+ for (
+ di,
+ Demand {
+ duration,
+ input: ItemIndex(input),
+ output,
+ points,
+ },
+ ) in data.demands.iter().enumerate()
+ {
+ let color = "#c4422b";
+ println!(
+ "d{di} [label=\"Demand\\ntakes {duration}s\\n{points} points\" shape=box color={color:?} fillcolor={color:?} style=filled]",
+ );
+ println!("i{input} -> d{di}");
+ if let Some(ItemIndex(output)) = output {
+ println!("d{di} -> i{output}");
+ }
+ }
+
+ println!("}}");
+ Ok(())
+}
diff --git a/server/tools/src/graph_summary.rs b/server/tools/src/graph_summary.rs
new file mode 100644
index 00000000..be53e768
--- /dev/null
+++ b/server/tools/src/graph_summary.rs
@@ -0,0 +1,149 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2025 Hurry Curry! Contributors
+
+ 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 <https://www.gnu.org/licenses/>.
+
+*/
+use anyhow::Result;
+use hurrycurry_protocol::{ItemIndex, Recipe, TileIndex};
+use hurrycurry_server::data::DataIndex;
+use std::collections::HashSet;
+
+pub(crate) fn graph_summary() -> Result<()> {
+ let mut index = DataIndex::default();
+ index.reload()?;
+
+ println!("digraph {{");
+
+ let (data, sdata, _) = index.generate("5star")?;
+
+ struct Node {
+ inputs: Vec<ItemIndex>,
+ outputs: Vec<ItemIndex>,
+ tool_item: Option<ItemIndex>,
+ tool_tile: Option<TileIndex>,
+ instant: bool,
+ demand: bool,
+ }
+
+ let map_items = sdata
+ .initial_map
+ .iter()
+ .flat_map(|(_, (_, i))| *i)
+ .collect::<HashSet<_>>();
+
+ 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::<ItemIndex>::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(())
+}
diff --git a/server/tools/src/main.rs b/server/tools/src/main.rs
new file mode 100644
index 00000000..bb8fbde2
--- /dev/null
+++ b/server/tools/src/main.rs
@@ -0,0 +1,46 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2025 Hurry Curry! Contributors
+
+ 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 <https://www.gnu.org/licenses/>.
+
+*/
+
+pub mod book;
+pub mod diagram_layout;
+pub mod graph;
+pub mod graph_summary;
+pub mod recipe_diagram;
+
+use crate::{book::book, graph::graph, graph_summary::graph_summary};
+use anyhow::Result;
+use clap::Parser;
+
+#[derive(Parser)]
+enum Action {
+ Graph,
+ GraphSummary,
+ Book,
+}
+
+fn main() -> Result<()> {
+ let action = Action::parse();
+
+ match action {
+ Action::Graph => graph()?,
+ Action::GraphSummary => graph_summary()?,
+ Action::Book => book()?,
+ }
+
+ Ok(())
+}
diff --git a/server/tools/src/recipe_diagram.rs b/server/tools/src/recipe_diagram.rs
new file mode 100644
index 00000000..25f8040c
--- /dev/null
+++ b/server/tools/src/recipe_diagram.rs
@@ -0,0 +1,112 @@
+/*
+ Hurry Curry! - a game about cooking
+ Copyright (C) 2025 Hurry Curry! Contributors
+
+ 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 <https://www.gnu.org/licenses/>.
+
+*/
+
+use anyhow::Result;
+use hurrycurry_protocol::book::{Diagram, DiagramEdge, DiagramNode};
+use hurrycurry_protocol::glam::Vec2;
+use hurrycurry_protocol::{ItemIndex, Message, RecipeIndex};
+use hurrycurry_server::data::DataIndex;
+use std::collections::{BTreeMap, BTreeSet, HashSet};
+
+pub(crate) fn recipe_diagram(target_items: &[String]) -> Result<Diagram> {
+ let mut index = DataIndex::default();
+ index.reload()?;
+
+ let (data, serverdata, _) = index.generate("5star")?;
+
+ let ambient_items = serverdata
+ .initial_map
+ .iter()
+ .flat_map(|(_, (_, item))| item)
+ .copied()
+ .collect::<HashSet<_>>();
+
+ let mut need = BTreeSet::from_iter(
+ target_items
+ .iter()
+ .map(|name| data.get_item_by_name(name).unwrap()),
+ );
+ let mut have = BTreeSet::<ItemIndex>::new();
+ let mut recipes = BTreeSet::new();
+
+ #[derive(PartialEq, PartialOrd, Eq, Ord)]
+ struct GraphRecipe {
+ index: RecipeIndex,
+ inputs: Vec<ItemIndex>,
+ outputs: Vec<ItemIndex>,
+ }
+
+ while let Some(item) = need.pop_last() {
+ for (ri, r) in data.recipes() {
+ if r.outputs().contains(&item) {
+ let gr = GraphRecipe {
+ inputs: r
+ .inputs()
+ .iter()
+ .filter(|i| !ambient_items.contains(i))
+ .copied()
+ .collect(),
+ outputs: r
+ .outputs()
+ .iter()
+ .filter(|i| !ambient_items.contains(i))
+ .copied()
+ .collect(),
+ index: ri,
+ };
+ need.extend(&gr.inputs);
+ have.extend(&gr.outputs);
+ recipes.insert(gr);
+ }
+ }
+ }
+
+ let mut diag = Diagram::default();
+ let mut item_index = BTreeMap::new();
+ for i in have {
+ item_index.insert(i, diag.nodes.len());
+ diag.nodes.push(DiagramNode {
+ label: Message::Item(i),
+ position: Vec2::ZERO,
+ });
+ }
+ for r in recipes {
+ let index = diag.nodes.len();
+ diag.nodes.push(DiagramNode {
+ position: Vec2::ZERO,
+ label: Message::Text("blub".to_string()),
+ });
+
+ for i in r.inputs {
+ diag.edges.push(DiagramEdge {
+ src: item_index[&i],
+ dst: index,
+ label: None,
+ });
+ }
+ for o in r.outputs {
+ diag.edges.push(DiagramEdge {
+ src: index,
+ dst: item_index[&o],
+ label: None,
+ });
+ }
+ }
+
+ Ok(diag)
+}