SeaBattle/rust/sea_battle_backend/src/server.rs

237 lines
6.9 KiB
Rust

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::{BoatsLayout, 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())
}
/// Get default game rules
async fn default_game_rules() -> impl Responder {
HttpResponse::Ok().json(GameRules::random_players_rules())
}
/// Validate game rules
async fn validate_game_rules(rules: web::Json<GameRules>) -> impl Responder {
HttpResponse::Ok().json(rules.get_errors())
}
/// Generate random boats layout
async fn gen_boats_layout(rules: web::Json<GameRules>) -> impl Responder {
let errors = rules.get_errors();
if !errors.is_empty() {
return HttpResponse::BadRequest().json(errors);
}
match BoatsLayout::gen_random_for_rules(&rules) {
Ok(l) => HttpResponse::Ok().json(l),
Err(e) => {
log::error!(
"Failed to generate boats layout for valid game rules: {} ! / Rules: {:?}",
e,
rules
);
HttpResponse::InternalServerError().json("Failed to generate random layout!")
}
}
}
#[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<BotPlayQuery>,
dispatcher: web::Data<Addr<DispatcherActor>>,
) -> Result<HttpResponse, actix_web::Error> {
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<CreateInviteQuery>,
dispatcher: web::Data<Addr<DispatcherActor>>,
) -> Result<HttpResponse, actix_web::Error> {
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<AcceptInviteQuery>,
dispatcher: web::Data<Addr<DispatcherActor>>,
) -> Result<HttpResponse, actix_web::Error> {
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<PlayRandomQuery>,
dispatcher: web::Data<Addr<DispatcherActor>>,
) -> Result<HttpResponse, actix_web::Error> {
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("/game_rules/default", web::get().to(default_game_rules))
.route("/game_rules/validate", web::post().to(validate_game_rules))
.route("/generate_boats_layout", web::post().to(gen_boats_layout))
.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)
}
}