zero_trust_rps/common/rps/
move_validation.rs

1use uuid::Uuid;
2
3use crate::common::{message::Round, rps::state::UserMoveMethods as _};
4
5use super::{move_kind::UserMoveKind, state::RpsState};
6
7pub struct UserState {
8    pub user_id: Uuid,
9    pub state: RpsState,
10}
11
12impl From<&crate::common::message::User> for UserState {
13    fn from(value: &crate::common::message::User) -> Self {
14        UserState {
15            user_id: value.id,
16            state: value.state.into(),
17        }
18    }
19}
20
21#[repr(u8)]
22#[derive(thiserror::Error, Debug)]
23pub enum MoveValidationError {
24    #[error("A user with that id was already in the room.")]
25    UserAlreadyInRoom,
26    #[error("User is not in the current round")]
27    UserNotInRound,
28    #[error("User can't do that move right now.")]
29    UserHasWrongState,
30    #[error("Other user isn't in the correct state")]
31    OtherHasWrongState,
32}
33
34#[inline]
35pub fn validate_move(
36    user_id: Uuid,
37    move_kind: impl Into<UserMoveKind>,
38    users: impl IntoIterator<Item = impl Into<UserState>>,
39    round: Option<&Round>,
40) -> Result<(), MoveValidationError> {
41    use MoveValidationError::*;
42
43    let move_kind: UserMoveKind = move_kind.into();
44
45    if matches!(move_kind, UserMoveKind::JoinRoom) {
46        // JoinRoom is only not allowed if a user with that id already is in room
47        if users
48            .into_iter()
49            .map(Into::<UserState>::into)
50            .any(|u| u.user_id == user_id)
51        {
52            Err(UserAlreadyInRoom)
53        } else {
54            Ok(())
55        }
56    } else if round.is_some_and(|round| !round.users.contains(&user_id)) {
57        Err(UserNotInRound) // User cannot move
58    } else {
59        let allowed_state = move_kind.allowed_state();
60        let mut users = users.into_iter().map(Into::<UserState>::into);
61        let others_allowed_states: [_; 2] = [allowed_state, move_kind.resulting_state()];
62        if let Some(round) = round {
63            for user in users {
64                if user.user_id == user_id {
65                    if user.state != allowed_state {
66                        return Err(UserHasWrongState);
67                    }
68                } else if round.users.contains(&user.user_id) {
69                    // only care about users in round
70                    if !others_allowed_states.contains(&user.state) {
71                        return Err(OtherHasWrongState);
72                    }
73                }
74            }
75
76            Ok(())
77        } else if allowed_state == RpsState::InRoom {
78            if users.all(|state| state.state == RpsState::InRoom) {
79                Ok(())
80            } else {
81                Err(OtherHasWrongState)
82            }
83        } else {
84            Err(UserHasWrongState)
85        }
86    }
87}