zero_trust_rps/common/rps/
move_validation.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
use uuid::Uuid;

use crate::common::{message::Round, rps::state::UserMoveMethods as _};

use super::{move_kind::UserMoveKind, state::RpsState};

pub struct UserState {
    pub user_id: Uuid,
    pub state: RpsState,
}

impl From<&crate::common::message::User> for UserState {
    fn from(value: &crate::common::message::User) -> Self {
        UserState {
            user_id: value.id,
            state: value.state.into(),
        }
    }
}

#[repr(u8)]
#[derive(thiserror::Error, Debug)]
pub enum MoveValidationError {
    #[error("A user with that id was already in the room.")]
    UserAlreadyInRoom,
    #[error("User is not in the current round")]
    UserNotInRound,
    #[error("User can't do that move right now.")]
    UserHasWrongState,
    #[error("Other user isn't in the correct state")]
    OtherHasWrongState,
}

#[inline]
pub fn validate_move(
    user_id: Uuid,
    move_kind: impl Into<UserMoveKind>,
    users: impl IntoIterator<Item = impl Into<UserState>>,
    round: Option<&Round>,
) -> Result<(), MoveValidationError> {
    use MoveValidationError::*;

    let move_kind: UserMoveKind = move_kind.into();

    if matches!(move_kind, UserMoveKind::JoinRoom) {
        // JoinRoom is only not allowed if a user with that id already is in room
        if users
            .into_iter()
            .map(Into::<UserState>::into)
            .any(|u| u.user_id == user_id)
        {
            Err(UserAlreadyInRoom)
        } else {
            Ok(())
        }
    } else if round.is_some_and(|round| !round.users.contains(&user_id)) {
        Err(UserNotInRound) // User cannot move
    } else {
        let allowed_state = move_kind.allowed_state();
        let mut users = users.into_iter().map(Into::<UserState>::into);
        let others_allowed_states: [_; 2] = [allowed_state, move_kind.resulting_state()];
        if let Some(round) = round {
            for user in users {
                if user.user_id == user_id {
                    if user.state != allowed_state {
                        return Err(UserHasWrongState);
                    }
                } else if round.users.contains(&user.user_id) {
                    // only care about users in round
                    if !others_allowed_states.contains(&user.state) {
                        return Err(OtherHasWrongState);
                    }
                }
            }

            Ok(())
        } else if allowed_state == RpsState::InRoom {
            if users.all(|state| state.state == RpsState::InRoom) {
                Ok(())
            } else {
                Err(OtherHasWrongState)
            }
        } else {
            Err(UserHasWrongState)
        }
    }
}