use crate::rules::PiratesRules; use crate::rules::*; use rand::Rng; use std::fs::{self, File}; use std::time::SystemTime; use std::{env, io::Read}; use weasel::creature::CreatureId; use weasel::team::TeamId; use weasel::{ ActivateAbility, AlterStatistics, Battle, BattleController, BattleState, Character, CreateCreature, CreateTeam, EndBattle, EndTurn, EntityId, EventKind, EventQueue, EventReceiver, EventTrigger, EventWrapper, FlatVersionedEvent, RemoveCreature, ResetEntropy, Server, StartTurn, }; // Constants to identify teams. const PLAYER_TEAM: &str = "player"; const ENEMY_TEAM: &str = "enemy"; // Constants to identify creatures (ships). static PLAYER_SHIP: CreatureId = 0; static ENEMY_SHIP: CreatureId = 1; pub struct Game { server: Server, } impl Game { pub fn new() -> Self { // Create a battle object with our game rules. // We attach a callback to the battle, so that we can display a brief commentary // when certain events happen! let battle = Battle::builder(PiratesRules::new()) .event_callback(Box::new(commentary)) .build(); // Create a server to orchestrate the game. let mut server = Server::builder(battle).build(); // Reset entropy with a 'random enough' seed. let time = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap(); ResetEntropy::trigger(&mut server) .seed(time.as_secs()) .fire() .unwrap(); // Create a team for the player. // Changes to the battle are always performed through events. To fire an event // just create a trigger object from the event itself, then call fire() on it. // // You must always give a valid 'EventProcessor' to a trigger, which can be either a // server or a client. CreateTeam::trigger(&mut server, PLAYER_TEAM.to_string()) // The objective of this team is to defeat ENEMY_TEAM. .objectives_seed(ENEMY_TEAM.to_string()) .fire() .unwrap(); // Now create one ship for the player. CreateCreature::trigger(&mut server, PLAYER_SHIP, PLAYER_TEAM.to_string(), ()) .fire() .unwrap(); // Do the same for the enemy. CreateTeam::trigger(&mut server, ENEMY_TEAM.to_string()) .objectives_seed(PLAYER_TEAM.to_string()) .fire() .unwrap(); CreateCreature::trigger(&mut server, ENEMY_SHIP, ENEMY_TEAM.to_string(), ()) .fire() .unwrap(); // Return a game object. Self { server } } pub fn fire_cannonball(&mut self) { // Before our ship can attack we must start a player turn. StartTurn::trigger(&mut self.server, EntityId::Creature(PLAYER_SHIP)) .fire() .unwrap(); // Now activate the 'cannonball' ability of the player's ship. // To do so we use an ActivateAbility event. We must specify the entity doing the ability, // the id of the ability itself and as 'activation' who is the target. ActivateAbility::trigger( &mut self.server, EntityId::Creature(PLAYER_SHIP), ABILITY_CANNONBALL.to_string(), ) .activation(EntityId::Creature(ENEMY_SHIP)) .fire() .unwrap(); // After the ship's attack we just end the player turn. EndTurn::trigger(&mut self.server).fire().unwrap(); } pub fn fire_grapeshot(&mut self) { // Some logic as fire_cannonball, but fire another ability. StartTurn::trigger(&mut self.server, EntityId::Creature(PLAYER_SHIP)) .fire() .unwrap(); ActivateAbility::trigger( &mut self.server, EntityId::Creature(PLAYER_SHIP), ABILITY_GRAPESHOT.to_string(), ) .activation(EntityId::Creature(ENEMY_SHIP)) .fire() .unwrap(); EndTurn::trigger(&mut self.server).fire().unwrap(); } pub fn enemy_turn(&mut self) { // Before the enemy ship can attack we must start an enemy turn. StartTurn::trigger(&mut self.server, EntityId::Creature(ENEMY_SHIP)) .fire() .unwrap(); // Fire a random ability. let mut rng = rand::thread_rng(); let rng_number = rng.gen_range(0, 2); let ability = if rng_number == 0 { ABILITY_CANNONBALL } else { ABILITY_GRAPESHOT }; ActivateAbility::trigger( &mut self.server, EntityId::Creature(ENEMY_SHIP), ability.to_string(), ) .activation(EntityId::Creature(PLAYER_SHIP)) .fire() .unwrap(); // After the ship's attack we just end the enemy turn. EndTurn::trigger(&mut self.server).fire().unwrap(); } /// Saves the battle's history as json in a temporary file. pub fn save(&mut self) { // Collect all events in a serializable format. let events: Vec> = self .server .battle() .versioned_events(std::ops::Range { start: 0, end: self.server.battle().history().len() as usize, }) .map(|e| e.into()) .collect(); // Serialize as json. let json = serde_json::to_string(&events).unwrap(); // Write the json into a temporary file. let mut path = env::temp_dir(); path.push("savegame"); fs::write(path, json).unwrap(); println!("game saved!"); } /// Restores the battle's history from a json temporary file. pub fn load(&mut self) { // Read the json stored in a temporary file. let mut json = String::new(); let mut path = env::temp_dir(); path.push("savegame"); let file = File::open(path); match file { Ok(mut file) => { file.read_to_string(&mut json).unwrap(); // Deserialize all events. let events: Vec> = serde_json::from_str(&json).unwrap(); // Replay all events in a new instance of server. let battle = Battle::builder(PiratesRules::new()).build(); self.server = Server::builder(battle).build(); for event in events { self.server.receive(event.into()).unwrap(); } // Attach the callback now to avoid invoking it while the events in history // are replayed. self.server.set_event_callback(Some(Box::new(commentary))); println!("savegame loaded!"); } Err(_) => println!("no savegame found!"), } } // Returns the statistics of a ship. fn ship_stats(&self, id: CreatureId) -> (i16, i16) { // Retrieve the creature for the list of entities. let creature = self.server.battle().entities().creature(&id); // Be careful because the ship might have been destroyed. match creature { Some(creature) => ( creature.statistic(&STAT_HULL).unwrap().value(), creature.statistic(&STAT_CREW).unwrap().value(), ), None => (0, 0), } } pub fn player_stats(&self) -> (i16, i16) { self.ship_stats(PLAYER_SHIP) } pub fn enemy_stats(&self) -> (i16, i16) { self.ship_stats(ENEMY_SHIP) } pub fn check_winner(&mut self) -> bool { // We want to return whether or not there's a winner. let winner: Vec<_> = self.server.battle().entities().victorious_id().collect(); if !winner.is_empty() { println!("{} won!", pretty_team_id(&winner[0])); // End the battle as well. EndBattle::trigger(&mut self.server).fire().unwrap(); true } else { false } } } /// Event callback that prints some commentary out of the events happening in the battle. fn commentary( event: &EventWrapper, _: &BattleState, _: &mut Option>, ) { match event.kind() { EventKind::AlterStatistics => { let event: &AlterStatistics = match event.as_any().downcast_ref::>() { Some(e) => e, None => panic!("incorrect cast!"), }; let (hull_damage, crew_damage) = event.alteration(); if *hull_damage != 0 { println!( "{} took {} hull damage!", pretty_creature_id(&event.id().creature().unwrap()), hull_damage ); } if *crew_damage != 0 { println!( "{} took {} crew damage!", pretty_creature_id(&event.id().creature().unwrap()), crew_damage ); } } EventKind::RemoveCreature => { let event: &RemoveCreature = match event.as_any().downcast_ref::>() { Some(e) => e, None => panic!("incorrect cast!"), }; println!("{} destroyed!", pretty_creature_id(event.id())); } _ => {} // Do nothing. } } fn pretty_creature_id(id: &CreatureId) -> &'static str { if *id == PLAYER_SHIP { "Player ship" } else { "Enemy ship" } } fn pretty_team_id(id: &TeamId) -> &'static str { if id == PLAYER_TEAM { "Player" } else { "Enemy" } }