zero-trust-rps 0.0.4

Online Multiplayer Rock Paper Scissors
Documentation
use ratatui::layout::Rect;
use tokio::sync::mpsc::UnboundedSender;
use tui_input::Input;
use uuid::Uuid;

use crate::common::{
    client::{
        channel::SendError,
        simple_move::SimpleUserMove,
        state::{ClientStateView, RpsState},
    },
    message::{HashWithData, RpsData, UserState},
};

use super::ClientOptions;

/// Application.
#[derive(Debug)]
pub struct App {
    /// Is the application running?
    pub running: bool,

    pub hide_inputs: bool,
    pub input: Input,
    pub user_tried_to_change_state_already: bool,
    pub submit_error: Option<String>,
    pub pop_up: Option<Rect>,

    pub client_state: ClientStateView,
    pub old_plays: Vec<Box<[(Uuid, HashWithData)]>>,

    pub confirm_button: Option<Rect>,

    umove_send: UnboundedSender<SimpleUserMove>,
}

impl App {
    /// Constructs a new instance of [`App`].
    pub fn new(umove_send: UnboundedSender<SimpleUserMove>, opts: &ClientOptions) -> Self {
        Self {
            running: true,
            user_tried_to_change_state_already: false,
            hide_inputs: opts.hide_inputs,
            confirm_button: None,
            submit_error: None,
            pop_up: None,
            input: Default::default(),
            client_state: Default::default(),
            old_plays: vec![],
            umove_send,
        }
    }

    /// Handles the tick event of the terminal.
    pub fn tick(&mut self) {}

    /// Set running to false to quit the application.
    pub fn quit(&mut self) {
        self.running = false;
    }

    pub fn do_move(&self, umove: SimpleUserMove) -> Result<(), SendError> {
        Ok(self.umove_send.send(umove)?)
    }

    pub fn text_input_allowed(&self) -> bool {
        match self.client_state.state {
            RpsState::BeforeRoom => true,
            RpsState::InRoom => true,
            RpsState::Played => false,
            RpsState::Confirmed => false,
        }
    }

    pub fn close_err_pop_up(&mut self) {
        self.pop_up = None;
        self.submit_error = None;
    }

    pub fn input_max_length(&self) -> u16 {
        const MAX_U64_LEN: u16 = u64::MAX.ilog10() as u16 + 1;
        const MAX_DATA_LEN: u16 = RpsData::LEN as u16;

        match self.client_state.state {
            RpsState::BeforeRoom => MAX_U64_LEN,
            RpsState::InRoom => MAX_DATA_LEN,
            RpsState::Played => 0,
            RpsState::Confirmed => 0,
        }
    }

    pub fn is_waiting_for_others(&self) -> bool {
        if let Some(room) = self.client_state.room.as_ref() {
            if let Some(round) = room.round.as_ref() {
                if !round.users.contains(&self.client_state.user) {
                    // user not in round
                    true
                } else {
                    let own_state = self.client_state.state;

                    for user in &round.users {
                        if *user == self.client_state.user {
                            continue;
                        }
                        if let Some(state) = self.client_state.cache.get(user) {
                            let rps_state = match state {
                                UserState::InRoom => RpsState::InRoom,
                                UserState::Played(_) => RpsState::Played,
                                UserState::Confirmed(_) => RpsState::Confirmed,
                            };
                            let is_waiting = match own_state {
                                RpsState::BeforeRoom => false,
                                RpsState::InRoom => rps_state == RpsState::Confirmed,
                                RpsState::Played => rps_state == RpsState::InRoom,
                                RpsState::Confirmed => rps_state == RpsState::Played,
                            };
                            if is_waiting {
                                return true;
                            }
                        }
                    }

                    false
                }
            } else {
                false
            }
        } else {
            false
        }
    }
}