use actix::{Actor, Addr}; use actix_cors::Cors; use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer, Responder}; use actix_web_actors::ws; use crate::args::Args; use crate::data::{GameRules, PlayConfiguration, VersionInfo}; use crate::dispatcher_actor::DispatcherActor; use crate::human_player_ws::{HumanPlayerWS, StartMode}; /// The default '/' route async fn index() -> impl Responder { HttpResponse::Ok().json("Sea battle backend") } /// The default 404 route async fn not_found() -> impl Responder { HttpResponse::NotFound().json("You missed your strike lol") } /// Get version information async fn version_information() -> impl Responder { HttpResponse::Ok().json(VersionInfo::load_static()) } /// Get game configuration async fn game_configuration() -> impl Responder { HttpResponse::Ok().json(PlayConfiguration::default()) } #[derive(serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)] pub struct BotPlayQuery { #[serde(flatten)] pub rules: GameRules, pub player_name: String, } /// Start bot game async fn start_bot_play( req: HttpRequest, stream: web::Payload, query: web::Query, dispatcher: web::Data>, ) -> Result { let errors = query.rules.get_errors(); if !errors.is_empty() { return Ok(HttpResponse::BadRequest().json(errors)); } let player_ws = HumanPlayerWS::new( StartMode::Bot(query.rules.clone()), &dispatcher, query.player_name.clone(), ); let resp = ws::start(player_ws, &req, stream); log::info!("New bot play with configuration: {:?}", &query.rules); resp } #[derive(serde::Serialize, serde::Deserialize)] pub struct CreateInviteQuery { #[serde(flatten)] pub rules: GameRules, pub player_name: String, } /// Start game by creating invite async fn start_create_invite( req: HttpRequest, stream: web::Payload, query: web::Query, dispatcher: web::Data>, ) -> Result { let errors = query.rules.get_errors(); if !errors.is_empty() { return Ok(HttpResponse::BadRequest().json(errors)); } let player_ws = HumanPlayerWS::new( StartMode::CreateInvite(query.rules.clone()), &dispatcher, query.0.player_name, ); let resp = ws::start(player_ws, &req, stream); log::info!( "New create invite play with configuration: {:?}", &query.0.rules ); resp } #[derive(serde::Serialize, serde::Deserialize)] pub struct AcceptInviteQuery { pub code: String, pub player_name: String, } /// Start game by creating invite async fn start_accept_invite( req: HttpRequest, stream: web::Payload, query: web::Query, dispatcher: web::Data>, ) -> Result { let player_ws = HumanPlayerWS::new( StartMode::AcceptInvite { code: query.code.clone(), }, &dispatcher, query.0.player_name, ); let resp = ws::start(player_ws, &req, stream); log::info!("New accept invite: {:?}", &query.0.code); resp } #[derive(serde::Serialize, serde::Deserialize)] pub struct PlayRandomQuery { pub player_name: String, } /// Start game, playing against a random person async fn start_random( req: HttpRequest, stream: web::Payload, query: web::Query, dispatcher: web::Data>, ) -> Result { let player_ws = HumanPlayerWS::new(StartMode::PlayRandom, &dispatcher, query.0.player_name); let resp = ws::start(player_ws, &req, stream); log::info!("New random play"); resp } pub async fn start_server(args: Args) -> std::io::Result<()> { log::info!("Start to listen on {}", args.listen_address); let args_clone = args.clone(); let dispatcher_actor = DispatcherActor::default().start(); HttpServer::new(move || { let mut cors = Cors::default(); match args_clone.cors.as_deref() { Some("*") => cors = cors.allow_any_origin(), Some(orig) => cors = cors.allowed_origin(orig), None => {} } App::new() .app_data(web::Data::new(dispatcher_actor.clone())) .wrap(cors) .route("/version", web::get().to(version_information)) .route("/config", web::get().to(game_configuration)) .route("/play/bot", web::get().to(start_bot_play)) .route("/play/create_invite", web::get().to(start_create_invite)) .route("/play/accept_invite", web::get().to(start_accept_invite)) .route("/play/random", web::get().to(start_random)) .route("/", web::get().to(index)) .route("{tail:.*}", web::get().to(not_found)) }) .bind(&args.listen_address)? .run() .await } #[cfg(test)] mod test { use crate::data::GameRules; use crate::server::BotPlayQuery; #[test] fn simple_bot_request_serialize_deserialize() { let query = BotPlayQuery { rules: Default::default(), player_name: "Player".to_string(), }; let string = serde_urlencoded::to_string(&query).unwrap(); let des = serde_urlencoded::from_str(&string).unwrap(); assert_eq!(query, des) } #[test] fn simple_bot_request_serialize_deserialize_no_timeout() { let query = BotPlayQuery { rules: GameRules { strike_timeout: None, ..Default::default() }, player_name: "Player".to_string(), }; let string = serde_urlencoded::to_string(&query).unwrap(); let des = serde_urlencoded::from_str(&string).unwrap(); assert_eq!(query, des) } }