Add random mode
This commit is contained in:
parent
a2b60ac08c
commit
65af3b0bba
@ -24,6 +24,11 @@ pub struct CreateInvite(pub GameRules, pub Addr<HumanPlayerWS>);
|
|||||||
#[rtype(result = "()")]
|
#[rtype(result = "()")]
|
||||||
pub struct AcceptInvite(pub String, pub Addr<HumanPlayerWS>);
|
pub struct AcceptInvite(pub String, pub Addr<HumanPlayerWS>);
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct PlayRandom(pub Addr<HumanPlayerWS>);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
struct PendingPlayer {
|
struct PendingPlayer {
|
||||||
player: Addr<HumanPlayerWS>,
|
player: Addr<HumanPlayerWS>,
|
||||||
rules: GameRules,
|
rules: GameRules,
|
||||||
@ -32,15 +37,14 @@ struct PendingPlayer {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct DispatcherActor {
|
pub struct DispatcherActor {
|
||||||
with_invite: HashMap<String, PendingPlayer>,
|
with_invite: HashMap<String, PendingPlayer>,
|
||||||
|
random_player: Option<PendingPlayer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for DispatcherActor {
|
impl DispatcherActor {
|
||||||
type Context = Context<Self>;
|
/// Run garbage collector
|
||||||
|
fn run_gc(&mut self) {
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
|
||||||
ctx.run_interval(HEARTBEAT_INTERVAL, |act, _ctx| {
|
|
||||||
// Garbage collect invites
|
// Garbage collect invites
|
||||||
let ids = act
|
let ids = self
|
||||||
.with_invite
|
.with_invite
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|p| !p.1.player.connected())
|
.filter(|p| !p.1.player.connected())
|
||||||
@ -49,8 +53,24 @@ impl Actor for DispatcherActor {
|
|||||||
|
|
||||||
for id in ids {
|
for id in ids {
|
||||||
log::debug!("Remove dead invite: {}", id);
|
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 = ();
|
type Result = ();
|
||||||
|
|
||||||
fn handle(&mut self, msg: AcceptInvite, _ctx: &mut Self::Context) -> Self::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) {
|
let entry = match self.with_invite.remove(&msg.0) {
|
||||||
None => {
|
None => {
|
||||||
msg.1.do_send(ServerMessage::InvalidInviteCode);
|
msg.1.do_send(ServerMessage::InvalidInviteCode);
|
||||||
@ -98,3 +120,27 @@ impl Handler<AcceptInvite> for DispatcherActor {
|
|||||||
msg.1.do_send(SetGame(game));
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,7 +9,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use crate::bot_player::BotPlayer;
|
use crate::bot_player::BotPlayer;
|
||||||
use crate::data::{BoatsLayout, Coordinates, CurrentGameStatus, FireResult, GameRules};
|
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::game::{AddPlayer, Game};
|
||||||
use crate::human_player::HumanPlayer;
|
use crate::human_player::HumanPlayer;
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ pub enum StartMode {
|
|||||||
Bot(GameRules),
|
Bot(GameRules),
|
||||||
CreateInvite(GameRules),
|
CreateInvite(GameRules),
|
||||||
AcceptInvite { code: String },
|
AcceptInvite { code: String },
|
||||||
// TODO : random human
|
PlayRandom,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
||||||
@ -178,6 +178,10 @@ impl Actor for HumanPlayerWS {
|
|||||||
self.dispatcher
|
self.dispatcher
|
||||||
.do_send(AcceptInvite(code.clone(), ctx.address()));
|
.do_send(AcceptInvite(code.clone(), ctx.address()));
|
||||||
}
|
}
|
||||||
|
StartMode::PlayRandom => {
|
||||||
|
log::info!("Start random play");
|
||||||
|
self.dispatcher.do_send(PlayRandom(ctx.address()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +112,24 @@ async fn start_accept_invite(
|
|||||||
resp
|
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<()> {
|
pub async fn start_server(args: Args) -> std::io::Result<()> {
|
||||||
let args_clone = args.clone();
|
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/bot", web::get().to(start_bot_play))
|
||||||
.route("/play/create_invite", web::get().to(start_create_invite))
|
.route("/play/create_invite", web::get().to(start_create_invite))
|
||||||
.route("/play/accept_invite", web::get().to(start_accept_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("/", web::get().to(index))
|
||||||
.route("{tail:.*}", web::get().to(not_found))
|
.route("{tail:.*}", web::get().to(not_found))
|
||||||
})
|
})
|
||||||
|
@ -6,7 +6,7 @@ use tokio_tungstenite::tungstenite::Message;
|
|||||||
|
|
||||||
use crate::data::{BoatsLayout, BotType, GameRules};
|
use crate::data::{BoatsLayout, BotType, GameRules};
|
||||||
use crate::human_player_ws::{ClientMessage, ServerMessage};
|
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";
|
const PLAYER_NAME: &str = "Bot client";
|
||||||
|
|
||||||
@ -18,6 +18,7 @@ pub enum RunMode {
|
|||||||
AcceptInvite {
|
AcceptInvite {
|
||||||
code: String,
|
code: String,
|
||||||
},
|
},
|
||||||
|
Random,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
@ -130,6 +131,16 @@ impl BotClient {
|
|||||||
.unwrap()
|
.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);
|
log::debug!("Connecting to {}...", url);
|
||||||
let (mut socket, _) = match tokio_tungstenite::connect_async(url).await {
|
let (mut socket, _) = match tokio_tungstenite::connect_async(url).await {
|
||||||
|
@ -17,6 +17,7 @@ enum TestPort {
|
|||||||
InviteModeFullGame,
|
InviteModeFullGame,
|
||||||
InviteModeFirstPlayerWin,
|
InviteModeFirstPlayerWin,
|
||||||
InviteModeSecondPlayerWin,
|
InviteModeSecondPlayerWin,
|
||||||
|
RandomModeFourGames,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestPort {
|
impl TestPort {
|
||||||
@ -40,10 +41,11 @@ impl Args {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod bot_client;
|
pub mod bot_client;
|
||||||
mod bot_client_bot_intermediate_play;
|
mod bot_intermediate_play;
|
||||||
mod bot_client_bot_linear_play;
|
mod bot_linear_play;
|
||||||
mod bot_client_bot_random_play;
|
mod bot_random_play;
|
||||||
mod bot_client_bot_smart_play;
|
mod bot_smart_play;
|
||||||
mod invite_mode;
|
mod invite_mode;
|
||||||
mod network_utils;
|
mod network_utils;
|
||||||
mod play_utils;
|
mod play_utils;
|
||||||
|
mod random_mode;
|
||||||
|
39
sea_battle_backend/src/test/random_mode.rs
Normal file
39
sea_battle_backend/src/test/random_mode.rs
Normal 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user