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}