akinator/
async_akinator.rs

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