Skip to main content

yog_ui/
inventory_ids.rs

1//! The ids inventory widgets carry, written and read in one place: a
2//! slot's `<SLOT_PREFIX><index>` (and `=<accepts>`, `|`-joined, when it
3//! only takes some items) and a machine marker's
4//! `<MACHINE_PREFIX><kind>;<result slot>;` then its places, `,`-joined
5//! `<kind's slot>/<index>=<inventory slot>`. A screen's widgets write them
6//! (`widget::slot`, `widget::machine`); yog-kit reads them back to make
7//! the inventory the game opens.
8
9pub use yog_ids::yog::{MACHINE_PREFIX, PLAYER_INVENTORY, SLOT_PREFIX};
10
11/// What follows a slot's index when it only takes some items, and what joins those.
12const ACCEPTS: char = '=';
13const ACCEPTS_SEP: &str = "|";
14/// What separates a machine's kind, result and places; the places; a
15/// place's slot from its index, and that from its inventory slot.
16const PART_SEP: char = ';';
17const PLACE_SEP: &str = ",";
18const PLACE_INDEX: char = '/';
19const PLACE_INV: char = '=';
20/// A machine id's parts: kind, result, places.
21const MACHINE_PARTS: usize = 3;
22
23/// Slot `index`'s id, taking only `accepts` (none: anything).
24pub fn slot_id(index: usize, accepts: &[&str]) -> String {
25    match accepts {
26        [] => format!("{SLOT_PREFIX}{index}"),
27        _ => format!("{SLOT_PREFIX}{index}{ACCEPTS}{}", accepts.join(ACCEPTS_SEP)),
28    }
29}
30
31/// A slot's index and what it accepts, if `id` is a slot's.
32pub fn read_slot(id: &str) -> Option<(usize, Vec<String>)> {
33    let rest = id.strip_prefix(SLOT_PREFIX)?;
34    let (index, accepts) = rest.split_once(ACCEPTS).unwrap_or((rest, ""));
35    let accepts = accepts
36        .split(ACCEPTS_SEP)
37        .filter(|a| !a.is_empty())
38        .map(str::to_owned)
39        .collect();
40    Some((index.parse().ok()?, accepts))
41}
42
43/// A machine making recipes of `kind`: its places (`(kind's slot, index,
44/// inventory slot)`) and its result slot.
45#[derive(Debug, Clone, PartialEq)]
46pub struct MachineId {
47    pub kind: String,
48    pub places: Vec<(String, usize, usize)>,
49    pub result: usize,
50}
51
52impl MachineId {
53    pub fn write(kind: &str, places: &[(&str, usize, usize)], result: usize) -> String {
54        let places: Vec<String> = places
55            .iter()
56            .map(|(s, i, inv)| format!("{s}{PLACE_INDEX}{i}{PLACE_INV}{inv}"))
57            .collect();
58        format!(
59            "{MACHINE_PREFIX}{kind}{PART_SEP}{result}{PART_SEP}{}",
60            places.join(PLACE_SEP)
61        )
62    }
63
64    /// The machine `id` names, if it's a machine's.
65    pub fn read(id: &str) -> Option<MachineId> {
66        let mut parts = id
67            .strip_prefix(MACHINE_PREFIX)?
68            .splitn(MACHINE_PARTS, PART_SEP);
69        let kind = parts.next()?.to_string();
70        let result = parts.next()?.parse().ok()?;
71        let places = parts
72            .next()
73            .unwrap_or_default()
74            .split(PLACE_SEP)
75            .filter(|p| !p.is_empty())
76            .filter_map(|p| {
77                let (slot, rest) = p.split_once(PLACE_INDEX)?;
78                let (index, inv) = rest.split_once(PLACE_INV)?;
79                Some((slot.to_string(), index.parse().ok()?, inv.parse().ok()?))
80            })
81            .collect();
82        Some(MachineId {
83            kind,
84            places,
85            result,
86        })
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn ids_read_back_as_written() {
96        assert_eq!(read_slot(&slot_id(3, &[])), Some((3, vec![])));
97        assert_eq!(
98            read_slot(&slot_id(4, &["a:b", "#c:d"])),
99            Some((4, vec!["a:b".into(), "#c:d".into()]))
100        );
101        let id = MachineId::write("m:k", &[("in", 0, 1), ("in", 1, 2)], 5);
102        assert_eq!(
103            MachineId::read(&id),
104            Some(MachineId {
105                kind: "m:k".into(),
106                places: vec![("in".into(), 0, 1), ("in".into(), 1, 2)],
107                result: 5
108            })
109        );
110        assert_eq!(read_slot("other"), None);
111    }
112}