use std::{fmt::Display, future::Future, ops::Index, time::Duration}; use rand::Rng; use tokio::{signal, time}; #[derive(Debug, Clone, Copy)] struct State([bool; 8]); impl Index for State { type Output = bool; fn index(&self, index: usize) -> &Self::Output { &self.0[index] } } fn map_state(rule: u8, state: &State) -> State { let mut new_state: [bool; 8] = [false; 8]; for i in 0..8 { let pattern = ((i != 0 && state[i - 1]) as u8 * 4) | (state[i] as u8 * 2) | ((i != 7 && state[i + 1]) as u8); new_state[i] = (rule >> pattern & 1) == 1; } State(new_state) } impl Display for State { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", self.0.map(|c| if c { "⬛" } else { "⬜" }).concat() ) } } impl Into for u8 { fn into(self) -> State { let mut state = [false; 8]; for i in 0..8 { state[i] = (self >> i & 1) == 1; } State(state) } } async fn send_status( rule: u8, states: String, ) -> impl Future> { let client = reqwest::Client::new(); client .post("https://mastodon.raptorpond.com/api/v1/statuses") .header( "Authorization", format!( "Bearer {}", std::env::var("RULEBOT_MASTODON_TOKEN").unwrap_or_default() ), ) .form(&[ ("status", format!("Rule: {}\n{}", rule, states)), ("visibility", String::from("private")), ]) .send() } fn generate(seed: u16) -> String { let rule: u8 = (seed & 0xff) as u8; let init_state = ((seed >> 8 & 0xff) as u8).into(); (0..8) .scan(init_state, |state, _i| { let old_state = *state; *state = map_state(rule, state); Some(old_state) }) .map(|state| state.to_string()) .collect::>() .join("\n") } #[tokio::main] async fn main() { env_logger::init(); let generator = tokio::spawn(async { let mut interval = time::interval(time::Duration::from_secs( std::env::var("RULEBOT_POST_INTERVAL") .unwrap_or("2".into()) .parse() .unwrap(), )); loop { interval.tick().await; let seed; // Block to drop rng before switching context { let mut rng = rand::thread_rng(); seed = rng.gen(); } let rule = (seed & 0xff) as u8; // Generate the states let states: String = generate(seed); log::info!("Rule: {}\n{}", rule, states); // Send toot let sent = tokio::time::timeout(Duration::from_secs(1), send_status(rule, states).await).await; // Figure out what went wrong match sent { Ok(result) => match result { Ok(response) => { if response.status() != 200 { log::error!("Send status error code: {}", response.status()) } } Err(err) => { log::error!("Send status error: {}", err); } }, Err(_) => { log::error!("Send status timed out!"); } } } }); match signal::ctrl_c().await { Ok(()) => {} Err(err) => { log::error!("Unable to listen for shutdown signal: {}", err); } } // Kill generator generator.abort() }