Skip to main content

rbp_server/analysis/
cli.rs

1//! Interactive CLI for poker analysis.
2//!
3//! Provides commands for type conversions and database queries.
4use super::*;
5use clap::Parser;
6use rbp_cards::*;
7use rbp_gameplay::*;
8use std::io::Write;
9
10pub struct CLI(API);
11
12impl From<API> for CLI {
13    fn from(api: API) -> Self {
14        Self(api)
15    }
16}
17
18impl CLI {
19    pub async fn run() -> () {
20        log::info!("entering analysis");
21        let cli = Self(API::from(rbp_database::db().await));
22        loop {
23            print!("> ");
24            let ref mut input = String::new();
25            std::io::stdout().flush().unwrap();
26            std::io::stdin().read_line(input).unwrap();
27            match input.trim() {
28                "quit" => break,
29                "exit" => break,
30                _ => match cli.handle(input).await {
31                    Err(e) => eprintln!("{}", e),
32                    Ok(_) => continue,
33                },
34            }
35        }
36    }
37    async fn handle(&self, input: &str) -> Result<(), Box<dyn std::error::Error>> {
38        match Query::try_parse_from(std::iter::once("> ").chain(input.split_whitespace()))? {
39            Query::Abstraction { target } => {
40                if let Ok(obs) = Observation::try_from(target.as_str()) {
41                    return Ok(println!("{}", self.0.obs_to_abs(obs).await?));
42                }
43                Err("invalid abstraction target".into())
44            }
45            Query::Distance { target1, target2 } => {
46                if let (Ok(o1), Ok(o2)) = (
47                    Observation::try_from(target1.as_str()),
48                    Observation::try_from(target2.as_str()),
49                ) {
50                    return Ok(println!("{:.4}", self.0.obs_distance(o1, o2).await?));
51                }
52                if let (Ok(a1), Ok(a2)) = (
53                    Abstraction::try_from(target1.as_str()),
54                    Abstraction::try_from(target2.as_str()),
55                ) {
56                    return Ok(println!("{:.4}", self.0.abs_distance(a1, a2).await?));
57                }
58                Err("invalid distance targets".into())
59            }
60            Query::Equity { target } => {
61                if let Ok(obs) = Observation::try_from(target.as_str()) {
62                    return Ok(println!("{:.4}", self.0.obs_equity(obs).await?));
63                }
64                if let Ok(abs) = Abstraction::try_from(target.as_str()) {
65                    return Ok(println!("{:.4}", self.0.abs_equity(abs).await?));
66                }
67                Err("invalid equity target".into())
68            }
69            Query::Population { target } => {
70                if let Ok(obs) = Observation::try_from(target.as_str()) {
71                    return Ok(println!("{}", self.0.obs_population(obs).await?));
72                }
73                if let Ok(abs) = Abstraction::try_from(target.as_str()) {
74                    return Ok(println!("{}", self.0.abs_population(abs).await?));
75                }
76                Err("invalid population target".into())
77            }
78            Query::Similar { target } => {
79                if let Ok(obs) = Observation::try_from(target.as_str()) {
80                    let members = self
81                        .0
82                        .obs_similar(obs)
83                        .await?
84                        .iter()
85                        .map(|obs| (obs, Strength::from(Hand::from(*obs))))
86                        .map(|(o, s)| format!(" - {:<18} {}", o, s))
87                        .collect::<Vec<String>>()
88                        .join("\n");
89                    return Ok(println!("{}", members));
90                }
91                if let Ok(abs) = Abstraction::try_from(target.as_str()) {
92                    let members = self
93                        .0
94                        .abs_similar(abs)
95                        .await?
96                        .iter()
97                        .map(|obs| (obs, Strength::from(Hand::from(*obs))))
98                        .map(|(o, s)| format!(" - {:<18} {}", o, s))
99                        .collect::<Vec<String>>()
100                        .join("\n");
101                    return Ok(println!("{}", members));
102                }
103                Err("invalid similarity target".into())
104            }
105            Query::Nearby { target } => {
106                if let Ok(obs) = Observation::try_from(target.as_str()) {
107                    let neighborhood = self
108                        .0
109                        .obs_nearby(obs)
110                        .await?
111                        .iter()
112                        .enumerate()
113                        .map(|(i, (abs, dist))| format!("{:>2}. {} ({:.4})", i + 1, abs, dist))
114                        .collect::<Vec<String>>()
115                        .join("\n");
116                    return Ok(println!("{}", neighborhood));
117                }
118                if let Ok(abs) = Abstraction::try_from(target.as_str()) {
119                    let neighborhood = self
120                        .0
121                        .abs_nearby(abs)
122                        .await?
123                        .iter()
124                        .enumerate()
125                        .map(|(i, (abs, dist))| format!("{:>2}. {} ({:.4})", i + 1, abs, dist))
126                        .collect::<Vec<String>>()
127                        .join("\n");
128                    return Ok(println!("{}", neighborhood));
129                }
130                Err("invalid neighborhood target".into())
131            }
132            Query::Composition { target } => {
133                if let Ok(obs) = Observation::try_from(target.as_str()) {
134                    let distribution = self
135                        .0
136                        .obs_histogram(obs)
137                        .await?
138                        .distribution()
139                        .iter()
140                        .enumerate()
141                        .map(|(i, (abs, dist))| format!("{:>2}. {} ({:.4})", i + 1, abs, dist))
142                        .collect::<Vec<String>>()
143                        .join("\n");
144                    return Ok(println!("{}", distribution));
145                }
146                if let Ok(abs) = Abstraction::try_from(target.as_str()) {
147                    let distribution = self
148                        .0
149                        .abs_histogram(abs)
150                        .await?
151                        .distribution()
152                        .iter()
153                        .enumerate()
154                        .map(|(i, (abs, dist))| format!("{:>2}. {} ({:.4})", i + 1, abs, dist))
155                        .collect::<Vec<String>>()
156                        .join("\n");
157                    return Ok(println!("{}", distribution));
158                }
159                Err("invalid histogram target".into())
160            }
161            Query::Path { value } => {
162                let path = Path::from(value);
163                println!("Path({})", value);
164                println!("  Display:  {}", path);
165                println!("  Length:   {}", path.length());
166                println!("  Aggro:    {}", path.aggression());
167                println!("  Edges:    {:?}", Vec::<Edge>::from(path));
168                Ok(())
169            }
170            Query::Edge { value } => {
171                let edge = Edge::from(value);
172                println!("Edge({})", value);
173                println!("  Display:  {}", edge);
174                println!("  Is choice: {}", edge.is_choice());
175                println!("  Is aggro:  {}", edge.is_aggro());
176                Ok(())
177            }
178            Query::AbsFromInt { value } => {
179                let abs = Abstraction::from(value);
180                println!("Abstraction({})", value);
181                println!("  Display:  {}", abs);
182                println!("  Street:   {}", abs.street());
183                println!("  Index:    {}", abs.index());
184                Ok(())
185            }
186            Query::ObsFromInt { value } => {
187                println!("Observation({})", value);
188                match std::panic::catch_unwind(|| Observation::from(value)) {
189                    Ok(obs) => {
190                        println!("  Display:  {}", obs);
191                        println!("  Street:   {}", obs.street());
192                        println!("  i64:      {}", i64::from(obs));
193                        Ok(())
194                    }
195                    Err(_) => {
196                        println!("  Error: Invalid observation encoding (assertions failed)");
197                        println!("  Note: Observations require valid poker hand representations");
198                        Ok(())
199                    }
200                }
201            }
202            Query::Isomorphism { value } => {
203                println!("Isomorphism({})", value);
204                match std::panic::catch_unwind(|| {
205                    let iso = Isomorphism::from(value);
206                    let obs = Observation::from(iso);
207                    (iso, obs)
208                }) {
209                    Ok((iso, obs)) => {
210                        println!("  Observation: {}", obs);
211                        println!("  Street:      {}", obs.street());
212                        println!("  i64:         {}", i64::from(iso));
213                        Ok(())
214                    }
215                    Err(_) => {
216                        println!("  Error: Invalid isomorphism encoding (assertions failed)");
217                        println!("  Note: Isomorphisms require valid poker hand representations");
218                        Ok(())
219                    }
220                }
221            }
222        }
223    }
224}