Add random mode

This commit is contained in:
Pierre HUBERT 2022-10-01 10:16:00 +02:00
parent a2b60ac08c
commit 65af3b0bba
10 changed files with 140 additions and 20 deletions

View File

@ -24,6 +24,11 @@ pub struct CreateInvite(pub GameRules, pub Addr<HumanPlayerWS>);
#[rtype(result = "()")]
pub struct AcceptInvite(pub String, pub Addr<HumanPlayerWS>);
#[derive(Message)]
#[rtype(result = "()")]
pub struct PlayRandom(pub Addr<HumanPlayerWS>);
#[derive(Debug, Clone)]
struct PendingPlayer {
player: Addr<HumanPlayerWS>,
rules: GameRules,
@ -32,15 +37,14 @@ struct PendingPlayer {
#[derive(Default)]
pub struct DispatcherActor {
with_invite: HashMap<String, PendingPlayer>,
random_player: Option<PendingPlayer>,
}
impl Actor for DispatcherActor {
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
ctx.run_interval(HEARTBEAT_INTERVAL, |act, _ctx| {
impl DispatcherActor {
/// Run garbage collector
fn run_gc(&mut self) {
// Garbage collect invites
let ids = act
let ids = self
.with_invite
.iter()
.filter(|p| !p.1.player.connected())
@ -49,8 +53,24 @@ impl Actor for DispatcherActor {
for id in ids {
log::debug!("Remove dead invite: {}", id);
act.with_invite.remove(&id);
self.with_invite.remove(&id);
}
// Garbage collect random player
if let Some(player) = self.random_player.clone() {
if !player.player.connected() {
self.random_player = None;
}
}
}
}
impl Actor for DispatcherActor {
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
ctx.run_interval(HEARTBEAT_INTERVAL, |act, _ctx| {
act.run_gc();
});
}
}
@ -84,6 +104,8 @@ impl Handler<AcceptInvite> for DispatcherActor {
type Result = ();
fn handle(&mut self, msg: AcceptInvite, _ctx: &mut Self::Context) -> Self::Result {
self.run_gc();
let entry = match self.with_invite.remove(&msg.0) {
None => {
msg.1.do_send(ServerMessage::InvalidInviteCode);
@ -98,3 +120,27 @@ impl Handler<AcceptInvite> for DispatcherActor {
msg.1.do_send(SetGame(game));
}
}
impl Handler<PlayRandom> for DispatcherActor {
type Result = ();
fn handle(&mut self, msg: PlayRandom, _ctx: &mut Self::Context) -> Self::Result {
self.run_gc();
match self.random_player.take() {
None => {
self.random_player = Some(PendingPlayer {
player: msg.0,
rules: GameRules::random_players_rules(),
});
}
Some(p) => {
let game = Game::new(p.rules).start();
p.player.do_send(SetGame(game.clone()));
msg.0.do_send(SetGame(game));
self.random_player = None;
}
}
}
}

View File

@ -9,7 +9,7 @@ use uuid::Uuid;
use crate::bot_player::BotPlayer;
use crate::data::{BoatsLayout, Coordinates, CurrentGameStatus, FireResult, GameRules};
use crate::dispatcher_actor::{AcceptInvite, CreateInvite, DispatcherActor};
use crate::dispatcher_actor::{AcceptInvite, CreateInvite, DispatcherActor, PlayRandom};
use crate::game::{AddPlayer, Game};
use crate::human_player::HumanPlayer;
@ -23,7 +23,7 @@ pub enum StartMode {
Bot(GameRules),
CreateInvite(GameRules),
AcceptInvite { code: String },
// TODO : random human
PlayRandom,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
@ -178,6 +178,10 @@ impl Actor for HumanPlayerWS {
self.dispatcher
.do_send(AcceptInvite(code.clone(), ctx.address()));
}
StartMode::PlayRandom => {
log::info!("Start random play");
self.dispatcher.do_send(PlayRandom(ctx.address()))
}
}
}

View File

@ -112,6 +112,24 @@ async fn start_accept_invite(
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<()> {
let args_clone = args.clone();
@ -132,7 +150,7 @@ pub async fn start_server(args: Args) -> std::io::Result<()> {
.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))
// TODO : join random
.route("/play/random", web::get().to(start_random))
.route("/", web::get().to(index))
.route("{tail:.*}", web::get().to(not_found))
})

View File

@ -6,7 +6,7 @@ use tokio_tungstenite::tungstenite::Message;
use crate::data::{BoatsLayout, BotType, GameRules};
use crate::human_player_ws::{ClientMessage, ServerMessage};
use crate::server::{AcceptInviteQuery, BotPlayQuery, CreateInviteQuery};
use crate::server::{AcceptInviteQuery, BotPlayQuery, CreateInviteQuery, PlayRandomQuery};
const PLAYER_NAME: &str = "Bot client";
@ -18,6 +18,7 @@ pub enum RunMode {
AcceptInvite {
code: String,
},
Random,
}
#[derive(Debug, Eq, PartialEq)]
@ -130,6 +131,16 @@ impl BotClient {
.unwrap()
)
}
RunMode::Random => {
format!(
"{}/play/random?{}",
self.server.replace("http", "ws"),
serde_urlencoded::to_string(&PlayRandomQuery {
player_name: PLAYER_NAME.to_string()
})
.unwrap()
)
}
};
log::debug!("Connecting to {}...", url);
let (mut socket, _) = match tokio_tungstenite::connect_async(url).await {

View File

@ -17,6 +17,7 @@ enum TestPort {
InviteModeFullGame,
InviteModeFirstPlayerWin,
InviteModeSecondPlayerWin,
RandomModeFourGames,
}
impl TestPort {
@ -40,10 +41,11 @@ impl Args {
#[cfg(test)]
pub mod bot_client;
mod bot_client_bot_intermediate_play;
mod bot_client_bot_linear_play;
mod bot_client_bot_random_play;
mod bot_client_bot_smart_play;
mod bot_intermediate_play;
mod bot_linear_play;
mod bot_random_play;
mod bot_smart_play;
mod invite_mode;
mod network_utils;
mod play_utils;
mod random_mode;

View File

@ -0,0 +1,39 @@
use crate::args::Args;
use crate::server::start_server;
use crate::test::bot_client::{ClientEndResult, RunMode};
use crate::test::network_utils::wait_for_port;
use crate::test::{bot_client, TestPort};
use std::error::Error;
use tokio::task;
async fn play_random(port: TestPort) -> Result<ClientEndResult, Box<dyn Error>> {
bot_client::BotClient::new(port.as_url())
.with_run_mode(RunMode::Random)
.run_client()
.await
}
#[tokio::test]
async fn four_games() {
let _ = env_logger::builder().is_test(true).try_init();
let local_set = task::LocalSet::new();
local_set
.run_until(async move {
task::spawn_local(start_server(Args::for_test(TestPort::RandomModeFourGames)));
wait_for_port(TestPort::RandomModeFourGames.port()).await;
let mut fut = vec![];
for _ in 0..4 {
fut.push(task::spawn_local(play_random(
TestPort::RandomModeFourGames,
)));
}
for handle in fut {
let res = handle.await.unwrap().unwrap();
assert!(matches!(res, ClientEndResult::Finished { .. }));
}
})
.await;
}