race_api/
effect.rs

1//! Effect for game handler
2
3use std::collections::HashMap;
4
5use borsh::{BorshDeserialize, BorshSerialize};
6
7use crate::{
8    engine::GameHandler,
9    error::{Error, HandleError, Result},
10    random::RandomSpec,
11    types::{DecisionId, RandomId, Settle, Transfer},
12};
13
14#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)]
15pub struct Ask {
16    pub player_addr: String,
17}
18
19#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)]
20pub struct Assign {
21    pub random_id: RandomId,
22    pub player_addr: String,
23    pub indexes: Vec<usize>,
24}
25
26#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)]
27pub struct Reveal {
28    pub random_id: RandomId,
29    pub indexes: Vec<usize>,
30}
31
32#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)]
33pub struct Release {
34    pub decision_id: DecisionId,
35}
36
37#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)]
38pub struct ActionTimeout {
39    pub player_addr: String,
40    pub timeout: u64,
41}
42
43/// An effect used in game handler provides reading and mutating to
44/// the game context.  An effect can be created from game context,
45/// manipulated by game handler and applied after event processing.
46///
47/// # Num of Players and Servers
48///
49/// [`Effect::count_players`] and [`Effect::count_servers`] return the total number of
50/// players and servers, respectively. The number includes those with pending status.
51/// These functions are useful when detecting if there's enough players/servers for
52/// a game to start.
53///
54/// # Randomness
55///
56/// To create a randomness, use [`Effect::init_random_state`] with a [`RandomSpec`].
57///
58/// ```
59/// # use race_api::effect::Effect;
60/// use race_api::random::RandomSpec;
61/// let mut effect = Effect::default();
62/// let random_spec = RandomSpec::deck_of_cards();
63/// let random_id = effect.init_random_state(random_spec);
64/// ```
65///
66/// To assign some items of the randomness to a specific player, use [`Effect::assign`].
67/// It makes those items visible only to this player.
68///
69/// ```
70/// # use race_api::effect::Effect;
71/// let mut effect = Effect::default();
72/// effect.assign(1 /* random_id */, "Alice", vec![0, 1, 2] /* indexes */);
73/// ```
74/// To reveal some items to the public, use [`Effect::reveal`].
75/// It makes those items visible to everyone, including servers.
76///
77/// ```
78/// # use race_api::effect::Effect;
79/// let mut effect = Effect::default();
80/// effect.reveal(1 /* random_id */, vec![0, 1, 2] /* indexes */);
81/// ```
82///
83/// # Decisions
84///
85/// To prompt a player for an hidden, immutable decision, use [`Effect::prompt`].
86///
87/// ```
88/// # use race_api::effect::Effect;
89/// let mut effect = Effect::default();
90/// let decision_id = effect.ask("Alice");
91/// ```
92///
93/// To reveal the answer, use [`Effect::reveal_answer`].
94///
95/// ```
96/// # use race_api::effect::Effect;
97/// let mut effect = Effect::default();
98/// effect.release(1 /* decision_id */);
99/// ```
100///
101/// # Timeouts
102///
103/// Two types of timeout event can be dispatched: `action_timeout` and
104/// `wait_timeout`.
105///
106/// - Action Timeout:
107/// Represent a player doesn't act in time, a player address is
108/// required in this case.
109///
110/// - Wait Timeout:
111/// Represent a general waiting. It's useful when you want to start a
112/// game in a certain timeout, regardless of how many players are
113/// available.
114///
115/// # Settle
116///
117/// Add settlements with [`Effect::settle`].
118///
119/// ```
120/// # use race_api::effect::Effect;
121/// use race_api::types::Settle;
122/// let mut effect = Effect::default();
123/// // Increase assets
124/// effect.settle(Settle::add("Alice", 100));
125/// // Decrease assets
126/// effect.settle(Settle::sub("Bob", 200));
127/// // Remove player from this game, its assets will be paid out
128/// effect.settle(Settle::eject("Charlie"));
129/// ```
130
131#[cfg_attr(test, derive(PartialEq, Eq))]
132#[derive(Default, BorshSerialize, BorshDeserialize, Debug)]
133pub struct Effect {
134    pub action_timeout: Option<ActionTimeout>,
135    pub wait_timeout: Option<u64>,
136    pub start_game: bool,
137    pub stop_game: bool,
138    pub cancel_dispatch: bool,
139    pub timestamp: u64,
140    pub curr_random_id: RandomId,
141    pub curr_decision_id: DecisionId,
142    pub players_count: u16,
143    pub servers_count: u16,
144    pub asks: Vec<Ask>,
145    pub assigns: Vec<Assign>,
146    pub reveals: Vec<Reveal>,
147    pub releases: Vec<Release>,
148    pub init_random_states: Vec<RandomSpec>,
149    pub revealed: HashMap<RandomId, HashMap<usize, String>>,
150    pub answered: HashMap<DecisionId, String>,
151    pub is_checkpoint: bool,
152    pub checkpoint: Option<Vec<u8>>,
153    pub settles: Vec<Settle>,
154    pub handler_state: Option<Vec<u8>>,
155    pub error: Option<HandleError>,
156    pub allow_exit: bool,
157    pub transfers: Vec<Transfer>,
158}
159
160impl Effect {
161    /// Return the number of players, including both the pending and joined.
162    pub fn count_players(&self) -> usize {
163        self.players_count as usize
164    }
165
166    /// Return the number of servers, including both the pending and joined.
167    pub fn count_servers(&self) -> usize {
168        self.servers_count as usize
169    }
170
171    /// Initialize a random state with random spec, return random id.
172    pub fn init_random_state(&mut self, spec: RandomSpec) -> RandomId {
173        self.init_random_states.push(spec);
174        let random_id = self.curr_random_id;
175        self.curr_random_id += 1;
176        random_id
177    }
178
179    /// Assign some random items to a specific player.
180    pub fn assign<S: Into<String>>(
181        &mut self,
182        random_id: RandomId,
183        player_addr: S,
184        indexes: Vec<usize>,
185    ) {
186        self.assigns.push(Assign {
187            random_id,
188            player_addr: player_addr.into(),
189            indexes,
190        })
191    }
192
193    /// Reveal some random items to the public.
194    pub fn reveal(&mut self, random_id: RandomId, indexes: Vec<usize>) {
195        self.reveals.push(Reveal { random_id, indexes })
196    }
197
198    /// Return the revealed random items by id.
199    ///
200    /// Return [`Error::RandomnessNotRevealed`] when invalid random id is given.
201    pub fn get_revealed(&self, random_id: RandomId) -> Result<&HashMap<usize, String>> {
202        self.revealed
203            .get(&random_id)
204            .ok_or(Error::RandomnessNotRevealed)
205    }
206
207    /// Return the answer of a decision by id.
208    ///
209    /// Return [`Error::AnswerNotAvailable`] when invalid decision id
210    /// is given or the answer is not ready.
211    pub fn get_answer(&self, decision_id: DecisionId) -> Result<&str> {
212        if let Some(a) = self.answered.get(&decision_id) {
213            Ok(a.as_ref())
214        } else {
215            Err(Error::AnswerNotAvailable)
216        }
217    }
218
219    /// Ask a player for a decision, return the new decision id.
220    pub fn ask<S: Into<String>>(&mut self, player_addr: S) -> DecisionId {
221        self.asks.push(Ask {
222            player_addr: player_addr.into(),
223        });
224        let decision_id = self.curr_decision_id;
225        self.curr_decision_id += 1;
226        decision_id
227    }
228
229    pub fn release(&mut self, decision_id: DecisionId) {
230        self.releases.push(Release { decision_id })
231    }
232
233    /// Dispatch action timeout event for a player after certain milliseconds.
234    pub fn action_timeout<S: Into<String>>(&mut self, player_addr: S, timeout: u64) {
235        self.action_timeout = Some(ActionTimeout {
236            player_addr: player_addr.into(),
237            timeout,
238        });
239    }
240
241    /// Return current timestamp.
242    ///
243    /// The event handling must be pure, so it's not allowed to use
244    /// timestamp from system API.
245    pub fn timestamp(&self) -> u64 {
246        self.timestamp
247    }
248
249    /// Dispatch waiting timeout event after certain milliseconds.
250    pub fn wait_timeout(&mut self, timeout: u64) {
251        self.wait_timeout = Some(timeout);
252    }
253
254    /// Start the game.
255    pub fn start_game(&mut self) {
256        self.start_game = true;
257    }
258
259    /// Stop the game.
260    pub fn stop_game(&mut self) {
261        self.stop_game = true;
262    }
263
264    /// Set if exiting game is allowed.
265    pub fn allow_exit(&mut self, allow_exit: bool) {
266        self.allow_exit = allow_exit
267    }
268
269
270    pub fn checkpoint(&mut self) {
271        self.is_checkpoint = true;
272    }
273
274    /// Submit settlements.
275    pub fn settle(&mut self, settle: Settle) {
276        self.settles.push(settle);
277    }
278
279    /// Transfer the assets to a recipient slot
280    pub fn transfer(&mut self, slot_id: u8, amount: u64) {
281        self.transfers.push(Transfer { slot_id, amount });
282    }
283
284    /// Get handler state.
285    ///
286    /// This is an internal function, DO NOT use in game handler.
287    pub fn __handler_state<S>(&self) -> S
288    where
289        S: GameHandler,
290    {
291        S::try_from_slice(self.handler_state.as_ref().unwrap()).unwrap()
292    }
293
294    /// Set handler state.
295    ///
296    /// This is an internal function, DO NOT use in game handler.
297    pub fn __set_handler_state<S: BorshSerialize>(&mut self, handler_state: S) {
298        if let Ok(state) = handler_state.try_to_vec() {
299            self.handler_state = Some(state);
300        } else {
301            self.error = Some(HandleError::SerializationError);
302        }
303    }
304
305    /// Set checkpoint.
306    ///
307    /// This is an internal function, DO NOT use in game handler.
308    pub fn __set_checkpoint<S: BorshSerialize>(&mut self, checkpoint_state: S) {
309        if let Ok(state) = checkpoint_state.try_to_vec() {
310            self.checkpoint = Some(state);
311        } else {
312            self.error = Some(HandleError::SerializationError);
313        }
314    }
315
316    pub fn __set_checkpoint_raw(&mut self, raw: Vec<u8>) {
317        self.checkpoint = Some(raw);
318    }
319
320    pub fn __checkpoint(&mut self) -> Option<Vec<u8>> {
321        std::mem::replace(&mut self.checkpoint, None)
322    }
323
324    /// Set error.
325    ///
326    /// This is an internal function, DO NOT use in game handler.
327    pub fn __set_error(&mut self, error: HandleError) {
328        self.error = Some(error);
329    }
330
331    /// Take error
332    ///
333    /// This is an internal function, DO NOT use in game handler.
334    pub fn __take_error(&mut self) -> Option<HandleError> {
335        std::mem::replace(&mut self.error, None)
336    }
337}
338
339#[cfg(test)]
340mod tests {
341
342    use super::*;
343
344    #[test]
345    fn abc() {
346        let data = vec![0,0,0,0,0,195,133,107,4,139,1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,2,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,103,0,0,0,0,0,0,0,0,0,0,0,16,39,0,0,0,0,0,0,32,78,0,0,0,0,0,0,32,78,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,1,0,43,0,0,0,70,97,105,108,101,100,32,116,111,32,102,105,110,100,32,97,32,112,108,97,121,101,114,32,102,111,114,32,116,104,101,32,110,101,120,116,32,98,117,116,116,111,110,1,0,0,0,0];
347        let effect = Effect::try_from_slice(&data);
348        println!("{:?}", effect);
349    }
350
351    #[test]
352    fn test_serialization() -> anyhow::Result<()> {
353        let mut answered = HashMap::new();
354        answered.insert(33, "A".into());
355
356        let mut revealed = HashMap::new();
357        {
358            let mut m = HashMap::new();
359            m.insert(11, "B".into());
360            revealed.insert(22, m);
361        }
362
363        let effect = Effect {
364            action_timeout: Some(ActionTimeout {
365                player_addr: "alice".into(),
366                timeout: 100,
367            }),
368            wait_timeout: Some(200),
369            start_game: true,
370            stop_game: true,
371            cancel_dispatch: true,
372            timestamp: 300_000,
373            curr_random_id: 1,
374            curr_decision_id: 1,
375            players_count: 4,
376            servers_count: 4,
377            asks: vec![Ask {
378                player_addr: "bob".into(),
379            }],
380            assigns: vec![Assign {
381                player_addr: "bob".into(),
382                random_id: 5,
383                indexes: vec![0, 1, 2],
384            }],
385            reveals: vec![Reveal {
386                random_id: 6,
387                indexes: vec![0, 1, 2],
388            }],
389            releases: vec![Release { decision_id: 7 }],
390            init_random_states: vec![RandomSpec::shuffled_list(vec!["a".into(), "b".into()])],
391            revealed,
392            answered,
393            settles: vec![Settle::add("alice", 200), Settle::sub("bob", 200)],
394            handler_state: Some(vec![1, 2, 3, 4]),
395            error: Some(HandleError::NoEnoughPlayers),
396            allow_exit: true,
397            transfers: vec![],
398            is_checkpoint: false,
399            checkpoint: None,
400        };
401        let bs = effect.try_to_vec()?;
402
403        let parsed = Effect::try_from_slice(&bs)?;
404
405        assert_eq!(effect, parsed);
406        Ok(())
407    }
408}