diff --git a/client/Cargo.lock b/client/Cargo.lock index f9b133c18..258429cfb 100644 --- a/client/Cargo.lock +++ b/client/Cargo.lock @@ -448,6 +448,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json-patch" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "159294d661a039f7644cea7e4d844e6b25aaf71c1ffe9d73a96d768c24b0faf4" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonptr" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -793,6 +815,7 @@ dependencies = [ "console_error_panic_hook", "hex2d", "itertools", + "json-patch", "num", "quad-rand", "serde", @@ -837,6 +860,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing" version = "0.1.41" diff --git a/client/src/local_client/bin/main.rs b/client/src/local_client/bin/main.rs index 8368bb004..ca35cc0a3 100644 --- a/client/src/local_client/bin/main.rs +++ b/client/src/local_client/bin/main.rs @@ -56,7 +56,7 @@ pub async fn run(mut game: Game, features: &Features) { GameSyncRequest::None => {} GameSyncRequest::ExecuteAction(a) => { let p = game.active_player(); - execute_action(&mut game, a, p); + game = execute_action(game, a, p); state.show_player = game.active_player(); sync_result = GameSyncResult::Update; } diff --git a/server/Cargo.lock b/server/Cargo.lock index 3797ea031..00150ed4f 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -320,6 +320,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json-patch" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "159294d661a039f7644cea7e4d844e6b25aaf71c1ffe9d73a96d768c24b0faf4" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonptr" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -579,6 +601,7 @@ dependencies = [ "console_error_panic_hook", "hex2d", "itertools", + "json-patch", "num", "quad-rand", "serde", @@ -608,6 +631,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing" version = "0.1.41" diff --git a/server/Cargo.toml b/server/Cargo.toml index c88a397b1..55e8c9abb 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -26,3 +26,4 @@ itertools = "0.14.0" quad-rand = "0.2.3" console_error_panic_hook = "0.1.7" num = "0.4" +json-patch = "4.0.0" diff --git a/server/src/ability_initializer.rs b/server/src/ability_initializer.rs index 4652bf2a4..b0a53324e 100644 --- a/server/src/ability_initializer.rs +++ b/server/src/ability_initializer.rs @@ -1,13 +1,12 @@ use crate::content::custom_phase_actions::{ - AdvanceRequest, CurrentEventHandler, CurrentEventRequest, CurrentEventResponse, - CurrentEventType, MultiRequest, PaymentRequest, PlayerRequest, PositionRequest, - ResourceRewardRequest, SelectedStructure, StructuresRequest, UnitTypeRequest, UnitsRequest, + AdvanceRequest, CurrentEventHandler, CurrentEventRequest, CurrentEventResponse, MultiRequest, + PaymentRequest, PlayerRequest, PositionRequest, ResourceRewardRequest, SelectedStructure, + StructuresRequest, UnitTypeRequest, UnitsRequest, }; use crate::events::{Event, EventOrigin}; -use crate::player_events::{CurrentEvent, PlayerCommands}; +use crate::player_events::CurrentEvent; use crate::position::Position; use crate::resource_pile::ResourcePile; -use crate::undo::UndoContext; use crate::unit::UnitType; use crate::{content::custom_actions::CustomActionType, game::Game, player_events::PlayerEvents}; use std::collections::HashMap; @@ -39,16 +38,6 @@ impl<'a, C, V> SelectedChoice<'a, C, V> { details, } } - - pub(crate) fn to_commands( - &self, - game: &mut Game, - gain: impl Fn(&mut PlayerCommands, &Game, &C) + 'static + Clone, - ) { - game.with_commands(self.player_index, |commands, game| { - gain(commands, game, &self.choice); - }); - } } pub struct AbilityListeners { @@ -228,10 +217,6 @@ pub(crate) trait AbilityInitializerSetup: Sized { .as_mut() .expect("current missing") .response = None; - if can_undo(¤t.event_type) { - game.undo_context_stack - .push(UndoContext::Event(Box::new(current))); - } let r = c.request.clone(); let a = action.clone(); phase.player.handler = None; @@ -758,16 +743,3 @@ pub(crate) fn join_ability_initializers(setup: Vec) -> Abili } }) } - -fn can_undo(event_type: &CurrentEventType) -> bool { - use CurrentEventType::*; - matches!( - event_type, - Advance(_) - | TurnStart - | Construct(_) - | Recruit(_) - | InfluenceCultureResolution(_) - | ExploreResolution(_) - ) -} diff --git a/server/src/action.rs b/server/src/action.rs index 5a5406742..b901a261b 100644 --- a/server/src/action.rs +++ b/server/src/action.rs @@ -11,15 +11,14 @@ use crate::incident::trigger_incident; use crate::log; use crate::map::Terrain::Unexplored; use crate::movement::{ - has_movable_units, move_units_destinations, stop_current_move, take_move_state, CurrentMove, - MoveState, + has_movable_units, move_units_destinations, take_move_state, CurrentMove, MoveState, }; use crate::playing_actions::PlayingAction; use crate::recruit::on_recruit; use crate::resource::check_for_waste; use crate::resource_pile::ResourcePile; use crate::status_phase::play_status_phase; -use crate::undo::{redo, undo, DisembarkUndoContext, UndoContext}; +use crate::undo::{clean_patch, redo, to_serde_value, undo}; use crate::unit::MovementAction::{Move, Stop}; use crate::unit::{get_current_move, MovementAction}; use crate::wonder::draw_wonder_card; @@ -78,36 +77,51 @@ impl Action { /// # Panics /// /// Panics if the action is illegal -pub fn execute_action(game: &mut Game, action: Action, player_index: usize) { +#[must_use] +pub fn execute_action(mut game: Game, action: Action, player_index: usize) -> Game { + let add_undo = !matches!(&action, Action::Undo); + let old = to_serde_value(&game); + let old_player = game.active_player(); + game = execute_without_undo(game, action, player_index); + let new = to_serde_value(&game); + let new_player = game.active_player(); + let patch = json_patch::diff(&new, &old); + if old_player != new_player { + game.lock_undo(); // don't undo player change + } else if add_undo && game.can_undo() { + game.action_log[game.action_log_index - 1].undo = clean_patch(patch.0); + } + game +} + +fn execute_without_undo(mut game: Game, action: Action, player_index: usize) -> Game { assert!(player_index == game.active_player(), "Illegal action"); if let Action::Undo = action { assert!( game.can_undo(), "actions revealing new information can't be undone" ); - undo(game, player_index); - return; + return undo(game); } if matches!(action, Action::Redo) { - assert!(game.can_redo(), "no action can be redone"); - redo(game, player_index); - return; + assert!(game.can_redo(), "action can't be redone"); + redo(&mut game, player_index); + return game; } - add_log_item_from_action(game, &action); - add_action_log_item(game, action.clone()); + add_log_item_from_action(&mut game, &action); + add_action_log_item(&mut game, action.clone()); if let Some(s) = game.current_event_handler_mut() { s.response = action.custom_phase_event(); let details = game.current_event().event_type.clone(); - execute_custom_phase_action(game, player_index, &details); + execute_custom_phase_action(&mut game, player_index, &details); } else { - execute_regular_action(game, action, player_index); + execute_regular_action(&mut game, action, player_index); } - check_for_waste(game); - - game.action_log[game.action_log_index - 1].undo = std::mem::take(&mut game.undo_context_stack); + check_for_waste(&mut game); + game } fn add_action_log_item(game: &mut Game, item: Action) { @@ -138,7 +152,6 @@ pub(crate) fn execute_custom_phase_action( start_combat(game); } CombatRoundEnd(r) => { - game.lock_undo(); if let Some(c) = combat_round_end(game, r) { combat_loop(game, c); } @@ -202,10 +215,7 @@ pub(crate) fn execute_movement_action( action: MovementAction, player_index: usize, ) { - let Some(GameState::Movement(saved_state)) = game.state_stack.last().cloned() else { - panic!("game should be in a movement state"); - }; - let (starting_position, disembarked_units) = match action { + match action { Move(m) => { let player = &game.players[player_index]; let starting_position = player @@ -213,17 +223,6 @@ pub(crate) fn execute_movement_action( "instead of providing no units to move a stop movement actions should be done", )) .position; - let disembarked_units = m - .units - .iter() - .filter_map(|unit| { - let unit = player.get_unit(*unit); - unit.carrier_id.map(|carrier_id| DisembarkUndoContext { - unit_id: unit.id, - carrier_id, - }) - }) - .collect(); match move_units_destinations( player, game, @@ -283,26 +282,18 @@ pub(crate) fn execute_movement_action( starting_position, m.destination, ); - stop_current_move(game); - return; // can't undo this action + return; } if move_with_possible_combat(game, player_index, starting_position, &m) { return; } - - (Some(starting_position), disembarked_units) } Stop => { game.pop_state(); return; } }; - game.push_undo_context(UndoContext::Movement { - starting_position, - move_state: saved_state, - disembarked_units, - }); let state = take_move_state(game); let all_moves_used = diff --git a/server/src/advance.rs b/server/src/advance.rs index eb4b6535a..2124cb984 100644 --- a/server/src/advance.rs +++ b/server/src/advance.rs @@ -204,18 +204,6 @@ pub(crate) fn gain_advance(game: &mut Game, player_index: usize, info: &AdvanceI } } -pub(crate) fn undo_advance( - game: &mut Game, - advance: &Advance, - player_index: usize, - was_custom_phase: bool, -) { - remove_advance(game, advance, player_index); - if !was_custom_phase { - game.players[player_index].incident_tokens += 1; - } -} - pub(crate) fn remove_advance(game: &mut Game, advance: &Advance, player_index: usize) { (advance.listeners.deinitializer)(game, player_index); (advance.listeners.undo_deinitializer)(game, player_index); diff --git a/server/src/city.rs b/server/src/city.rs index daeda5d4c..5ce5ec2a0 100644 --- a/server/src/city.rs +++ b/server/src/city.rs @@ -94,15 +94,6 @@ impl City { self.angry_activation = false; } - pub fn undo_activate(&mut self) { - self.activations -= 1; - if self.angry_activation { - self.angry_activation = false; - } else if self.is_activated() { - self.increase_mood_state(); - } - } - #[must_use] pub fn is_activated(&self) -> bool { self.activations > 0 diff --git a/server/src/collect.rs b/server/src/collect.rs index e617df26f..1c7efd4b6 100644 --- a/server/src/collect.rs +++ b/server/src/collect.rs @@ -56,23 +56,19 @@ pub fn get_total_collection( } pub(crate) fn collect(game: &mut Game, player_index: usize, c: &Collect) { - let i = get_total_collection(game, player_index, c.city_position, &c.collections) + let mut i = get_total_collection(game, player_index, c.city_position, &c.collections) .expect("Illegal action"); let city = game.players[player_index].get_city_mut(c.city_position); assert!(city.can_activate(), "Illegal action"); city.activate(); + let _ = game + .get_player(player_index) + .events + .on_collect + .get() + .trigger(&mut i, game, &()); + i.info.execute(game); game.players[player_index].gain_resources(i.total.clone()); - i.info.execute_with_options(game, |c| { - c.gained_resources = i.total.clone(); - }); - game.trigger_command_event(player_index, |e| &mut e.on_collect, &c.city_position); -} - -pub(crate) fn undo_collect(game: &mut Game, player_index: usize, c: &Collect) { - game.players[player_index] - .get_city_mut(c.city_position) - .undo_activate(); - // resources are handled with player commands } pub(crate) struct CollectContext { @@ -86,6 +82,7 @@ pub(crate) struct CollectContext { pub struct CollectInfo { pub choices: HashMap>, pub modifiers: Vec, + pub city: Position, pub total: ResourcePile, pub(crate) info: ActionInfo, } @@ -94,12 +91,14 @@ impl CollectInfo { pub(crate) fn new( choices: HashMap>, player: &Player, + city: Position, ) -> CollectInfo { CollectInfo { choices, modifiers: Vec::new(), total: ResourcePile::empty(), info: ActionInfo::new(player), + city, } } } @@ -146,7 +145,7 @@ pub fn possible_resource_collections( .collect_options .get() .trigger_with_minimal_modifiers( - &CollectInfo::new(collect_options, &game.players[player_index]), + &CollectInfo::new(collect_options, &game.players[player_index], city_pos), &CollectContext { player_index, city_position: city_pos, diff --git a/server/src/combat.rs b/server/src/combat.rs index c2bf4f310..3a3849ede 100644 --- a/server/src/combat.rs +++ b/server/src/combat.rs @@ -186,7 +186,9 @@ pub fn initiate_combat( } pub(crate) fn start_combat(game: &mut Game) { - game.lock_undo(); + game.lock_undo(); // combat should not be undoable + stop_current_move(game); + let combat = get_combat(game); let attacker = combat.attacker; let defender = combat.defender; @@ -369,7 +371,6 @@ pub(crate) fn draw(game: &mut Game, c: Combat) -> Option { } pub(crate) fn end_combat(game: &mut Game, combat: Combat, r: CombatResult) -> Option { - game.lock_undo(); let attacker = combat.attacker; let defender = combat.defender; let defender_position = combat.defender_position; @@ -389,9 +390,6 @@ pub(crate) fn end_combat(game: &mut Game, combat: Combat, r: CombatResult) -> Op } take_combat(game); - if let Some(GameState::Movement(_)) = game.state_stack.last() { - stop_current_move(game); - } None } @@ -532,9 +530,6 @@ pub(crate) fn conquer_city( let Some(mut city) = game.players[old_player_index].take_city(position) else { panic!("player should have this city") }; - // undo would only be possible if the old owner can't spawn a settler - // and this would be hard to understand - game.lock_undo(); game.add_to_last_log_item(&format!( " and captured {}'s city at {position}", game.player_name(old_player_index) @@ -735,7 +730,6 @@ pub mod tests { wonder_amount_left: 0, incidents_left: Vec::new(), permanent_incident_effects: Vec::new(), - undo_context_stack: Vec::new(), } } diff --git a/server/src/content/advances_agriculture.rs b/server/src/content/advances_agriculture.rs index 7bd58fd01..e49ee1754 100644 --- a/server/src/content/advances_agriculture.rs +++ b/server/src/content/advances_agriculture.rs @@ -30,9 +30,6 @@ fn storage() -> AdvanceBuilder { .add_one_time_ability_initializer(|game, player_index| { game.players[player_index].resource_limit.food = 7; }) - .add_ability_undo_deinitializer(|game, player_index| { - game.players[player_index].resource_limit.food = 2; - }) .with_advance_bonus(MoodToken) } diff --git a/server/src/content/advances_culture.rs b/server/src/content/advances_culture.rs index 06749e165..45bf89552 100644 --- a/server/src/content/advances_culture.rs +++ b/server/src/content/advances_culture.rs @@ -7,7 +7,7 @@ use crate::content::advances::{advance_group_builder, AdvanceGroup}; use crate::content::custom_actions::CustomActionType; use crate::game::Game; use crate::payment::{PaymentConversion, PaymentConversionType, PaymentOptions}; -use crate::playing_actions::{increase_happiness, undo_increase_happiness}; +use crate::playing_actions::increase_happiness; use crate::position::Position; use crate::resource::ResourceType; use crate::resource_pile::ResourcePile; @@ -81,16 +81,6 @@ pub(crate) fn execute_sports( increase_happiness(game, player_index, &[(pos, payment.culture_tokens)], None); } -pub(crate) fn undo_sports( - game: &mut Game, - player_index: usize, - pos: Position, - payment: &ResourcePile, -) { - undo_increase_happiness(game, player_index, &[(pos, payment.culture_tokens)], None); - game.players[player_index].gain_resources_in_undo(payment.clone()); -} - #[must_use] pub fn theaters_options() -> PaymentOptions { PaymentOptions::sum(1, &[ResourceType::CultureTokens, ResourceType::MoodTokens]) @@ -101,11 +91,6 @@ pub(crate) fn execute_theaters(game: &mut Game, player_index: usize, payment: &R game.players[player_index].pay_cost(&theaters_options(), payment); } -pub(crate) fn undo_theaters(game: &mut Game, player_index: usize, payment: &ResourcePile) { - game.players[player_index].lose_resources(theater_opposite(payment)); - game.players[player_index].gain_resources_in_undo(payment.clone()); -} - fn theater_opposite(payment: &ResourcePile) -> ResourcePile { if payment.mood_tokens > 0 { ResourcePile::culture_tokens(1) diff --git a/server/src/content/advances_education.rs b/server/src/content/advances_education.rs index 387e804dc..0144b5449 100644 --- a/server/src/content/advances_education.rs +++ b/server/src/content/advances_education.rs @@ -33,11 +33,14 @@ fn public_education() -> AdvanceBuilder { .with_advance_bonus(MoodToken) .add_once_per_turn_listener( |event| &mut event.on_collect, - |e| &mut e.content.info, - |player, game, pos| { - if game.get_city(player.index, *pos).pieces.academy.is_some() { - player.gain_resources(ResourcePile::ideas(1)); - player.add_info_log_item("Public Education gained 1 idea"); + |e| &mut e.info.info, + |i, game, ()| { + let player = game.get_player(game.active_player()); + if player.get_city(i.city).pieces.academy.is_some() { + i.total += ResourcePile::ideas(1); + i.info + .log + .push("Public Education gained 1 idea".to_string()); } }, 0, @@ -69,19 +72,21 @@ fn free_education() -> AdvanceBuilder { None } }, - |game, payment| { - payment.to_commands(game, |c, _game, payment| { - let pile = &payment[0]; - if pile.is_empty() { - c.add_info_log_item(&format!("{} declined to pay for free education", c.name)); - return; - } - c.add_info_log_item(&format!( - "{} paid {} for free education to gain 1 mood token", - c.name, pile + |game, s| { + let pile = &s.choice[0]; + if pile.is_empty() { + game.add_info_log_item(&format!( + "{} declined to pay for free education", + s.player_name )); - c.gain_resources(ResourcePile::mood_tokens(1)); - }); + return; + } + game.add_info_log_item(&format!( + "{} paid {} for free education to gain 1 mood token", + s.player_name, pile + )); + game.get_player_mut(s.player_index) + .gain_resources(ResourcePile::mood_tokens(1)); }, ) } @@ -94,9 +99,6 @@ fn philosophy() -> AdvanceBuilder { .add_one_time_ability_initializer(|game, player_index| { game.players[player_index].gain_resources(ResourcePile::ideas(1)); }) - .add_ability_undo_deinitializer(|game, player_index| { - game.players[player_index].lose_resources(ResourcePile::ideas(1)); - }) .add_simple_current_event_listener( |event| &mut event.on_advance, 0, @@ -106,11 +108,9 @@ fn philosophy() -> AdvanceBuilder { .iter() .any(|a| a.name == advance.name) { - game.with_commands(player_index, |player, _game| { - player.gain_resources(ResourcePile::ideas(1)); - player - .add_info_log_item(&format!("{player_name} gained 1 idea from Philosophy")); - }); + let player = game.get_player_mut(player_index); + player.gain_resources(ResourcePile::ideas(1)); + game.add_info_log_item(&format!("{player_name} gained 1 idea from Philosophy")); } }, ) diff --git a/server/src/content/advances_seafearing.rs b/server/src/content/advances_seafearing.rs index 7852a28ec..e85a40843 100644 --- a/server/src/content/advances_seafearing.rs +++ b/server/src/content/advances_seafearing.rs @@ -57,16 +57,16 @@ fn cartography() -> AdvanceBuilder { .add_player_event_listener( |event| &mut event.before_move, 0, - |player, g, i| { + |game, i, ()| { // info is the action that we last used this ability for - let key = g.actions_left.to_string(); - if player.content.info.get("Cartography").is_some_and(|info| info == &key) { + let key = game.actions_left.to_string(); + if game.get_player(i.player).event_info.get("Cartography").is_some_and(|info| info == &key) { return; } let mut ship = false; let mut navigation = false; for id in &i.units { - let unit = g.get_player(i.player).get_unit(*id); + let unit = game.get_player(i.player).get_unit(*id); if unit.unit_type.is_ship() { ship = true; if !unit.position.is_neighbor(i.to) { @@ -75,12 +75,13 @@ fn cartography() -> AdvanceBuilder { } } if ship { - player.content.info.insert("Cartography".to_string(), key); + let player = game.get_player_mut(i.player); + player.event_info.insert("Cartography".to_string(), key); player.gain_resources(ResourcePile::ideas(1)); - player.add_info_log_item("Cartography gained 1 idea"); + game.add_info_log_item("Cartography gained 1 idea"); if navigation { - player.gain_resources(ResourcePile::culture_tokens(1)); - player.add_info_log_item(" and 1 culture token (for using navigation)"); + game.get_player_mut(i.player).gain_resources(ResourcePile::culture_tokens(1)); + game.add_info_log_item(" and 1 culture token (for using navigation)"); } } }, diff --git a/server/src/content/advances_theocracy.rs b/server/src/content/advances_theocracy.rs index 678496df0..a72aae328 100644 --- a/server/src/content/advances_theocracy.rs +++ b/server/src/content/advances_theocracy.rs @@ -95,9 +95,9 @@ fn conversion() -> AdvanceBuilder { .add_player_event_listener( |event| &mut event.on_influence_culture_success, 0, - |c, _, ()| { - c.gain_resources(ResourcePile::culture_tokens(1)); - c.add_info_log_item("Player gained 1 culture token for a successful Influence Culture attempt for Conversion Advance"); + |game, player, ()| { + game.get_player_mut(*player).gain_resources(ResourcePile::culture_tokens(1)); + game.add_info_log_item("Player gained 1 culture token for a successful Influence Culture attempt for Conversion Advance"); }, ) } diff --git a/server/src/content/custom_actions.rs b/server/src/content/custom_actions.rs index b776f8bf3..b643bf4af 100644 --- a/server/src/content/custom_actions.rs +++ b/server/src/content/custom_actions.rs @@ -1,10 +1,8 @@ use serde::{Deserialize, Serialize}; use crate::action::Action; -use crate::collect::{collect, undo_collect}; -use crate::content::advances_culture::{ - execute_sports, execute_theaters, undo_sports, undo_theaters, -}; +use crate::collect::collect; +use crate::content::advances_culture::{execute_sports, execute_theaters}; use crate::content::advances_economy::collect_taxes; use crate::content::wonders::construct_wonder; use crate::cultural_influence::influence_culture_attempt; @@ -14,8 +12,7 @@ use crate::log::{ }; use crate::player::Player; use crate::playing_actions::{ - increase_happiness, undo_increase_happiness, Collect, IncreaseHappiness, - InfluenceCultureAttempt, PlayingAction, + increase_happiness, Collect, IncreaseHappiness, InfluenceCultureAttempt, PlayingAction, }; use crate::{ game::Game, playing_actions::ActionType, position::Position, resource_pile::ResourcePile, @@ -112,55 +109,6 @@ impl CustomAction { } } - pub(crate) fn undo(self, game: &mut Game, player_index: usize) { - let action = self.custom_action_type(); - if action.action_type().once_per_turn { - game.players[player_index] - .played_once_per_turn_actions - .retain(|a| a != &action); - } - match self { - CustomAction::ConstructWonder { - city_position, - wonder: _, - payment, - } => { - game.players[player_index].gain_resources_in_undo(payment); - let wonder = game.undo_build_wonder(city_position, player_index); - game.players[player_index].wonder_cards.push(wonder); - } - CustomAction::AbsolutePower => game.actions_left -= 1, - CustomAction::ForcedLabor => { - // we check that the action was played - } - CustomAction::CivilRights => { - game.players[player_index].lose_resources(ResourcePile::mood_tokens(3)); - } - CustomAction::ArtsInfluenceCultureAttempt(_) => panic!("Action can't be undone"), - CustomAction::VotingIncreaseHappiness(i) => { - undo_increase_happiness( - game, - player_index, - &i.happiness_increases, - Some(i.payment), - ); - } - CustomAction::FreeEconomyCollect(c) => undo_collect(game, player_index, &c), - CustomAction::Taxes(r) => { - game.players[player_index].lose_resources(r); - } - CustomAction::Theaters(r) => { - undo_theaters(game, player_index, &r); - } - CustomAction::Sports { - city_position, - payment, - } => { - undo_sports(game, player_index, city_position, &payment); - } - } - } - #[must_use] pub fn format_log_item(&self, game: &Game, player: &Player, player_name: &str) -> String { match self { diff --git a/server/src/content/custom_phase_actions.rs b/server/src/content/custom_phase_actions.rs index 0265ea5a2..2ee9281a5 100644 --- a/server/src/content/custom_phase_actions.rs +++ b/server/src/content/custom_phase_actions.rs @@ -1,21 +1,17 @@ -use crate::action::{execute_custom_phase_action, Action}; -use crate::advance::undo_advance; +use crate::action::execute_custom_phase_action; use crate::barbarians::BarbariansEventState; use crate::city_pieces::Building; use crate::combat_listeners::{CombatResult, CombatRoundResult}; -use crate::content::advances::get_advance; -use crate::cultural_influence::undo_cultural_influence_resolution_action; use crate::events::EventOrigin; -use crate::explore::{undo_explore_resolution, ExploreResolutionState}; +use crate::explore::ExploreResolutionState; use crate::game::Game; use crate::map::Rotation; use crate::payment::PaymentOptions; use crate::player_events::{AdvanceInfo, IncidentInfo}; -use crate::playing_actions::{PlayingAction, Recruit}; +use crate::playing_actions::Recruit; use crate::position::Position; use crate::resource_pile::ResourcePile; use crate::status_phase::ChangeGovernmentType; -use crate::undo::UndoContext; use crate::unit::UnitType; use itertools::Itertools; use num::Zero; @@ -319,56 +315,6 @@ impl ChangeGovernmentRequest { } impl CurrentEventResponse { - pub(crate) fn undo(self, game: &mut Game, player_index: usize) { - let Some(UndoContext::Event(e)) = game.pop_undo_context() else { - panic!("when undoing custom phase event, the undo context stack should have a custom phase event") - }; - let state = *e; - match self { - CurrentEventResponse::ExploreResolution(_r) => { - if let CurrentEventType::ExploreResolution(r) = &state.event_type { - undo_explore_resolution(game, player_index, r); - } else { - panic!("explore resolution should have been requested") - } - } - CurrentEventResponse::Payment(p) => { - if let CurrentEventType::InfluenceCultureResolution(ref c) = state.event_type { - undo_cultural_influence_resolution_action(game, c); - } - - let player = &mut game.players[player_index]; - for p in p { - player.gain_resources_in_undo(p); - } - } - CurrentEventResponse::ResourceReward(r) => { - game.players[player_index].lose_resources(r); - } - CurrentEventResponse::SelectAdvance(n) => { - undo_advance(game, &get_advance(&n), player_index, false); - } - CurrentEventResponse::Bool(_) - | CurrentEventResponse::ChangeGovernmentType(_) - | CurrentEventResponse::SelectUnits(_) - | CurrentEventResponse::SelectPlayer(_) - | CurrentEventResponse::SelectPositions(_) - | CurrentEventResponse::SelectStructures(_) - | CurrentEventResponse::SelectUnitType(_) => { - // done with payer commands - or can't undo - } - } - game.current_events.push(state); - if game.action_log_index > 0 { - if let Some(action) = game.action_log.get(game.action_log_index - 1) { - // is there a better way to do this? - if let Action::Playing(PlayingAction::Advance { .. }) = action.action { - game.players[player_index].incident_tokens += 1; - } - } - } - } - pub(crate) fn redo(self, game: &mut Game, player_index: usize) { let Some(s) = game.current_event_handler_mut() else { panic!("current custom phase event should be set") diff --git a/server/src/content/incidents_trojan.rs b/server/src/content/incidents_trojan.rs index ce0a3fcf1..7634d526a 100644 --- a/server/src/content/incidents_trojan.rs +++ b/server/src/content/incidents_trojan.rs @@ -209,7 +209,6 @@ pub(crate) fn anarchy_advance() -> Builtin { |e| matches!(e, PermanentIncidentEffect::Anarchy(_))) { if player_index == a.player { - game.lock_undo(); // until generic undo is implemented game.add_info_log_item(&format!( "{player_name} gained a government advance, taking a game event token instead of triggering a game event (and losing 1 victory point)", )); diff --git a/server/src/cultural_influence.rs b/server/src/cultural_influence.rs index 5d07c0c7b..dd8d7ca38 100644 --- a/server/src/cultural_influence.rs +++ b/server/src/cultural_influence.rs @@ -134,29 +134,6 @@ pub(crate) fn cultural_influence_resolution() -> Builtin { .build() } -pub(crate) fn undo_cultural_influence_resolution_action( - game: &mut Game, - roll_boost_cost: &ResourcePile, -) { - let cultural_influence_attempt_action = game.action_log[game.action_log_index - 1].action.playing_ref().expect("any log item previous to a cultural influence resolution action log item should a cultural influence attempt action log item"); - let PlayingAction::InfluenceCultureAttempt(c) = cultural_influence_attempt_action else { - panic!("any log item previous to a cultural influence resolution action log item should a cultural influence attempt action log item"); - }; - - let city_piece = c.city_piece; - let target_player_index = c.target_player_index; - let target_city_position = c.target_city_position; - - if roll_boost_cost.is_empty() { - return; - } - game.players[target_player_index] - .get_city_mut(target_city_position) - .pieces - .set_building(city_piece, target_player_index); - game.successful_cultural_influence = false; -} - fn influence_distance( game: &Game, src: Position, @@ -257,9 +234,10 @@ pub fn influence_culture( .set_building(building, influencer_index); game.successful_cultural_influence = true; - game.trigger_command_event( + game.trigger_event_with_game_value( influencer_index, |e| &mut e.on_influence_culture_success, + &influencer_index, &(), ); } diff --git a/server/src/explore.rs b/server/src/explore.rs index 444b92cbc..9d831489e 100644 --- a/server/src/explore.rs +++ b/server/src/explore.rs @@ -5,8 +5,8 @@ use crate::content::custom_phase_actions::{ CurrentEventRequest, CurrentEventResponse, CurrentEventType, }; use crate::game::Game; -use crate::map::{Block, BlockPosition, Map, Rotation, Terrain, UnexploredBlock}; -use crate::movement::{move_units, stop_current_move, undo_move_units}; +use crate::map::{Block, BlockPosition, Map, Rotation, UnexploredBlock}; +use crate::movement::{move_units, stop_current_move}; use crate::position::Position; use itertools::Itertools; use serde::{Deserialize, Serialize}; @@ -27,6 +27,9 @@ pub(crate) fn move_to_unexplored_tile( start: Position, destination: Position, ) -> bool { + game.lock_undo(); // tile is revealed, so we can't undo the move + stop_current_move(game); + for b in &game.map.unexplored_blocks.clone() { for (position, _tile) in b.block.tiles(&b.position, b.position.rotation) { if position == destination { @@ -60,7 +63,6 @@ pub(crate) fn move_to_unexplored_block( let ship_explore = is_any_ship(game, player_index, units); let instant_explore = |game: &mut Game, rotation: Rotation, ship_can_teleport| { - game.lock_undo(); move_to_explored_tile( game, move_to, @@ -122,7 +124,6 @@ pub(crate) fn move_to_unexplored_block( return instant_explore(game, rotation, false); } - game.lock_undo(); let start = game.get_player(player_index).get_unit(units[0]).position; let resolution_state = ExploreResolutionState { @@ -301,32 +302,7 @@ pub(crate) fn explore_resolution() -> Builtin { r.destination, r.ship_can_teleport, ); - stop_current_move(game); }, ) .build() } - -pub(crate) fn undo_explore_resolution( - game: &mut Game, - player_index: usize, - s: &ExploreResolutionState, -) { - let unexplored_block = &s.block; - - let block = &unexplored_block.block; - block - .tiles( - &unexplored_block.position, - unexplored_block.position.rotation, - ) - .into_iter() - .for_each(|(position, _tile)| { - game.map.tiles.insert(position, Terrain::Unexplored); - }); - - game.map - .add_unexplored_blocks(vec![unexplored_block.clone()]); - - undo_move_units(game, player_index, s.units.clone(), s.start); -} diff --git a/server/src/game.rs b/server/src/game.rs index b96fbd4c4..d8501de8e 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -1,8 +1,3 @@ -use itertools::Itertools; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::vec; - use crate::ability_initializer::AbilityListeners; use crate::combat::{Combat, CombatDieRoll, COMBAT_DIE_SIDES}; use crate::consts::{ACTIONS, NON_HUMAN_PLAYERS}; @@ -15,12 +10,9 @@ use crate::events::{Event, EventOrigin}; use crate::incident::PermanentIncidentEffect; use crate::movement::{CurrentMove, MoveState}; use crate::pirates::get_pirates_player; -use crate::player_events::{ - CurrentEvent, CurrentEventInfo, PlayerCommandEvent, PlayerCommands, PlayerEvents, -}; +use crate::player_events::{CurrentEvent, CurrentEventInfo, PlayerEvents}; use crate::resource::check_for_waste; use crate::status_phase::enter_status_phase; -use crate::undo::{undo_commands, CommandUndoInfo, UndoContext}; use crate::unit::UnitType; use crate::utils::Rng; use crate::utils::Shuffle; @@ -34,6 +26,11 @@ use crate::{ status_phase::StatusPhaseState::{self}, wonder::Wonder, }; +use itertools::Itertools; +use json_patch::PatchOperation; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::vec; pub struct Game { pub state_stack: Vec, @@ -60,7 +57,6 @@ pub struct Game { pub wonder_amount_left: usize, // todo is this redundant? pub incidents_left: Vec, pub permanent_incident_effects: Vec, - pub undo_context_stack: Vec, // transient } impl Clone for Game { @@ -158,7 +154,6 @@ impl Game { wonder_amount_left: wonder_amount, incidents_left: Vec::new(), permanent_incident_effects: Vec::new(), - undo_context_stack: Vec::new(), }; for i in 0..game.players.len() { builtin::init_player(&mut game, i); @@ -189,7 +184,7 @@ impl Game { round: data.round, age: data.age, messages: data.messages, - rng: data.rng, + rng: Rng::from_seed_string(&data.rng), dice_roll_outcomes: data.dice_roll_outcomes, dice_roll_log: data.dice_roll_log, dropped_players: data.dropped_players, @@ -202,7 +197,6 @@ impl Game { incidents_left: data.incidents_left, permanent_incident_effects: data.permanent_incident_effects, current_events: data.current_events, - undo_context_stack: Vec::new(), }; for player in data.players { Player::initialize_player(player, &mut game); @@ -228,7 +222,7 @@ impl Game { round: self.round, age: self.age, messages: self.messages, - rng: self.rng, + rng: self.rng.seed.to_string(), dice_roll_outcomes: self.dice_roll_outcomes, dice_roll_log: self.dice_roll_log, dropped_players: self.dropped_players, @@ -261,7 +255,7 @@ impl Game { round: self.round, age: self.age, messages: self.messages.clone(), - rng: self.rng.clone(), + rng: self.rng.seed.to_string(), dice_roll_outcomes: self.dice_roll_outcomes.clone(), dice_roll_log: self.dice_roll_log.clone(), dropped_players: self.dropped_players.clone(), @@ -456,38 +450,6 @@ impl Game { event(&mut self.players[player_index].events).set(e); } - pub(crate) fn trigger_command_event( - &mut self, - player_index: usize, - event: fn(&mut PlayerEvents) -> &mut PlayerCommandEvent, - details: &V, - ) { - let e = event(&mut self.players[player_index].events).take(); - self.with_commands(player_index, |commands, game| { - let _ = e.trigger(commands, game, details); - }); - event(&mut self.players[player_index].events).set(e); - } - - pub(crate) fn with_commands( - &mut self, - player_index: usize, - callback: impl FnOnce(&mut PlayerCommands, &mut Game), - ) { - let p = self.get_player(player_index); - let info = CommandUndoInfo::new(p); - let mut commands = PlayerCommands::new(player_index, p.get_name(), p.event_info.clone()); - - callback(&mut commands, self); - - info.apply(self, commands.content.clone()); - self.players[player_index].gain_resources(commands.content.gained_resources); - - for edit in commands.log { - self.add_info_log_item(&edit); - } - } - #[must_use] pub fn can_undo(&self) -> bool { self.undo_limit < self.action_log_index @@ -497,53 +459,6 @@ impl Game { pub fn can_redo(&self) -> bool { self.action_log_index < self.action_log.len() } - - pub(crate) fn push_undo_context(&mut self, context: UndoContext) { - self.undo_context_stack.push(context); - } - - pub(crate) fn pop_undo_context(&mut self) -> Option { - self.maybe_pop_undo_context(|_| true) - } - - pub(crate) fn maybe_pop_undo_context( - &mut self, - pred: fn(&UndoContext) -> bool, - ) -> Option { - loop { - if let Some(context) = &self.undo_context_stack.last() { - match context { - UndoContext::WastedResources { .. } => { - let Some(UndoContext::WastedResources { - resources, - player_index, - }) = self.undo_context_stack.pop() - else { - panic!("when popping a wasted resources undo context, the undo context stack should have a wasted resources undo context") - }; - let pile = resources.clone(); - self.get_player_mut(player_index) - .gain_resources_in_undo(pile); - } - UndoContext::Command(_) => { - let Some(UndoContext::Command(c)) = self.undo_context_stack.pop() else { - panic!("when popping a command undo context, the undo context stack should have a command undo context") - }; - undo_commands(self, &c); - } - _ => { - if pred(context) { - return self.undo_context_stack.pop(); - } - return None; - } - } - } else { - return None; - } - } - } - pub(crate) fn is_pirate_zone(&self, position: Position) -> bool { if self.map.is_sea(position) { let pirate = get_pirates_player(self); @@ -603,8 +518,6 @@ impl Game { } self.successful_cultural_influence = false; - self.lock_undo(); - self.start_turn(); } @@ -702,7 +615,6 @@ impl Game { pub fn next_age(&mut self) { self.age += 1; self.current_player_index = self.starting_player_index; - self.lock_undo(); self.push_state(GameState::Playing); self.add_info_log_group(format!("Age {} has started", self.age)); self.add_info_log_group(String::from("Round 1/3")); @@ -722,7 +634,7 @@ impl Game { } pub(crate) fn get_next_dice_roll(&mut self) -> CombatDieRoll { - self.lock_undo(); + self.lock_undo(); // dice rolls are not undoable let dice_roll = if self.dice_roll_outcomes.is_empty() { self.rng.range(0, 12) as u8 } else { @@ -819,25 +731,6 @@ impl Game { .push(wonder); } - /// . - /// - /// # Panics - /// - /// Panics if city or wonder does not exist - pub fn undo_build_wonder(&mut self, city_position: Position, player_index: usize) -> Wonder { - let player = &mut self.players[player_index]; - player.wonders_build.pop(); - let wonder = player - .get_city_mut(city_position) - .pieces - .wonders - .pop() - .expect("city should have a wonder"); - (wonder.listeners.deinitializer)(self, player_index); - (wonder.listeners.undo_deinitializer)(self, player_index); - wonder - } - /// /// # Panics /// @@ -892,8 +785,8 @@ pub struct GameData { #[serde(skip_serializing_if = "Vec::is_empty")] dice_roll_outcomes: Vec, // for testing purposes #[serde(default)] - #[serde(skip_serializing_if = "Rng::is_zero")] - rng: Rng, + #[serde(skip_serializing_if = "is_string_zero")] + rng: String, dice_roll_log: Vec, dropped_players: Vec, wonders_left: Vec, @@ -906,6 +799,10 @@ pub struct GameData { permanent_incident_effects: Vec, } +fn is_string_zero(s: &String) -> bool { + s == "0" +} + #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] pub enum GameState { Playing, @@ -927,7 +824,7 @@ pub struct ActionLogItem { pub action: Action, #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] - pub undo: Vec, + pub undo: Vec, } impl ActionLogItem { diff --git a/server/src/game_api.rs b/server/src/game_api.rs index d6606f9f9..2ffe40a5b 100644 --- a/server/src/game_api.rs +++ b/server/src/game_api.rs @@ -16,9 +16,8 @@ pub fn init(player_amount: usize, seed: String) -> Game { } #[must_use] -pub fn execute(mut game: Game, action: Action, player_index: usize) -> Game { - execute_action(&mut game, action, player_index); - game +pub fn execute(game: Game, action: Action, player_index: usize) -> Game { + execute_action(game, action, player_index) } #[must_use] diff --git a/server/src/incident.rs b/server/src/incident.rs index e1949fa8a..544fe56a2 100644 --- a/server/src/incident.rs +++ b/server/src/incident.rs @@ -440,7 +440,7 @@ impl AbilityInitializerSetup for IncidentBuilder { } pub(crate) fn trigger_incident(game: &mut Game, player_index: usize, info: &IncidentInfo) { - game.lock_undo(); + game.lock_undo(); // new information is revealed if game.incidents_left.is_empty() { game.incidents_left = incidents::get_all().iter().map(|i| i.id).collect_vec(); diff --git a/server/src/movement.rs b/server/src/movement.rs index a01f2c3d8..02d7bfba4 100644 --- a/server/src/movement.rs +++ b/server/src/movement.rs @@ -73,13 +73,15 @@ pub(crate) fn take_move_state(game: &mut Game) -> MoveState { } pub(crate) fn stop_current_move(game: &mut Game) { - let mut move_state = take_move_state(game); - move_state.current_move = CurrentMove::None; + if let Movement(_) = game.state() { + let mut move_state = take_move_state(game); + move_state.current_move = CurrentMove::None; - if move_state.movement_actions_left == 0 { - return; + if move_state.movement_actions_left == 0 { + return; + } + game.push_state(Movement(move_state)); } - game.push_state(Movement(move_state)); } pub(crate) fn move_units( @@ -92,7 +94,7 @@ pub(crate) fn move_units( let p = game.get_player(player_index); let from = p.get_unit(units[0]).position; let info = MoveInfo::new(player_index, units.to_vec(), from, to); - game.trigger_command_event(player_index, |e| &mut e.before_move, &info); + game.trigger_event_with_game_value(player_index, |e| &mut e.before_move, &info, &()); for unit_id in units { move_unit(game, player_index, *unit_id, to, embark_carrier_id); @@ -119,37 +121,6 @@ fn move_unit( } } -pub(crate) fn undo_move_units( - game: &mut Game, - player_index: usize, - units: Vec, - starting_position: Position, -) { - let Some(unit) = units.first() else { - return; - }; - let destination = game.players[player_index].get_unit(*unit).position; - - for unit_id in units { - let unit = game.players[player_index].get_unit_mut(unit_id); - unit.position = starting_position; - - if let Some(terrain) = terrain_movement_restriction(&game.map, destination, unit) { - unit.movement_restrictions - .iter() - .position(|r| r == &terrain) - .map(|i| unit.movement_restrictions.remove(i)); - } - - if !game.map.is_sea(starting_position) { - unit.carrier_id = None; - } - for id in &carried_units(unit_id, &game.players[player_index]) { - game.players[player_index].get_unit_mut(*id).position = starting_position; - } - } -} - /// # Errors /// /// Will return `Err` if the unit cannot move. diff --git a/server/src/player.rs b/server/src/player.rs index 2abdf9f36..0c7365aa1 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -10,7 +10,7 @@ use crate::resource::ResourceType; use crate::unit::{carried_units, UnitData, UnitType}; use crate::{ city::{City, CityData}, - city_pieces::Building::{self, *}, + city_pieces::Building::{self}, civilization::Civilization, consts::{ ADVANCE_COST, ADVANCE_VICTORY_POINTS, BUILDING_VICTORY_POINTS, @@ -423,11 +423,6 @@ impl Player { .find_map(|advance| advance.government.clone()) } - pub(crate) fn gain_resources_in_undo(&mut self, resources: ResourcePile) { - // resource limit may be adjusted later - self.resources += resources; - } - pub fn gain_resources(&mut self, resources: ResourcePile) { self.resources += resources; let waste = self.resources.apply_resource_limit(&self.resource_limit); @@ -745,23 +740,6 @@ impl Player { } } - /// - /// - /// # Panics - /// - /// Panics if city does not exist - pub fn undo_construct(&mut self, building: Building, city_position: Position) { - let city = self.get_city_mut(city_position); - city.undo_activate(); - city.pieces.remove_building(building); - if matches!(building, Port) { - city.port_position = None; - } - if matches!(building, Academy) { - self.lose_resources(ResourcePile::ideas(2)); - } - } - pub fn add_unit(&mut self, position: Position, unit_type: UnitType) { let unit = Unit::new(self.index, position, unit_type, self.next_unit_id); self.units.push(unit); diff --git a/server/src/player_events.rs b/server/src/player_events.rs index 7b2e9f856..b71e03b6d 100644 --- a/server/src/player_events.rs +++ b/server/src/player_events.rs @@ -8,7 +8,6 @@ use crate::game::Game; use crate::map::Terrain; use crate::payment::PaymentOptions; use crate::playing_actions::{PlayingActionType, Recruit}; -use crate::undo::{CommandContext, CommandUndoInfo}; use crate::unit::Units; use crate::{ city::City, city_pieces::Building, player::Player, position::Position, @@ -20,20 +19,18 @@ use std::collections::{HashMap, HashSet}; pub(crate) type CurrentEvent = Event; -pub(crate) type PlayerCommandEvent = Event; - #[derive(Default)] pub(crate) struct PlayerEvents { pub on_construct: CurrentEvent, pub on_construct_wonder: Event, pub on_draw_wonder_card: CurrentEvent, - pub on_collect: PlayerCommandEvent, + pub on_collect: Event, pub on_advance: CurrentEvent, pub on_recruit: CurrentEvent, pub on_influence_culture_attempt: Event, - pub on_influence_culture_success: PlayerCommandEvent, + pub on_influence_culture_success: Event, pub on_influence_culture_resolution: CurrentEvent, - pub before_move: PlayerCommandEvent, + pub before_move: Event, pub on_explore_resolution: CurrentEvent, pub construct_cost: Event, @@ -66,7 +63,6 @@ impl PlayerEvents { #[derive(Clone, PartialEq)] pub(crate) struct ActionInfo { pub(crate) player: usize, - pub(crate) undo: CommandUndoInfo, pub(crate) info: HashMap, pub(crate) log: Vec, } @@ -75,23 +71,19 @@ impl ActionInfo { pub(crate) fn new(player: &Player) -> ActionInfo { ActionInfo { player: player.index, - undo: CommandUndoInfo::new(player), info: player.event_info.clone(), log: Vec::new(), } } pub(crate) fn execute(&self, game: &mut Game) { - self.execute_with_options(game, |_| {}); - } - - pub(crate) fn execute_with_options(&self, game: &mut Game, c: impl Fn(&mut CommandContext)) { for l in self.log.iter().unique() { game.add_info_log_item(l); } - let mut context = CommandContext::new(self.info.clone()); - c(&mut context); - self.undo.apply(game, context); + let player = game.get_player_mut(self.player); + for (k, v) in self.info.clone() { + player.event_info.insert(k, v); + } } } @@ -139,7 +131,7 @@ impl CostInfo { } pub(crate) fn pay(&self, game: &mut Game, payment: &ResourcePile) { - game.players[self.info.undo.player].pay_cost(&self.cost, payment); + game.players[self.info.player].pay_cost(&self.cost, payment); self.info.execute(game); } } @@ -228,31 +220,3 @@ impl InfluenceCultureInfo { self.possible = InfluenceCulturePossible::NoBoost; } } - -#[derive(Clone, PartialEq)] -pub(crate) struct PlayerCommands { - pub name: String, - pub index: usize, - pub log: Vec, - pub content: CommandContext, -} - -impl PlayerCommands { - #[must_use] - pub fn new(player_index: usize, name: String, info: HashMap) -> PlayerCommands { - PlayerCommands { - name, - index: player_index, - log: Vec::new(), - content: CommandContext::new(info), - } - } - - pub fn gain_resources(&mut self, resources: ResourcePile) { - self.content.gained_resources += resources; - } - - pub fn add_info_log_item(&mut self, log: &str) { - self.log.push(log.to_string()); - } -} diff --git a/server/src/playing_actions.rs b/server/src/playing_actions.rs index c639fd911..f0e1b06b1 100644 --- a/server/src/playing_actions.rs +++ b/server/src/playing_actions.rs @@ -3,17 +3,16 @@ use serde::{Deserialize, Serialize}; use PlayingAction::*; use crate::action::Action; -use crate::advance::{advance_with_incident_token, undo_advance}; +use crate::advance::advance_with_incident_token; use crate::city::MoodState; -use crate::collect::{collect, undo_collect}; +use crate::collect::collect; use crate::content::advances::get_advance; use crate::content::custom_phase_actions::CurrentEventType; use crate::cultural_influence::influence_culture_attempt; use crate::game::GameState; use crate::player_events::PlayingActionInfo; -use crate::recruit::{recruit, recruit_cost, undo_recruit}; -use crate::undo::UndoContext; -use crate::unit::{Unit, Units}; +use crate::recruit::{recruit, recruit_cost}; +use crate::unit::Units; use crate::{ city::City, city_pieces::Building::{self, *}, @@ -150,8 +149,6 @@ impl PlayingAction { let player = &mut game.players[player_index]; let city = City::new(player_index, settler.position); player.cities.push(city); - let unit_data = settler.data(player); - game.push_undo_context(UndoContext::FoundCity { settler: unit_data }); } Construct(c) => { let player = &game.players[player_index]; @@ -276,70 +273,6 @@ impl PlayingAction { PlayingAction::EndTurn => PlayingActionType::EndTurn, } } - - /// - /// - /// # Panics - /// - /// Panics if no temple bonus is given when undoing a construct temple action - pub fn undo(self, game: &mut Game, player_index: usize, was_custom_phase: bool) { - let free_action = self.action_type().free; - if !free_action { - game.actions_left += 1; - } - game.players[player_index].gain_resources_in_undo(self.action_type().cost); - - match self { - Advance { advance, payment } => { - let player = &mut game.players[player_index]; - player.gain_resources_in_undo(payment); - undo_advance(game, &get_advance(&advance), player_index, was_custom_phase); - } - FoundCity { settler: _ } => { - let Some(UndoContext::FoundCity { settler }) = game.pop_undo_context() else { - panic!("Settler context should be stored in undo context"); - }; - let player = &mut game.players[player_index]; - let units = Unit::from_data(player_index, settler); - player.units.push( - units - .into_iter() - .next() - .expect("The player should have a unit after founding a city"), - ); - player - .cities - .pop() - .expect("The player should have a city after founding one"); - } - Construct(c) => { - let player = &mut game.players[player_index]; - player.undo_construct(c.city_piece, c.city_position); - player.gain_resources_in_undo(c.payment); - } - Collect(c) => undo_collect(game, player_index, &c), - Recruit(r) => { - game.players[player_index].gain_resources_in_undo(r.payment); - undo_recruit( - game, - player_index, - r.units, - r.city_position, - r.leader_name.as_ref(), - ); - } - IncreaseHappiness(i) => { - undo_increase_happiness( - game, - player_index, - &i.happiness_increases, - Some(i.payment), - ); - } - Custom(custom_action) => custom_action.undo(game, player_index), - InfluenceCultureAttempt(_) | EndTurn => panic!("Action can't be undone"), - } - } } #[derive(Default)] @@ -411,34 +344,6 @@ pub(crate) fn increase_happiness( .increase_happiness_total_cost(count, Some(&r)) .pay(game, &r); } - game.push_undo_context(UndoContext::IncreaseHappiness { angry_activations }); -} - -pub(crate) fn undo_increase_happiness( - game: &mut Game, - player_index: usize, - happiness_increases: &[(Position, u32)], - payment: Option, -) { - let player = &mut game.players[player_index]; - for &(city_position, steps) in happiness_increases { - let city = player.get_city_mut(city_position); - for _ in 0..steps { - city.decrease_mood_state(); - } - } - if let Some(r) = payment { - player.gain_resources_in_undo(r); - } - - if let Some(UndoContext::IncreaseHappiness { angry_activations }) = game.pop_undo_context() { - for city_position in angry_activations { - let city = game.players[player_index].get_city_mut(city_position); - city.angry_activation = true; - } - } else { - panic!("Increase happiness context should be stored in undo context") - } } pub(crate) fn roll_boost_cost(roll: u8) -> ResourcePile { diff --git a/server/src/recruit.rs b/server/src/recruit.rs index fe57c3895..582b18d50 100644 --- a/server/src/recruit.rs +++ b/server/src/recruit.rs @@ -8,11 +8,9 @@ use crate::player_events::CostInfo; use crate::playing_actions::Recruit; use crate::position::Position; use crate::resource_pile::ResourcePile; -use crate::undo::UndoContext; -use crate::unit::{Unit, UnitType, Units}; +use crate::unit::{UnitType, Units}; pub(crate) fn recruit(game: &mut Game, player_index: usize, r: &Recruit) { - let mut replaced_leader = None; if let Some(leader_name) = &r.leader_name { if let Some(previous_leader) = game.players[player_index].active_leader.take() { Player::with_leader( @@ -23,7 +21,6 @@ pub(crate) fn recruit(game: &mut Game, player_index: usize, r: &Recruit) { (previous_leader.listeners.deinitializer)(game, player_index); }, ); - replaced_leader = Some(previous_leader); } set_active_leader(game, leader_name.clone(), player_index); } @@ -38,10 +35,6 @@ pub(crate) fn recruit(game: &mut Game, player_index: usize, r: &Recruit) { let unit = u.data(game.get_player(player_index)); replaced_units_undo_context.push(unit); } - game.push_undo_context(UndoContext::Recruit { - replaced_units: replaced_units_undo_context, - replaced_leader, - }); let player = game.get_player_mut(player_index); let vec = r.units.clone().to_vec(); player.units.reserve_exact(vec.len()); @@ -112,84 +105,6 @@ pub(crate) fn on_recruit(game: &mut Game, player_index: usize, r: &Recruit) { } } -/// -/// -/// # Panics -/// -/// Panics if city does not exist -pub fn undo_recruit( - game: &mut Game, - player_index: usize, - units: Units, - city_position: Position, - leader_name: Option<&String>, -) { - undo_recruit_without_activate(game, player_index, &units.to_vec(), leader_name); - game.players[player_index] - .get_city_mut(city_position) - .undo_activate(); - if let Some(UndoContext::Recruit { - replaced_units, - replaced_leader, - }) = game.pop_undo_context() - { - let player = game.get_player_mut(player_index); - for unit in replaced_units { - player.units.extend(Unit::from_data(player_index, unit)); - } - if let Some(replaced_leader) = replaced_leader { - player.active_leader = Some(replaced_leader.clone()); - Player::with_leader( - &replaced_leader, - game, - player_index, - |game, replaced_leader| { - (replaced_leader.listeners.initializer)(game, player_index); - (replaced_leader.listeners.one_time_initializer)(game, player_index); - }, - ); - } - } -} - -fn undo_recruit_without_activate( - game: &mut Game, - player_index: usize, - units: &[UnitType], - leader_name: Option<&String>, -) { - if let Some(leader_name) = leader_name { - let current_leader = game.players[player_index] - .active_leader - .take() - .expect("the player should have an active leader"); - Player::with_leader( - ¤t_leader, - game, - player_index, - |game, current_leader| { - (current_leader.listeners.deinitializer)(game, player_index); - (current_leader.listeners.undo_deinitializer)(game, player_index); - }, - ); - - game.players[player_index] - .available_leaders - .push(leader_name.clone()); - game.players[player_index].available_leaders.sort(); - - game.players[player_index].active_leader = None; - } - let player = game.get_player_mut(player_index); - for _ in 0..units.len() { - player - .units - .pop() - .expect("the player should have the recruited units when undoing"); - player.next_unit_id -= 1; - } -} - /// /// /// # Panics diff --git a/server/src/resource.rs b/server/src/resource.rs index 7689720e4..524af59a8 100644 --- a/server/src/resource.rs +++ b/server/src/resource.rs @@ -1,6 +1,5 @@ use crate::game::Game; use crate::resource_pile::ResourcePile; -use crate::undo::UndoContext; use serde::{Deserialize, Serialize}; use std::{fmt, mem}; @@ -61,10 +60,6 @@ pub(crate) fn check_for_waste(game: &mut Game) { "{} could not store {wasted_resources}", game.player_name(p) )); - game.push_undo_context(UndoContext::WastedResources { - resources: wasted_resources, - player_index: p, - }); } } } diff --git a/server/src/status_phase.rs b/server/src/status_phase.rs index 3c0ffa45a..71a0690ce 100644 --- a/server/src/status_phase.rs +++ b/server/src/status_phase.rs @@ -55,7 +55,6 @@ pub(crate) fn enter_status_phase(game: &mut Game) { pub(crate) fn play_status_phase(game: &mut Game) { use StatusPhaseState::*; - game.lock_undo(); loop { let StatusPhase(phase) = game.state().clone() else { diff --git a/server/src/undo.rs b/server/src/undo.rs index 5fb4b2360..d1b8adccc 100644 --- a/server/src/undo.rs +++ b/server/src/undo.rs @@ -1,143 +1,52 @@ use crate::action::{add_log_item_from_action, execute_movement_action, Action}; -use crate::consts::MOVEMENT_ACTIONS; -use crate::content::custom_phase_actions::CurrentEventState; use crate::game::Game; -use crate::game::GameState::Movement; -use crate::movement::{undo_move_units, MoveState}; -use crate::player::Player; -use crate::position::Position; use crate::resource::check_for_waste; -use crate::resource_pile::ResourcePile; -use crate::unit::MovementAction::Move; -use crate::unit::{MovementAction, UnitData}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub struct CommandUndoInfo { - pub player: usize, - pub info: HashMap, -} - -impl CommandUndoInfo { - #[must_use] - pub fn new(player: &Player) -> Self { - Self { - info: player.event_info.clone(), - player: player.index, - } - } - - pub fn apply(&self, game: &mut Game, mut undo: CommandContext) { - let player = game.get_player_mut(self.player); - for (k, v) in undo.info.clone() { - player.event_info.insert(k, v); - } - - if undo.info != self.info || !undo.gained_resources.is_empty() { - undo.info.clone_from(&self.info); - game.push_undo_context(UndoContext::Command(undo)); - } - } -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub enum UndoContext { - FoundCity { - settler: UnitData, - }, - Recruit { - #[serde(skip_serializing_if = "Vec::is_empty")] - #[serde(default)] - replaced_units: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - replaced_leader: Option, - }, - Movement { - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - starting_position: Option, - #[serde(flatten)] - move_state: MoveState, - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] - disembarked_units: Vec, - }, - WastedResources { - resources: ResourcePile, - player_index: usize, - }, - IncreaseHappiness { - angry_activations: Vec, - }, - Event(Box), - Command(CommandContext), -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub struct DisembarkUndoContext { - pub unit_id: u32, - pub carrier_id: u32, -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] -pub struct CommandContext { - #[serde(default)] - #[serde(skip_serializing_if = "HashMap::is_empty")] - pub info: HashMap, - #[serde(default)] - #[serde(skip_serializing_if = "ResourcePile::is_empty")] - pub gained_resources: ResourcePile, -} - -impl CommandContext { - #[must_use] - pub fn new(info: HashMap) -> Self { - Self { - info, - gained_resources: ResourcePile::empty(), - } - } +use json_patch::{patch, PatchOperation}; +use serde_json::Value; + +const IGNORE_PATHS: [&str; 3] = ["/action_log/", "/action_log_index", "/log/"]; + +pub(crate) fn clean_patch(mut patch: Vec) -> Vec { + patch.retain(|op| { + IGNORE_PATHS + .iter() + .all(|ignore| !op.path().as_str().starts_with(ignore)) + }); + patch } -pub(crate) fn undo(game: &mut Game, player_index: usize) { +pub(crate) fn undo(mut game: Game) -> Game { game.action_log_index -= 1; game.log.remove(game.log.len() - 1); - let item = &game.action_log[game.action_log_index]; - game.undo_context_stack = item.undo.clone(); - let action = item.action.clone(); - let was_custom_phase = game.current_event_handler().is_some(); - if was_custom_phase { - game.current_events.pop(); - } + let option = game + .action_log + .iter() + .rposition(|a| !a.undo.is_empty()) + .expect("should have undoable action"); - match action { - Action::Playing(action) => action.clone().undo(game, player_index, was_custom_phase), - Action::Movement(action) => { - undo_movement_action(game, action.clone(), player_index); - } - Action::Response(action) => action.clone().undo(game, player_index), + let item = game + .action_log + .get_mut(option) + .expect("should have undoable action"); + let p = std::mem::take(&mut item.undo); + + match &item.action { Action::Undo => panic!("undo action can't be undone"), Action::Redo => panic!("redo action can't be undone"), + _ => {} } - maybe_undo_waste(game); + let mut v = to_serde_value(&game); - while game.maybe_pop_undo_context(|_| false).is_some() { - // pop all undo contexts until action start - } + patch(&mut v, &p).expect("could not patch game data"); + + Game::from_data(serde_json::from_value(v).expect("should be able to deserialize game")) } -fn maybe_undo_waste(game: &mut Game) { - if let Some(UndoContext::WastedResources { - resources, - player_index, - }) = game.maybe_pop_undo_context(|c| matches!(c, UndoContext::WastedResources { .. })) - { - game.players[player_index].gain_resources_in_undo(resources.clone()); - } +pub(crate) fn to_serde_value(game: &Game) -> Value { + let s = serde_json::to_string(&game.cloned_data()).expect("game should be serializable"); + serde_json::from_str(&s).expect("game should be serializable") } pub fn redo(game: &mut Game, player_index: usize) { @@ -155,41 +64,3 @@ pub fn redo(game: &mut Game, player_index: usize) { game.action_log_index += 1; check_for_waste(game); } - -pub(crate) fn undo_commands(game: &mut Game, c: &CommandContext) { - let p = game.active_player(); - game.players[p].event_info.clone_from(&c.info); - game.players[p].lose_resources(c.gained_resources.clone()); -} - -fn undo_movement_action(game: &mut Game, action: MovementAction, player_index: usize) { - let Some(UndoContext::Movement { - starting_position, - move_state, - disembarked_units, - }) = game.pop_undo_context() - else { - panic!("when undoing a movement action, the game should have stored movement context") - }; - if let Move(m) = action { - undo_move_units( - game, - player_index, - m.units, - starting_position - .expect("undo context should contain the starting position if units where moved"), - ); - game.players[player_index].gain_resources_in_undo(m.payment); - for unit in disembarked_units { - game.players[player_index] - .get_unit_mut(unit.unit_id) - .carrier_id = Some(unit.carrier_id); - } - } - game.pop_state(); - if move_state.movement_actions_left == MOVEMENT_ACTIONS { - game.actions_left += 1; - } else { - game.push_state(Movement(move_state)); - } -} diff --git a/server/src/utils.rs b/server/src/utils.rs index 2ce750b4d..87759a4ed 100644 --- a/server/src/utils.rs +++ b/server/src/utils.rs @@ -1,5 +1,3 @@ -use serde::{Deserialize, Serialize}; - pub fn format_list(list: &[String], empty_message: &str) -> String { match list { [] => empty_message.to_string(), @@ -49,9 +47,9 @@ pub fn ordinal_number(value: u32) -> String { ) } -#[derive(Serialize, Deserialize, Clone, Default, PartialEq)] +#[derive(Clone, Default)] pub struct Rng { - seed: u128, + pub(crate) seed: u128, } impl Rng { @@ -59,6 +57,15 @@ impl Rng { Self { seed } } + pub fn from_seed_string(seed: &str) -> Self { + let seed = if seed.is_empty() { + 0 + } else { + seed.parse().expect("seed should be a number") + }; + Self { seed } + } + pub fn range(&mut self, start: usize, end: usize) -> usize { self.seed = next_seed(self.seed); let range = (end - start) as u128; diff --git a/server/src/wonder.rs b/server/src/wonder.rs index 79284b128..c6f004c72 100644 --- a/server/src/wonder.rs +++ b/server/src/wonder.rs @@ -91,7 +91,6 @@ impl AbilityInitializerSetup for WonderBuilder { } pub(crate) fn draw_wonder_card(game: &mut Game, player_index: usize) { - game.lock_undo(); game.trigger_current_event( &[player_index], |e| &mut e.on_draw_wonder_card, @@ -105,13 +104,13 @@ pub(crate) fn draw_wonder_from_pile(game: &mut Game) -> Option { if game.wonder_amount_left == 0 { return None; } + game.lock_undo(); // new information is revealed game.wonder_amount_left -= 1; game.wonders_left.pop() } fn gain_wonder(game: &mut Game, player_index: usize, wonder: Wonder) { game.players[player_index].wonder_cards.push(wonder); - game.lock_undo(); } pub(crate) fn on_draw_wonder_card() -> Builtin { diff --git a/server/tests/advances_tests.rs b/server/tests/advances_tests.rs index 211d20146..4a6ed752b 100644 --- a/server/tests/advances_tests.rs +++ b/server/tests/advances_tests.rs @@ -1,4 +1,4 @@ -use crate::common::{illegal_action_test, influence_action, load_game, JsonTest, TestAction}; +use crate::common::{illegal_action_test, influence_action, JsonTest, TestAction}; use server::action::{execute_action, Action}; use server::city_pieces::Building::{Fortress, Temple}; use server::content::custom_actions::CustomAction; @@ -65,10 +65,10 @@ fn test_sanitation_and_draft() { fn test_separation_of_power() { illegal_action_test(|test| { let mut game = load_culture(); - execute_action(&mut game, Action::Playing(EndTurn), 1); + game = execute_action(game, Action::Playing(EndTurn), 1); if test.fail { - execute_action( - &mut game, + game = execute_action( + game, Action::Playing(Advance { advance: String::from("Separation of Power"), payment: ResourcePile::food(1) + ResourcePile::gold(1), @@ -76,9 +76,9 @@ fn test_separation_of_power() { 0, ); } - execute_action(&mut game, Action::Playing(EndTurn), 0); + game = execute_action(game, Action::Playing(EndTurn), 0); test.setup_done = true; - execute_action(&mut game, influence_action(), 1); + let _ = execute_action(game, influence_action(), 1); }); } @@ -86,10 +86,10 @@ fn test_separation_of_power() { fn test_devotion() { illegal_action_test(|test| { let mut game = load_culture(); - execute_action(&mut game, Action::Playing(EndTurn), 1); + game = execute_action(game, Action::Playing(EndTurn), 1); if test.fail { - execute_action( - &mut game, + game = execute_action( + game, Action::Playing(Advance { advance: String::from("Devotion"), payment: ResourcePile::food(1) + ResourcePile::gold(1), @@ -97,24 +97,24 @@ fn test_devotion() { 0, ); } - execute_action(&mut game, Action::Playing(EndTurn), 0); + game = execute_action(game, Action::Playing(EndTurn), 0); test.setup_done = true; - execute_action(&mut game, influence_action(), 1); + let _ = execute_action(game, influence_action(), 1); }); } fn load_culture() -> Game { - load_game("base/cultural_influence") + JsonTest::new("base").load_game("cultural_influence") } #[test] fn test_totalitarianism() { illegal_action_test(|test| { let mut game = load_culture(); - execute_action(&mut game, Action::Playing(EndTurn), 1); + game = execute_action(game, Action::Playing(EndTurn), 1); if test.fail { - execute_action( - &mut game, + game = execute_action( + game, Action::Playing(Advance { advance: String::from("Totalitarianism"), payment: ResourcePile::food(1) + ResourcePile::gold(1), @@ -122,9 +122,9 @@ fn test_totalitarianism() { 0, ); } - execute_action(&mut game, Action::Playing(EndTurn), 0); + game = execute_action(game, Action::Playing(EndTurn), 0); test.setup_done = true; - execute_action(&mut game, influence_action(), 1); + let _ = execute_action(game, influence_action(), 1); }); } @@ -132,10 +132,10 @@ fn test_totalitarianism() { fn test_monuments() { illegal_action_test(|test| { let mut game = load_culture(); - execute_action(&mut game, Action::Playing(EndTurn), 1); + game = execute_action(game, Action::Playing(EndTurn), 1); if test.fail { - execute_action( - &mut game, + game = execute_action( + game, Action::Playing(Advance { advance: String::from("Monuments"), payment: ResourcePile::food(1) + ResourcePile::gold(1), @@ -143,8 +143,8 @@ fn test_monuments() { 0, ); } - execute_action( - &mut game, + game = execute_action( + game, Action::Playing(Custom(ConstructWonder { city_position: Position::from_offset("C2"), wonder: String::from("Pyramids"), @@ -152,9 +152,9 @@ fn test_monuments() { })), 0, ); - execute_action(&mut game, Action::Playing(EndTurn), 0); + game = execute_action(game, Action::Playing(EndTurn), 0); test.setup_done = true; - execute_action(&mut game, influence_action(), 1); + let _ = execute_action(game, influence_action(), 1); }); } diff --git a/server/tests/combat_tests.rs b/server/tests/combat_tests.rs index 3fc864331..56389afa0 100644 --- a/server/tests/combat_tests.rs +++ b/server/tests/combat_tests.rs @@ -22,7 +22,7 @@ fn test_remove_casualties_attacker() { 0, move_action(vec![0, 1, 2, 3], Position::from_offset("C1")), ), - TestAction::not_undoable( + TestAction::undoable( 0, Action::Response(CurrentEventResponse::SelectUnits(vec![0, 1])), ), @@ -119,7 +119,7 @@ fn test_retreat() { "retreat", vec![ TestAction::not_undoable(0, move_action(vec![0], Position::from_offset("C1"))), - TestAction::not_undoable(0, Action::Response(CurrentEventResponse::Bool(true))), + TestAction::undoable(0, Action::Response(CurrentEventResponse::Bool(true))), ], ); } @@ -141,7 +141,7 @@ fn test_ship_combat() { "ship_combat", vec![ TestAction::not_undoable(0, move_action(vec![7, 8], Position::from_offset("D2"))), - TestAction::not_undoable( + TestAction::undoable( 0, Action::Response(CurrentEventResponse::SelectUnits(vec![1])), ), @@ -185,7 +185,7 @@ fn test_recruit_combat() { 0, Action::Response(CurrentEventResponse::ResourceReward(ResourcePile::gold(1))), ), - TestAction::not_undoable( + TestAction::undoable( 0, Action::Response(CurrentEventResponse::ResourceReward( ResourcePile::culture_tokens(1), diff --git a/server/tests/game_api_tests.rs b/server/tests/game_api_tests.rs index cb1f389e0..73578dd62 100644 --- a/server/tests/game_api_tests.rs +++ b/server/tests/game_api_tests.rs @@ -199,27 +199,35 @@ fn undo() { assert_undo(&game, false, false, 0, 0, 0); assert_eq!(Angry, game.players[0].cities[0].mood_state); + let game = increase_happiness(game); assert_undo(&game, true, false, 1, 1, 0); assert_eq!(Neutral, game.players[0].cities[0].mood_state); + let game = increase_happiness(game); assert_undo(&game, true, false, 2, 2, 0); assert_eq!(Happy, game.players[0].cities[0].mood_state); + let game = game_api::execute(game, Action::Undo, 0); assert_undo(&game, true, true, 2, 1, 0); assert_eq!(Neutral, game.players[0].cities[0].mood_state); + let game = game_api::execute(game, Action::Undo, 0); assert_undo(&game, false, true, 2, 0, 0); assert_eq!(Angry, game.players[0].cities[0].mood_state); + let game = game_api::execute(game, Action::Redo, 0); assert_undo(&game, true, true, 2, 1, 0); assert_eq!(Neutral, game.players[0].cities[0].mood_state); + let game = game_api::execute(game, Action::Redo, 0); assert_undo(&game, true, false, 2, 2, 0); assert_eq!(Happy, game.players[0].cities[0].mood_state); + let game = game_api::execute(game, Action::Undo, 0); assert_undo(&game, true, true, 2, 1, 0); assert_eq!(Neutral, game.players[0].cities[0].mood_state); + let game = game_api::execute(game, Action::Undo, 0); assert_undo(&game, false, true, 2, 0, 0); assert_eq!(Angry, game.players[0].cities[0].mood_state); diff --git a/server/tests/incident_tests.rs b/server/tests/incident_tests.rs index b4845e8e6..5a62533e0 100644 --- a/server/tests/incident_tests.rs +++ b/server/tests/incident_tests.rs @@ -412,14 +412,14 @@ fn test_envoy() { payment: ResourcePile::gold(2), }), ), - TestAction::not_undoable( + TestAction::undoable( 0, Action::Playing(Advance { advance: String::from("Monuments"), payment: ResourcePile::gold(2), }), ), - TestAction::not_undoable(0, Action::Response(CurrentEventResponse::Bool(true))), + TestAction::undoable(0, Action::Response(CurrentEventResponse::Bool(true))), ], ); } @@ -482,7 +482,7 @@ fn test_anarchy() { payment: ResourcePile::gold(2), }), ), - TestAction::not_undoable( + TestAction::undoable( 0, Action::Playing(Advance { advance: String::from("Dogma"), diff --git a/server/tests/movement_tests.rs b/server/tests/movement_tests.rs index e733145be..34f5dce6d 100644 --- a/server/tests/movement_tests.rs +++ b/server/tests/movement_tests.rs @@ -150,7 +150,7 @@ fn test_ship_disembark() { fn test_ship_disembark_capture_empty_city() { JSON.test( "ship_disembark_capture_empty_city", - vec![TestAction::not_undoable( + vec![TestAction::undoable( 0, move_action(vec![1, 2], Position::from_offset("B2")), )], diff --git a/server/tests/test_games/advances/absolute_power.outcome.json b/server/tests/test_games/advances/absolute_power.outcome.json index 5376f5671..ad3c4ec7c 100644 --- a/server/tests/test_games/advances/absolute_power.outcome.json +++ b/server/tests/test_games/advances/absolute_power.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -257,7 +259,23 @@ "Playing": { "Custom": "AbsolutePower" } - } + }, + "undo": [ + { + "op": "replace", + "path": "/actions_left", + "value": 0 + }, + { + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 9 + }, + { + "op": "remove", + "path": "/players/0/played_once_per_turn_actions" + } + ] } ], "action_log_index": 1, @@ -290,4 +308,4 @@ "dropped_players": [], "wonders_left": [], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/advances/civil_liberties.outcome.json b/server/tests/test_games/advances/civil_liberties.outcome.json index 77bc15a28..d12275c90 100644 --- a/server/tests/test_games/advances/civil_liberties.outcome.json +++ b/server/tests/test_games/advances/civil_liberties.outcome.json @@ -217,7 +217,19 @@ "Playing": { "Custom": "CivilRights" } - } + }, + "undo": [ + { + "op": "replace", + "path": "/actions_left", + "value": 3 + }, + { + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 6 + } + ] } ], "action_log_index": 1, @@ -252,4 +264,4 @@ "Pyramids" ], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/advances/civil_liberties.outcome1.json b/server/tests/test_games/advances/civil_liberties.outcome1.json index 7590345f1..aba066c8d 100644 --- a/server/tests/test_games/advances/civil_liberties.outcome1.json +++ b/server/tests/test_games/advances/civil_liberties.outcome1.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -220,7 +222,19 @@ "Playing": { "Custom": "CivilRights" } - } + }, + "undo": [ + { + "op": "replace", + "path": "/actions_left", + "value": 3 + }, + { + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 6 + } + ] }, { "action": { @@ -238,7 +252,28 @@ }, "undo": [ { - "Recruit": {} + "op": "replace", + "path": "/actions_left", + "value": 2 + }, + { + "op": "replace", + "path": "/players/0/cities/0/activations", + "value": 0 + }, + { + "op": "replace", + "path": "/players/0/next_unit_id", + "value": 8 + }, + { + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 9 + }, + { + "op": "remove", + "path": "/players/0/units/8" } ] } @@ -279,4 +314,4 @@ "Pyramids" ], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/advances/collect_free_economy.outcome.json b/server/tests/test_games/advances/collect_free_economy.outcome.json index cad283632..9b7ef26b3 100644 --- a/server/tests/test_games/advances/collect_free_economy.outcome.json +++ b/server/tests/test_games/advances/collect_free_economy.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -238,21 +240,18 @@ }, "undo": [ { - "Command": { - "gained_resources": { - "ore": 1, - "gold": 1 - } - } + "op": "replace", + "path": "/players/0/cities/1/activations", + "value": 0 }, { - "WastedResources": { - "resources": { - "ore": 1, - "gold": 1 - }, - "player_index": 0 - } + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 6 + }, + { + "op": "remove", + "path": "/players/0/played_once_per_turn_actions" } ] } diff --git a/server/tests/test_games/advances/collect_husbandry.json b/server/tests/test_games/advances/collect_husbandry.json index b003492d7..afada8d55 100644 --- a/server/tests/test_games/advances/collect_husbandry.json +++ b/server/tests/test_games/advances/collect_husbandry.json @@ -313,9 +313,7 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], diff --git a/server/tests/test_games/advances/collect_husbandry.outcome.json b/server/tests/test_games/advances/collect_husbandry.outcome.json index 17697fb45..d0c02baa0 100644 --- a/server/tests/test_games/advances/collect_husbandry.outcome.json +++ b/server/tests/test_games/advances/collect_husbandry.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -310,19 +312,23 @@ }, "undo": [ { - "Command": { - "gained_resources": { - "food": 1 - } - } + "op": "replace", + "path": "/actions_left", + "value": 2 }, { - "WastedResources": { - "resources": { - "food": 1 - }, - "player_index": 0 - } + "op": "replace", + "path": "/players/0/cities/3/activations", + "value": 4 + }, + { + "op": "replace", + "path": "/players/0/cities/3/mood_state", + "value": "Happy" + }, + { + "op": "remove", + "path": "/players/0/event_info" } ] } @@ -355,11 +361,9 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/advances/cultural_influence_with_conversion.outcome1.json b/server/tests/test_games/advances/cultural_influence_with_conversion.outcome1.json index c2ad8bd3f..504e5d547 100644 --- a/server/tests/test_games/advances/cultural_influence_with_conversion.outcome1.json +++ b/server/tests/test_games/advances/cultural_influence_with_conversion.outcome1.json @@ -247,63 +247,75 @@ }, "undo": [ { - "Event": { - "event_type": { - "InfluenceCultureResolution": { - "culture_tokens": 3 - } - }, - "player": 1, - "last_priority_used": 0, - "handler": { - "priority": 0, - "request": { - "Payment": [ - { - "cost": { - "default": { - "culture_tokens": 3 - }, - "conversions": [ - { - "from": [ - { - "food": 1 - }, - { - "wood": 1 - }, - { - "ore": 1 + "op": "add", + "path": "/current_events", + "value": [ + { + "event_type": { + "InfluenceCultureResolution": { + "culture_tokens": 3 + } + }, + "handler": { + "origin": { + "Builtin": "Influence Culture" + }, + "priority": 0, + "request": { + "Payment": [ + { + "cost": { + "conversions": [ + { + "from": [ + { + "food": 1 + }, + { + "wood": 1 + }, + { + "ore": 1 + }, + { + "ideas": 1 + } + ], + "to": { + "gold": 1 }, - { - "ideas": 1 - } - ], - "to": { - "gold": 1 - }, - "type": "Unlimited" + "type": "Unlimited" + } + ], + "default": { + "culture_tokens": 3 } - ] - }, - "name": "Pay 3 culture tokens to increase the dice roll", - "optional": true - } - ] + }, + "name": "Pay 3 culture tokens to increase the dice roll", + "optional": true + } + ] + } }, - "origin": { - "Builtin": "Influence Culture" - } + "last_priority_used": 0, + "player": 1 } - } + ] }, { - "Command": { - "gained_resources": { - "culture_tokens": 1 - } - } + "op": "replace", + "path": "/players/0/cities/2/city_pieces/temple", + "value": 0 + }, + { + "op": "replace", + "path": "/players/1/resources/culture_tokens", + "value": 6 + }, + { + "op": "replace", + "path": "/successful_cultural_influence", + "value": false } ] } diff --git a/server/tests/test_games/advances/dogma.json b/server/tests/test_games/advances/dogma.json index 08dafe5c2..37a8e1d4b 100644 --- a/server/tests/test_games/advances/dogma.json +++ b/server/tests/test_games/advances/dogma.json @@ -317,9 +317,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/dogma.outcome.json b/server/tests/test_games/advances/dogma.outcome.json index 843edb1d5..ce649443f 100644 --- a/server/tests/test_games/advances/dogma.outcome.json +++ b/server/tests/test_games/advances/dogma.outcome.json @@ -309,12 +309,53 @@ }, "undo": [ { - "WastedResources": { - "resources": { - "ideas": 2 - }, - "player_index": 1 - } + "op": "replace", + "path": "/actions_left", + "value": 3 + }, + { + "op": "replace", + "path": "/players/1/advances/0", + "value": "Farming" + }, + { + "op": "replace", + "path": "/players/1/advances/1", + "value": "Fishing" + }, + { + "op": "replace", + "path": "/players/1/advances/2", + "value": "Mining" + }, + { + "op": "replace", + "path": "/players/1/advances/3", + "value": "Myths" + }, + { + "op": "replace", + "path": "/players/1/advances/4", + "value": "State Religion" + }, + { + "op": "remove", + "path": "/players/1/advances/5" + }, + { + "op": "replace", + "path": "/players/1/incident_tokens", + "value": 3 + }, + { + "op": "replace", + "path": "/players/1/resource_limit/ideas", + "value": 7 + }, + { + "op": "replace", + "path": "/players/1/resources/ideas", + "value": 6 } ] } @@ -347,9 +388,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/dogma.outcome1.json b/server/tests/test_games/advances/dogma.outcome1.json index 87bdeeb64..09bad0694 100644 --- a/server/tests/test_games/advances/dogma.outcome1.json +++ b/server/tests/test_games/advances/dogma.outcome1.json @@ -352,12 +352,53 @@ }, "undo": [ { - "WastedResources": { - "resources": { - "ideas": 2 - }, - "player_index": 1 - } + "op": "replace", + "path": "/actions_left", + "value": 3 + }, + { + "op": "replace", + "path": "/players/1/advances/0", + "value": "Farming" + }, + { + "op": "replace", + "path": "/players/1/advances/1", + "value": "Fishing" + }, + { + "op": "replace", + "path": "/players/1/advances/2", + "value": "Mining" + }, + { + "op": "replace", + "path": "/players/1/advances/3", + "value": "Myths" + }, + { + "op": "replace", + "path": "/players/1/advances/4", + "value": "State Religion" + }, + { + "op": "remove", + "path": "/players/1/advances/5" + }, + { + "op": "replace", + "path": "/players/1/incident_tokens", + "value": 3 + }, + { + "op": "replace", + "path": "/players/1/resource_limit/ideas", + "value": 7 + }, + { + "op": "replace", + "path": "/players/1/resources/ideas", + "value": 6 } ] }, @@ -377,7 +418,36 @@ }, "undo": [ { - "Command": {} + "op": "replace", + "path": "/actions_left", + "value": 2 + }, + { + "op": "replace", + "path": "/players/1/cities/0/activations", + "value": 0 + }, + { + "op": "remove", + "path": "/players/1/cities/0/city_pieces/temple" + }, + { + "op": "replace", + "path": "/players/1/resources/ore", + "value": 5 + }, + { + "op": "replace", + "path": "/players/1/resources/wood", + "value": 5 + }, + { + "op": "remove", + "path": "/players/1/event_info" + }, + { + "op": "remove", + "path": "/current_events" } ] } @@ -414,9 +484,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/dogma.outcome2.json b/server/tests/test_games/advances/dogma.outcome2.json index 0036fe847..0f520f53d 100644 --- a/server/tests/test_games/advances/dogma.outcome2.json +++ b/server/tests/test_games/advances/dogma.outcome2.json @@ -338,12 +338,53 @@ }, "undo": [ { - "WastedResources": { - "resources": { - "ideas": 2 - }, - "player_index": 1 - } + "op": "replace", + "path": "/actions_left", + "value": 3 + }, + { + "op": "replace", + "path": "/players/1/advances/0", + "value": "Farming" + }, + { + "op": "replace", + "path": "/players/1/advances/1", + "value": "Fishing" + }, + { + "op": "replace", + "path": "/players/1/advances/2", + "value": "Mining" + }, + { + "op": "replace", + "path": "/players/1/advances/3", + "value": "Myths" + }, + { + "op": "replace", + "path": "/players/1/advances/4", + "value": "State Religion" + }, + { + "op": "remove", + "path": "/players/1/advances/5" + }, + { + "op": "replace", + "path": "/players/1/incident_tokens", + "value": 3 + }, + { + "op": "replace", + "path": "/players/1/resource_limit/ideas", + "value": 7 + }, + { + "op": "replace", + "path": "/players/1/resources/ideas", + "value": 6 } ] }, @@ -363,7 +404,36 @@ }, "undo": [ { - "Command": {} + "op": "replace", + "path": "/actions_left", + "value": 2 + }, + { + "op": "replace", + "path": "/players/1/cities/0/activations", + "value": 0 + }, + { + "op": "remove", + "path": "/players/1/cities/0/city_pieces/temple" + }, + { + "op": "replace", + "path": "/players/1/resources/ore", + "value": 5 + }, + { + "op": "replace", + "path": "/players/1/resources/wood", + "value": 5 + }, + { + "op": "remove", + "path": "/players/1/event_info" + }, + { + "op": "remove", + "path": "/current_events" } ] }, @@ -377,42 +447,53 @@ }, "undo": [ { - "Event": { - "event_type": { - "Construct": "Temple" - }, - "player": 1, - "last_priority_used": 1, - "handler": { - "priority": 1, - "request": { - "ResourceReward": { - "reward": { - "default": { + "op": "replace", + "path": "/current_events/0/handler/origin/Advance", + "value": "Myths" + }, + { + "op": "replace", + "path": "/current_events/0/handler/priority", + "value": 1 + }, + { + "op": "add", + "path": "/current_events/0/handler/request/ResourceReward", + "value": { + "name": "Select Temple bonus", + "reward": { + "conversions": [ + { + "from": [ + { "mood_tokens": 1 - }, - "conversions": [ - { - "from": [ - { - "mood_tokens": 1 - } - ], - "to": { - "culture_tokens": 1 - }, - "type": "Unlimited" - } - ] + } + ], + "to": { + "culture_tokens": 1 }, - "name": "Select Temple bonus" + "type": "Unlimited" } - }, - "origin": { - "Advance": "Myths" + ], + "default": { + "mood_tokens": 1 } } } + }, + { + "op": "remove", + "path": "/current_events/0/handler/request/SelectAdvance" + }, + { + "op": "replace", + "path": "/current_events/0/last_priority_used", + "value": 1 + }, + { + "op": "replace", + "path": "/players/1/resources/culture_tokens", + "value": 9 } ] } @@ -452,9 +533,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/dogma.outcome3.json b/server/tests/test_games/advances/dogma.outcome3.json index c3bbb062a..516a55379 100644 --- a/server/tests/test_games/advances/dogma.outcome3.json +++ b/server/tests/test_games/advances/dogma.outcome3.json @@ -315,12 +315,53 @@ }, "undo": [ { - "WastedResources": { - "resources": { - "ideas": 2 - }, - "player_index": 1 - } + "op": "replace", + "path": "/actions_left", + "value": 3 + }, + { + "op": "replace", + "path": "/players/1/advances/0", + "value": "Farming" + }, + { + "op": "replace", + "path": "/players/1/advances/1", + "value": "Fishing" + }, + { + "op": "replace", + "path": "/players/1/advances/2", + "value": "Mining" + }, + { + "op": "replace", + "path": "/players/1/advances/3", + "value": "Myths" + }, + { + "op": "replace", + "path": "/players/1/advances/4", + "value": "State Religion" + }, + { + "op": "remove", + "path": "/players/1/advances/5" + }, + { + "op": "replace", + "path": "/players/1/incident_tokens", + "value": 3 + }, + { + "op": "replace", + "path": "/players/1/resource_limit/ideas", + "value": 7 + }, + { + "op": "replace", + "path": "/players/1/resources/ideas", + "value": 6 } ] }, @@ -340,7 +381,36 @@ }, "undo": [ { - "Command": {} + "op": "replace", + "path": "/actions_left", + "value": 2 + }, + { + "op": "replace", + "path": "/players/1/cities/0/activations", + "value": 0 + }, + { + "op": "remove", + "path": "/players/1/cities/0/city_pieces/temple" + }, + { + "op": "replace", + "path": "/players/1/resources/ore", + "value": 5 + }, + { + "op": "replace", + "path": "/players/1/resources/wood", + "value": 5 + }, + { + "op": "remove", + "path": "/players/1/event_info" + }, + { + "op": "remove", + "path": "/current_events" } ] }, @@ -354,42 +424,53 @@ }, "undo": [ { - "Event": { - "event_type": { - "Construct": "Temple" - }, - "player": 1, - "last_priority_used": 1, - "handler": { - "priority": 1, - "request": { - "ResourceReward": { - "reward": { - "default": { + "op": "replace", + "path": "/current_events/0/handler/origin/Advance", + "value": "Myths" + }, + { + "op": "replace", + "path": "/current_events/0/handler/priority", + "value": 1 + }, + { + "op": "add", + "path": "/current_events/0/handler/request/ResourceReward", + "value": { + "name": "Select Temple bonus", + "reward": { + "conversions": [ + { + "from": [ + { "mood_tokens": 1 - }, - "conversions": [ - { - "from": [ - { - "mood_tokens": 1 - } - ], - "to": { - "culture_tokens": 1 - }, - "type": "Unlimited" - } - ] + } + ], + "to": { + "culture_tokens": 1 }, - "name": "Select Temple bonus" + "type": "Unlimited" } - }, - "origin": { - "Advance": "Myths" + ], + "default": { + "mood_tokens": 1 } } } + }, + { + "op": "remove", + "path": "/current_events/0/handler/request/SelectAdvance" + }, + { + "op": "replace", + "path": "/current_events/0/last_priority_used", + "value": 1 + }, + { + "op": "replace", + "path": "/players/1/resources/culture_tokens", + "value": 9 } ] }, @@ -401,28 +482,66 @@ }, "undo": [ { - "Event": { - "event_type": { - "Construct": "Temple" - }, - "player": 1, - "last_priority_used": 0, - "handler": { - "priority": 0, - "request": { - "SelectAdvance": { - "choices": [ - "Devotion", - "Conversion", - "Fanaticism" - ] + "op": "add", + "path": "/current_events", + "value": [ + { + "event_type": { + "Construct": "Temple" + }, + "handler": { + "origin": { + "Advance": "Dogma" + }, + "priority": 0, + "request": { + "SelectAdvance": { + "choices": [ + "Devotion", + "Conversion", + "Fanaticism" + ] + } } }, - "origin": { - "Advance": "Dogma" - } + "last_priority_used": 0, + "player": 1 } - } + ] + }, + { + "op": "replace", + "path": "/players/1/advances/1", + "value": "Farming" + }, + { + "op": "replace", + "path": "/players/1/advances/2", + "value": "Fishing" + }, + { + "op": "replace", + "path": "/players/1/advances/3", + "value": "Mining" + }, + { + "op": "replace", + "path": "/players/1/advances/4", + "value": "Myths" + }, + { + "op": "replace", + "path": "/players/1/advances/5", + "value": "State Religion" + }, + { + "op": "remove", + "path": "/players/1/advances/6" + }, + { + "op": "replace", + "path": "/players/1/incident_tokens", + "value": 2 } ] } @@ -465,9 +584,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/forced_labor.outcome.json b/server/tests/test_games/advances/forced_labor.outcome.json index d079d87d4..09f20279a 100644 --- a/server/tests/test_games/advances/forced_labor.outcome.json +++ b/server/tests/test_games/advances/forced_labor.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -257,7 +259,18 @@ "Playing": { "Custom": "ForcedLabor" } - } + }, + "undo": [ + { + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 9 + }, + { + "op": "remove", + "path": "/players/0/played_once_per_turn_actions" + } + ] } ], "action_log_index": 1, @@ -290,4 +303,4 @@ "dropped_players": [], "wonders_left": [], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/advances/forced_labor.outcome1.json b/server/tests/test_games/advances/forced_labor.outcome1.json index 01e1ce1c9..d33398712 100644 --- a/server/tests/test_games/advances/forced_labor.outcome1.json +++ b/server/tests/test_games/advances/forced_labor.outcome1.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -257,7 +259,18 @@ "Playing": { "Custom": "ForcedLabor" } - } + }, + "undo": [ + { + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 9 + }, + { + "op": "remove", + "path": "/players/0/played_once_per_turn_actions" + } + ] }, { "action": { @@ -283,21 +296,19 @@ }, "undo": [ { - "Command": { - "gained_resources": { - "food": 1, - "wood": 1 - } - } + "op": "replace", + "path": "/actions_left", + "value": 1 }, { - "WastedResources": { - "resources": { - "food": 1, - "wood": 1 - }, - "player_index": 0 - } + "op": "replace", + "path": "/players/0/cities/0/activations", + "value": 0 + }, + { + "op": "replace", + "path": "/players/0/cities/0/angry_activation", + "value": false } ] } @@ -336,4 +347,4 @@ "dropped_players": [], "wonders_left": [], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/advances/free_education.json b/server/tests/test_games/advances/free_education.json index 29b541ebb..3517840ed 100644 --- a/server/tests/test_games/advances/free_education.json +++ b/server/tests/test_games/advances/free_education.json @@ -291,71 +291,8 @@ }, "starting_player_index": 0, "current_player_index": 0, - "action_log": [ - { - "action": { - "Playing": { - "Advance": { - "advance": "Writing", - "payment": { - "ideas": 2 - } - } - } - }, - "undo": [ - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - } - ] - }, - { - "action": { - "Playing": { - "Advance": { - "advance": "Free Education", - "payment": { - "ideas": 2 - } - } - } - } - } - ], - "action_log_index": 2, + "action_log": [], + "action_log_index": 0, "log": [], "undo_limit": 0, "actions_left": 1, @@ -377,9 +314,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/free_education.outcome.json b/server/tests/test_games/advances/free_education.outcome.json index 7ace949ad..ce6b97fe8 100644 --- a/server/tests/test_games/advances/free_education.outcome.json +++ b/server/tests/test_games/advances/free_education.outcome.json @@ -353,79 +353,87 @@ "action": { "Playing": { "Advance": { - "advance": "Writing", + "advance": "Draft", "payment": { - "ideas": 2 + "food": 1, + "gold": 1 } } } }, "undo": [ { - "Recruit": {} + "op": "replace", + "path": "/actions_left", + "value": 1 + }, + { + "op": "replace", + "path": "/players/0/advances/0", + "value": "Farming" + }, + { + "op": "replace", + "path": "/players/0/advances/1", + "value": "Free Economy" }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/advances/2", + "value": "Free Education" }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/advances/3", + "value": "Mining" }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/advances/4", + "value": "Storage" }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/advances/5", + "value": "Tactics" }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/advances/6", + "value": "Voting" }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/advances/7", + "value": "Writing" }, { - "Recruit": {} + "op": "remove", + "path": "/players/0/advances/8" }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/resources/culture_tokens", + "value": 10 }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/resources/food", + "value": 7 }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/resources/gold", + "value": 5 }, { - "Recruit": {} + "op": "remove", + "path": "/current_events" } ] - }, - { - "action": { - "Playing": { - "Advance": { - "advance": "Free Education", - "payment": { - "ideas": 2 - } - } - } - } - }, - { - "action": { - "Playing": { - "Advance": { - "advance": "Draft", - "payment": { - "food": 1, - "gold": 1 - } - } - } - } } ], - "action_log_index": 3, + "action_log_index": 1, "log": [ [ "Player1 paid 1 food and 1 gold to get the Draft advance", @@ -452,9 +460,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/free_education.outcome1.json b/server/tests/test_games/advances/free_education.outcome1.json index 25d16f81c..0205ef0e5 100644 --- a/server/tests/test_games/advances/free_education.outcome1.json +++ b/server/tests/test_games/advances/free_education.outcome1.json @@ -296,77 +296,85 @@ "action": { "Playing": { "Advance": { - "advance": "Writing", + "advance": "Draft", "payment": { - "ideas": 2 + "food": 1, + "gold": 1 } } } }, "undo": [ { - "Recruit": {} + "op": "replace", + "path": "/actions_left", + "value": 1 + }, + { + "op": "replace", + "path": "/players/0/advances/0", + "value": "Farming" + }, + { + "op": "replace", + "path": "/players/0/advances/1", + "value": "Free Economy" }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/advances/2", + "value": "Free Education" }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/advances/3", + "value": "Mining" }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/advances/4", + "value": "Storage" }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/advances/5", + "value": "Tactics" }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/advances/6", + "value": "Voting" }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/advances/7", + "value": "Writing" }, { - "Recruit": {} + "op": "remove", + "path": "/players/0/advances/8" }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/resources/culture_tokens", + "value": 10 }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/resources/food", + "value": 7 }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/resources/gold", + "value": 5 }, { - "Recruit": {} + "op": "remove", + "path": "/current_events" } ] }, - { - "action": { - "Playing": { - "Advance": { - "advance": "Free Education", - "payment": { - "ideas": 2 - } - } - } - } - }, - { - "action": { - "Playing": { - "Advance": { - "advance": "Draft", - "payment": { - "food": 1, - "gold": 1 - } - } - } - } - }, { "action": { "Response": { @@ -379,72 +387,84 @@ }, "undo": [ { - "Event": { - "event_type": { - "Advance": { - "name": "Draft", - "payment": { - "food": 1, - "gold": 1 + "op": "add", + "path": "/current_events", + "value": [ + { + "event_type": { + "Advance": { + "name": "Draft", + "payment": { + "food": 1, + "gold": 1 + } } - } - }, - "player": 0, - "last_priority_used": 1, - "handler": { - "priority": 1, - "request": { - "Payment": [ - { - "cost": { - "default": { - "ideas": 1 - }, - "conversions": [ - { - "from": [ - { - "food": 1 - }, - { - "wood": 1 - }, - { - "ore": 1 + }, + "handler": { + "origin": { + "Advance": "Free Education" + }, + "priority": 1, + "request": { + "Payment": [ + { + "cost": { + "conversions": [ + { + "from": [ + { + "food": 1 + }, + { + "wood": 1 + }, + { + "ore": 1 + }, + { + "ideas": 1 + } + ], + "to": { + "gold": 1 }, - { - "ideas": 1 - } - ], - "to": { - "gold": 1 - }, - "type": "Unlimited" + "type": "Unlimited" + } + ], + "default": { + "ideas": 1 } - ] - }, - "name": "Pay extra 1 idea for a mood token", - "optional": true - } - ] + }, + "name": "Pay extra 1 idea for a mood token", + "optional": true + } + ] + } }, - "origin": { - "Advance": "Free Education" - } + "last_priority_used": 1, + "player": 0 } - } + ] }, { - "Command": { - "gained_resources": { - "mood_tokens": 1 - } - } + "op": "replace", + "path": "/players/0/incident_tokens", + "value": 2 + }, + { + "op": "add", + "path": "/players/0/resources/ideas", + "value": 1 + }, + { + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 11 } ] } ], - "action_log_index": 4, + "action_log_index": 2, "log": [ [ "Player1 paid 1 food and 1 gold to get the Draft advance", @@ -474,9 +494,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/increase_happiness_sports.json b/server/tests/test_games/advances/increase_happiness_sports.json index a48254463..02acf26cb 100644 --- a/server/tests/test_games/advances/increase_happiness_sports.json +++ b/server/tests/test_games/advances/increase_happiness_sports.json @@ -297,9 +297,7 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], diff --git a/server/tests/test_games/advances/increase_happiness_sports.outcome.json b/server/tests/test_games/advances/increase_happiness_sports.outcome.json index 5d5c7d82f..341cafa7e 100644 --- a/server/tests/test_games/advances/increase_happiness_sports.outcome.json +++ b/server/tests/test_games/advances/increase_happiness_sports.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -288,11 +290,24 @@ }, "undo": [ { - "IncreaseHappiness": { - "angry_activations": [ - "C2" - ] - } + "op": "replace", + "path": "/actions_left", + "value": 3 + }, + { + "op": "replace", + "path": "/players/0/cities/1/angry_activation", + "value": true + }, + { + "op": "replace", + "path": "/players/0/cities/1/mood_state", + "value": "Angry" + }, + { + "op": "replace", + "path": "/players/0/resources/culture_tokens", + "value": 9 } ] } @@ -323,11 +338,9 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/advances/increase_happiness_sports2.json b/server/tests/test_games/advances/increase_happiness_sports2.json index a48254463..02acf26cb 100644 --- a/server/tests/test_games/advances/increase_happiness_sports2.json +++ b/server/tests/test_games/advances/increase_happiness_sports2.json @@ -297,9 +297,7 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], diff --git a/server/tests/test_games/advances/increase_happiness_sports2.outcome.json b/server/tests/test_games/advances/increase_happiness_sports2.outcome.json index fb8265c17..73ad8682b 100644 --- a/server/tests/test_games/advances/increase_happiness_sports2.outcome.json +++ b/server/tests/test_games/advances/increase_happiness_sports2.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -288,11 +290,24 @@ }, "undo": [ { - "IncreaseHappiness": { - "angry_activations": [ - "C2" - ] - } + "op": "replace", + "path": "/actions_left", + "value": 3 + }, + { + "op": "replace", + "path": "/players/0/cities/1/angry_activation", + "value": true + }, + { + "op": "replace", + "path": "/players/0/cities/1/mood_state", + "value": "Angry" + }, + { + "op": "replace", + "path": "/players/0/resources/culture_tokens", + "value": 9 } ] } @@ -323,11 +338,9 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/advances/increase_happiness_voting.json b/server/tests/test_games/advances/increase_happiness_voting.json index 7fb168674..b9304f754 100644 --- a/server/tests/test_games/advances/increase_happiness_voting.json +++ b/server/tests/test_games/advances/increase_happiness_voting.json @@ -296,9 +296,7 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], diff --git a/server/tests/test_games/advances/increase_happiness_voting.outcome.json b/server/tests/test_games/advances/increase_happiness_voting.outcome.json index 236dcf0ae..4481b8d9c 100644 --- a/server/tests/test_games/advances/increase_happiness_voting.outcome.json +++ b/server/tests/test_games/advances/increase_happiness_voting.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -296,12 +298,29 @@ }, "undo": [ { - "IncreaseHappiness": { - "angry_activations": [ - "C2", - "B3" - ] - } + "op": "replace", + "path": "/players/0/cities/1/angry_activation", + "value": true + }, + { + "op": "replace", + "path": "/players/0/cities/1/mood_state", + "value": "Angry" + }, + { + "op": "replace", + "path": "/players/0/cities/3/angry_activation", + "value": true + }, + { + "op": "replace", + "path": "/players/0/cities/3/mood_state", + "value": "Angry" + }, + { + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 9 } ] } @@ -332,11 +351,9 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/advances/increase_happiness_voting_rituals.json b/server/tests/test_games/advances/increase_happiness_voting_rituals.json index 356f552be..dc1239e0c 100644 --- a/server/tests/test_games/advances/increase_happiness_voting_rituals.json +++ b/server/tests/test_games/advances/increase_happiness_voting_rituals.json @@ -297,9 +297,7 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], diff --git a/server/tests/test_games/advances/increase_happiness_voting_rituals.outcome.json b/server/tests/test_games/advances/increase_happiness_voting_rituals.outcome.json index e8c0347c6..46954b819 100644 --- a/server/tests/test_games/advances/increase_happiness_voting_rituals.outcome.json +++ b/server/tests/test_games/advances/increase_happiness_voting_rituals.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -301,12 +303,49 @@ }, "undo": [ { - "IncreaseHappiness": { - "angry_activations": [ - "C2", - "B3" - ] - } + "op": "replace", + "path": "/players/0/cities/1/angry_activation", + "value": true + }, + { + "op": "replace", + "path": "/players/0/cities/1/mood_state", + "value": "Angry" + }, + { + "op": "replace", + "path": "/players/0/cities/3/angry_activation", + "value": true + }, + { + "op": "replace", + "path": "/players/0/cities/3/mood_state", + "value": "Angry" + }, + { + "op": "replace", + "path": "/players/0/resources/food", + "value": 2 + }, + { + "op": "replace", + "path": "/players/0/resources/gold", + "value": 5 + }, + { + "op": "replace", + "path": "/players/0/resources/ideas", + "value": 5 + }, + { + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 9 + }, + { + "op": "replace", + "path": "/players/0/resources/ore", + "value": 5 } ] } @@ -338,11 +377,9 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/advances/movement_on_roads_from_city.json b/server/tests/test_games/advances/movement_on_roads_from_city.json index ab6963dc6..e0e013357 100644 --- a/server/tests/test_games/advances/movement_on_roads_from_city.json +++ b/server/tests/test_games/advances/movement_on_roads_from_city.json @@ -372,9 +372,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/movement_on_roads_from_city.outcome.json b/server/tests/test_games/advances/movement_on_roads_from_city.outcome.json index 519c080de..8537d73e0 100644 --- a/server/tests/test_games/advances/movement_on_roads_from_city.outcome.json +++ b/server/tests/test_games/advances/movement_on_roads_from_city.outcome.json @@ -380,10 +380,28 @@ }, "undo": [ { - "Movement": { - "starting_position": "D8", - "movement_actions_left": 2 - } + "op": "add", + "path": "/players/1/resources/food", + "value": 1 + }, + { + "op": "add", + "path": "/players/1/resources/ore", + "value": 1 + }, + { + "op": "replace", + "path": "/players/1/units/0/position", + "value": "D8" + }, + { + "op": "replace", + "path": "/state/1/Movement/movement_actions_left", + "value": 2 + }, + { + "op": "remove", + "path": "/state/1/Movement/moved_units" } ] } @@ -402,9 +420,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/movement_on_roads_to_city.json b/server/tests/test_games/advances/movement_on_roads_to_city.json index aa35eae05..098137869 100644 --- a/server/tests/test_games/advances/movement_on_roads_to_city.json +++ b/server/tests/test_games/advances/movement_on_roads_to_city.json @@ -366,9 +366,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/movement_on_roads_to_city.outcome.json b/server/tests/test_games/advances/movement_on_roads_to_city.outcome.json index 0107ccccb..cb1cd23f1 100644 --- a/server/tests/test_games/advances/movement_on_roads_to_city.outcome.json +++ b/server/tests/test_games/advances/movement_on_roads_to_city.outcome.json @@ -373,10 +373,28 @@ }, "undo": [ { - "Movement": { - "starting_position": "F7", - "movement_actions_left": 2 - } + "op": "add", + "path": "/players/1/resources/food", + "value": 1 + }, + { + "op": "add", + "path": "/players/1/resources/ore", + "value": 1 + }, + { + "op": "replace", + "path": "/players/1/units/0/position", + "value": "F7" + }, + { + "op": "replace", + "path": "/state/1/Movement/movement_actions_left", + "value": 2 + }, + { + "op": "remove", + "path": "/state/1/Movement/moved_units" } ] } @@ -395,9 +413,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/priesthood.json b/server/tests/test_games/advances/priesthood.json index b07ca7fa7..5cc30ab50 100644 --- a/server/tests/test_games/advances/priesthood.json +++ b/server/tests/test_games/advances/priesthood.json @@ -318,9 +318,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/priesthood.outcome.json b/server/tests/test_games/advances/priesthood.outcome.json index a87209fab..9dee16e7b 100644 --- a/server/tests/test_games/advances/priesthood.outcome.json +++ b/server/tests/test_games/advances/priesthood.outcome.json @@ -312,17 +312,51 @@ }, "undo": [ { - "Command": {} + "op": "replace", + "path": "/actions_left", + "value": 2 }, { - "Command": { - "info": { - "Priesthood": "used" - }, - "gained_resources": { - "ideas": 1 - } - } + "op": "replace", + "path": "/players/1/advances/3", + "value": "Mining" + }, + { + "op": "replace", + "path": "/players/1/advances/4", + "value": "Myths" + }, + { + "op": "replace", + "path": "/players/1/advances/5", + "value": "Philosophy" + }, + { + "op": "replace", + "path": "/players/1/advances/6", + "value": "Priesthood" + }, + { + "op": "remove", + "path": "/players/1/advances/7" + }, + { + "op": "replace", + "path": "/players/1/incident_tokens", + "value": 3 + }, + { + "op": "replace", + "path": "/players/1/resources/culture_tokens", + "value": 9 + }, + { + "op": "remove", + "path": "/players/1/resources/ideas" + }, + { + "op": "remove", + "path": "/players/1/event_info" } ] } @@ -356,9 +390,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/priesthood.outcome1.json b/server/tests/test_games/advances/priesthood.outcome1.json index eeca195b3..75a26a83e 100644 --- a/server/tests/test_games/advances/priesthood.outcome1.json +++ b/server/tests/test_games/advances/priesthood.outcome1.json @@ -313,17 +313,51 @@ }, "undo": [ { - "Command": {} + "op": "replace", + "path": "/actions_left", + "value": 2 }, { - "Command": { - "info": { - "Priesthood": "used" - }, - "gained_resources": { - "ideas": 1 - } - } + "op": "replace", + "path": "/players/1/advances/3", + "value": "Mining" + }, + { + "op": "replace", + "path": "/players/1/advances/4", + "value": "Myths" + }, + { + "op": "replace", + "path": "/players/1/advances/5", + "value": "Philosophy" + }, + { + "op": "replace", + "path": "/players/1/advances/6", + "value": "Priesthood" + }, + { + "op": "remove", + "path": "/players/1/advances/7" + }, + { + "op": "replace", + "path": "/players/1/incident_tokens", + "value": 3 + }, + { + "op": "replace", + "path": "/players/1/resources/culture_tokens", + "value": 9 + }, + { + "op": "remove", + "path": "/players/1/resources/ideas" + }, + { + "op": "remove", + "path": "/players/1/event_info" } ] }, @@ -340,14 +374,73 @@ }, "undo": [ { - "Command": { - "info": { - "Priesthood": "used" - }, - "gained_resources": { - "ideas": 1 - } - } + "op": "replace", + "path": "/actions_left", + "value": 1 + }, + { + "op": "replace", + "path": "/players/1/advances/0", + "value": "Dogma" + }, + { + "op": "replace", + "path": "/players/1/advances/1", + "value": "Farming" + }, + { + "op": "replace", + "path": "/players/1/advances/2", + "value": "Fishing" + }, + { + "op": "replace", + "path": "/players/1/advances/3", + "value": "Math" + }, + { + "op": "replace", + "path": "/players/1/advances/4", + "value": "Mining" + }, + { + "op": "replace", + "path": "/players/1/advances/5", + "value": "Myths" + }, + { + "op": "replace", + "path": "/players/1/advances/6", + "value": "Philosophy" + }, + { + "op": "replace", + "path": "/players/1/advances/7", + "value": "Priesthood" + }, + { + "op": "remove", + "path": "/players/1/advances/8" + }, + { + "op": "replace", + "path": "/players/1/incident_tokens", + "value": 2 + }, + { + "op": "replace", + "path": "/players/1/resources/culture_tokens", + "value": 10 + }, + { + "op": "replace", + "path": "/players/1/resources/gold", + "value": 5 + }, + { + "op": "replace", + "path": "/players/1/resources/ideas", + "value": 1 } ] } @@ -386,9 +479,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/roads_unit_test.json b/server/tests/test_games/advances/roads_unit_test.json index b4e62eab1..c7a24ec58 100644 --- a/server/tests/test_games/advances/roads_unit_test.json +++ b/server/tests/test_games/advances/roads_unit_test.json @@ -394,9 +394,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/sanitation_and_draft.json b/server/tests/test_games/advances/sanitation_and_draft.json index 7ba152977..9d8298d4e 100644 --- a/server/tests/test_games/advances/sanitation_and_draft.json +++ b/server/tests/test_games/advances/sanitation_and_draft.json @@ -282,71 +282,8 @@ }, "starting_player_index": 0, "current_player_index": 0, - "action_log": [ - { - "action": { - "Playing": { - "Advance": { - "advance": "Writing", - "payment": { - "ideas": 2 - } - } - } - }, - "undo": [ - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - } - ] - }, - { - "action": { - "Playing": { - "Advance": { - "advance": "Free Education", - "payment": { - "ideas": 2 - } - } - } - } - } - ], - "action_log_index": 2, + "action_log": [], + "action_log_index": 0, "log": [], "undo_limit": 0, "actions_left": 1, @@ -368,9 +305,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/sanitation_and_draft.outcome.json b/server/tests/test_games/advances/sanitation_and_draft.outcome.json index 7bea6b7ed..cbb92c432 100644 --- a/server/tests/test_games/advances/sanitation_and_draft.outcome.json +++ b/server/tests/test_games/advances/sanitation_and_draft.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -294,89 +296,57 @@ { "action": { "Playing": { - "Advance": { - "advance": "Writing", + "Recruit": { + "units": { + "settlers": 1, + "infantry": 1 + }, + "city_position": "A1", "payment": { - "ideas": 2 + "gold": 2, + "mood_tokens": 1 } } } }, "undo": [ { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} + "op": "replace", + "path": "/actions_left", + "value": 1 }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/cities/0/activations", + "value": 0 }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/next_unit_id", + "value": 10 }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/resources/gold", + "value": 5 }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 11 }, { - "Recruit": {} + "op": "remove", + "path": "/players/0/units/6" }, { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - } - ] - }, - { - "action": { - "Playing": { - "Advance": { - "advance": "Free Education", - "payment": { - "ideas": 2 - } - } - } - } - }, - { - "action": { - "Playing": { - "Recruit": { - "units": { - "settlers": 1, - "infantry": 1 - }, - "city_position": "A1", - "payment": { - "gold": 2, - "mood_tokens": 1 - } - } - } - }, - "undo": [ - { - "Recruit": {} + "op": "remove", + "path": "/players/0/units/6" } ] } ], - "action_log_index": 3, + "action_log_index": 1, "log": [ [ "Player1 paid 2 gold and 1 mood token to recruit 1 settler and 1 infantry in the city at A1", @@ -403,13 +373,11 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ "Pyramids" ], "wonder_amount_left": 2 -} +} \ No newline at end of file diff --git a/server/tests/test_games/advances/taxes.json b/server/tests/test_games/advances/taxes.json index c499023ca..6cccc7a29 100644 --- a/server/tests/test_games/advances/taxes.json +++ b/server/tests/test_games/advances/taxes.json @@ -294,71 +294,8 @@ }, "starting_player_index": 0, "current_player_index": 0, - "action_log": [ - { - "action": { - "Playing": { - "Advance": { - "advance": "Writing", - "payment": { - "ideas": 2 - } - } - } - }, - "undo": [ - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - } - ] - }, - { - "action": { - "Playing": { - "Advance": { - "advance": "Free Education", - "payment": { - "ideas": 2 - } - } - } - } - } - ], - "action_log_index": 2, + "action_log": [], + "action_log_index": 0, "log": [], "undo_limit": 0, "actions_left": 1, @@ -380,9 +317,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/taxes.outcome.json b/server/tests/test_games/advances/taxes.outcome.json index eee42bdc9..9ffebfc54 100644 --- a/server/tests/test_games/advances/taxes.outcome.json +++ b/server/tests/test_games/advances/taxes.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -299,91 +301,50 @@ { "action": { "Playing": { - "Advance": { - "advance": "Writing", - "payment": { - "ideas": 2 + "Custom": { + "Taxes": { + "food": 1, + "wood": 1, + "ore": 1, + "gold": 1 } } } }, "undo": [ { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} + "op": "replace", + "path": "/actions_left", + "value": 1 }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/resources/gold", + "value": 5 }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 11 }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/resources/ore", + "value": 5 }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/resources/wood", + "value": 5 }, { - "Recruit": {} - } - ] - }, - { - "action": { - "Playing": { - "Advance": { - "advance": "Free Education", - "payment": { - "ideas": 2 - } - } - } - } - }, - { - "action": { - "Playing": { - "Custom": { - "Taxes": { - "food": 1, - "wood": 1, - "ore": 1, - "gold": 1 - } - } - } - }, - "undo": [ - { - "WastedResources": { - "resources": { - "food": 1 - }, - "player_index": 0 - } + "op": "remove", + "path": "/players/0/played_once_per_turn_actions" } ] } ], - "action_log_index": 3, + "action_log_index": 1, "log": [ [ "Player1 paid 1 mood token to collect 1 food, 1 wood, 1 ore and 1 gold using Taxes", @@ -410,13 +371,11 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ "Pyramids" ], "wonder_amount_left": 2 -} +} \ No newline at end of file diff --git a/server/tests/test_games/advances/theaters.json b/server/tests/test_games/advances/theaters.json index 78caa1325..97474795c 100644 --- a/server/tests/test_games/advances/theaters.json +++ b/server/tests/test_games/advances/theaters.json @@ -294,95 +294,8 @@ }, "starting_player_index": 0, "current_player_index": 0, - "action_log": [ - { - "action": { - "Playing": { - "Advance": { - "advance": "Writing", - "payment": { - "ideas": 2 - } - } - } - }, - "undo": [ - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - } - ] - }, - { - "action": { - "Playing": { - "Advance": { - "advance": "Free Education", - "payment": { - "ideas": 2 - } - } - } - } - }, - { - "action": { - "Playing": { - "Custom": { - "Taxes": { - "food": 1, - "wood": 1, - "ore": 1, - "gold": 1 - } - } - } - }, - "undo": [ - { - "WastedResources": { - "resources": { - "food": 1 - }, - "player_index": 0 - } - } - ] - } - ], - "action_log_index": 3, + "action_log": [], + "action_log_index": 0, "log": [], "undo_limit": 0, "actions_left": 0, @@ -404,9 +317,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/theaters.outcome.json b/server/tests/test_games/advances/theaters.outcome.json index c899ebdc8..4da90865c 100644 --- a/server/tests/test_games/advances/theaters.outcome.json +++ b/server/tests/test_games/advances/theaters.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -299,102 +301,32 @@ { "action": { "Playing": { - "Advance": { - "advance": "Writing", - "payment": { - "ideas": 2 + "Custom": { + "Theaters": { + "culture_tokens": 1 } } } }, "undo": [ { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} + "op": "replace", + "path": "/players/0/resources/culture_tokens", + "value": 10 }, { - "Recruit": {} + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 10 }, { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} - }, - { - "Recruit": {} + "op": "remove", + "path": "/players/0/played_once_per_turn_actions" } ] - }, - { - "action": { - "Playing": { - "Advance": { - "advance": "Free Education", - "payment": { - "ideas": 2 - } - } - } - } - }, - { - "action": { - "Playing": { - "Custom": { - "Taxes": { - "food": 1, - "wood": 1, - "ore": 1, - "gold": 1 - } - } - } - }, - "undo": [ - { - "WastedResources": { - "resources": { - "food": 1 - }, - "player_index": 0 - } - } - ] - }, - { - "action": { - "Playing": { - "Custom": { - "Theaters": { - "culture_tokens": 1 - } - } - } - } } ], - "action_log_index": 4, + "action_log_index": 1, "log": [ [ "Player1 paid 1 culture token to convert resources using Theaters" @@ -420,13 +352,11 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ "Pyramids" ], "wonder_amount_left": 2 -} +} \ No newline at end of file diff --git a/server/tests/test_games/advances/trade_routes.json b/server/tests/test_games/advances/trade_routes.json index 5145689da..60aa1ce6e 100644 --- a/server/tests/test_games/advances/trade_routes.json +++ b/server/tests/test_games/advances/trade_routes.json @@ -427,9 +427,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/trade_routes.outcome.json b/server/tests/test_games/advances/trade_routes.outcome.json index b02bdae10..1d4420c02 100644 --- a/server/tests/test_games/advances/trade_routes.outcome.json +++ b/server/tests/test_games/advances/trade_routes.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -419,17 +421,7 @@ { "action": { "Playing": "EndTurn" - }, - "undo": [ - { - "WastedResources": { - "resources": { - "food": 1 - }, - "player_index": 1 - } - } - ] + } } ], "action_log_index": 1, @@ -457,13 +449,11 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ "Pyramids" ], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/advances/trade_routes_unit_test.json b/server/tests/test_games/advances/trade_routes_unit_test.json index f7b7e4519..8f302b849 100644 --- a/server/tests/test_games/advances/trade_routes_unit_test.json +++ b/server/tests/test_games/advances/trade_routes_unit_test.json @@ -462,9 +462,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/trade_routes_with_currency.json b/server/tests/test_games/advances/trade_routes_with_currency.json index b24e70141..4f728dc24 100644 --- a/server/tests/test_games/advances/trade_routes_with_currency.json +++ b/server/tests/test_games/advances/trade_routes_with_currency.json @@ -431,9 +431,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/trade_routes_with_currency.outcome.json b/server/tests/test_games/advances/trade_routes_with_currency.outcome.json index cef11a7ca..85f3f3d32 100644 --- a/server/tests/test_games/advances/trade_routes_with_currency.outcome.json +++ b/server/tests/test_games/advances/trade_routes_with_currency.outcome.json @@ -483,9 +483,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/advances/trade_routes_with_currency.outcome1.json b/server/tests/test_games/advances/trade_routes_with_currency.outcome1.json index 07ce67b82..b0dae7716 100644 --- a/server/tests/test_games/advances/trade_routes_with_currency.outcome1.json +++ b/server/tests/test_games/advances/trade_routes_with_currency.outcome1.json @@ -438,40 +438,53 @@ }, "undo": [ { - "Event": { - "event_type": "TurnStart", - "player": 1, - "last_priority_used": 0, - "handler": { - "priority": 0, - "request": { - "ResourceReward": { - "reward": { - "default": { - "gold": 2 - }, - "conversions": [ - { - "from": [ - { - "gold": 1 - } - ], - "to": { - "food": 1 - }, - "type": "Unlimited" + "op": "add", + "path": "/current_events", + "value": [ + { + "event_type": "TurnStart", + "handler": { + "origin": { + "Advance": "Trade Routes" + }, + "priority": 0, + "request": { + "ResourceReward": { + "name": "Collect trade routes reward", + "reward": { + "conversions": [ + { + "from": [ + { + "gold": 1 + } + ], + "to": { + "food": 1 + }, + "type": "Unlimited" + } + ], + "default": { + "gold": 2 } - ] - }, - "name": "Collect trade routes reward" + } + } } }, - "origin": { - "Advance": "Trade Routes" - } + "last_priority_used": 0, + "player": 1 } - } + ] + }, + { + "op": "replace", + "path": "/players/1/resources/food", + "value": 1 + }, + { + "op": "remove", + "path": "/players/1/resources/gold" } ] } @@ -502,9 +515,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/base/collect.outcome.json b/server/tests/test_games/base/collect.outcome.json index bf574da11..17f9aedd4 100644 --- a/server/tests/test_games/base/collect.outcome.json +++ b/server/tests/test_games/base/collect.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -235,27 +237,28 @@ }, "undo": [ { - "Command": { - "gained_resources": { - "wood": 1, - "ore": 1 - } - } + "op": "replace", + "path": "/actions_left", + "value": 3 }, { - "Command": { - "gained_resources": { - "ideas": 1 - } - } + "op": "replace", + "path": "/players/0/cities/1/activations", + "value": 0 }, { - "WastedResources": { - "resources": { - "ore": 1 - }, - "player_index": 0 - } + "op": "replace", + "path": "/players/0/resources/ideas", + "value": 5 + }, + { + "op": "replace", + "path": "/players/0/resources/wood", + "value": 6 + }, + { + "op": "remove", + "path": "/players/0/event_info" } ] } @@ -294,4 +297,4 @@ "Pyramids" ], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/base/construct.outcome.json b/server/tests/test_games/base/construct.outcome.json index 617ef3d04..9e3d1189f 100644 --- a/server/tests/test_games/base/construct.outcome.json +++ b/server/tests/test_games/base/construct.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -221,7 +223,43 @@ "port_position": null } } - } + }, + "undo": [ + { + "op": "replace", + "path": "/actions_left", + "value": 1 + }, + { + "op": "replace", + "path": "/players/0/cities/1/activations", + "value": 8 + }, + { + "op": "remove", + "path": "/players/0/cities/1/city_pieces/observatory" + }, + { + "op": "replace", + "path": "/players/0/cities/1/mood_state", + "value": "Happy" + }, + { + "op": "replace", + "path": "/players/0/resources/food", + "value": 2 + }, + { + "op": "replace", + "path": "/players/0/resources/ore", + "value": 7 + }, + { + "op": "replace", + "path": "/players/0/resources/wood", + "value": 7 + } + ] } ], "action_log_index": 1, @@ -256,4 +294,4 @@ "Pyramids" ], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/base/construct_port.outcome.json b/server/tests/test_games/base/construct_port.outcome.json index 55ffcac29..682b0cab9 100644 --- a/server/tests/test_games/base/construct_port.outcome.json +++ b/server/tests/test_games/base/construct_port.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -223,7 +225,42 @@ "port_position": "A2" } } - } + }, + "undo": [ + { + "op": "replace", + "path": "/actions_left", + "value": 3 + }, + { + "op": "replace", + "path": "/players/0/cities/0/activations", + "value": 0 + }, + { + "op": "remove", + "path": "/players/0/cities/0/city_pieces/port" + }, + { + "op": "remove", + "path": "/players/0/cities/0/port_position" + }, + { + "op": "replace", + "path": "/players/0/resources/food", + "value": 2 + }, + { + "op": "replace", + "path": "/players/0/resources/ore", + "value": 7 + }, + { + "op": "replace", + "path": "/players/0/resources/wood", + "value": 7 + } + ] } ], "action_log_index": 1, @@ -258,4 +295,4 @@ "Pyramids" ], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/base/cultural_influence.outcome1.json b/server/tests/test_games/base/cultural_influence.outcome1.json index 6ab45225a..a99cc5213 100644 --- a/server/tests/test_games/base/cultural_influence.outcome1.json +++ b/server/tests/test_games/base/cultural_influence.outcome1.json @@ -246,56 +246,75 @@ }, "undo": [ { - "Event": { - "event_type": { - "InfluenceCultureResolution": { - "culture_tokens": 4 - } - }, - "player": 1, - "last_priority_used": 0, - "handler": { - "priority": 0, - "request": { - "Payment": [ - { - "cost": { - "default": { - "culture_tokens": 4 - }, - "conversions": [ - { - "from": [ - { - "food": 1 - }, - { - "wood": 1 - }, - { - "ore": 1 + "op": "add", + "path": "/current_events", + "value": [ + { + "event_type": { + "InfluenceCultureResolution": { + "culture_tokens": 4 + } + }, + "handler": { + "origin": { + "Builtin": "Influence Culture" + }, + "priority": 0, + "request": { + "Payment": [ + { + "cost": { + "conversions": [ + { + "from": [ + { + "food": 1 + }, + { + "wood": 1 + }, + { + "ore": 1 + }, + { + "ideas": 1 + } + ], + "to": { + "gold": 1 }, - { - "ideas": 1 - } - ], - "to": { - "gold": 1 - }, - "type": "Unlimited" + "type": "Unlimited" + } + ], + "default": { + "culture_tokens": 4 } - ] - }, - "name": "Pay 4 culture tokens to increase the dice roll", - "optional": true - } - ] + }, + "name": "Pay 4 culture tokens to increase the dice roll", + "optional": true + } + ] + } }, - "origin": { - "Builtin": "Influence Culture" - } + "last_priority_used": 0, + "player": 1 } - } + ] + }, + { + "op": "replace", + "path": "/players/0/cities/2/city_pieces/temple", + "value": 0 + }, + { + "op": "replace", + "path": "/players/1/resources/culture_tokens", + "value": 6 + }, + { + "op": "replace", + "path": "/successful_cultural_influence", + "value": false } ] } diff --git a/server/tests/test_games/base/found_city.outcome.json b/server/tests/test_games/base/found_city.outcome.json index 56df1141f..ec982667d 100644 --- a/server/tests/test_games/base/found_city.outcome.json +++ b/server/tests/test_games/base/found_city.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -216,12 +218,41 @@ }, "undo": [ { - "FoundCity": { - "settler": { - "position": "B2", - "unit_type": "Settler", - "id": 4 - } + "op": "replace", + "path": "/actions_left", + "value": 2 + }, + { + "op": "remove", + "path": "/players/0/cities/2" + }, + { + "op": "replace", + "path": "/players/0/units/4/id", + "value": 4 + }, + { + "op": "replace", + "path": "/players/0/units/4/position", + "value": "B2" + }, + { + "op": "replace", + "path": "/players/0/units/5/id", + "value": 5 + }, + { + "op": "replace", + "path": "/players/0/units/6/id", + "value": 6 + }, + { + "op": "add", + "path": "/players/0/units/7", + "value": { + "id": 7, + "position": "C2", + "unit_type": "Settler" } } ] @@ -259,4 +290,4 @@ "Pyramids" ], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/base/increase_happiness.json b/server/tests/test_games/base/increase_happiness.json index 7fb168674..b9304f754 100644 --- a/server/tests/test_games/base/increase_happiness.json +++ b/server/tests/test_games/base/increase_happiness.json @@ -296,9 +296,7 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], diff --git a/server/tests/test_games/base/increase_happiness.outcome.json b/server/tests/test_games/base/increase_happiness.outcome.json index 70a4a47cd..dfb7d002c 100644 --- a/server/tests/test_games/base/increase_happiness.outcome.json +++ b/server/tests/test_games/base/increase_happiness.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -294,12 +296,34 @@ }, "undo": [ { - "IncreaseHappiness": { - "angry_activations": [ - "C2", - "B3" - ] - } + "op": "replace", + "path": "/actions_left", + "value": 3 + }, + { + "op": "replace", + "path": "/players/0/cities/1/angry_activation", + "value": true + }, + { + "op": "replace", + "path": "/players/0/cities/1/mood_state", + "value": "Angry" + }, + { + "op": "replace", + "path": "/players/0/cities/3/angry_activation", + "value": true + }, + { + "op": "replace", + "path": "/players/0/cities/3/mood_state", + "value": "Angry" + }, + { + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 9 } ] } @@ -330,11 +354,9 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/base/recruit.outcome.json b/server/tests/test_games/base/recruit.outcome.json index aa45f1e8e..871b708a2 100644 --- a/server/tests/test_games/base/recruit.outcome.json +++ b/server/tests/test_games/base/recruit.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -238,15 +240,78 @@ }, "undo": [ { - "Recruit": { - "replaced_units": [ - { - "position": "C2", - "unit_type": "Settler", - "id": 4 - } - ] - } + "op": "replace", + "path": "/actions_left", + "value": 2 + }, + { + "op": "replace", + "path": "/players/0/cities/0/activations", + "value": 0 + }, + { + "op": "replace", + "path": "/players/0/next_unit_id", + "value": 10 + }, + { + "op": "add", + "path": "/players/0/resources/food", + "value": 1 + }, + { + "op": "replace", + "path": "/players/0/resources/gold", + "value": 7 + }, + { + "op": "replace", + "path": "/players/0/resources/ore", + "value": 7 + }, + { + "op": "replace", + "path": "/players/0/units/4/id", + "value": 4 + }, + { + "op": "replace", + "path": "/players/0/units/5/id", + "value": 5 + }, + { + "op": "replace", + "path": "/players/0/units/6/id", + "value": 6 + }, + { + "op": "replace", + "path": "/players/0/units/7/id", + "value": 7 + }, + { + "op": "replace", + "path": "/players/0/units/7/position", + "value": "C2" + }, + { + "op": "replace", + "path": "/players/0/units/7/unit_type", + "value": "Settler" + }, + { + "op": "replace", + "path": "/players/0/units/8/id", + "value": 9 + }, + { + "op": "replace", + "path": "/players/0/units/8/unit_type", + "value": "Infantry" + }, + { + "op": "remove", + "path": "/players/0/units/9" } ] } @@ -283,4 +348,4 @@ "Pyramids" ], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/base/recruit_leader.json b/server/tests/test_games/base/recruit_leader.json index 7fb168674..b9304f754 100644 --- a/server/tests/test_games/base/recruit_leader.json +++ b/server/tests/test_games/base/recruit_leader.json @@ -296,9 +296,7 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], diff --git a/server/tests/test_games/base/recruit_leader.outcome.json b/server/tests/test_games/base/recruit_leader.outcome.json index cb1c1893b..6cc0283e9 100644 --- a/server/tests/test_games/base/recruit_leader.outcome.json +++ b/server/tests/test_games/base/recruit_leader.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -294,7 +296,48 @@ }, "undo": [ { - "Recruit": {} + "op": "replace", + "path": "/actions_left", + "value": 3 + }, + { + "op": "replace", + "path": "/players/0/active_leader", + "value": null + }, + { + "op": "replace", + "path": "/players/0/available_leaders/0", + "value": "Alexander" + }, + { + "op": "add", + "path": "/players/0/available_leaders/1", + "value": "Kleopatra" + }, + { + "op": "replace", + "path": "/players/0/cities/0/activations", + "value": 0 + }, + { + "op": "replace", + "path": "/players/0/next_unit_id", + "value": 10 + }, + { + "op": "replace", + "path": "/players/0/resources/culture_tokens", + "value": 9 + }, + { + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 9 + }, + { + "op": "remove", + "path": "/players/0/units/10" } ] } @@ -325,11 +368,9 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/base/replace_leader.json b/server/tests/test_games/base/replace_leader.json index 5eac1f783..0fdea5d7c 100644 --- a/server/tests/test_games/base/replace_leader.json +++ b/server/tests/test_games/base/replace_leader.json @@ -300,9 +300,7 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], diff --git a/server/tests/test_games/base/replace_leader.outcome.json b/server/tests/test_games/base/replace_leader.outcome.json index c22667cf0..9f4e7cd83 100644 --- a/server/tests/test_games/base/replace_leader.outcome.json +++ b/server/tests/test_games/base/replace_leader.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -295,16 +297,49 @@ }, "undo": [ { - "Recruit": { - "replaced_units": [ - { - "position": "A1", - "unit_type": "Leader", - "id": 10 - } - ], - "replaced_leader": "Alexander" - } + "op": "replace", + "path": "/actions_left", + "value": 2 + }, + { + "op": "replace", + "path": "/players/0/active_leader", + "value": "Alexander" + }, + { + "op": "add", + "path": "/players/0/available_leaders/0", + "value": "Kleopatra" + }, + { + "op": "replace", + "path": "/players/0/cities/0/activations", + "value": 1 + }, + { + "op": "replace", + "path": "/players/0/cities/0/mood_state", + "value": "Happy" + }, + { + "op": "replace", + "path": "/players/0/next_unit_id", + "value": 11 + }, + { + "op": "replace", + "path": "/players/0/resources/culture_tokens", + "value": 8 + }, + { + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 8 + }, + { + "op": "replace", + "path": "/players/0/units/10/id", + "value": 10 } ] } @@ -335,11 +370,9 @@ 10, 10 ], - "rng": { - "seed": 234162992961072890508432380903651342097 - }, + "rng": "234162992961072890508432380903651342097", "dice_roll_log": [], "dropped_players": [], "wonders_left": [], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/base/wonder.outcome.json b/server/tests/test_games/base/wonder.outcome.json index ea28d8078..c231b7156 100644 --- a/server/tests/test_games/base/wonder.outcome.json +++ b/server/tests/test_games/base/wonder.outcome.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -226,7 +228,47 @@ } } } - } + }, + "undo": [ + { + "op": "replace", + "path": "/actions_left", + "value": 2 + }, + { + "op": "remove", + "path": "/players/0/cities/0/city_pieces/wonders" + }, + { + "op": "replace", + "path": "/players/0/resources/culture_tokens", + "value": 7 + }, + { + "op": "add", + "path": "/players/0/resources/food", + "value": 2 + }, + { + "op": "replace", + "path": "/players/0/resources/ore", + "value": 7 + }, + { + "op": "replace", + "path": "/players/0/resources/wood", + "value": 7 + }, + { + "op": "add", + "path": "/players/0/wonder_cards/0", + "value": "Pyramids" + }, + { + "op": "remove", + "path": "/players/0/wonders_build/0" + } + ] } ], "action_log_index": 1, @@ -259,4 +301,4 @@ "dropped_players": [], "wonders_left": [], "wonder_amount_left": 0 -} +} \ No newline at end of file diff --git a/server/tests/test_games/civ/civ_maya_leader_pakal.json b/server/tests/test_games/civ/civ_maya_leader_pakal.json index b3ef8f66a..af3d186f3 100644 --- a/server/tests/test_games/civ/civ_maya_leader_pakal.json +++ b/server/tests/test_games/civ/civ_maya_leader_pakal.json @@ -315,9 +315,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/civ/civ_maya_leader_pakal.outcome.json b/server/tests/test_games/civ/civ_maya_leader_pakal.outcome.json index bcef6bece..a92c0c29b 100644 --- a/server/tests/test_games/civ/civ_maya_leader_pakal.outcome.json +++ b/server/tests/test_games/civ/civ_maya_leader_pakal.outcome.json @@ -359,17 +359,7 @@ "destination": "B1" } } - }, - "undo": [ - { - "WastedResources": { - "resources": { - "gold": 3 - }, - "player_index": 0 - } - } - ] + } } ], "action_log_index": 1, @@ -405,9 +395,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [ 10 ], diff --git a/server/tests/test_games/civ/civ_maya_leader_pakal.outcome1.json b/server/tests/test_games/civ/civ_maya_leader_pakal.outcome1.json index 776d96c2c..a23b1d6aa 100644 --- a/server/tests/test_games/civ/civ_maya_leader_pakal.outcome1.json +++ b/server/tests/test_games/civ/civ_maya_leader_pakal.outcome1.json @@ -368,9 +368,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [ 10 ], diff --git a/server/tests/test_games/combat/combat_all_modifiers.outcome3.json b/server/tests/test_games/combat/combat_all_modifiers.outcome3.json index ffb59fed3..71c3bf347 100644 --- a/server/tests/test_games/combat/combat_all_modifiers.outcome3.json +++ b/server/tests/test_games/combat/combat_all_modifiers.outcome3.json @@ -276,17 +276,7 @@ } ] } - }, - "undo": [ - { - "WastedResources": { - "resources": { - "gold": 1 - }, - "player_index": 0 - } - } - ] + } } ], "action_log_index": 4, diff --git a/server/tests/test_games/combat/combat_fanaticism.outcome.json b/server/tests/test_games/combat/combat_fanaticism.outcome.json index 12fd6da0e..f6f8ab88e 100644 --- a/server/tests/test_games/combat/combat_fanaticism.outcome.json +++ b/server/tests/test_games/combat/combat_fanaticism.outcome.json @@ -251,17 +251,7 @@ "destination": "C1" } } - }, - "undo": [ - { - "WastedResources": { - "resources": { - "gold": 1 - }, - "player_index": 0 - } - } - ] + } } ], "action_log_index": 1, diff --git a/server/tests/test_games/combat/direct_capture_city_fortress.outcome.json b/server/tests/test_games/combat/direct_capture_city_fortress.outcome.json index fb0aea755..6b7477f31 100644 --- a/server/tests/test_games/combat/direct_capture_city_fortress.outcome.json +++ b/server/tests/test_games/combat/direct_capture_city_fortress.outcome.json @@ -234,17 +234,7 @@ "destination": "C1" } } - }, - "undo": [ - { - "WastedResources": { - "resources": { - "gold": 1 - }, - "player_index": 0 - } - } - ] + } } ], "action_log_index": 1, diff --git a/server/tests/test_games/combat/direct_capture_city_metallurgy.outcome.json b/server/tests/test_games/combat/direct_capture_city_metallurgy.outcome.json index b421f1a36..fb6309aeb 100644 --- a/server/tests/test_games/combat/direct_capture_city_metallurgy.outcome.json +++ b/server/tests/test_games/combat/direct_capture_city_metallurgy.outcome.json @@ -235,17 +235,7 @@ "destination": "C1" } } - }, - "undo": [ - { - "WastedResources": { - "resources": { - "gold": 1 - }, - "player_index": 0 - } - } - ] + } } ], "action_log_index": 1, diff --git a/server/tests/test_games/combat/direct_capture_city_only_fortress.outcome.json b/server/tests/test_games/combat/direct_capture_city_only_fortress.outcome.json index 9548fde10..40d7c37bd 100644 --- a/server/tests/test_games/combat/direct_capture_city_only_fortress.outcome.json +++ b/server/tests/test_games/combat/direct_capture_city_only_fortress.outcome.json @@ -234,17 +234,7 @@ "destination": "C1" } } - }, - "undo": [ - { - "WastedResources": { - "resources": { - "gold": 1 - }, - "player_index": 0 - } - } - ] + } } ], "action_log_index": 1, diff --git a/server/tests/test_games/combat/recruit_combat.json b/server/tests/test_games/combat/recruit_combat.json index ca0008d38..a986ab22d 100644 --- a/server/tests/test_games/combat/recruit_combat.json +++ b/server/tests/test_games/combat/recruit_combat.json @@ -268,33 +268,8 @@ }, "starting_player_index": 0, "current_player_index": 0, - "action_log": [ - { - "action": { - "Movement": "Stop" - }, - "undo": [ - { - "Movement": { - "movement_actions_left": 1, - "moved_units": [ - 7, - 8 - ], - "current_move": { - "Fleet": { - "units": [ - 7, - 8 - ] - } - } - } - } - ] - } - ], - "action_log_index": 1, + "action_log": [], + "action_log_index": 0, "log": [ [ "Player1 ended the movement action" diff --git a/server/tests/test_games/combat/recruit_combat.outcome.json b/server/tests/test_games/combat/recruit_combat.outcome.json index f6ce8c620..150cc2972 100644 --- a/server/tests/test_games/combat/recruit_combat.outcome.json +++ b/server/tests/test_games/combat/recruit_combat.outcome.json @@ -331,30 +331,6 @@ "starting_player_index": 0, "current_player_index": 0, "action_log": [ - { - "action": { - "Movement": "Stop" - }, - "undo": [ - { - "Movement": { - "movement_actions_left": 1, - "moved_units": [ - 7, - 8 - ], - "current_move": { - "Fleet": { - "units": [ - 7, - 8 - ] - } - } - } - } - ] - }, { "action": { "Playing": { @@ -372,12 +348,55 @@ }, "undo": [ { - "Recruit": {} + "op": "replace", + "path": "/actions_left", + "value": 2 + }, + { + "op": "replace", + "path": "/players/0/cities/1/activations", + "value": 4 + }, + { + "op": "replace", + "path": "/players/0/cities/1/mood_state", + "value": "Happy" + }, + { + "op": "replace", + "path": "/players/0/next_unit_id", + "value": 10 + }, + { + "op": "replace", + "path": "/players/0/resources/gold", + "value": 7 + }, + { + "op": "replace", + "path": "/players/0/resources/wood", + "value": 7 + }, + { + "op": "remove", + "path": "/players/0/units/4" + }, + { + "op": "remove", + "path": "/players/0/units/4" + }, + { + "op": "remove", + "path": "/players/0/units/4" + }, + { + "op": "remove", + "path": "/current_events" } ] } ], - "action_log_index": 2, + "action_log_index": 1, "log": [ [ "Player1 ended the movement action" diff --git a/server/tests/test_games/combat/recruit_combat.outcome1.json b/server/tests/test_games/combat/recruit_combat.outcome1.json index 11510c60f..0895a7b2a 100644 --- a/server/tests/test_games/combat/recruit_combat.outcome1.json +++ b/server/tests/test_games/combat/recruit_combat.outcome1.json @@ -331,30 +331,6 @@ "starting_player_index": 0, "current_player_index": 0, "action_log": [ - { - "action": { - "Movement": "Stop" - }, - "undo": [ - { - "Movement": { - "movement_actions_left": 1, - "moved_units": [ - 7, - 8 - ], - "current_move": { - "Fleet": { - "units": [ - 7, - 8 - ] - } - } - } - } - ] - }, { "action": { "Playing": { @@ -372,7 +348,50 @@ }, "undo": [ { - "Recruit": {} + "op": "replace", + "path": "/actions_left", + "value": 2 + }, + { + "op": "replace", + "path": "/players/0/cities/1/activations", + "value": 4 + }, + { + "op": "replace", + "path": "/players/0/cities/1/mood_state", + "value": "Happy" + }, + { + "op": "replace", + "path": "/players/0/next_unit_id", + "value": 10 + }, + { + "op": "replace", + "path": "/players/0/resources/gold", + "value": 7 + }, + { + "op": "replace", + "path": "/players/0/resources/wood", + "value": 7 + }, + { + "op": "remove", + "path": "/players/0/units/4" + }, + { + "op": "remove", + "path": "/players/0/units/4" + }, + { + "op": "remove", + "path": "/players/0/units/4" + }, + { + "op": "remove", + "path": "/current_events" } ] }, @@ -386,64 +405,66 @@ }, "undo": [ { - "Event": { - "event_type": { - "Recruit": { - "units": { - "ships": 3 - }, - "city_position": "C2", - "payment": { - "wood": 5, - "gold": 1 - } - } - }, - "player": 0, - "last_priority_used": 1, - "handler": { - "priority": 1, - "request": { - "ResourceReward": { - "reward": { - "default": { - "mood_tokens": 1 - }, - "conversions": [ - { - "from": [ - { - "mood_tokens": 1 - } - ], - "to": { - "culture_tokens": 1 - }, - "type": "Unlimited" - } - ] - }, - "name": "Select token to gain" - } - }, - "origin": { - "Advance": "Nationalism" - } - } - } + "op": "replace", + "path": "/current_events/0/handler/origin/Advance", + "value": "Nationalism" }, { - "WastedResources": { - "resources": { - "food": 4 - }, - "player_index": 0 - } + "op": "replace", + "path": "/current_events/0/handler/priority", + "value": 1 + }, + { + "op": "replace", + "path": "/current_events/0/handler/request/ResourceReward/name", + "value": "Select token to gain" + }, + { + "op": "add", + "path": "/current_events/0/handler/request/ResourceReward/reward/conversions/0/from/0/mood_tokens", + "value": 1 + }, + { + "op": "remove", + "path": "/current_events/0/handler/request/ResourceReward/reward/conversions/0/from/0/wood" + }, + { + "op": "add", + "path": "/current_events/0/handler/request/ResourceReward/reward/conversions/0/to/culture_tokens", + "value": 1 + }, + { + "op": "remove", + "path": "/current_events/0/handler/request/ResourceReward/reward/conversions/0/to/gold" + }, + { + "op": "add", + "path": "/current_events/0/handler/request/ResourceReward/reward/default/mood_tokens", + "value": 1 + }, + { + "op": "remove", + "path": "/current_events/0/handler/request/ResourceReward/reward/default/wood" + }, + { + "op": "replace", + "path": "/current_events/0/last_priority_used", + "value": 1 + }, + { + "op": "replace", + "path": "/players/0/resources/food", + "value": 6 + }, + { + "op": "replace", + "path": "/players/0/resources/mood_tokens", + "value": 9 } ] } ], - "action_log_index": 3, + "action_log_index": 2, "log": [ [ "Player1 ended the movement action" diff --git a/server/tests/test_games/combat/recruit_combat.outcome2.json b/server/tests/test_games/combat/recruit_combat.outcome2.json index 10cae1536..dd79ae825 100644 --- a/server/tests/test_games/combat/recruit_combat.outcome2.json +++ b/server/tests/test_games/combat/recruit_combat.outcome2.json @@ -314,11 +314,6 @@ "starting_player_index": 0, "current_player_index": 0, "action_log": [ - { - "action": { - "Movement": "Stop" - } - }, { "action": { "Playing": { @@ -351,67 +346,10 @@ "gold": 1 } } - }, - "undo": [ - { - "Event": { - "event_type": { - "Recruit": { - "units": { - "ships": 3 - }, - "city_position": "C2", - "payment": { - "wood": 5, - "gold": 1 - } - } - }, - "player": 0, - "last_priority_used": 0, - "handler": { - "priority": 0, - "request": { - "ResourceReward": { - "reward": { - "default": { - "wood": 1 - }, - "conversions": [ - { - "from": [ - { - "wood": 1 - } - ], - "to": { - "gold": 1 - }, - "type": "Unlimited" - } - ] - }, - "name": "Select resource to gain back" - } - }, - "origin": { - "Advance": "Medicine" - } - } - } - }, - { - "WastedResources": { - "resources": { - "gold": 2 - }, - "player_index": 0 - } - } - ] + } } ], - "action_log_index": 4, + "action_log_index": 3, "log": [ [ "Player1 ended the movement action" @@ -439,7 +377,7 @@ "Player1 could not store 2 gold" ] ], - "undo_limit": 4, + "undo_limit": 3, "actions_left": 1, "successful_cultural_influence": false, "round": 6, diff --git a/server/tests/test_games/combat/recruit_combat.outcome3.json b/server/tests/test_games/combat/recruit_combat.outcome3.json index 00d54fce1..3d8de6630 100644 --- a/server/tests/test_games/combat/recruit_combat.outcome3.json +++ b/server/tests/test_games/combat/recruit_combat.outcome3.json @@ -1,5 +1,7 @@ { - "state": ["Playing"], + "state": [ + "Playing" + ], "players": [ { "name": null, @@ -261,11 +263,6 @@ "starting_player_index": 0, "current_player_index": 0, "action_log": [ - { - "action": { - "Movement": "Stop" - } - }, { "action": { "Playing": { @@ -307,10 +304,76 @@ "culture_tokens": 1 } } - } + }, + "undo": [ + { + "op": "add", + "path": "/current_events", + "value": [ + { + "event_type": { + "CombatEnd": "AttackerWins" + }, + "handler": { + "origin": { + "Builtin": "Barbarians bonus" + }, + "priority": 3, + "request": { + "ResourceReward": { + "name": "Select a reward for fighting the Pirates", + "reward": { + "conversions": [ + { + "from": [ + { + "mood_tokens": 1 + } + ], + "to": { + "culture_tokens": 1 + }, + "type": "Unlimited" + } + ], + "default": { + "mood_tokens": 1 + } + } + } + } + }, + "last_priority_used": 3, + "player": 0 + } + ] + }, + { + "op": "replace", + "path": "/players/0/resources/culture_tokens", + "value": 10 + }, + { + "op": "add", + "path": "/state/1", + "value": { + "Combat": { + "attacker": 0, + "attacker_position": "C2", + "attackers": [ + 12 + ], + "defender": 2, + "defender_position": "C3", + "retreat": "CannotRetreat", + "round": 1 + } + } + } + ] } ], - "action_log_index": 5, + "action_log_index": 4, "log": [ [ "Player1 ended the movement action" @@ -341,7 +404,7 @@ "Player1 gained 1 culture token for fighting the Pirates" ] ], - "undo_limit": 5, + "undo_limit": 3, "actions_left": 1, "successful_cultural_influence": false, "round": 6, @@ -366,4 +429,4 @@ "dropped_players": [], "wonders_left": [], "wonder_amount_left": 1 -} +} \ No newline at end of file diff --git a/server/tests/test_games/combat/remove_casualties_attacker.outcome1.json b/server/tests/test_games/combat/remove_casualties_attacker.outcome1.json index 259a7575e..a1c6ee185 100644 --- a/server/tests/test_games/combat/remove_casualties_attacker.outcome1.json +++ b/server/tests/test_games/combat/remove_casualties_attacker.outcome1.json @@ -228,11 +228,192 @@ }, "undo": [ { - "WastedResources": { - "resources": { - "gold": 1 - }, - "player_index": 0 + "op": "add", + "path": "/current_events", + "value": [ + { + "event_type": { + "CombatRoundEnd": { + "attacker_casualties": { + "fighters": 2 + }, + "can_retreat": false, + "defender_casualties": { + "fighters": 2 + }, + "final_result": "AttackerWins" + } + }, + "handler": { + "origin": { + "Builtin": "Choose Casualties" + }, + "priority": 1, + "request": { + "SelectUnits": { + "choices": [ + 0, + 1, + 2, + 3 + ], + "description": "Remove 2 attacking units", + "needed": { + "end": 2, + "start": 2 + }, + "player": 0 + } + } + }, + "last_priority_used": 1, + "player": 0 + } + ] + }, + { + "op": "remove", + "path": "/players/0/cities/2" + }, + { + "op": "replace", + "path": "/players/0/units/0/id", + "value": 0 + }, + { + "op": "replace", + "path": "/players/0/units/0/position", + "value": "C2" + }, + { + "op": "replace", + "path": "/players/0/units/0/unit_type", + "value": "Infantry" + }, + { + "op": "replace", + "path": "/players/0/units/1/id", + "value": 1 + }, + { + "op": "replace", + "path": "/players/0/units/1/position", + "value": "C2" + }, + { + "op": "replace", + "path": "/players/0/units/1/unit_type", + "value": "Cavalry" + }, + { + "op": "replace", + "path": "/players/0/units/2/id", + "value": 2 + }, + { + "op": "add", + "path": "/players/0/units/2/movement_restrictions", + "value": [ + "Battle" + ] + }, + { + "op": "replace", + "path": "/players/0/units/2/unit_type", + "value": "Leader" + }, + { + "op": "replace", + "path": "/players/0/units/3/id", + "value": 3 + }, + { + "op": "add", + "path": "/players/0/units/3/movement_restrictions", + "value": [ + "Battle" + ] + }, + { + "op": "replace", + "path": "/players/0/units/3/unit_type", + "value": "Elephant" + }, + { + "op": "replace", + "path": "/players/0/units/4/id", + "value": 4 + }, + { + "op": "replace", + "path": "/players/0/units/5/id", + "value": 5 + }, + { + "op": "add", + "path": "/players/0/units/6", + "value": { + "id": 6, + "position": "C2", + "unit_type": "Settler" + } + }, + { + "op": "add", + "path": "/players/0/units/7", + "value": { + "id": 7, + "position": "C2", + "unit_type": "Settler" + } + }, + { + "op": "add", + "path": "/players/1/cities/0", + "value": { + "activations": 2, + "angry_activation": false, + "city_pieces": {}, + "mood_state": "Angry", + "position": "C1" + } + }, + { + "op": "add", + "path": "/players/1/units/0", + "value": { + "id": 0, + "position": "C1", + "unit_type": "Infantry" + } + }, + { + "op": "add", + "path": "/players/1/units/1", + "value": { + "id": 1, + "position": "C1", + "unit_type": "Infantry" + } + }, + { + "op": "add", + "path": "/state/2", + "value": { + "Combat": { + "attacker": 0, + "attacker_position": "C2", + "attackers": [ + 0, + 1, + 2, + 3 + ], + "defender": 1, + "defender_position": "C1", + "retreat": "CanRetreat", + "round": 1 + } } } ] @@ -257,7 +438,7 @@ "Player1 could not store 1 gold" ] ], - "undo_limit": 2, + "undo_limit": 1, "actions_left": 1, "successful_cultural_influence": false, "round": 1, diff --git a/server/tests/test_games/combat/retreat.outcome1.json b/server/tests/test_games/combat/retreat.outcome1.json index 2318ca215..0410bf3a1 100644 --- a/server/tests/test_games/combat/retreat.outcome1.json +++ b/server/tests/test_games/combat/retreat.outcome1.json @@ -235,7 +235,52 @@ "Response": { "Bool": true } - } + }, + "undo": [ + { + "op": "add", + "path": "/current_events", + "value": [ + { + "event_type": { + "CombatRoundEnd": { + "attacker_casualties": {}, + "can_retreat": true, + "defender_casualties": {} + } + }, + "handler": { + "origin": { + "Builtin": "Offer Retreat" + }, + "priority": 0, + "request": { + "BoolRequest": "Do you want to retreat?" + } + }, + "last_priority_used": 0, + "player": 0 + } + ] + }, + { + "op": "add", + "path": "/state/2", + "value": { + "Combat": { + "attacker": 0, + "attacker_position": "C2", + "attackers": [ + 0 + ], + "defender": 1, + "defender_position": "C1", + "retreat": "CanRetreat", + "round": 1 + } + } + } + ] } ], "action_log_index": 2, @@ -253,7 +298,7 @@ "Player1 retreats" ] ], - "undo_limit": 2, + "undo_limit": 1, "actions_left": 1, "successful_cultural_influence": false, "round": 1, diff --git a/server/tests/test_games/combat/ship_combat.outcome1.json b/server/tests/test_games/combat/ship_combat.outcome1.json index 1939ae3b9..4bbded00f 100644 --- a/server/tests/test_games/combat/ship_combat.outcome1.json +++ b/server/tests/test_games/combat/ship_combat.outcome1.json @@ -7,7 +7,14 @@ "moved_units": [ 7, 8 - ] + ], + "current_move": { + "Fleet": { + "units": [ + 8 + ] + } + } } } ], @@ -299,7 +306,132 @@ 1 ] } - } + }, + "undo": [ + { + "op": "add", + "path": "/current_events", + "value": [ + { + "event_type": { + "CombatRoundEnd": { + "attacker_casualties": { + "carried_units": 1, + "fighters": 1 + }, + "can_retreat": false, + "defender_casualties": { + "fighters": 1 + }, + "final_result": "AttackerWins" + } + }, + "handler": { + "origin": { + "Builtin": "Choose Casualties (carried units)" + }, + "priority": 2, + "request": { + "SelectUnits": { + "choices": [ + 2, + 22, + 1 + ], + "description": "Remove 1 attacking units", + "needed": { + "end": 1, + "start": 1 + }, + "player": 0 + } + } + }, + "last_priority_used": 2, + "player": 0 + } + ] + }, + { + "op": "add", + "path": "/players/0/units/5/carried_units", + "value": [ + { + "id": 2, + "unit_type": "Elephant" + }, + { + "id": 22, + "unit_type": "Settler" + } + ] + }, + { + "op": "replace", + "path": "/players/0/units/5/id", + "value": 7 + }, + { + "op": "replace", + "path": "/players/0/units/5/position", + "value": "C3" + }, + { + "op": "add", + "path": "/players/0/units/6", + "value": { + "carried_units": [ + { + "id": 1, + "unit_type": "Cavalry" + } + ], + "id": 8, + "movement_restrictions": [ + "Battle" + ], + "position": "C3", + "unit_type": "Ship" + } + }, + { + "op": "add", + "path": "/players/1/units/0", + "value": { + "id": 1, + "position": "D2", + "unit_type": "Ship" + } + }, + { + "op": "replace", + "path": "/state/1/Movement/current_move/Fleet/units/0", + "value": 7 + }, + { + "op": "add", + "path": "/state/1/Movement/current_move/Fleet/units/1", + "value": 8 + }, + { + "op": "add", + "path": "/state/2", + "value": { + "Combat": { + "attacker": 0, + "attacker_position": "C3", + "attackers": [ + 7, + 8 + ], + "defender": 1, + "defender_position": "D2", + "retreat": "CanRetreat", + "round": 1 + } + } + } + ] } ], "action_log_index": 2, @@ -322,7 +454,7 @@ "Attacker wins" ] ], - "undo_limit": 2, + "undo_limit": 1, "actions_left": 2, "successful_cultural_influence": false, "round": 6, diff --git a/server/tests/test_games/incidents/anarchy.outcome1.json b/server/tests/test_games/incidents/anarchy.outcome1.json index 4f4dc268b..a993296c4 100644 --- a/server/tests/test_games/incidents/anarchy.outcome1.json +++ b/server/tests/test_games/incidents/anarchy.outcome1.json @@ -253,12 +253,70 @@ }, "undo": [ { - "WastedResources": { - "resources": { - "ideas": 1 - }, - "player_index": 0 - } + "op": "replace", + "path": "/actions_left", + "value": 2 + }, + { + "op": "add", + "path": "/permanent_incident_effects", + "value": [ + { + "Anarchy": { + "advances_lost": 1, + "player": 0 + } + } + ] + }, + { + "op": "replace", + "path": "/players/0/advances/0", + "value": "Farming" + }, + { + "op": "replace", + "path": "/players/0/advances/1", + "value": "Mining" + }, + { + "op": "replace", + "path": "/players/0/advances/2", + "value": "State Religion" + }, + { + "op": "replace", + "path": "/players/0/advances/3", + "value": "Storage" + }, + { + "op": "replace", + "path": "/players/0/advances/4", + "value": "Tactics" + }, + { + "op": "remove", + "path": "/players/0/advances/5" + }, + { + "op": "replace", + "path": "/players/0/event_victory_points", + "value": 1.0 + }, + { + "op": "replace", + "path": "/players/0/resource_limit/ideas", + "value": 7 + }, + { + "op": "replace", + "path": "/players/0/resources/gold", + "value": 5 + }, + { + "op": "replace", + "path": "/players/0/resources/ideas", + "value": 3 } ] } @@ -280,7 +338,7 @@ "Player1 could not store 1 idea" ] ], - "undo_limit": 2, + "undo_limit": 1, "actions_left": 1, "successful_cultural_influence": false, "round": 2, diff --git a/server/tests/test_games/incidents/barbarians_recapture_city.outcome.json b/server/tests/test_games/incidents/barbarians_recapture_city.outcome.json index ea4b645b2..340809fc1 100644 --- a/server/tests/test_games/incidents/barbarians_recapture_city.outcome.json +++ b/server/tests/test_games/incidents/barbarians_recapture_city.outcome.json @@ -255,17 +255,7 @@ "destination": "C2" } } - }, - "undo": [ - { - "WastedResources": { - "resources": { - "gold": 3 - }, - "player_index": 0 - } - } - ] + } } ], "action_log_index": 1, @@ -294,9 +284,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 294774916637212236631404434476213304938 - }, + "rng": "294774916637212236631404434476213304938", "dice_roll_log": [ 10, 10, diff --git a/server/tests/test_games/incidents/barbarians_spawn.outcome2.json b/server/tests/test_games/incidents/barbarians_spawn.outcome2.json index f81da6b26..8808627ce 100644 --- a/server/tests/test_games/incidents/barbarians_spawn.outcome2.json +++ b/server/tests/test_games/incidents/barbarians_spawn.outcome2.json @@ -283,17 +283,7 @@ "Response": { "SelectUnitType": "Elephant" } - }, - "undo": [ - { - "WastedResources": { - "resources": { - "food": 1 - }, - "player_index": 1 - } - } - ] + } } ], "action_log_index": 3, @@ -346,4 +336,4 @@ "incidents_left": [ 9 ] -} +} \ No newline at end of file diff --git a/server/tests/test_games/incidents/envoy.outcome1.json b/server/tests/test_games/incidents/envoy.outcome1.json index 06d07a1eb..690c28d26 100644 --- a/server/tests/test_games/incidents/envoy.outcome1.json +++ b/server/tests/test_games/incidents/envoy.outcome1.json @@ -300,7 +300,57 @@ } } } - } + }, + "undo": [ + { + "op": "replace", + "path": "/actions_left", + "value": 2 + }, + { + "op": "replace", + "path": "/players/0/advances/3", + "value": "State Religion" + }, + { + "op": "replace", + "path": "/players/0/advances/4", + "value": "Storage" + }, + { + "op": "replace", + "path": "/players/0/advances/5", + "value": "Tactics" + }, + { + "op": "replace", + "path": "/players/0/advances/6", + "value": "Voting" + }, + { + "op": "remove", + "path": "/players/0/advances/7" + }, + { + "op": "replace", + "path": "/players/0/incident_tokens", + "value": 3 + }, + { + "op": "replace", + "path": "/players/0/resources/culture_tokens", + "value": 8 + }, + { + "op": "replace", + "path": "/players/0/resources/gold", + "value": 5 + }, + { + "op": "remove", + "path": "/current_events" + } + ] } ], "action_log_index": 2, @@ -323,7 +373,7 @@ "Player gained 1 culture token as advance bonus" ] ], - "undo_limit": 2, + "undo_limit": 1, "actions_left": 1, "successful_cultural_influence": false, "round": 1, diff --git a/server/tests/test_games/incidents/envoy.outcome2.json b/server/tests/test_games/incidents/envoy.outcome2.json index 7b0c2ea18..2ef87cc6b 100644 --- a/server/tests/test_games/incidents/envoy.outcome2.json +++ b/server/tests/test_games/incidents/envoy.outcome2.json @@ -286,14 +286,99 @@ } } } - } + }, + "undo": [ + { + "op": "replace", + "path": "/actions_left", + "value": 2 + }, + { + "op": "replace", + "path": "/players/0/advances/3", + "value": "State Religion" + }, + { + "op": "replace", + "path": "/players/0/advances/4", + "value": "Storage" + }, + { + "op": "replace", + "path": "/players/0/advances/5", + "value": "Tactics" + }, + { + "op": "replace", + "path": "/players/0/advances/6", + "value": "Voting" + }, + { + "op": "remove", + "path": "/players/0/advances/7" + }, + { + "op": "replace", + "path": "/players/0/incident_tokens", + "value": 3 + }, + { + "op": "replace", + "path": "/players/0/resources/culture_tokens", + "value": 8 + }, + { + "op": "replace", + "path": "/players/0/resources/gold", + "value": 5 + }, + { + "op": "remove", + "path": "/current_events" + } + ] }, { "action": { "Response": { "Bool": true } - } + }, + "undo": [ + { + "op": "add", + "path": "/current_events", + "value": [ + { + "event_type": "DrawWonderCard", + "handler": { + "origin": { + "Builtin": "Draw Wonder Card" + }, + "priority": 0, + "request": { + "BoolRequest": "Do you want to draw the public wonder card Pyramids?" + } + }, + "last_priority_used": 0, + "player": 0 + } + ] + }, + { + "op": "add", + "path": "/permanent_incident_effects", + "value": [ + { + "PublicWonderCard": "Pyramids" + } + ] + }, + { + "op": "remove", + "path": "/players/0/wonder_cards/0" + } + ] } ], "action_log_index": 3, @@ -319,7 +404,7 @@ "Player1 drew the public wonder card Pyramids" ] ], - "undo_limit": 3, + "undo_limit": 1, "actions_left": 1, "successful_cultural_influence": false, "round": 1, diff --git a/server/tests/test_games/incidents/exhausted_land.outcome1.json b/server/tests/test_games/incidents/exhausted_land.outcome1.json index 0a3dab656..ed9a62c2c 100644 --- a/server/tests/test_games/incidents/exhausted_land.outcome1.json +++ b/server/tests/test_games/incidents/exhausted_land.outcome1.json @@ -259,17 +259,7 @@ "B2" ] } - }, - "undo": [ - { - "WastedResources": { - "resources": { - "food": 1 - }, - "player_index": 1 - } - } - ] + } } ], "action_log_index": 2, @@ -318,4 +308,4 @@ "incidents_left": [ 13 ] -} +} \ No newline at end of file diff --git a/server/tests/test_games/incidents/revolution.outcome3.json b/server/tests/test_games/incidents/revolution.outcome3.json index 86d036d31..7ba123a40 100644 --- a/server/tests/test_games/incidents/revolution.outcome3.json +++ b/server/tests/test_games/incidents/revolution.outcome3.json @@ -298,17 +298,7 @@ } } } - }, - "undo": [ - { - "WastedResources": { - "resources": { - "ideas": 3 - }, - "player_index": 0 - } - } - ] + } } ], "action_log_index": 4, diff --git a/server/tests/test_games/movement/explore_resolution.json b/server/tests/test_games/movement/explore_resolution.json index fc2332ed1..b0ed89268 100644 --- a/server/tests/test_games/movement/explore_resolution.json +++ b/server/tests/test_games/movement/explore_resolution.json @@ -1,6 +1,11 @@ { "state": [ - "Playing" + "Playing", + { + "Movement": { + "movement_actions_left": 1 + } + } ], "players": [ { @@ -438,59 +443,8 @@ }, "starting_player_index": 1, "current_player_index": 1, - "action_log": [ - { - "action": { - "Movement": { - "Move": { - "units": [ - 0 - ], - "destination": "E8" - } - } - }, - "undo": [ - { - "Movement": { - "starting_position": "D8", - "movement_actions_left": 3 - } - } - ] - }, - { - "action": { - "Movement": "Stop" - } - }, - { - "action": { - "Movement": { - "Move": { - "units": [ - 0 - ], - "destination": "D7" - } - } - }, - "undo": [ - { - "Movement": { - "starting_position": "E8", - "movement_actions_left": 3 - } - } - ] - }, - { - "action": { - "Movement": "Stop" - } - } - ], - "action_log_index": 4, + "action_log": [], + "action_log_index": 0, "log": [ [ "The game has started" @@ -522,9 +476,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 175288996178563520907356813494494136334 - }, + "rng": "175288996178563520907356813494494136334", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/movement/explore_resolution.outcome.json b/server/tests/test_games/movement/explore_resolution.outcome.json index 5039cf4e2..8f3327d47 100644 --- a/server/tests/test_games/movement/explore_resolution.outcome.json +++ b/server/tests/test_games/movement/explore_resolution.outcome.json @@ -1,14 +1,6 @@ { "state": [ - "Playing", - { - "Movement": { - "movement_actions_left": 2, - "moved_units": [ - 0 - ] - } - } + "Playing" ], "current_events": [ { @@ -484,40 +476,6 @@ "starting_player_index": 1, "current_player_index": 1, "action_log": [ - { - "action": { - "Movement": { - "Move": { - "units": [ - 0 - ], - "destination": "E8" - } - } - } - }, - { - "action": { - "Movement": "Stop" - } - }, - { - "action": { - "Movement": { - "Move": { - "units": [ - 0 - ], - "destination": "D7" - } - } - } - }, - { - "action": { - "Movement": "Stop" - } - }, { "action": { "Movement": { @@ -531,7 +489,7 @@ } } ], - "action_log_index": 5, + "action_log_index": 1, "log": [ [ "The game has started" @@ -558,17 +516,15 @@ "Player2 marched 1 settler from D7 to D6" ] ], - "undo_limit": 5, - "actions_left": 0, + "undo_limit": 1, + "actions_left": 1, "successful_cultural_influence": false, "round": 1, "age": 1, "messages": [ "The game has started" ], - "rng": { - "seed": 175288996178563520907356813494494136334 - }, + "rng": "175288996178563520907356813494494136334", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/movement/explore_resolution.outcome1.json b/server/tests/test_games/movement/explore_resolution.outcome1.json index e88ea4e83..e535eea8e 100644 --- a/server/tests/test_games/movement/explore_resolution.outcome1.json +++ b/server/tests/test_games/movement/explore_resolution.outcome1.json @@ -1,14 +1,6 @@ { "state": [ - "Playing", - { - "Movement": { - "movement_actions_left": 2, - "moved_units": [ - 0 - ] - } - } + "Playing" ], "players": [ { @@ -433,40 +425,6 @@ "starting_player_index": 1, "current_player_index": 1, "action_log": [ - { - "action": { - "Movement": { - "Move": { - "units": [ - 0 - ], - "destination": "E8" - } - } - } - }, - { - "action": { - "Movement": "Stop" - } - }, - { - "action": { - "Movement": { - "Move": { - "units": [ - 0 - ], - "destination": "D7" - } - } - } - }, - { - "action": { - "Movement": "Stop" - } - }, { "action": { "Movement": { @@ -487,46 +445,158 @@ }, "undo": [ { - "Event": { - "event_type": { - "ExploreResolution": { - "block": { - "position": { - "top_tile": "D5", - "rotation": 0 - }, + "op": "add", + "path": "/current_events", + "value": [ + { + "event_type": { + "ExploreResolution": { "block": { - "terrain": [ - "Fertile", - "Fertile", - "Fertile", - "Forest" - ] - } + "block": { + "terrain": [ + "Fertile", + "Fertile", + "Fertile", + "Forest" + ] + }, + "position": { + "rotation": 0, + "top_tile": "D5" + } + }, + "destination": "D6", + "ship_can_teleport": false, + "start": "D7", + "units": [ + 0 + ] + } + }, + "handler": { + "origin": { + "Builtin": "Explore Resolution" }, - "units": [ - 0 - ], - "start": "D7", - "destination": "D6", - "ship_can_teleport": false - } + "priority": 0, + "request": "ExploreResolution" + }, + "last_priority_used": 0, + "player": 1 + } + ] + }, + { + "op": "replace", + "path": "/map/tiles/13/1", + "value": "Unexplored" + }, + { + "op": "replace", + "path": "/map/tiles/20/1", + "value": "Unexplored" + }, + { + "op": "replace", + "path": "/map/tiles/21/1", + "value": "Unexplored" + }, + { + "op": "replace", + "path": "/map/tiles/28/1", + "value": "Unexplored" + }, + { + "op": "replace", + "path": "/map/unexplored_blocks/4/block/terrain/0", + "value": "Fertile" + }, + { + "op": "replace", + "path": "/map/unexplored_blocks/4/block/terrain/1", + "value": "Fertile" + }, + { + "op": "replace", + "path": "/map/unexplored_blocks/4/block/terrain/3", + "value": "Forest" + }, + { + "op": "replace", + "path": "/map/unexplored_blocks/4/position/top_tile", + "value": "D5" + }, + { + "op": "replace", + "path": "/map/unexplored_blocks/5/block/terrain/0", + "value": "Forest" + }, + { + "op": "replace", + "path": "/map/unexplored_blocks/5/block/terrain/1", + "value": "Mountain" + }, + { + "op": "replace", + "path": "/map/unexplored_blocks/5/block/terrain/2", + "value": "Fertile" + }, + { + "op": "replace", + "path": "/map/unexplored_blocks/5/block/terrain/3", + "value": "Mountain" + }, + { + "op": "replace", + "path": "/map/unexplored_blocks/5/position/top_tile", + "value": "F2" + }, + { + "op": "replace", + "path": "/map/unexplored_blocks/6/block/terrain/1", + "value": "Water" + }, + { + "op": "replace", + "path": "/map/unexplored_blocks/6/block/terrain/2", + "value": "Forest" + }, + { + "op": "replace", + "path": "/map/unexplored_blocks/6/block/terrain/3", + "value": "Forest" + }, + { + "op": "replace", + "path": "/map/unexplored_blocks/6/position/top_tile", + "value": "F4" + }, + { + "op": "add", + "path": "/map/unexplored_blocks/7", + "value": { + "block": { + "terrain": [ + "Water", + "Forest", + "Water", + "Fertile" + ] }, - "player": 1, - "last_priority_used": 0, - "handler": { - "priority": 0, - "request": "ExploreResolution", - "origin": { - "Builtin": "Explore Resolution" - } + "position": { + "rotation": 0, + "top_tile": "F6" } } + }, + { + "op": "replace", + "path": "/players/1/units/0/position", + "value": "D7" } ] } ], - "action_log_index": 6, + "action_log_index": 2, "log": [ [ "The game has started" @@ -557,17 +627,15 @@ "Explored tiles C6=Fertile, D5=Forest, D6=Fertile, E6=Fertile" ] ], - "undo_limit": 5, - "actions_left": 0, + "undo_limit": 1, + "actions_left": 1, "successful_cultural_influence": false, "round": 1, "age": 1, "messages": [ "The game has started" ], - "rng": { - "seed": 175288996178563520907356813494494136334 - }, + "rng": "175288996178563520907356813494494136334", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/movement/movement.outcome.json b/server/tests/test_games/movement/movement.outcome.json index 5d476ac33..b0953b4af 100644 --- a/server/tests/test_games/movement/movement.outcome.json +++ b/server/tests/test_games/movement/movement.outcome.json @@ -225,10 +225,18 @@ }, "undo": [ { - "Movement": { - "starting_position": "B2", - "movement_actions_left": 2 - } + "op": "replace", + "path": "/players/0/units/4/position", + "value": "B2" + }, + { + "op": "replace", + "path": "/state/1/Movement/movement_actions_left", + "value": 2 + }, + { + "op": "remove", + "path": "/state/1/Movement/moved_units" } ] } diff --git a/server/tests/test_games/movement/ship_disembark.outcome.json b/server/tests/test_games/movement/ship_disembark.outcome.json index 2fa1f9505..e56ada41b 100644 --- a/server/tests/test_games/movement/ship_disembark.outcome.json +++ b/server/tests/test_games/movement/ship_disembark.outcome.json @@ -285,10 +285,23 @@ }, "undo": [ { - "Movement": { - "starting_position": "C3", - "movement_actions_left": 2 - } + "op": "replace", + "path": "/players/0/units/1/position", + "value": "C3" + }, + { + "op": "replace", + "path": "/players/0/units/2/position", + "value": "C3" + }, + { + "op": "replace", + "path": "/state/1/Movement/movement_actions_left", + "value": 2 + }, + { + "op": "remove", + "path": "/state/1/Movement/moved_units" } ] } diff --git a/server/tests/test_games/movement/ship_disembark_capture_empty_city.outcome.json b/server/tests/test_games/movement/ship_disembark_capture_empty_city.outcome.json index dba07a5c5..daff0fdd8 100644 --- a/server/tests/test_games/movement/ship_disembark_capture_empty_city.outcome.json +++ b/server/tests/test_games/movement/ship_disembark_capture_empty_city.outcome.json @@ -298,28 +298,39 @@ }, "undo": [ { - "Movement": { - "starting_position": "C3", - "movement_actions_left": 2, - "disembarked_units": [ - { - "unit_id": 1, - "carrier_id": 8 - }, - { - "unit_id": 2, - "carrier_id": 7 - } - ] - } + "op": "remove", + "path": "/players/0/cities/4" + }, + { + "op": "remove", + "path": "/players/0/units/5/carried_units/0/movement_restrictions" + }, + { + "op": "remove", + "path": "/players/0/units/6/carried_units/0/movement_restrictions" }, { - "WastedResources": { - "resources": { - "gold": 2 + "op": "add", + "path": "/players/1/cities/1", + "value": { + "activations": 0, + "angry_activation": false, + "city_pieces": { + "port": 1 }, - "player_index": 0 + "mood_state": "Neutral", + "port_position": "C3", + "position": "B2" } + }, + { + "op": "replace", + "path": "/state/1/Movement/movement_actions_left", + "value": 2 + }, + { + "op": "remove", + "path": "/state/1/Movement/moved_units" } ] } @@ -331,7 +342,7 @@ "Player1 could not store 2 gold" ] ], - "undo_limit": 1, + "undo_limit": 0, "actions_left": 2, "successful_cultural_influence": false, "round": 6, diff --git a/server/tests/test_games/movement/ship_embark.outcome.json b/server/tests/test_games/movement/ship_embark.outcome.json index 2d1050205..bf8a6d384 100644 --- a/server/tests/test_games/movement/ship_embark.outcome.json +++ b/server/tests/test_games/movement/ship_embark.outcome.json @@ -301,10 +301,84 @@ }, "undo": [ { - "Movement": { - "starting_position": "B3", - "movement_actions_left": 2 + "op": "replace", + "path": "/players/0/units/3/id", + "value": 3 + }, + { + "op": "replace", + "path": "/players/0/units/4/id", + "value": 4 + }, + { + "op": "replace", + "path": "/players/0/units/5/id", + "value": 5 + }, + { + "op": "replace", + "path": "/players/0/units/5/position", + "value": "B3" + }, + { + "op": "replace", + "path": "/players/0/units/5/unit_type", + "value": "Settler" + }, + { + "op": "replace", + "path": "/players/0/units/6/id", + "value": 6 + }, + { + "op": "replace", + "path": "/players/0/units/6/position", + "value": "B3" + }, + { + "op": "replace", + "path": "/players/0/units/6/unit_type", + "value": "Settler" + }, + { + "op": "remove", + "path": "/players/0/units/6/carried_units" + }, + { + "op": "replace", + "path": "/players/0/units/7/id", + "value": 7 + }, + { + "op": "add", + "path": "/players/0/units/8", + "value": { + "id": 8, + "position": "C3", + "unit_type": "Ship" + } + }, + { + "op": "add", + "path": "/players/0/units/9", + "value": { + "id": 9, + "position": "C3", + "unit_type": "Ship" } + }, + { + "op": "replace", + "path": "/state/1/Movement/movement_actions_left", + "value": 2 + }, + { + "op": "remove", + "path": "/state/1/Movement/current_move" + }, + { + "op": "remove", + "path": "/state/1/Movement/moved_units" } ] } diff --git a/server/tests/test_games/movement/ship_embark_continue.outcome.json b/server/tests/test_games/movement/ship_embark_continue.outcome.json index 31c5ac8c1..df5f60678 100644 --- a/server/tests/test_games/movement/ship_embark_continue.outcome.json +++ b/server/tests/test_games/movement/ship_embark_continue.outcome.json @@ -303,20 +303,69 @@ }, "undo": [ { - "Movement": { - "starting_position": "B3", - "movement_actions_left": 2, - "moved_units": [ - 3, - 4 - ], - "current_move": { - "Embark": { - "source": "B3", - "destination": "C3" - } - } + "op": "replace", + "path": "/players/0/units/5/id", + "value": 5 + }, + { + "op": "replace", + "path": "/players/0/units/5/position", + "value": "B3" + }, + { + "op": "replace", + "path": "/players/0/units/5/unit_type", + "value": "Settler" + }, + { + "op": "replace", + "path": "/players/0/units/6/id", + "value": 6 + }, + { + "op": "replace", + "path": "/players/0/units/6/position", + "value": "B3" + }, + { + "op": "replace", + "path": "/players/0/units/6/unit_type", + "value": "Settler" + }, + { + "op": "replace", + "path": "/players/0/units/7/id", + "value": 7 + }, + { + "op": "remove", + "path": "/players/0/units/7/carried_units" + }, + { + "op": "add", + "path": "/players/0/units/8", + "value": { + "id": 8, + "position": "C3", + "unit_type": "Ship" + } + }, + { + "op": "add", + "path": "/players/0/units/9", + "value": { + "id": 9, + "position": "C3", + "unit_type": "Ship" } + }, + { + "op": "remove", + "path": "/state/1/Movement/moved_units/2" + }, + { + "op": "remove", + "path": "/state/1/Movement/moved_units/2" } ] } diff --git a/server/tests/test_games/movement/ship_explore.json b/server/tests/test_games/movement/ship_explore.json index 4922b07e7..a9f6f2b43 100644 --- a/server/tests/test_games/movement/ship_explore.json +++ b/server/tests/test_games/movement/ship_explore.json @@ -371,9 +371,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/movement/ship_explore.outcome.json b/server/tests/test_games/movement/ship_explore.outcome.json index c6b29d1da..34dc381f7 100644 --- a/server/tests/test_games/movement/ship_explore.outcome.json +++ b/server/tests/test_games/movement/ship_explore.outcome.json @@ -378,13 +378,11 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ "Pyramids" ], "wonder_amount_left": 1 -} \ No newline at end of file +} diff --git a/server/tests/test_games/movement/ship_explore_move_not_possible.json b/server/tests/test_games/movement/ship_explore_move_not_possible.json index f2d054264..fecc36b64 100644 --- a/server/tests/test_games/movement/ship_explore_move_not_possible.json +++ b/server/tests/test_games/movement/ship_explore_move_not_possible.json @@ -411,9 +411,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/movement/ship_explore_move_not_possible.outcome.json b/server/tests/test_games/movement/ship_explore_move_not_possible.outcome.json index dd187c8e3..b05bf578a 100644 --- a/server/tests/test_games/movement/ship_explore_move_not_possible.outcome.json +++ b/server/tests/test_games/movement/ship_explore_move_not_possible.outcome.json @@ -358,39 +358,81 @@ }, "undo": [ { - "Event": { - "event_type": { - "ExploreResolution": { - "block": { - "position": { - "top_tile": "F4", - "rotation": 0 - }, + "op": "add", + "path": "/current_events", + "value": [ + { + "event_type": { + "ExploreResolution": { "block": { - "terrain": [ - "Fertile", - "Mountain", - "Forest", - "Fertile" - ] - } + "block": { + "terrain": [ + "Fertile", + "Mountain", + "Forest", + "Fertile" + ] + }, + "position": { + "rotation": 0, + "top_tile": "F4" + } + }, + "destination": "E5", + "ship_can_teleport": false, + "start": "D5", + "units": [ + 1 + ] + } + }, + "handler": { + "origin": { + "Builtin": "Explore Resolution" }, - "units": [ - 1 - ], - "start": "D5", - "destination": "E5", - "ship_can_teleport": false - } + "priority": 0, + "request": "ExploreResolution" + }, + "last_priority_used": 0, + "player": 1 + } + ] + }, + { + "op": "replace", + "path": "/map/tiles/27/1", + "value": "Unexplored" + }, + { + "op": "replace", + "path": "/map/tiles/33/1", + "value": "Unexplored" + }, + { + "op": "replace", + "path": "/map/tiles/34/1", + "value": "Unexplored" + }, + { + "op": "replace", + "path": "/map/tiles/38/1", + "value": "Unexplored" + }, + { + "op": "add", + "path": "/map/unexplored_blocks/4", + "value": { + "block": { + "terrain": [ + "Fertile", + "Mountain", + "Forest", + "Fertile" + ] }, - "player": 1, - "last_priority_used": 0, - "handler": { - "priority": 0, - "request": "ExploreResolution", - "origin": { - "Builtin": "Explore Resolution" - } + "position": { + "rotation": 0, + "top_tile": "F4" } } } @@ -413,9 +455,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/movement/ship_explore_teleport.json b/server/tests/test_games/movement/ship_explore_teleport.json index 975fe4054..2702fdfc0 100644 --- a/server/tests/test_games/movement/ship_explore_teleport.json +++ b/server/tests/test_games/movement/ship_explore_teleport.json @@ -367,9 +367,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/movement/ship_explore_teleport.outcome.json b/server/tests/test_games/movement/ship_explore_teleport.outcome.json index 006547d94..96c2004e5 100644 --- a/server/tests/test_games/movement/ship_explore_teleport.outcome.json +++ b/server/tests/test_games/movement/ship_explore_teleport.outcome.json @@ -365,13 +365,11 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ "Pyramids" ], "wonder_amount_left": 1 -} \ No newline at end of file +} diff --git a/server/tests/test_games/movement/ship_navigate.json b/server/tests/test_games/movement/ship_navigate.json index f22841f18..7037a1a5d 100644 --- a/server/tests/test_games/movement/ship_navigate.json +++ b/server/tests/test_games/movement/ship_navigate.json @@ -331,9 +331,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/movement/ship_navigate.outcome.json b/server/tests/test_games/movement/ship_navigate.outcome.json index ed168b7b0..26f3c9774 100644 --- a/server/tests/test_games/movement/ship_navigate.outcome.json +++ b/server/tests/test_games/movement/ship_navigate.outcome.json @@ -342,18 +342,30 @@ }, "undo": [ { - "Command": { - "gained_resources": { - "ideas": 1, - "culture_tokens": 1 - } - } + "op": "remove", + "path": "/players/1/resources/culture_tokens" }, { - "Movement": { - "starting_position": "B5", - "movement_actions_left": 2 - } + "op": "remove", + "path": "/players/1/resources/ideas" + }, + { + "op": "replace", + "path": "/players/1/units/0/position", + "value": "B5" + }, + { + "op": "remove", + "path": "/players/1/event_info" + }, + { + "op": "replace", + "path": "/state/1/Movement/movement_actions_left", + "value": 2 + }, + { + "op": "remove", + "path": "/state/1/Movement/moved_units" } ] } @@ -374,9 +386,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/movement/ship_navigate_explore_move.json b/server/tests/test_games/movement/ship_navigate_explore_move.json index 83704cedc..8d3f64d19 100644 --- a/server/tests/test_games/movement/ship_navigate_explore_move.json +++ b/server/tests/test_games/movement/ship_navigate_explore_move.json @@ -3,7 +3,7 @@ "Playing", { "Movement": { - "movement_actions_left": 2, + "movement_actions_left": 0, "moved_units": [ 1 ], @@ -340,9 +340,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/movement/ship_navigate_explore_move.outcome.json b/server/tests/test_games/movement/ship_navigate_explore_move.outcome.json index 84f1b1cc8..115b81956 100644 --- a/server/tests/test_games/movement/ship_navigate_explore_move.outcome.json +++ b/server/tests/test_games/movement/ship_navigate_explore_move.outcome.json @@ -1,14 +1,6 @@ { "state": [ - "Playing", - { - "Movement": { - "movement_actions_left": 2, - "moved_units": [ - 1 - ] - } - } + "Playing" ], "players": [ { @@ -338,9 +330,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/movement/ship_navigate_explore_not_move.json b/server/tests/test_games/movement/ship_navigate_explore_not_move.json index 23a06a445..6731885c3 100644 --- a/server/tests/test_games/movement/ship_navigate_explore_not_move.json +++ b/server/tests/test_games/movement/ship_navigate_explore_not_move.json @@ -340,9 +340,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/movement/ship_navigate_explore_not_move.outcome.json b/server/tests/test_games/movement/ship_navigate_explore_not_move.outcome.json index 8d45a1a75..d9538cee7 100644 --- a/server/tests/test_games/movement/ship_navigate_explore_not_move.outcome.json +++ b/server/tests/test_games/movement/ship_navigate_explore_not_move.outcome.json @@ -387,13 +387,11 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ "Pyramids" ], "wonder_amount_left": 1 -} \ No newline at end of file +} diff --git a/server/tests/test_games/movement/ship_navigation_unit_test.json b/server/tests/test_games/movement/ship_navigation_unit_test.json index 1e0332df6..28c68f747 100644 --- a/server/tests/test_games/movement/ship_navigation_unit_test.json +++ b/server/tests/test_games/movement/ship_navigation_unit_test.json @@ -310,9 +310,7 @@ "messages": [ "The game has started" ], - "rng": { - "seed": 216866240505556079347337134957831925981 - }, + "rng": "216866240505556079347337134957831925981", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/movement/ship_transport.outcome.json b/server/tests/test_games/movement/ship_transport.outcome.json index 40a74cc84..6593c10da 100644 --- a/server/tests/test_games/movement/ship_transport.outcome.json +++ b/server/tests/test_games/movement/ship_transport.outcome.json @@ -290,10 +290,22 @@ }, "undo": [ { - "Movement": { - "starting_position": "C3", - "movement_actions_left": 2 - } + "op": "replace", + "path": "/players/0/units/5/position", + "value": "C3" + }, + { + "op": "replace", + "path": "/state/1/Movement/movement_actions_left", + "value": 2 + }, + { + "op": "remove", + "path": "/state/1/Movement/current_move" + }, + { + "op": "remove", + "path": "/state/1/Movement/moved_units" } ] } diff --git a/server/tests/test_games/movement/ship_transport_same_sea.outcome.json b/server/tests/test_games/movement/ship_transport_same_sea.outcome.json index 674f88e52..eb13dc6c4 100644 --- a/server/tests/test_games/movement/ship_transport_same_sea.outcome.json +++ b/server/tests/test_games/movement/ship_transport_same_sea.outcome.json @@ -290,20 +290,9 @@ }, "undo": [ { - "Movement": { - "starting_position": "D2", - "movement_actions_left": 2, - "moved_units": [ - 7 - ], - "current_move": { - "Fleet": { - "units": [ - 7 - ] - } - } - } + "op": "replace", + "path": "/players/0/units/7/position", + "value": "D2" } ] } diff --git a/server/tests/test_games/status_phase/change_government.outcome.json b/server/tests/test_games/status_phase/change_government.outcome.json index ae5ea3816..afa3c3405 100644 --- a/server/tests/test_games/status_phase/change_government.outcome.json +++ b/server/tests/test_games/status_phase/change_government.outcome.json @@ -254,17 +254,7 @@ } } } - }, - "undo": [ - { - "WastedResources": { - "resources": { - "ideas": 1 - }, - "player_index": 0 - } - } - ] + } } ], "action_log_index": 1, diff --git a/server/tests/test_games/status_phase/end_game.json b/server/tests/test_games/status_phase/end_game.json index 70ba35989..4cf6170b8 100644 --- a/server/tests/test_games/status_phase/end_game.json +++ b/server/tests/test_games/status_phase/end_game.json @@ -317,9 +317,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/status_phase/end_game.outcome.json b/server/tests/test_games/status_phase/end_game.outcome.json index f8d1597ac..9bc46262c 100644 --- a/server/tests/test_games/status_phase/end_game.outcome.json +++ b/server/tests/test_games/status_phase/end_game.outcome.json @@ -335,13 +335,11 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ "Pyramids" ], "wonder_amount_left": 2 -} \ No newline at end of file +} diff --git a/server/tests/test_games/status_phase/free_advance.json b/server/tests/test_games/status_phase/free_advance.json index 2dab2add1..7f93ccc02 100644 --- a/server/tests/test_games/status_phase/free_advance.json +++ b/server/tests/test_games/status_phase/free_advance.json @@ -317,9 +317,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/status_phase/free_advance.outcome.json b/server/tests/test_games/status_phase/free_advance.outcome.json index 3bb6566bc..41bc06c31 100644 --- a/server/tests/test_games/status_phase/free_advance.outcome.json +++ b/server/tests/test_games/status_phase/free_advance.outcome.json @@ -371,9 +371,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [ diff --git a/server/tests/test_games/status_phase/free_advance.outcome1.json b/server/tests/test_games/status_phase/free_advance.outcome1.json index f9316f2de..af27d85e1 100644 --- a/server/tests/test_games/status_phase/free_advance.outcome1.json +++ b/server/tests/test_games/status_phase/free_advance.outcome1.json @@ -386,9 +386,7 @@ 10, 10 ], - "rng": { - "seed": 46312381643103681595563341886777350953 - }, + "rng": "46312381643103681595563341886777350953", "dice_roll_log": [], "dropped_players": [], "wonders_left": [