Skip to main content

deepwoken_reqparse/model/
req.rs

1use core::fmt;
2use std::{borrow::Borrow, collections::{BTreeSet, HashSet}};
3
4use crate::{Stat, util::statmap::StatMap};
5
6#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
7pub enum Reducability {
8    Reducible,
9    Strict,
10}
11
12impl fmt::Display for Reducability {
13    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
14        match self {
15            Reducability::Reducible => write!(f, "r"),
16            Reducability::Strict => write!(f, "s"),
17        }
18    }
19}
20
21pub type StatSet = BTreeSet<Stat>;
22
23#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
24pub struct Atom {
25    pub(crate) reducability: Reducability,
26    pub(crate) value: i64,
27    /// Stats to sum up to meet value (mostly will be a singular stat)
28    pub(crate) stats: StatSet,
29}
30
31impl Atom {
32    pub fn new(r: Reducability) -> Self {
33        Self {
34            reducability: r,
35            value: 0,
36            stats: BTreeSet::new(),
37        }
38    }
39
40    pub fn strict() -> Self {
41        Self {
42            reducability: Reducability::Strict,
43            value: 0,
44            stats: BTreeSet::new(),
45        }
46    }
47
48    pub fn reducible() -> Self {
49        Self {
50            reducability: Reducability::Reducible,
51            value: 0,
52            stats: BTreeSet::new(),
53        }
54    }
55
56    pub fn value(mut self, v: i64) -> Self {
57        self.value = v;
58        self
59    }
60
61    pub fn reducability(mut self, r: Reducability) -> Self {
62        self.reducability = r;
63        self
64    }
65
66    /// Adds a stat to the stat summation requirement.
67    pub fn stat(mut self, stat: Stat) -> Self {
68        self.stats.insert(stat);
69        self
70    }
71
72    pub fn add_stat(&mut self, stat: Stat) {
73        self.stats.insert(stat);
74    }
75
76    pub fn satisfied_by(&self, stats: &StatMap) -> bool {
77        let sum: i64 = self
78            .stats
79            .iter()
80            .map(|s| {
81                if s == &Stat::Total {
82                    stats.cost()
83                } else {
84                    stats.get(s)
85                }
86            })
87            .sum();
88
89        sum >= self.value
90    }
91
92    // is it trivially satisfied
93    pub fn is_empty(&self) -> bool {
94        self.stats.is_empty() && self.value == 0
95    }
96}
97
98impl fmt::Display for Atom {
99    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100        if self.stats.len() == 1 {
101            write!(
102                f, "{}{} {}", 
103                self.value, 
104                self.reducability, 
105                self.stats.first().unwrap().short_name()
106            )
107        } else {
108            // multi-stat (display as expr)
109            let sum_expr = self
110                .stats
111                .iter()
112                .map(|s| s.short_name().to_string())
113                .collect::<Vec<String>>()
114                .join(" + ");
115
116            write!(f, "{} = {}{}", sum_expr, self.value, self.reducability)
117        }
118    }
119}
120
121#[derive(Clone, Debug, PartialEq, Eq, Hash)]
122pub enum ClauseType {
123    And,
124    Or,
125}
126
127#[derive(Clone, Debug, Hash, PartialEq, Eq)]
128pub struct Clause {
129    pub(crate) clause_type: ClauseType,
130    pub(crate) atoms: BTreeSet<Atom>,
131}
132
133impl Clause {
134    pub fn new(clause_type: ClauseType) -> Self {
135        Self {
136            clause_type,
137            atoms: BTreeSet::new(),
138        }
139    }
140
141    pub fn and() -> Self {
142        Self {
143            clause_type: ClauseType::And,
144            atoms: BTreeSet::new(),
145        }
146    }
147
148    pub fn or() -> Self {
149        Self {
150            clause_type: ClauseType::Or,
151            atoms: BTreeSet::new(),
152        }
153    }
154
155    pub fn clause_type(mut self, ct: ClauseType) -> Self {
156        self.clause_type = ct;
157        self
158    }
159
160    pub fn atoms(&self) -> &BTreeSet<Atom> {
161        &self.atoms
162    }
163
164    pub fn atoms_mut(&mut self) -> &mut BTreeSet<Atom> {
165        &mut self.atoms
166    }
167
168    pub fn insert(mut self, stats: StatSet, mut atom: Atom) -> Self {
169        atom.stats = stats;
170        self.atoms.insert(atom);
171        self
172    }
173
174    pub fn atom(mut self, atom: Atom) -> Self {
175        self.atoms.insert(atom);
176        self
177    }
178
179    pub fn add_atom(&mut self, atom: Atom) {
180        self.atoms.insert(atom);
181    }
182
183    pub fn satisfied_by(&self, stats: &StatMap) -> bool {
184        match self.clause_type {
185            ClauseType::And => self.atoms.iter().all(|atom| atom.satisfied_by(stats)),
186            ClauseType::Or => self.atoms.iter().any(|atom| atom.satisfied_by(stats)),
187        }
188    }
189
190    pub fn is_empty(&self) -> bool {
191        !self.atoms().iter().any(|a| !a.is_empty())
192    }
193}
194
195impl fmt::Display for Clause {
196    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
197        let joiner = match self.clause_type {
198            ClauseType::And => ", ",
199            ClauseType::Or => " OR ",
200        };
201
202        let atom_strs: Vec<String> = self
203            .atoms
204            .iter()
205            .filter(|a| !a.is_empty())
206            .map(|atom| format!("{}", atom))
207            .collect();
208
209        write!(f, "{}", atom_strs.join(joiner))
210    }
211}
212
213#[derive(Clone, Debug, Hash)]
214pub struct Requirement {
215    // optional name for the req for referencing elsewhere
216    pub(crate) name: Option<String>,
217    // DIRECT prerequisites (does not include transitive)
218    pub(crate) prereqs: Vec<String>,
219
220    pub(crate) clauses: Vec<Clause>,
221}
222
223impl PartialEq for Requirement {
224    fn eq(&self, other: &Self) -> bool {
225        if self.clauses.len() != other.clauses.len() {
226            return false;
227        }
228
229        // a \subseteq b and b \subseteq a iff a = b ahh comparison
230        self.clauses.iter().all(|c| other.clauses.contains(c))
231            && other.clauses.iter().all(|c| self.clauses.contains(c))
232            && self.name_or_default() == other.name_or_default() 
233            // also check if string names are the same (yes names will matter now)
234    }
235}
236
237impl Eq for Requirement {}
238
239impl Requirement {
240    pub fn new() -> Self {
241        Self {
242            name: None,
243            prereqs: Vec::new(),
244            clauses: Vec::new(),
245        }
246    }
247
248    pub fn add_clause(&mut self, clause: Clause) -> &mut Self {
249        self.clauses.push(clause);
250        self
251    }
252
253    pub fn add_prereq(&mut self, prereq: &str) -> &mut Self {
254        self.prereqs.push(prereq.to_string());
255        self
256    }
257
258    pub fn name(&mut self, name: &str) -> &mut Self {
259        self.name = Some(name.to_string());
260        self
261    }
262
263    pub fn name_or_default(&self) -> String {
264        match &self.name {
265            Some(n) => n.clone(),
266            None => self.to_string(),
267        }
268    }
269
270    pub fn iter(&self) -> impl Iterator<Item = &Clause> {
271        self.clauses.iter()
272    }
273
274    pub fn and_iter(&self) -> impl Iterator<Item = &Clause> {
275        self.clauses
276            .iter()
277            .filter(|c| c.clause_type == ClauseType::And)
278    }
279
280    pub fn or_iter(&self) -> impl Iterator<Item = &Clause> {
281        self.clauses
282            .iter()
283            .filter(|c| c.clause_type == ClauseType::Or)
284    }
285
286    pub fn atoms(&self) -> impl Iterator<Item = &Atom> {
287        self.clauses.iter().flat_map(|clause| clause.atoms.iter())
288    }
289
290    pub fn add_to_all(&mut self, val: i64) -> &mut Self {
291        // construct new atoms
292        for clause in &mut self.clauses {
293            clause.atoms = clause
294                .atoms
295                .iter()
296                .map(|atom| {
297                    let mut new_atom = atom.clone();
298                    new_atom.value += val;
299                    new_atom.value = new_atom.value.clamp(0, 100);
300                    new_atom
301                })
302                .collect();
303        }
304        self
305    }
306
307    pub fn strict_atoms(&self) -> impl Iterator<Item = &Atom> {
308        self.clauses.iter().flat_map(|clause| {
309            clause
310                .atoms
311                .iter()
312                .filter(|atom| atom.reducability == Reducability::Strict)
313        })
314    }
315
316    /// Grab all the stats present in a requirement
317    pub fn used_stats(&self) -> HashSet<Stat> {
318        self.atoms().fold(HashSet::new(), |mut acc, atom| {
319            for stat in &atom.stats {
320                if stat == &Stat::Total {
321                    continue;
322                }
323
324                acc.insert(stat.clone());
325            }
326            acc
327        })
328    }
329
330    pub fn satisfied_by(&self, stats: &StatMap) -> bool {
331        self.clauses.iter().all(|clause| clause.satisfied_by(stats))
332    }
333
334    /// The requirement requires nothing and is therefore trivially satisfied (wow!)
335    pub fn is_empty(&self) -> bool {
336        !self.clauses.iter().any(|c| !c.is_empty())
337    }
338}
339
340impl From<Clause> for Requirement {
341    fn from(clause: Clause) -> Self {
342        Self {
343            name: None,
344            prereqs: Vec::new(),
345            clauses: vec![clause],
346        }
347    }
348}
349
350impl fmt::Display for Requirement {
351    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
352        if !self.prereqs.is_empty() {
353            write!(f, "{} => ", self.prereqs.join(", "))?;
354        }
355        if let Some(name) = &self.name {
356            write!(f, "{} := ", name)?;
357        }
358        if self.is_empty() {
359            write!(f, "()")
360        } else {
361            let clause_strs: Vec<String> = self
362                .clauses
363                .iter()
364                .filter(|clause| !clause.is_empty())
365                .map(|clause| clause.to_string())
366                .collect();
367
368            write!(f, "{}", clause_strs.join(", "))
369        }
370    }
371}
372
373// Utility for dealing with a group of reqs
374pub trait ReqVecExt {
375    fn map_names<F>(&mut self, f: F) 
376    where
377        F: Fn(&str) -> String;
378}
379
380impl ReqVecExt for Vec<Requirement> {
381    fn map_names<F>(&mut self, f: F)
382    where
383        F: Fn(&str) -> String,
384    {
385        for req in self.iter_mut() {
386            req.name = req.name.as_ref().map(|name| f(&name));
387
388            req.prereqs = req.prereqs.iter().map(|name| f(&name)).collect();
389        }
390    }
391}
392
393pub trait ReqIterExt {
394    fn max_map(self) -> StatMap;
395
396    fn max_total_req(self) -> i64;
397}
398
399impl<I> ReqIterExt for I
400where
401    I: Iterator,
402    I::Item: Borrow<Requirement>, 
403{
404    fn max_map(self) -> StatMap {
405        let mut maxes: StatMap = StatMap::new();
406
407        for req in self {
408            let req = req.borrow();
409
410            for atom in req.atoms() {
411                for &stat in &atom.stats {
412                    if stat == Stat::Total {
413                        continue;
414                    }
415
416                    // TODO! we cant do a trivial per-stat max here,
417                    // bc of sum reqs.
418                    maxes
419                        .entry(stat)
420                        .and_modify(|cur| *cur = (*cur).max(atom.value))
421                        .or_insert(atom.value);
422                }
423            }
424        }
425
426        maxes
427    }
428
429    fn max_total_req(self) -> i64 {
430        let mut max: i64 = 0;
431
432        for req in self {
433            let req = req.borrow();
434
435            for atom in req.atoms() {
436                if atom.stats.contains(&Stat::Total) {
437                    max = max.max(atom.value);
438                }
439            }
440        }
441
442        max
443    }
444}
445
446#[derive(Clone, Copy, Debug)]
447pub enum Timing {
448    Free,
449    Post,
450}