use crate::rules::*; use std::io::Read; use weasel::creature::CreatureId; use weasel::team::TeamId; use weasel::{ ActivateAbility, Battle, BattleController, CreateCreature, CreateTeam, EndTurn, EntityId, EventKind, EventReceiver, EventTrigger, Server, StartTurn, VersionedEventWrapper, }; mod rules; static TEAM_ID: TeamId = 0; static CREATURE_ID: CreatureId = 0; static ENTITY_ID: EntityId = EntityId::Creature(CREATURE_ID); fn main() { print_intro(); // The loop where the game progresses. game_loop(); // When this point is reached, the game has ended. println!(); println!("Goodbye!"); } fn print_intro() { println!("Undo"); println!(); println!("Example to demonstrate how to undo/redo player actions with weasel."); println!("Move around the soldier on the battlefield."); println!(); print_controls(); } fn print_controls() { println!(" Controls:"); println!(" w - Move up"); println!(" s - Move down"); println!(" d - Move right"); println!(" a - Move left"); println!(" u - Undo"); println!(" r - Redo"); println!(" h - Display the controls"); println!(" q - Quit"); } fn game_loop() { // Create a server. let mut server = create_game(); // Create a buffer to keep events for redo. let mut event_buffer = Vec::new(); println!(); display_world(&server); // Main loop. loop { // Read a char from stdin. let input: Option = std::io::stdin() .bytes() .next() .and_then(|result| result.ok()) .map(|byte| byte as char); // Take an action depending on the user input. if let Some(key) = input { match key { 'w' => { walk(&mut server, &mut event_buffer, Direction::Up); display_world(&server); } 's' => { walk(&mut server, &mut event_buffer, Direction::Down); display_world(&server); } 'd' => { walk(&mut server, &mut event_buffer, Direction::Right); display_world(&server); } 'a' => { walk(&mut server, &mut event_buffer, Direction::Left); display_world(&server); } 'u' => { server = undo(server, &mut event_buffer); display_world(&server); } 'r' => { redo(&mut server, &mut event_buffer); display_world(&server); } 'h' => print_controls(), 'q' => break, _ => {} } } } } fn display_world(server: &Server) { // Display the number of steps taken and the space model. let steps = server .battle() .history() .events() .iter() .filter(|e| e.kind() == EventKind::ActivateAbility) .count(); let battlefield = server.battle().space().model(); println!("Steps: {}\nBattlefield:\n{}", steps, battlefield); } /// Creates a new server fn create_server() -> Server { let battle = Battle::builder(CustomRules::new()).build(); Server::builder(battle).build() } /// Creates a new game: a server with a team and a creature. fn create_game() -> Server { let mut server = create_server(); // Create a team and a creature. CreateTeam::trigger(&mut server, TEAM_ID).fire().unwrap(); CreateCreature::trigger(&mut server, CREATURE_ID, TEAM_ID, Square { x: 0, y: 0 }) .fire() .unwrap(); server } /// Moves the creature on step towards the given direction. fn walk( server: &mut Server, event_buffer: &mut Vec>, direction: Direction, ) { // Clean the buffered events to invalidate the redo action. event_buffer.clear(); // Start a turn. StartTurn::trigger(server, ENTITY_ID).fire().unwrap(); // Activate the 'walk' ability of the creature. let result = ActivateAbility::trigger(server, ENTITY_ID, WALK) .activation(direction) .fire(); // We print the error in case the movement is not allowed. if let Err(e) = result { println!("{:?}", e.unfold()); } // End the turn. EndTurn::trigger(server).fire().unwrap(); } /// Undo the last action. fn undo( server: Server, event_buffer: &mut Vec>, ) -> Server { // Retrieve the last event of type ActivateAbility. let last_activation_index = server .battle() .history() .events() .iter() .rposition(|e| e.kind() == EventKind::ActivateAbility); match last_activation_index { Some(last_activation_index) => { // We are gonna undo this turn. // First save the current history in a buffer, if it's empty. If it's not, it means // we are already undoing a series of events. if event_buffer.is_empty() { let mut events = server .battle() .versioned_events(std::ops::Range { start: 0, end: server.battle().history().len() as usize, }) .collect(); event_buffer.append(&mut events); } // Create a completely new server. let mut server = create_server(); // Replay history up to the last ActivateAbility before 'last', to skip turns in // which the player did a wrong move. // To nicely wrap the turn we should undo also the StartTurn event. let previous_start_turn_index = &event_buffer[..last_activation_index] .iter() .rposition(|e| e.kind() == EventKind::StartTurn); // We replay all events in the buffer up to the start turn (excluded). // There will always be a StartTurn before an ActivateAbility. for event in event_buffer .iter() .take(previous_start_turn_index.unwrap() as usize) { server.receive(event.clone()).unwrap(); } server } None => { // No single action was taken yet. We can't undo anything, so return the same server. server } } } /// Redo the last undoed action. fn redo( server: &mut Server, event_buffer: &mut Vec>, ) { let history_len = server.battle().history().events().len(); if event_buffer.len() > history_len { // There are some events to redo. // It's enough to replay the missing events on top of the existing server. // Let's first find the next ActivateAbility. let future_events = &event_buffer[history_len..]; let next_activation = future_events .iter() .position(|e| e.kind() == EventKind::ActivateAbility); // However, since we want to redo an entire turn, replay up to the EndTurn (included). if let Some(next_activation) = next_activation { // Find the EndTurn immediately after 'next_activation'. let end_turn = &future_events[next_activation..] .iter() .position(|e| e.kind() == EventKind::EndTurn) .unwrap(); // Add 'next_activation' index to get the index of 'end_turn' in 'future_events'. let end_turn = end_turn + next_activation; // Replay events up 'end_turn' (included). for event in &future_events[..=end_turn] { server.receive(event.clone()).unwrap(); } } } // Nothing to redo. }