#![allow(clippy::incompatible_msrv)]
use std::collections::HashMap;
use ractor::cast;
use ractor::Actor;
use ractor::ActorId;
use ractor::ActorProcessingErr;
use ractor::ActorRef;
use rand::thread_rng;
use rand::Rng;
struct GameState {
funds: i64,
wager: u32,
total_rounds: u32,
current_round: u32,
results_vec: Vec<i64>,
}
impl Default for GameState {
fn default() -> Self {
Self {
funds: 10_000,
wager: 100,
total_rounds: 100,
current_round: 1,
results_vec: vec![],
}
}
}
impl GameState {
fn roll_dice() -> bool {
let mut rng = thread_rng();
matches!(rng.gen_range(0..101), x if x > 51)
}
}
struct Game;
struct GameMessage(ActorRef<GameManagerMessage>);
#[cfg(feature = "cluster")]
impl ractor::Message for GameMessage {}
#[cfg_attr(feature = "async-trait", ractor::async_trait)]
impl Actor for Game {
type Msg = GameMessage;
type State = GameState;
type Arguments = ();
async fn pre_start(
&self,
_myself: ActorRef<Self::Msg>,
_: (),
) -> Result<Self::State, ActorProcessingErr> {
Ok(GameState::default())
}
async fn handle(
&self,
myself: ActorRef<Self::Msg>,
message: Self::Msg,
state: &mut Self::State,
) -> Result<(), ActorProcessingErr> {
if state.current_round <= state.total_rounds {
state.current_round += 1;
match Self::State::roll_dice() {
true => state.funds += state.wager as i64,
false => state.funds -= state.wager as i64,
}
state.results_vec.push(state.funds);
cast!(myself, message).expect("Failed to send message");
} else {
cast!(
message.0,
GameManagerMessage {
id: myself.get_id(),
results: state.results_vec.clone(),
}
)?;
myself.stop(None);
}
Ok(())
}
}
struct GameManager;
struct GameManagerMessage {
id: ActorId,
results: Vec<i64>,
}
#[cfg(feature = "cluster")]
impl ractor::Message for GameManagerMessage {}
struct GameManagerState {
games_finished: u32,
total_games: u32,
results: HashMap<ActorId, Vec<i64>>,
}
impl GameManagerState {
fn new(total_games: u32) -> Self {
Self {
games_finished: 0,
total_games,
results: HashMap::new(),
}
}
}
#[cfg_attr(feature = "async-trait", ractor::async_trait)]
impl Actor for GameManager {
type Msg = GameManagerMessage;
type State = GameManagerState;
type Arguments = u32;
async fn pre_start(
&self,
myself: ActorRef<Self::Msg>,
num_games: u32,
) -> Result<Self::State, ActorProcessingErr> {
let game_conditions = GameState::default();
tracing::info!("Starting funds: ${}", game_conditions.funds);
tracing::info!("Wager per round: ${}", game_conditions.wager);
tracing::info!("Rounds per game: {}", game_conditions.total_rounds);
tracing::info!("Running simulations...");
for _ in 0..num_games {
let (actor, _) = Actor::spawn_linked(None, Game, (), myself.clone().into())
.await
.expect("Failed to start game");
cast!(actor, GameMessage(myself.clone())).expect("Failed to send message");
}
Ok(GameManagerState::new(num_games))
}
async fn handle(
&self,
myself: ActorRef<Self::Msg>,
message: Self::Msg,
state: &mut Self::State,
) -> Result<(), ActorProcessingErr> {
state.results.insert(message.id, message.results);
state.games_finished += 1;
if state.games_finished >= state.total_games {
let average_funds = state
.results
.values()
.map(|v| v.last().unwrap())
.sum::<i64>()
/ state.total_games as i64;
tracing::info!("Simulations ran: {}", state.results.len());
tracing::info!("Final average funds: ${average_funds}");
myself.stop(None);
}
Ok(())
}
}
const NUM_GAMES: u32 = 100;
fn init_logging() {
let dir = tracing_subscriber::filter::Directive::from(tracing::Level::DEBUG);
use std::io::stderr;
use std::io::IsTerminal;
use tracing_glog::Glog;
use tracing_glog::GlogFields;
use tracing_subscriber::filter::EnvFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::Registry;
let fmt = tracing_subscriber::fmt::Layer::default()
.with_ansi(stderr().is_terminal())
.with_writer(std::io::stderr)
.event_format(Glog::default().with_timer(tracing_glog::LocalTime::default()))
.fmt_fields(GlogFields::default().compact());
let filter = vec![dir]
.into_iter()
.fold(EnvFilter::from_default_env(), |filter, directive| {
filter.add_directive(directive)
});
let subscriber = Registry::default().with(filter).with(fmt);
tracing::subscriber::set_global_default(subscriber).expect("to set global subscriber");
}
#[ractor_example_entry_proc::ractor_example_entry]
async fn main() {
init_logging();
let manager = GameManager;
let (_actor, handle) = Actor::spawn(None, manager, NUM_GAMES)
.await
.expect("Failed to start game manager");
handle.await.unwrap();
}