zero-trust-rps 0.0.5

Online Multiplayer Rock Paper Scissors
Documentation
use crate::common::{
    blake3::Blake3Hash as _,
    message::{ClientMessage, Hash, HashWithData, Round},
    rps::{
        move_validation::validate_move, simple_move::SimpleUserMove, state::UserMoveMethods as _,
    },
    utils::hash_users,
};

use super::{
    channel::{AsyncChannelSender, SendError},
    connection::read::SimplifiedServerMessage,
    state::ClientState,
};

#[derive(thiserror::Error, Debug)]
pub enum MoveError {
    #[error("{}", .0)]
    SendError(#[from] SendError),
    #[error("{}", .0)]
    Other(String),
}

impl From<String> for MoveError {
    fn from(value: String) -> Self {
        MoveError::Other(value)
    }
}

pub async fn do_move(
    state: &mut ClientState,
    umove: SimpleUserMove,
    sender: impl AsyncChannelSender<ClientMessage>,
    answer: impl AsyncChannelSender<SimplifiedServerMessage>,
) -> Result<(), MoveError> {
    if let Err(error) = validate_move(
        state.user,
        &umove,
        state.room.iter().flat_map(|room| room.users.iter()),
        state.room.as_ref().and_then(|room| room.round.as_ref()),
    ) {
        let message = format!(
            "Could not do move: {error}. Tried to play {umove:?}, state is {:?} and should be {:?}",
            state.state,
            umove.allowed_state(),
        );
        log::warn!("{message}");
        answer.send(SimplifiedServerMessage::Error(message)).await?;

        return Ok(());
    }
    match umove {
        SimpleUserMove::JoinRoom(id) => {
            sender
                .send(ClientMessage::Join {
                    user: state.user,
                    room_id: id,
                })
                .await?
        }
        SimpleUserMove::Play(play) => {
            state.last_play = Some(play);

            let room = state.room.as_ref().expect("state is InRoom");

            let round = room.round.clone().unwrap_or_else(|| {
                let users: Box<[_]> = room.users.iter().map(|u| u.id).collect();
                Round {
                    round_id: hash_users(users.iter().copied()),
                    users,
                }
            });

            let hash = state
                .last_play
                .as_ref()
                .expect("set above")
                .hash_keyed(&state.secret, &round);

            sender
                .send(ClientMessage::Play {
                    value: Hash::B3sum(hash),
                    round,
                })
                .await?;
        }
        SimpleUserMove::ConfirmPlay => {
            let last_play = state
                .last_play
                .as_ref()
                .ok_or_else(|| "Can't get last play, should be there!".to_string())?;
            let hash = last_play.hash_keyed(
                &state.secret,
                state
                    .room
                    .as_ref()
                    .expect("room")
                    .round
                    .as_ref()
                    .expect("round"),
            );

            sender
                .send(ClientMessage::ConfirmPlay(HashWithData::B3sum {
                    hash,
                    key: state.secret,
                    data: *last_play,
                }))
                .await?;
        }
        SimpleUserMove::BackToRoom => {
            sender
                .send(ClientMessage::RoundFinished {
                    round_id: state
                        .room
                        .as_ref()
                        .expect("room")
                        .round
                        .as_ref()
                        .expect("round")
                        .round_id,
                })
                .await?
        }
    };

    Ok(())
}