diff --git a/client/assets/crown-svgrepo-com.png b/client/assets/crown-svgrepo-com.png new file mode 100644 index 00000000..5b00ea98 Binary files /dev/null and b/client/assets/crown-svgrepo-com.png differ diff --git a/client/assets/justice-hammer-svgrepo-com.png b/client/assets/justice-hammer-svgrepo-com.png new file mode 100644 index 00000000..16d505da Binary files /dev/null and b/client/assets/justice-hammer-svgrepo-com.png differ diff --git a/client/src/action_buttons.rs b/client/src/action_buttons.rs index 3c2fc7b0..6caa850b 100644 --- a/client/src/action_buttons.rs +++ b/client/src/action_buttons.rs @@ -128,6 +128,7 @@ fn generic_custom_action( )))); } } + return None; } match custom_action_type { @@ -142,6 +143,12 @@ fn generic_custom_action( CustomActionType::AbsolutePower => Some(StateUpdate::execute(Action::Playing( PlayingAction::Custom(CustomAction::AbsolutePower), ))), + CustomActionType::ForcedLabor => Some(StateUpdate::execute(Action::Playing( + PlayingAction::Custom(CustomAction::ForcedLabor), + ))), + CustomActionType::CivilRights => Some(StateUpdate::execute(Action::Playing( + PlayingAction::Custom(CustomAction::CivilRights), + ))), CustomActionType::Taxes => Some(StateUpdate::OpenDialog(ActiveDialog::Taxes( Payment::new_gain(&tax_options(rc.shown_player), "Collect taxes"), ))), diff --git a/client/src/assets.rs b/client/src/assets.rs index 80437626..5f67007e 100644 --- a/client/src/assets.rs +++ b/client/src/assets.rs @@ -219,8 +219,16 @@ impl Assets { [ ( CustomActionType::AbsolutePower, + load_png(include_bytes!("../assets/crown-svgrepo-com.png")), + ), + ( + CustomActionType::ForcedLabor, load_png(include_bytes!("../assets/slavery-whip-svgrepo-com.png")), ), + ( + CustomActionType::CivilRights, + load_png(include_bytes!("../assets/justice-hammer-svgrepo-com.png")), + ), ( CustomActionType::Taxes, load_png(include_bytes!("../assets/tax-svgrepo-com.png")), diff --git a/client/src/collect_ui.rs b/client/src/collect_ui.rs index 93eb45d7..85f51b1d 100644 --- a/client/src/collect_ui.rs +++ b/client/src/collect_ui.rs @@ -69,7 +69,8 @@ impl CollectResources { pub fn extra_resources(&self, game: &Game) -> i8 { let city = game.get_city(self.player_index, self.city_position); - city.mood_modified_size() as i8 - self.collections.len() as i8 + city.mood_modified_size(game.get_player(self.player_index)) as i8 + - self.collections.len() as i8 } pub fn collected(&self) -> ResourcePile { diff --git a/server/src/city.rs b/server/src/city.rs index da674dfa..09fe69ac 100644 --- a/server/src/city.rs +++ b/server/src/city.rs @@ -3,6 +3,7 @@ use std::ops::{Add, Sub}; use serde::{Deserialize, Serialize}; use crate::consts::MAX_CITY_SIZE; +use crate::content::custom_actions::CustomActionType::ForcedLabor; use crate::{ city_pieces::{Building, CityPieces, CityPiecesData}, game::Game, @@ -187,11 +188,17 @@ impl City { } #[must_use] - pub fn mood_modified_size(&self) -> usize { + pub fn mood_modified_size(&self, player: &Player) -> usize { match self.mood_state { Happy => self.size() + 1, Neutral => self.size(), - Angry => 1, + Angry => { + if player.played_once_per_turn_actions.contains(&ForcedLabor) { + self.size() + } else { + 1 + } + } } } diff --git a/server/src/collect.rs b/server/src/collect.rs index 078af4d2..893c0572 100644 --- a/server/src/collect.rs +++ b/server/src/collect.rs @@ -23,7 +23,7 @@ pub fn get_total_collection( ) -> Option<(CollectOptionsInfo, ResourcePile)> { let player = &game.players[player_index]; let city = player.get_city(city_position)?; - if city.mood_modified_size() < collections.len() || city.player_index != player_index { + if city.mood_modified_size(player) < collections.len() || city.player_index != player_index { return None; } let i = possible_resource_collections( diff --git a/server/src/content/advances_autocracy.rs b/server/src/content/advances_autocracy.rs index 3ad46873..1103f9fe 100644 --- a/server/src/content/advances_autocracy.rs +++ b/server/src/content/advances_autocracy.rs @@ -1,10 +1,13 @@ use crate::ability_initializer::AbilityInitializerSetup; use crate::advance::{Advance, AdvanceBuilder}; use crate::content::advances::{advance_group_builder, AdvanceGroup}; -use crate::content::custom_actions::CustomActionType::AbsolutePower; +use crate::content::custom_actions::CustomActionType::{AbsolutePower, ForcedLabor}; pub(crate) fn autocracy() -> AdvanceGroup { - advance_group_builder("Autocracy", vec![nationalism(), absolute_power()]) + advance_group_builder( + "Autocracy", + vec![nationalism(), absolute_power(), forced_labor()], + ) } fn nationalism() -> AdvanceBuilder { @@ -18,3 +21,11 @@ fn absolute_power() -> AdvanceBuilder { ) .add_custom_action(AbsolutePower) } + +fn forced_labor() -> AdvanceBuilder { + Advance::builder( + "Forced Labor", + "Once per turn, as a free action, you may spend 1 mood token to treat your Angry cities as neutral for the rest of the turn", + ) + .add_custom_action(ForcedLabor) +} diff --git a/server/src/content/advances_construction.rs b/server/src/content/advances_construction.rs index aadb0d05..e95833e3 100644 --- a/server/src/content/advances_construction.rs +++ b/server/src/content/advances_construction.rs @@ -38,7 +38,7 @@ fn sanitation() -> AdvanceBuilder { .with_advance_bonus(MoodToken) .add_player_event_listener( |event| &mut event.recruit_cost, - |cost, units, ()| { + |cost, units, _| { if units.settlers > 0 { // insert at beginning so that it's preferred over gold cost.info @@ -55,6 +55,6 @@ fn sanitation() -> AdvanceBuilder { ); } }, - 0, + 1, ) } diff --git a/server/src/content/advances_democracy.rs b/server/src/content/advances_democracy.rs index ead5ad69..fed09fe7 100644 --- a/server/src/content/advances_democracy.rs +++ b/server/src/content/advances_democracy.rs @@ -2,22 +2,28 @@ use crate::ability_initializer::AbilityInitializerSetup; use crate::advance::{Advance, AdvanceBuilder}; use crate::content::advances::{advance_group_builder, AdvanceGroup}; use crate::content::custom_actions::CustomActionType::{ - FreeEconomyCollect, VotingIncreaseHappiness, + CivilRights, FreeEconomyCollect, VotingIncreaseHappiness, }; use crate::playing_actions::PlayingActionType; pub(crate) fn democracy() -> AdvanceGroup { - advance_group_builder( - "Democracy", - vec![ - Advance::builder( - "Voting", - "As a free action, you may spend 1 mood token to gain an action 'Increase happiness'", - ) - .add_custom_action(VotingIncreaseHappiness), - free_economy() - ], + advance_group_builder("Democracy", vec![voting(), civil_rights(), free_economy()]) +} + +fn voting() -> AdvanceBuilder { + Advance::builder( + "Voting", + "As a free action, you may spend 1 mood token to gain an action 'Increase happiness'", + ) + .add_custom_action(VotingIncreaseHappiness) +} + +fn civil_rights() -> AdvanceBuilder { + Advance::builder( + "Civil Rights", + "As a free action, you may gain 3 mood tokens. The cost of Draft is increased to 2 mood token", ) + .add_custom_action(CivilRights) } fn free_economy() -> AdvanceBuilder { diff --git a/server/src/content/advances_warfare.rs b/server/src/content/advances_warfare.rs index 4eeb00b6..d95c8b3b 100644 --- a/server/src/content/advances_warfare.rs +++ b/server/src/content/advances_warfare.rs @@ -46,20 +46,21 @@ fn draft() -> AdvanceBuilder { .with_advance_bonus(CultureToken) .add_player_event_listener( |event| &mut event.recruit_cost, - |cost, units, ()| { + |cost, units, player| { if units.infantry > 0 { // insert at beginning so that it's preferred over gold + + let pile = ResourcePile::mood_tokens(if player.has_advance("Civil Rights") { + 2 + } else { + 1 + }); cost.info .log - .push("Draft reduced the cost of 1 Infantry to 1 mood token".to_string()); - + .push(format!("Draft reduced the cost of 1 Infantry to {pile}")); cost.cost.conversions.insert( 0, - PaymentConversion::limited( - UnitType::cost(&UnitType::Infantry), - ResourcePile::mood_tokens(1), - 1, - ), + PaymentConversion::limited(UnitType::cost(&UnitType::Infantry), pile, 1), ); } }, diff --git a/server/src/content/custom_actions.rs b/server/src/content/custom_actions.rs index 803266c1..2d5135e2 100644 --- a/server/src/content/custom_actions.rs +++ b/server/src/content/custom_actions.rs @@ -28,6 +28,8 @@ pub enum CustomAction { payment: ResourcePile, }, AbsolutePower, + ForcedLabor, + CivilRights, ArtsInfluenceCultureAttempt(InfluenceCultureAttempt), VotingIncreaseHappiness(IncreaseHappiness), FreeEconomyCollect(Collect), @@ -43,6 +45,8 @@ pub enum CustomAction { pub enum CustomActionType { ConstructWonder, AbsolutePower, + ForcedLabor, + CivilRights, ArtsInfluenceCultureAttempt, VotingIncreaseHappiness, FreeEconomyCollect, @@ -65,6 +69,12 @@ impl CustomAction { payment, } => construct_wonder(game, player_index, city_position, &wonder, payment), CustomAction::AbsolutePower => game.actions_left += 1, + CustomAction::ForcedLabor => { + // we check that the action was played + } + CustomAction::CivilRights => { + game.players[player_index].gain_resources(ResourcePile::mood_tokens(3)); + } CustomAction::ArtsInfluenceCultureAttempt(c) => { influence_culture_attempt(game, player_index, &c); } @@ -88,6 +98,8 @@ impl CustomAction { match self { CustomAction::ConstructWonder { .. } => CustomActionType::ConstructWonder, CustomAction::AbsolutePower => CustomActionType::AbsolutePower, + CustomAction::ForcedLabor => CustomActionType::ForcedLabor, + CustomAction::CivilRights => CustomActionType::CivilRights, CustomAction::ArtsInfluenceCultureAttempt(_) => { CustomActionType::ArtsInfluenceCultureAttempt } @@ -117,6 +129,12 @@ impl CustomAction { 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( @@ -149,6 +167,10 @@ impl CustomAction { format!("{player_name} paid {payment} to construct the {wonder} wonder in the city at {city_position}"), CustomAction::AbsolutePower => format!("{player_name} paid 2 mood tokens to get an extra action using Forced Labor"), + CustomAction::ForcedLabor => + format!("{player_name} paid 1 mood token to treat Angry cities as neutral"), + CustomAction::CivilRights => + format!("{player_name} gained 3 mood tokens using Civil Rights"), CustomAction::ArtsInfluenceCultureAttempt(c) => format!("{} using Arts", format_cultural_influence_attempt_log_item(game, player_name, c)), CustomAction::VotingIncreaseHappiness(i) => @@ -170,20 +192,21 @@ impl CustomActionType { #[must_use] pub fn action_type(&self) -> ActionType { match self { - CustomActionType::ConstructWonder => ActionType::default(), CustomActionType::AbsolutePower => { ActionType::free_and_once_per_turn(ResourcePile::mood_tokens(2)) } + CustomActionType::CivilRights + | CustomActionType::Sports + | CustomActionType::ConstructWonder => ActionType::default(), CustomActionType::ArtsInfluenceCultureAttempt => { ActionType::free_and_once_per_turn(ResourcePile::culture_tokens(1)) } CustomActionType::VotingIncreaseHappiness => { ActionType::free(ResourcePile::mood_tokens(1)) } - CustomActionType::FreeEconomyCollect => { + CustomActionType::FreeEconomyCollect | CustomActionType::ForcedLabor => { ActionType::free_and_once_per_turn(ResourcePile::mood_tokens(1)) } - CustomActionType::Sports => ActionType::new(false, false, ResourcePile::empty()), CustomActionType::Taxes => ActionType::once_per_turn(ResourcePile::mood_tokens(1)), CustomActionType::Theaters => ActionType::free_and_once_per_turn(ResourcePile::empty()), } diff --git a/server/src/game.rs b/server/src/game.rs index 1bd56a2c..599e1f43 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -1472,8 +1472,8 @@ impl Game { " and captured {}'s city at {position}", self.players[old_player_index].get_name() )); - self.players[new_player_index] - .gain_resources(ResourcePile::gold(city.mood_modified_size() as u32)); + let size = city.mood_modified_size(&self.players[new_player_index]); + self.players[new_player_index].gain_resources(ResourcePile::gold(size as u32)); let take_over = self.players[new_player_index].is_city_available(); if take_over { diff --git a/server/src/player.rs b/server/src/player.rs index 2c2185de..f7d7e757 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -794,13 +794,13 @@ impl Player { |e| &e.recruit_cost, &PaymentOptions::resources(vec.iter().map(UnitType::cost).sum()), units, - &(), + self, execute, ); if !self.can_afford(&cost.cost) { return None; } - if vec.len() > city.mood_modified_size() { + if vec.len() > city.mood_modified_size(self) { return None; } if vec.iter().any(|unit| matches!(unit, Cavalry | Elephant)) && city.pieces.market.is_none() @@ -1050,15 +1050,21 @@ impl Player { details: &V, execute: Option<&ResourcePile>, ) -> CostInfo { - get_event(&self.events) - .get() - .trigger_with_minimal_modifiers( - &CostInfo::new(self, value.clone()), + let event = get_event(&self.events).get(); + let mut cost_info = CostInfo::new(self, value.clone()); + if let Some(execute) = execute { + event.trigger_with_minimal_modifiers( + &cost_info, info, details, - |i| execute.as_ref().is_none_or(|r| i.cost.is_valid_payment(r)), + |i| i.cost.is_valid_payment(execute), |i, m| i.cost.modifiers = m, ) + } else { + let m = event.trigger(&mut cost_info, info, details); + cost_info.cost.modifiers = m; + cost_info + } } pub(crate) fn trigger_player_event( diff --git a/server/src/player_events.rs b/server/src/player_events.rs index 32602ec9..207e6f40 100644 --- a/server/src/player_events.rs +++ b/server/src/player_events.rs @@ -28,7 +28,7 @@ pub(crate) struct PlayerEvents { pub wonder_cost: Event, pub advance_cost: Event, pub happiness_cost: Event, - pub recruit_cost: Event, + pub recruit_cost: Event, pub is_playing_action_available: Event, pub terrain_collect_options: Event>>, @@ -46,6 +46,7 @@ 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, @@ -54,6 +55,7 @@ pub(crate) struct ActionInfo { 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(), diff --git a/server/src/playing_actions.rs b/server/src/playing_actions.rs index e593129c..462373e6 100644 --- a/server/src/playing_actions.rs +++ b/server/src/playing_actions.rs @@ -346,6 +346,11 @@ impl ActionType { Self::new(true, true, cost) } + #[must_use] + pub fn regular(cost: ResourcePile) -> Self { + Self::new(false, false, cost) + } + #[must_use] pub fn new(free: bool, once_per_turn: bool, cost: ResourcePile) -> Self { Self { diff --git a/server/tests/game_api_tests.rs b/server/tests/game_api_tests.rs index eccb78f1..a5685217 100644 --- a/server/tests/game_api_tests.rs +++ b/server/tests/game_api_tests.rs @@ -7,6 +7,7 @@ use server::status_phase::{ }; use server::content::trade_routes::find_trade_routes; +use server::events::EventOrigin; use server::unit::{MoveUnits, Units}; use server::{ action::Action, @@ -157,7 +158,7 @@ fn basic_actions() { player .get_city(city_position) .expect("player should have a city at this position") - .mood_modified_size() + .mood_modified_size(player) ); assert_eq!(1, game.actions_left); @@ -427,39 +428,49 @@ fn game_path(name: &str) -> String { format!("tests{SEPARATOR}test_games{SEPARATOR}{name}.json") } +type TestAssert = Vec>; + struct TestAction { action: Action, undoable: bool, illegal_action_test: bool, player_index: usize, + pre_asserts: TestAssert, + post_asserts: TestAssert, } impl TestAction { - fn illegal(player_index: usize, action: Action) -> Self { + fn new(action: Action, undoable: bool, illegal_action_test: bool, player_index: usize) -> Self { Self { action, - undoable: false, - illegal_action_test: true, + undoable, + illegal_action_test, player_index, + pre_asserts: vec![], + post_asserts: vec![], } } + fn illegal(player_index: usize, action: Action) -> Self { + Self::new(action, false, true, player_index) + } fn undoable(player_index: usize, action: Action) -> Self { - Self { - action, - undoable: true, - illegal_action_test: false, - player_index, - } + Self::new(action, true, false, player_index) } fn not_undoable(player_index: usize, action: Action) -> Self { - Self { - action, - undoable: false, - illegal_action_test: false, - player_index, - } + Self::new(action, false, false, player_index) + } + + fn with_pre_assert(mut self, pre_assert: impl FnOnce(&Game) + 'static) -> Self { + self.pre_asserts.push(Box::new(pre_assert)); + self + } + + #[allow(dead_code)] + fn with_post_assert(mut self, post_assert: impl FnOnce(&Game) + 'static) -> Self { + self.post_asserts.push(Box::new(post_assert)); + self } } @@ -485,6 +496,8 @@ fn test_actions(name: &str, actions: Vec) { action.player_index, action.undoable, action.illegal_action_test, + action.pre_asserts, + action.post_asserts, ); })); assert!(err.is_ok(), "test action {} should not panic", i); @@ -506,9 +519,12 @@ fn test_action( player_index, undoable, illegal_action_test, + vec![], + vec![], ); } +#[allow(clippy::too_many_arguments)] fn test_action_internal( name: &str, outcome: &str, @@ -516,10 +532,15 @@ fn test_action_internal( player_index: usize, undoable: bool, illegal_action_test: bool, + pre_asserts: TestAssert, + post_asserts: TestAssert, ) { let a = serde_json::to_string(&action).expect("action should be serializable"); let a2 = serde_json::from_str(&a).expect("action should be deserializable"); let game = load_game(name); + for pre_assert in pre_asserts { + pre_assert(&game); + } if illegal_action_test { let err = catch_unwind(AssertUnwindSafe(|| { @@ -541,6 +562,9 @@ fn test_action_internal( assert!(!game.can_undo(), "should not be able to undo"); return; } + for post_assert in post_asserts { + post_assert(&game); + } undo_redo( name, player_index, @@ -914,16 +938,56 @@ fn test_increase_happiness_voting_rituals() { } #[test] -fn test_custom_action_forced_labor() { +fn test_absolute_power() { test_action( - "custom_action_forced_labor", - Action::Playing(Custom(AbsolutePower {})), + "absolute_power", + Action::Playing(Custom(AbsolutePower)), 0, true, false, ); } +#[test] +fn test_forced_labor() { + test_actions( + "forced_labor", + vec![ + TestAction::undoable(0, Action::Playing(Custom(ForcedLabor))), + TestAction::undoable( + 0, + Action::Playing(Collect(playing_actions::Collect { + city_position: Position::from_offset("A1"), + collections: vec![ + (Position::from_offset("A1"), ResourcePile::food(1)), + (Position::from_offset("A2"), ResourcePile::wood(1)), + ], + })), + ), + ], + ); +} + +#[test] +fn test_civil_rights() { + test_actions( + "civil_rights", + vec![ + TestAction::undoable(0, Action::Playing(Custom(CivilRights))), + TestAction::undoable( + 0, + Action::Playing(Recruit(server::playing_actions::Recruit { + units: Units::new(0, 1, 0, 0, 0, 0), + city_position: Position::from_offset("A1"), + payment: ResourcePile::mood_tokens(2), + leader_name: None, + replaced_units: vec![], + })), + ), + ], + ); +} + #[test] fn test_recruit() { test_action( @@ -961,18 +1025,36 @@ fn test_overpay() { #[test] fn test_sanitation_and_draft() { // we should figure out that sanitation or draft are used, but not both - test_action( + let units = Units::new(1, 1, 0, 0, 0, 0); + let city_position = Position::from_offset("A1"); + test_actions( "sanitation_and_draft", - Action::Playing(Recruit(server::playing_actions::Recruit { - units: Units::new(1, 1, 0, 0, 0, 0), - city_position: Position::from_offset("A1"), - payment: ResourcePile::mood_tokens(1) + ResourcePile::gold(2), - leader_name: None, - replaced_units: vec![], - })), - 0, - true, - false, + vec![TestAction::undoable( + 0, + Action::Playing(Recruit(server::playing_actions::Recruit { + units: units.clone(), + city_position, + payment: ResourcePile::mood_tokens(1) + ResourcePile::gold(2), + leader_name: None, + replaced_units: vec![], + })), + ) + .with_pre_assert(move |game| { + let options = game.players[0] + .recruit_cost_without_replaced(&units, city_position, None, None) + .unwrap() + .cost; + assert_eq!(3, options.conversions.len()); + assert_eq!(ResourcePile::mood_tokens(1), options.conversions[0].to); + assert_eq!(ResourcePile::mood_tokens(1), options.conversions[1].to); + assert_eq!( + vec![ + EventOrigin::Advance("Sanitation".to_string()), + EventOrigin::Advance("Draft".to_string()) + ], + options.modifiers + ); + })], ); } diff --git a/server/tests/test_games/custom_action_forced_labor.json b/server/tests/test_games/absolute_power.json similarity index 100% rename from server/tests/test_games/custom_action_forced_labor.json rename to server/tests/test_games/absolute_power.json diff --git a/server/tests/test_games/custom_action_forced_labor.outcome.json b/server/tests/test_games/absolute_power.outcome.json similarity index 100% rename from server/tests/test_games/custom_action_forced_labor.outcome.json rename to server/tests/test_games/absolute_power.outcome.json diff --git a/server/tests/test_games/civil_rights.json b/server/tests/test_games/civil_rights.json new file mode 100644 index 00000000..88487ea9 --- /dev/null +++ b/server/tests/test_games/civil_rights.json @@ -0,0 +1,241 @@ +{ + "state": "Playing", + "players": [ + { + "name": null, + "id": 0, + "resources": { + "food": 1, + "wood": 6, + "ore": 7, + "ideas": 5, + "gold": 7, + "mood_tokens": 6, + "culture_tokens": 7 + }, + "resource_limit": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 7, + "culture_tokens": 7 + }, + "cities": [ + { + "city_pieces": {}, + "mood_state": "Happy", + "activations": 0, + "angry_activation": false, + "position": "A1" + }, + { + "city_pieces": { + "academy": 0 + }, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "C2" + } + ], + "units": [ + { + "position": "C2", + "unit_type": "Infantry", + "id": 0 + }, + { + "position": "C2", + "unit_type": "Cavalry", + "id": 1 + }, + { + "position": "C2", + "unit_type": "Leader", + "id": 2 + }, + { + "position": "C2", + "unit_type": "Elephant", + "id": 3 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 4 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 5 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 6 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 7 + } + ], + "civilization": "test0", + "active_leader": null, + "available_leaders": [], + "advances": [ + "Civil Rights", + "Draft", + "Farming", + "Math", + "Mining", + "Public Education" + ], + "unlocked_special_advance": [], + "wonders_build": [], + "game_event_tokens": 2, + "completed_objectives": [], + "captured_leaders": [], + "event_victory_points": 0.0, + "wonder_cards": [], + "next_unit_id": 8 + }, + { + "name": null, + "id": 1, + "resources": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 7, + "culture_tokens": 7 + }, + "resource_limit": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 7, + "culture_tokens": 7 + }, + "cities": [ + { + "city_pieces": {}, + "mood_state": "Angry", + "activations": 0, + "angry_activation": false, + "position": "C1" + } + ], + "units": [ + { + "position": "C1", + "unit_type": "Infantry", + "id": 0 + }, + { + "position": "C1", + "unit_type": "Infantry", + "id": 1 + } + ], + "civilization": "test1", + "active_leader": null, + "available_leaders": [], + "advances": [ + "Farming", + "Mining" + ], + "unlocked_special_advance": [], + "wonders_build": [], + "game_event_tokens": 3, + "completed_objectives": [], + "captured_leaders": [], + "event_victory_points": 0.0, + "wonder_cards": [], + "next_unit_id": 2 + } + ], + "map": { + "tiles": [ + [ + "A1", + "Fertile" + ], + [ + "A2", + "Water" + ], + [ + "A3", + { + "Exhausted": "Forest" + } + ], + [ + "B1", + "Mountain" + ], + [ + "B2", + "Forest" + ], + [ + "B3", + "Fertile" + ], + [ + "C1", + "Barren" + ], + [ + "C2", + "Forest" + ], + [ + "C3", + "Water" + ], + [ + "D2", + "Water" + ] + ] + }, + "starting_player_index": 0, + "current_player_index": 0, + "action_log": [], + "action_log_index": 0, + "log": [], + "undo_limit": 0, + "actions_left": 3, + "successful_cultural_influence": false, + "round": 2, + "age": 1, + "messages": [ + "The game has started" + ], + "dice_roll_outcomes": [ + 1, + 1, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10 + ], + "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/civil_rights.outcome.json b/server/tests/test_games/civil_rights.outcome.json new file mode 100644 index 00000000..1752a667 --- /dev/null +++ b/server/tests/test_games/civil_rights.outcome.json @@ -0,0 +1,253 @@ +{ + "state": "Playing", + "players": [ + { + "name": null, + "id": 0, + "resources": { + "food": 1, + "wood": 6, + "ore": 7, + "ideas": 5, + "gold": 7, + "mood_tokens": 9, + "culture_tokens": 7 + }, + "resource_limit": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 7, + "culture_tokens": 7 + }, + "cities": [ + { + "city_pieces": {}, + "mood_state": "Happy", + "activations": 0, + "angry_activation": false, + "position": "A1" + }, + { + "city_pieces": { + "academy": 0 + }, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "C2" + } + ], + "units": [ + { + "position": "C2", + "unit_type": "Infantry", + "id": 0 + }, + { + "position": "C2", + "unit_type": "Cavalry", + "id": 1 + }, + { + "position": "C2", + "unit_type": "Leader", + "id": 2 + }, + { + "position": "C2", + "unit_type": "Elephant", + "id": 3 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 4 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 5 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 6 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 7 + } + ], + "civilization": "test0", + "active_leader": null, + "available_leaders": [], + "advances": [ + "Civil Rights", + "Draft", + "Farming", + "Math", + "Mining", + "Public Education" + ], + "unlocked_special_advance": [], + "wonders_build": [], + "game_event_tokens": 2, + "completed_objectives": [], + "captured_leaders": [], + "event_victory_points": 0.0, + "wonder_cards": [], + "next_unit_id": 8 + }, + { + "name": null, + "id": 1, + "resources": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 7, + "culture_tokens": 7 + }, + "resource_limit": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 7, + "culture_tokens": 7 + }, + "cities": [ + { + "city_pieces": {}, + "mood_state": "Angry", + "activations": 0, + "angry_activation": false, + "position": "C1" + } + ], + "units": [ + { + "position": "C1", + "unit_type": "Infantry", + "id": 0 + }, + { + "position": "C1", + "unit_type": "Infantry", + "id": 1 + } + ], + "civilization": "test1", + "active_leader": null, + "available_leaders": [], + "advances": [ + "Farming", + "Mining" + ], + "unlocked_special_advance": [], + "wonders_build": [], + "game_event_tokens": 3, + "completed_objectives": [], + "captured_leaders": [], + "event_victory_points": 0.0, + "wonder_cards": [], + "next_unit_id": 2 + } + ], + "map": { + "tiles": [ + [ + "A1", + "Fertile" + ], + [ + "A2", + "Water" + ], + [ + "A3", + { + "Exhausted": "Forest" + } + ], + [ + "B1", + "Mountain" + ], + [ + "B2", + "Forest" + ], + [ + "B3", + "Fertile" + ], + [ + "C1", + "Barren" + ], + [ + "C2", + "Forest" + ], + [ + "C3", + "Water" + ], + [ + "D2", + "Water" + ] + ] + }, + "starting_player_index": 0, + "current_player_index": 0, + "action_log": [ + { + "action": { + "Playing": { + "Custom": "CivilRights" + } + } + } + ], + "action_log_index": 1, + "log": [ + [ + "Player1 gained 3 mood tokens using Civil Rights" + ] + ], + "undo_limit": 0, + "actions_left": 2, + "successful_cultural_influence": false, + "round": 2, + "age": 1, + "messages": [ + "The game has started" + ], + "dice_roll_outcomes": [ + 1, + 1, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10 + ], + "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/civil_rights.outcome1.json b/server/tests/test_games/civil_rights.outcome1.json new file mode 100644 index 00000000..be81e22d --- /dev/null +++ b/server/tests/test_games/civil_rights.outcome1.json @@ -0,0 +1,282 @@ +{ + "state": "Playing", + "players": [ + { + "name": null, + "id": 0, + "resources": { + "food": 1, + "wood": 6, + "ore": 7, + "ideas": 5, + "gold": 7, + "mood_tokens": 7, + "culture_tokens": 7 + }, + "resource_limit": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 7, + "culture_tokens": 7 + }, + "cities": [ + { + "city_pieces": {}, + "mood_state": "Happy", + "activations": 1, + "angry_activation": false, + "position": "A1" + }, + { + "city_pieces": { + "academy": 0 + }, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "C2" + } + ], + "units": [ + { + "position": "C2", + "unit_type": "Infantry", + "id": 0 + }, + { + "position": "C2", + "unit_type": "Cavalry", + "id": 1 + }, + { + "position": "C2", + "unit_type": "Leader", + "id": 2 + }, + { + "position": "C2", + "unit_type": "Elephant", + "id": 3 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 4 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 5 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 6 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 7 + }, + { + "position": "A1", + "unit_type": "Infantry", + "id": 8 + } + ], + "civilization": "test0", + "active_leader": null, + "available_leaders": [], + "advances": [ + "Civil Rights", + "Draft", + "Farming", + "Math", + "Mining", + "Public Education" + ], + "unlocked_special_advance": [], + "wonders_build": [], + "game_event_tokens": 2, + "completed_objectives": [], + "captured_leaders": [], + "event_victory_points": 0.0, + "wonder_cards": [], + "next_unit_id": 9 + }, + { + "name": null, + "id": 1, + "resources": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 7, + "culture_tokens": 7 + }, + "resource_limit": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 7, + "culture_tokens": 7 + }, + "cities": [ + { + "city_pieces": {}, + "mood_state": "Angry", + "activations": 0, + "angry_activation": false, + "position": "C1" + } + ], + "units": [ + { + "position": "C1", + "unit_type": "Infantry", + "id": 0 + }, + { + "position": "C1", + "unit_type": "Infantry", + "id": 1 + } + ], + "civilization": "test1", + "active_leader": null, + "available_leaders": [], + "advances": [ + "Farming", + "Mining" + ], + "unlocked_special_advance": [], + "wonders_build": [], + "game_event_tokens": 3, + "completed_objectives": [], + "captured_leaders": [], + "event_victory_points": 0.0, + "wonder_cards": [], + "next_unit_id": 2 + } + ], + "map": { + "tiles": [ + [ + "A1", + "Fertile" + ], + [ + "A2", + "Water" + ], + [ + "A3", + { + "Exhausted": "Forest" + } + ], + [ + "B1", + "Mountain" + ], + [ + "B2", + "Forest" + ], + [ + "B3", + "Fertile" + ], + [ + "C1", + "Barren" + ], + [ + "C2", + "Forest" + ], + [ + "C3", + "Water" + ], + [ + "D2", + "Water" + ] + ] + }, + "starting_player_index": 0, + "current_player_index": 0, + "action_log": [ + { + "action": { + "Playing": { + "Custom": "CivilRights" + } + } + }, + { + "action": { + "Playing": { + "Recruit": { + "units": { + "infantry": 1 + }, + "city_position": "A1", + "payment": { + "mood_tokens": 2 + } + } + } + }, + "undo": [ + { + "Recruit": {} + } + ] + } + ], + "action_log_index": 2, + "log": [ + [ + "Player1 gained 3 mood tokens using Civil Rights" + ], + [ + "Player1 paid 2 mood tokens to recruit 1 infantry in the city at A1", + "Draft reduced the cost of 1 Infantry to 2 mood tokens" + ] + ], + "undo_limit": 0, + "actions_left": 1, + "successful_cultural_influence": false, + "round": 2, + "age": 1, + "messages": [ + "The game has started" + ], + "dice_roll_outcomes": [ + 1, + 1, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10 + ], + "dice_roll_log": [], + "dropped_players": [], + "wonders_left": [ + "Pyramids" + ], + "wonder_amount_left": 1 +} diff --git a/server/tests/test_games/forced_labor.json b/server/tests/test_games/forced_labor.json new file mode 100644 index 00000000..ade3e334 --- /dev/null +++ b/server/tests/test_games/forced_labor.json @@ -0,0 +1,278 @@ +{ + "state": "Playing", + "players": [ + { + "name": null, + "id": 0, + "resources": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 9, + "culture_tokens": 10 + }, + "resource_limit": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7 + }, + "cities": [ + { + "city_pieces": { + "market": 1 + }, + "mood_state": "Angry", + "activations": 0, + "angry_activation": false, + "position": "A1" + }, + { + "city_pieces": { + "academy": 1, + "port": 1 + }, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "C2", + "port_position": "C3" + }, + { + "city_pieces": { + "obelisk": 1, + "observatory": 1, + "fortress": 1, + "temple": 1 + }, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "B1" + }, + { + "city_pieces": {}, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "B3" + } + ], + "units": [ + { + "position": "C2", + "unit_type": "Infantry", + "id": 0 + }, + { + "position": "C2", + "unit_type": "Cavalry", + "id": 1 + }, + { + "position": "C2", + "unit_type": "Elephant", + "id": 2 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 3 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 4 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 5 + }, + { + "position": "B3", + "unit_type": "Settler", + "id": 6 + } + ], + "civilization": "test1", + "active_leader": null, + "available_leaders": [], + "advances": [ + "Farming", + "Forced Labor", + "Math", + "Mining", + "Nationalism" + ], + "unlocked_special_advance": [], + "wonders_build": [], + "game_event_tokens": 3, + "completed_objectives": [], + "captured_leaders": [], + "event_victory_points": 0.0, + "wonder_cards": [ + "Pyramids" + ], + "next_unit_id": 7 + }, + { + "name": null, + "id": 1, + "resources": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 9, + "culture_tokens": 10 + }, + "resource_limit": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7 + }, + "cities": [ + { + "city_pieces": {}, + "mood_state": "Angry", + "activations": 0, + "angry_activation": false, + "position": "C1" + }, + { + "city_pieces": { + "port": 1 + }, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "B2", + "port_position": "C3" + } + ], + "units": [ + { + "position": "C1", + "unit_type": "Infantry", + "id": 0 + }, + { + "position": "C1", + "unit_type": "Infantry", + "id": 1 + }, + { + "position": "C3", + "unit_type": "Ship", + "id": 2 + } + ], + "civilization": "test0", + "active_leader": null, + "available_leaders": [], + "advances": [ + "Farming", + "Math", + "Mining" + ], + "unlocked_special_advance": [], + "wonders_build": [], + "game_event_tokens": 2, + "completed_objectives": [], + "captured_leaders": [], + "event_victory_points": 0.0, + "wonder_cards": [], + "next_unit_id": 3 + } + ], + "map": { + "tiles": [ + [ + "A1", + "Fertile" + ], + [ + "A2", + "Forest" + ], + [ + "A3", + { + "Exhausted": "Forest" + } + ], + [ + "A4", + "Mountain" + ], + [ + "B1", + "Mountain" + ], + [ + "B2", + "Forest" + ], + [ + "B3", + "Fertile" + ], + [ + "B4", + "Fertile" + ], + [ + "C1", + "Barren" + ], + [ + "C2", + "Forest" + ], + [ + "C3", + "Water" + ], + [ + "D2", + "Water" + ] + ] + }, + "starting_player_index": 1, + "current_player_index": 0, + "action_log": [], + "action_log_index": 0, + "log": [], + "undo_limit": 0, + "actions_left": 1, + "successful_cultural_influence": false, + "round": 1, + "age": 2, + "messages": [ + "The game has started" + ], + "dice_roll_outcomes": [ + 1, + 1, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10 + ], + "dice_roll_log": [], + "dropped_players": [], + "wonders_left": [], + "wonder_amount_left": 1 +} \ No newline at end of file diff --git a/server/tests/test_games/forced_labor.outcome.json b/server/tests/test_games/forced_labor.outcome.json new file mode 100644 index 00000000..333f0733 --- /dev/null +++ b/server/tests/test_games/forced_labor.outcome.json @@ -0,0 +1,293 @@ +{ + "state": "Playing", + "players": [ + { + "name": null, + "id": 0, + "resources": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 8, + "culture_tokens": 10 + }, + "resource_limit": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7 + }, + "cities": [ + { + "city_pieces": { + "market": 1 + }, + "mood_state": "Angry", + "activations": 0, + "angry_activation": false, + "position": "A1" + }, + { + "city_pieces": { + "academy": 1, + "port": 1 + }, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "C2", + "port_position": "C3" + }, + { + "city_pieces": { + "obelisk": 1, + "observatory": 1, + "fortress": 1, + "temple": 1 + }, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "B1" + }, + { + "city_pieces": {}, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "B3" + } + ], + "units": [ + { + "position": "C2", + "unit_type": "Infantry", + "id": 0 + }, + { + "position": "C2", + "unit_type": "Cavalry", + "id": 1 + }, + { + "position": "C2", + "unit_type": "Elephant", + "id": 2 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 3 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 4 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 5 + }, + { + "position": "B3", + "unit_type": "Settler", + "id": 6 + } + ], + "civilization": "test1", + "active_leader": null, + "available_leaders": [], + "advances": [ + "Farming", + "Forced Labor", + "Math", + "Mining", + "Nationalism" + ], + "unlocked_special_advance": [], + "wonders_build": [], + "game_event_tokens": 3, + "completed_objectives": [], + "captured_leaders": [], + "event_victory_points": 0.0, + "wonder_cards": [ + "Pyramids" + ], + "next_unit_id": 7, + "played_once_per_turn_actions": [ + "ForcedLabor" + ] + }, + { + "name": null, + "id": 1, + "resources": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 9, + "culture_tokens": 10 + }, + "resource_limit": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7 + }, + "cities": [ + { + "city_pieces": {}, + "mood_state": "Angry", + "activations": 0, + "angry_activation": false, + "position": "C1" + }, + { + "city_pieces": { + "port": 1 + }, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "B2", + "port_position": "C3" + } + ], + "units": [ + { + "position": "C1", + "unit_type": "Infantry", + "id": 0 + }, + { + "position": "C1", + "unit_type": "Infantry", + "id": 1 + }, + { + "position": "C3", + "unit_type": "Ship", + "id": 2 + } + ], + "civilization": "test0", + "active_leader": null, + "available_leaders": [], + "advances": [ + "Farming", + "Math", + "Mining" + ], + "unlocked_special_advance": [], + "wonders_build": [], + "game_event_tokens": 2, + "completed_objectives": [], + "captured_leaders": [], + "event_victory_points": 0.0, + "wonder_cards": [], + "next_unit_id": 3 + } + ], + "map": { + "tiles": [ + [ + "A1", + "Fertile" + ], + [ + "A2", + "Forest" + ], + [ + "A3", + { + "Exhausted": "Forest" + } + ], + [ + "A4", + "Mountain" + ], + [ + "B1", + "Mountain" + ], + [ + "B2", + "Forest" + ], + [ + "B3", + "Fertile" + ], + [ + "B4", + "Fertile" + ], + [ + "C1", + "Barren" + ], + [ + "C2", + "Forest" + ], + [ + "C3", + "Water" + ], + [ + "D2", + "Water" + ] + ] + }, + "starting_player_index": 1, + "current_player_index": 0, + "action_log": [ + { + "action": { + "Playing": { + "Custom": "ForcedLabor" + } + } + } + ], + "action_log_index": 1, + "log": [ + [ + "Player1 paid 1 mood token to treat Angry cities as neutral" + ] + ], + "undo_limit": 0, + "actions_left": 1, + "successful_cultural_influence": false, + "round": 1, + "age": 2, + "messages": [ + "The game has started" + ], + "dice_roll_outcomes": [ + 1, + 1, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10 + ], + "dice_roll_log": [], + "dropped_players": [], + "wonders_left": [], + "wonder_amount_left": 1 +} \ No newline at end of file diff --git a/server/tests/test_games/forced_labor.outcome1.json b/server/tests/test_games/forced_labor.outcome1.json new file mode 100644 index 00000000..ef102877 --- /dev/null +++ b/server/tests/test_games/forced_labor.outcome1.json @@ -0,0 +1,331 @@ +{ + "state": "Playing", + "players": [ + { + "name": null, + "id": 0, + "resources": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 8, + "culture_tokens": 10 + }, + "resource_limit": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7 + }, + "cities": [ + { + "city_pieces": { + "market": 1 + }, + "mood_state": "Angry", + "activations": 1, + "angry_activation": true, + "position": "A1" + }, + { + "city_pieces": { + "academy": 1, + "port": 1 + }, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "C2", + "port_position": "C3" + }, + { + "city_pieces": { + "obelisk": 1, + "observatory": 1, + "fortress": 1, + "temple": 1 + }, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "B1" + }, + { + "city_pieces": {}, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "B3" + } + ], + "units": [ + { + "position": "C2", + "unit_type": "Infantry", + "id": 0 + }, + { + "position": "C2", + "unit_type": "Cavalry", + "id": 1 + }, + { + "position": "C2", + "unit_type": "Elephant", + "id": 2 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 3 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 4 + }, + { + "position": "C2", + "unit_type": "Settler", + "id": 5 + }, + { + "position": "B3", + "unit_type": "Settler", + "id": 6 + } + ], + "civilization": "test1", + "active_leader": null, + "available_leaders": [], + "advances": [ + "Farming", + "Forced Labor", + "Math", + "Mining", + "Nationalism" + ], + "unlocked_special_advance": [], + "wonders_build": [], + "game_event_tokens": 3, + "completed_objectives": [], + "captured_leaders": [], + "event_victory_points": 0.0, + "wonder_cards": [ + "Pyramids" + ], + "next_unit_id": 7, + "played_once_per_turn_actions": [ + "ForcedLabor" + ] + }, + { + "name": null, + "id": 1, + "resources": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7, + "mood_tokens": 9, + "culture_tokens": 10 + }, + "resource_limit": { + "food": 2, + "wood": 7, + "ore": 7, + "ideas": 7, + "gold": 7 + }, + "cities": [ + { + "city_pieces": {}, + "mood_state": "Angry", + "activations": 0, + "angry_activation": false, + "position": "C1" + }, + { + "city_pieces": { + "port": 1 + }, + "mood_state": "Neutral", + "activations": 0, + "angry_activation": false, + "position": "B2", + "port_position": "C3" + } + ], + "units": [ + { + "position": "C1", + "unit_type": "Infantry", + "id": 0 + }, + { + "position": "C1", + "unit_type": "Infantry", + "id": 1 + }, + { + "position": "C3", + "unit_type": "Ship", + "id": 2 + } + ], + "civilization": "test0", + "active_leader": null, + "available_leaders": [], + "advances": [ + "Farming", + "Math", + "Mining" + ], + "unlocked_special_advance": [], + "wonders_build": [], + "game_event_tokens": 2, + "completed_objectives": [], + "captured_leaders": [], + "event_victory_points": 0.0, + "wonder_cards": [], + "next_unit_id": 3 + } + ], + "map": { + "tiles": [ + [ + "A1", + "Fertile" + ], + [ + "A2", + "Forest" + ], + [ + "A3", + { + "Exhausted": "Forest" + } + ], + [ + "A4", + "Mountain" + ], + [ + "B1", + "Mountain" + ], + [ + "B2", + "Forest" + ], + [ + "B3", + "Fertile" + ], + [ + "B4", + "Fertile" + ], + [ + "C1", + "Barren" + ], + [ + "C2", + "Forest" + ], + [ + "C3", + "Water" + ], + [ + "D2", + "Water" + ] + ] + }, + "starting_player_index": 1, + "current_player_index": 0, + "action_log": [ + { + "action": { + "Playing": { + "Custom": "ForcedLabor" + } + } + }, + { + "action": { + "Playing": { + "Collect": { + "city_position": "A1", + "collections": [ + [ + "A1", + { + "food": 1 + } + ], + [ + "A2", + { + "wood": 1 + } + ] + ] + } + } + }, + "undo": [ + { + "WastedResources": { + "resources": { + "food": 1, + "wood": 1 + }, + "player_index": 0 + } + } + ] + } + ], + "action_log_index": 2, + "log": [ + [ + "Player1 paid 1 mood token to treat Angry cities as neutral" + ], + [ + "Player1 collects 1 food and 1 wood in the city at A1", + "Player1 Could not store 1 food and 1 wood" + ] + ], + "undo_limit": 0, + "actions_left": 0, + "successful_cultural_influence": false, + "round": 1, + "age": 2, + "messages": [ + "The game has started" + ], + "dice_roll_outcomes": [ + 1, + 1, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10 + ], + "dice_roll_log": [], + "dropped_players": [], + "wonders_left": [], + "wonder_amount_left": 1 +} \ No newline at end of file