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}