zero_trust_rps/common/client/
bot.rsuse std::time::Duration;
use quinn::{Connection, VarInt};
use rand::{thread_rng, Rng};
use tokio::{
sync::mpsc::{error::SendError, unbounded_channel},
task::JoinError,
};
use crate::common::message::{RoomId, RpsData, UserState};
use super::{
connection::{
conn::{run_client, RunClientError},
init::initialize_connection,
},
simple_move::SimpleUserMove,
state::{ClientStateView, RpsState},
};
#[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) = unbounded_channel::<SimpleUserMove>();
let (state_send, mut state_recv) = unbounded_channel::<ClientStateView>();
let jh = tokio::spawn(run_client(None, writer, reader, (), state_send, umove_recv));
let mut last_state = RpsState::BeforeRoom;
let mut requested_state_change = false;
while let Some(state) = state_recv.recv().await {
if state.state != last_state {
requested_state_change = false;
last_state = state.state;
} else {
let duration = Duration::from_millis(thread_rng().gen_range(500..900));
tokio::time::sleep(duration).await;
}
if requested_state_change {
continue;
}
use super::state::RpsState::*;
match state.state {
BeforeRoom => {
requested_state_change = true;
umove_send.send(SimpleUserMove::JoinRoom(room_id))?
}
InRoom => {
if let Some(room) = state.room.as_ref() {
if let Some(round) = room.round.as_ref() {
if room
.users
.iter()
.filter(|u| round.users.contains(&u.id))
.all(|u| matches!(u.state, UserState::InRoom | UserState::Played(_)))
{
requested_state_change = true;
umove_send.send(SimpleUserMove::Play(
RPS_CHOICES[rand::thread_rng().gen_range(0..RPS_CHOICES.len())],
))?
}
} else if room.users.len() > 1 {
requested_state_change = true;
umove_send.send(SimpleUserMove::Play(
RPS_CHOICES[rand::thread_rng().gen_range(0..RPS_CHOICES.len())],
))?
}
}
}
Played => {
if let Some(room) = state.room.as_ref() {
if let Some(round) = room.round.as_ref() {
if room
.users
.iter()
.filter(|u| round.users.contains(&u.id))
.all(|u| {
matches!(u.state, UserState::Played(_) | UserState::Confirmed(_))
})
{
requested_state_change = true;
umove_send.send(SimpleUserMove::ConfirmPlay)?
}
}
}
}
Confirmed => {
if let Some(room) = state.room.as_ref() {
if let Some(round) = room.round.as_ref() {
if room
.users
.iter()
.filter(|u| round.users.contains(&u.id))
.all(|u| matches!(u.state, UserState::InRoom | UserState::Confirmed(_)))
{
requested_state_change = true;
umove_send.send(SimpleUserMove::BackToRoom)?
}
}
}
}
}
}
let _: () = jh.await??;
connection.close(VarInt::from_u32(0), &[]);
Ok(())
}