akinator/blocking_akinator.rs
1use crate::{
2 enums::{
3 Theme,
4 Answer,
5 Language,
6 },
7 error::Error,
8 models::Guess,
9};
10
11use akinator_rs::Akinator as AkinatorStruct;
12use lazy_static::lazy_static;
13use tokio::runtime::Runtime;
14use pyo3::prelude::*;
15
16lazy_static! {
17 static ref RUNTIME: Runtime = Runtime::new().unwrap();
18}
19
20
21/// Represents an akinator game
22///
23/// Parameters are also set as properties which also have a setter to change the values if necessary in the future
24///
25/// Parameters
26/// ----------
27/// theme : Optional[:class:`Theme`]
28/// the theme of the akinator game, would be one of ``Characters``, ``Animals`` or ``Objects``
29/// pass in using an answer enum, using the ``from_str`` classmethod if necessary, defaults to ``Characters``
30/// language : Optional[:class:`Language`]
31/// the language for the akinator game, refer to the ``Language`` enum
32/// child_mode : Optional[:class:`bool`]
33/// when set to ``True``, NSFW content will not be provided
34#[pyclass]
35#[derive(Debug, Clone)]
36#[pyo3(text_signature = "(*, theme, language, child_mode)")]
37pub struct Akinator(
38 AkinatorStruct,
39);
40
41#[pymethods]
42impl Akinator {
43 #[new]
44 #[args("*", theme, language, child_mode)]
45 fn constructor(
46 theme: Option<Theme>,
47 language: Option<Language>,
48 child_mode: Option<bool>,
49 ) -> Self {
50 let mut akinator =
51 AkinatorStruct::new();
52
53 if let Some(theme) = theme {
54 akinator = akinator.with_theme(theme.into());
55 }
56
57 if let Some(language) = language {
58 akinator = akinator.with_language(language.into());
59 }
60
61 if child_mode.unwrap_or(false) {
62 akinator = akinator.with_child_mode();
63 }
64
65 Self(akinator)
66 }
67
68 fn __repr__(&self) -> String {
69 format!(
70 "<Akinator theme=\"{:?}\" language=\"{:?}\" child_mode={}>",
71 self.theme(),
72 self.language(),
73 self.child_mode(),
74 )
75 }
76
77 /// Starts the akinator game
78 /// and returns the first question
79 ///
80 /// Returns
81 /// -------
82 /// Optional[:class:`str`]
83 ///
84 /// Raises
85 /// ------
86 /// :class:`RuntimeError`
87 /// Something internal went wrong, this could be in this case:
88 /// - getting the starting timestamp failed
89 /// - the data required to start the game such as the server url, frontaddr or game UID could not be found
90 /// - request error: any sort of error when making the HTTP requests
91 /// - updating the internal data fields errored (either a field was missing or was of the wrong type)
92 /// :class:`ValueError`
93 /// Could not parse the returned JSON properly (invalid, missing fields etc.)
94 /// ``Other api errors``
95 /// Refer to the exceptions at the bottom of the page
96 fn start_game<'a>(&'a mut self, _py: Python<'a>) -> PyResult<Option<String>> {
97 RUNTIME.block_on(
98 async move {
99 self.0.start().await
100 .map_err(|e| Error::from(e).into())
101 }
102 )
103 }
104
105 /// Answers the akinator's current question
106 /// with the provided ``answer``
107 /// and returns the next question
108 ///
109 /// Parameters
110 /// ----------
111 /// answer : :class:`Answer`
112 /// the answer to the current question
113 ///
114 /// Returns
115 /// -------
116 /// Optional[:class:`str`]
117 ///
118 /// Raises
119 /// ------
120 /// :class:`RuntimeError`
121 /// Something internal went wrong, this could be in this case:
122 /// - missing required data to continue
123 /// - request error: any sort of error when making the HTTP requests
124 /// - updating the internal data fields errored (either a field was missing or was of the wrong type)
125 /// :class:`ValueError`
126 /// Could not parse the returned JSON properly (invalid, missing fields etc.)
127 /// ``Other api errors``
128 /// Refer to the exceptions at the bottom of the page
129 #[pyo3(text_signature = "(self, answer)")]
130 fn answer<'a>(&'a mut self, _py: Python<'a>, answer: Answer) -> PyResult<Option<String>> {
131 RUNTIME.block_on(
132 async move {
133 self.0.answer(answer.into()).await
134 .map_err(|e| Error::from(e).into())
135 }
136 )
137 }
138
139 /// Tells the akinator to end the game and make its guess
140 /// should be called once when the ``progression`` is high enough such as ``>=80.0``
141 /// and returns its best guess
142 ///
143 /// Returns
144 /// -------
145 /// Optional[:class:`Guess`]
146 ///
147 /// Raises
148 /// ------
149 /// :class:`RuntimeError`
150 /// Something internal went wrong, this could be in this case:
151 /// - missing required data to continue
152 /// - request error: any sort of error when making the HTTP requests
153 /// - updating the internal data fields errored (either a field was missing or was of the wrong type)
154 /// :class:`ValueError`
155 /// Could not parse the returned JSON properly (invalid, missing fields etc.)
156 /// ``Other api errors``
157 /// Refer to the exceptions at the bottom of the page
158 fn win<'a>(&'a mut self, _py: Python<'a>) -> PyResult<Option<Guess>> {
159 RUNTIME.block_on(
160 async move {
161 self.0.win().await
162 .map(|result| {
163 result.map(Guess)
164 })
165 .map_err(|e| Error::from(e).into())
166 }
167 )
168 }
169
170 /// Goes back a question
171 /// and returns said (current) question
172 ///
173 /// Returns
174 /// -------
175 /// Optional[:class:`str`]
176 ///
177 /// Raises
178 /// ------
179 /// :class:`CantGoBackAnyFurther`
180 /// Could not go back anymore, likely that we are already on the first question
181 /// :class:`RuntimeError`
182 /// Something internal went wrong, this could be in this case:
183 /// - missing required data to continue
184 /// - request error: any sort of error when making the HTTP requests
185 /// - updating the internal data fields errored (either a field was missing or was of the wrong type)
186 /// :class:`ValueError`
187 /// Could not parse the returned JSON properly (invalid, missing fields etc.)
188 /// ``Other api errors``
189 /// Refer to the exceptions at the bottom of the page
190 fn back<'a>(&'a mut self, _py: Python<'a>) -> PyResult<Option<String>> {
191 RUNTIME.block_on(
192 async move {
193 self.0.back().await
194 .map_err(|e| Error::from(e).into())
195 }
196 )
197 }
198
199 /// :class:`Theme`: the theme of the akinator game
200 #[getter]
201 fn theme(&self) -> Theme {
202 self.0.theme.into()
203 }
204
205 /// :class:`Language`: the language of the akinator game
206 #[getter]
207 fn language(&self) -> Language {
208 self.0.language.into()
209 }
210
211 /// :class:`bool`: whether ``child_mode`` is on or off for the akinator game
212 #[getter]
213 const fn child_mode(&self) -> bool {
214 self.0.child_mode
215 }
216
217 /// Optional[:class:`str`]: the current question of the akinator game
218 #[getter]
219 fn question(&self) -> Option<String> {
220 self.0.current_question.clone()
221 }
222
223 /// :class:`float`: the progression of the akinator
224 #[getter]
225 const fn progression(&self) -> f32 {
226 self.0.progression
227 }
228
229 /// :class:`int`: a counter for the question # the akinator is on currently
230 #[getter]
231 const fn step(&self) -> usize {
232 self.0.step
233 }
234
235 /// Optional[:class:`Guess`]: the akinator's best guess
236 #[getter]
237 fn first_guess(&self) -> Option<Guess> {
238 self.0.first_guess
239 .clone()
240 .map(Guess)
241 }
242
243 /// List[:class:`Guess`]: a list of all the akinator's potential guesses, ordered
244 #[getter]
245 fn guesses(&self) -> Vec<Guess> {
246 self.0.guesses
247 .clone()
248 .into_iter()
249 .map(Guess)
250 .collect()
251 }
252
253 /// property setter to set ``self.theme``
254 #[setter]
255 fn set_theme(&mut self, theme: Theme) {
256 self.0.theme = theme.into();
257 }
258
259 /// property setter to set ``self.language``
260 #[setter]
261 fn set_language(&mut self, language: Language) {
262 self.0.language = language.into();
263 }
264
265 /// property setter to set ``self.child_mode``
266 #[setter]
267 fn set_child_mode(&mut self, child_mode: bool) {
268 self.0.child_mode = child_mode;
269 }
270}