zero_trust_rps/common/client/
bot.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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use std::time::Duration;

use quinn::{Connection, VarInt};
use rand::{thread_rng, Rng};
use tokio::{
    sync::mpsc::{channel, error::SendError},
    task::JoinError,
};

use crate::common::{
    message::{RoomId, RpsData},
    rps::{move_validation::validate_move, simple_move::SimpleUserMove, state::RpsState},
};

use super::{
    connection::{
        conn::{run_client, RunClientError},
        init::initialize_connection,
    },
    state::ClientStateView,
};

#[allow(unused)]
const RPS_CHOICES: [RpsData; 3] = unsafe {
    [
        RpsData::new_unchecked([
            240, 159, 170, 168, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        ]),
        RpsData::new_unchecked([
            240, 159, 147, 156, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        ]),
        RpsData::new_unchecked([
            226, 156, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        ]),
    ]
};

#[derive(thiserror::Error, Debug)]
#[allow(clippy::enum_variant_names)]
pub enum RunBotError {
    #[error("Could not send over channel: {}", .0)]
    ChannelSendError(#[from] SendError<SimpleUserMove>),
    #[error("Failed to initialize Connection: {}", .0)]
    #[allow(unused)]
    InitError(String),
    #[error("Error while running: {}", .0)]
    RunClientError(#[from] RunClientError),
    #[error("Could not join: {}", .0)]
    TokioError(#[from] JoinError),
}

#[allow(dead_code)]
pub async fn run_bot(
    id: usize,
    connection: Connection,
    room_id: RoomId,
    domain: String,
    port: u16,
) -> Result<(), RunBotError> {
    log::info!("Run bot {id} for room {room_id}, connect to {domain}:{port} ({connection:?})");
    match _run_bot(connection, room_id, domain.as_str(), port).await {
        Ok(()) => {
            log::info!("Bot {id} finished");
            Ok(())
        }
        Err(err) => {
            log::error!("Bot {id} exited with error: {err:?}");
            Err(err)
        }
    }
}

async fn _run_bot(
    connection: Connection,
    room_id: RoomId,
    domain: &str,
    port: u16,
) -> Result<(), RunBotError> {
    let (connection, writer, reader) = initialize_connection(connection, domain, port)
        .await
        .map_err(|err| RunBotError::InitError(format!("{err}")))?;

    let (umove_send, umove_recv) = channel::<SimpleUserMove>(1);
    let (state_send, mut state_recv) = channel::<ClientStateView>(1);

    let jh = tokio::spawn(run_client(None, writer, reader, (), state_send, umove_recv));

    let mut error = None;
    let mut last_state = RpsState::BeforeRoom;
    let mut requested_state_change = false;

    while let Some(state) = state_recv.recv().await {
        log::trace!("Got new state: {state:?}");
        // TODO on error
        if state.state != last_state || error != state.last_error {
            requested_state_change = false;
            last_state = state.state;
            error = state.last_error.clone();
        } else {
            let duration = Duration::from_millis(thread_rng().gen_range(500..900));
            log::trace!("Waiting for: {duration:?}");
            tokio::time::sleep(duration).await;
        }
        if requested_state_change {
            continue;
        }
        if state.state == RpsState::InRoom
            && state
                .room
                .as_ref()
                .map(|room| room.users.len())
                .unwrap_or(0)
                <= 1
        {
            continue; // do not do anything if we are alone in room
        }
        use super::super::rps::state::RpsState::*;
        let bot_move = match state.state {
            BeforeRoom => SimpleUserMove::JoinRoom(room_id),
            InRoom => {
                let choice: usize = rand::thread_rng().gen_range(0..RPS_CHOICES.len());
                SimpleUserMove::Play(RPS_CHOICES[choice])
            }
            Played => SimpleUserMove::ConfirmPlay,
            Confirmed => SimpleUserMove::BackToRoom,
        };

        if let Ok(()) = validate_move(
            state.user,
            &bot_move,
            state.room.iter().flat_map(|room| room.users.iter()),
            state.room.as_ref().and_then(|room| room.round.as_ref()),
        ) {
            requested_state_change = true;
            log::trace!("{} doing move {bot_move:?}", state.user);
            umove_send.send(bot_move).await?
        }
    }

    let _: () = jh.await??;

    connection.close(VarInt::from_u32(0), &[]);

    Ok(())
}