use axum::{ response::Html, routing::{get, post}, Json, Router, }; use once_cell::sync::Lazy; use rand::{thread_rng, Rng}; use rand::seq::SliceRandom; use serde::Deserialize; use std::net::SocketAddr; use tokio::sync::Mutex; use log::debug; use log::error; use log::info; use log::warn; #[macro_use] extern crate log; #[tokio::main] async fn main() { env_logger::init(); info!("let's go!"); // build our application with some routes let app = Router::new() .route("/", get(|| async { Html(INDEX_HTML.as_str()) })) .route("/api", post(input)); // run it with hyper let addr = SocketAddr::from(([127, 0, 0, 1], 3067)); let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); axum::serve(listener, app.into_make_service()) .await .unwrap(); } static INDEX_HTML: Lazy = Lazy::new(|| { let content = include_str!("index.html"); let options: String = everyone() .into_iter() .map(|person| format!(r#""#)) .collect(); content.replace("__OPTIONS__", &options) }); static STATE: Lazy> = Lazy::new(Mutex::default); struct State { participants: Vec, remaining: Vec, } fn has_forbidden_adjacent(participants: &Vec) -> bool { let forbidden_pairs = vec![("Alice".into(), "Bob".into())]; // Check pairs of adjacent elements, including the circular pair (last, first) ((1..participants.len()).any(|i| { match (participants[i - 1].as_str(), participants[i].as_str()) { // Check if the pair is forbidden (a, b) if forbidden_pairs.contains(&(a.to_string(), b.to_string())) || forbidden_pairs.contains(&(b.to_string(), a.to_string())) => { info!("forbidden pair detected : {:?}/{:?} in {:?}", a, b, participants.join(" => ")); return true; }, _ => false, } } ) || // Check the pair (last, first) for circular adjacency match (participants.last().unwrap().as_str(), participants[0].as_str()) { (a, b) if forbidden_pairs.contains(&(a.to_string(), b.to_string())) || forbidden_pairs.contains(&(b.to_string(), a.to_string())) => { info!("forbidden pair detected : {:?}/{:?} in {:?}", a, b, participants.join(" => ")); return true; }, _ => false, } ) } fn everyone() -> Vec { vec!["Alice".into(), "Bob".into(), "Carol".into(), "Dave".into(), "Edgar".into(), "France".into()] } impl Default for State { fn default() -> Self { let mut p = everyone(); let mut tries = 0; while tries < 100 { p.shuffle(&mut thread_rng()); if !has_forbidden_adjacent(&p) { info!("distribution : {:?}", p.join(" => ")); return Self { participants: p, remaining: everyone() } } else { } tries += 1; } eprintln!("ERROR (could not generate a correct list)"); std::process::exit(1) } } #[derive(Deserialize, Debug)] struct Input { person: String, } async fn input(Json(input): Json) -> String { let mut state = STATE.lock().await; if state.remaining.is_empty() { return "... He mais tout le monde a déja pioché !!".into(); } info!("joueurs qui restent : {:?}", state.remaining.join(",")); match state.remaining.iter().position(|p| input.person == *p).map(|e| state.remaining.remove(e)) { Some(rem) => { match state.participants.iter().position(|p| input.person == *p) { Some(pos) => { info!("joueur qui pioche : {:?}", state.participants[pos]); return match state.participants.iter().nth(pos+1) { Some(x) => { info!("joueur qui suit : {:?}", x); return x.to_string(); } None => return match state.participants.first() { Some(x) => { info!("joueur qui suit : {:?}", x); return x.to_string(); } None => "ERROR".to_string() } } } None => "ERROR".to_string() } } None => "... Hem mais tu as déja pioché !".to_string() } }