From 4a8ef4d6593572ff750d19d8d212e88c6927efc3 Mon Sep 17 00:00:00 2001 From: wvpm <24685035+wvpm@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:17:55 +0100 Subject: [PATCH] Implement artisanal production --- .../economy/GoodInstance.cpp | 59 ++++++++- .../economy/GoodInstance.hpp | 8 ++ .../economy/production/ArtisanalProducer.cpp | 115 ++++++++++++++++-- .../economy/production/ArtisanalProducer.hpp | 19 ++- .../economy/trading/BuyResult.cpp | 11 ++ .../economy/trading/BuyResult.hpp | 16 +++ .../economy/trading/BuyUpToOrder.cpp | 25 ++++ .../economy/trading/BuyUpToOrder.hpp | 35 ++++++ .../economy/trading/MarketInstance.cpp | 6 + .../economy/trading/MarketInstance.hpp | 2 + 10 files changed, 279 insertions(+), 17 deletions(-) create mode 100644 src/openvic-simulation/economy/trading/BuyResult.cpp create mode 100644 src/openvic-simulation/economy/trading/BuyResult.hpp create mode 100644 src/openvic-simulation/economy/trading/BuyUpToOrder.cpp create mode 100644 src/openvic-simulation/economy/trading/BuyUpToOrder.hpp diff --git a/src/openvic-simulation/economy/GoodInstance.cpp b/src/openvic-simulation/economy/GoodInstance.cpp index 6163c8a4..66e16c41 100644 --- a/src/openvic-simulation/economy/GoodInstance.cpp +++ b/src/openvic-simulation/economy/GoodInstance.cpp @@ -4,13 +4,34 @@ using namespace OpenVic; GoodInstance::GoodInstance(GoodDefinition const& new_good_definition) : HasIdentifierAndColour { new_good_definition }, + buy_lock { std::make_unique() }, sell_lock { std::make_unique() }, good_definition { new_good_definition }, price { new_good_definition.get_base_price() }, + max_next_price {}, + min_next_price {}, is_available { new_good_definition.get_is_available_from_start() }, + total_demand_yesterday { fixed_point_t::_0() }, total_supply_yesterday { fixed_point_t::_0() }, + buy_up_to_orders {}, market_sell_orders {} - {} + { update_next_price_limits(); } + +void GoodInstance::update_next_price_limits() { + max_next_price = std::min( + good_definition.get_base_price() * 5, + price + fixed_point_t::_1() / fixed_point_t::_100() + ); + min_next_price = std::max( + good_definition.get_base_price() * 22 / fixed_point_t::_100(), + price - fixed_point_t::_1() / fixed_point_t::_100() + ); +} + +void GoodInstance::add_buy_up_to_order(const GoodBuyUpToOrder buy_up_to_order) { + const std::lock_guard lock {*buy_lock}; + buy_up_to_orders.push_back(buy_up_to_order); +} void GoodInstance::add_market_sell_order(const GoodMarketSellOrder market_sell_order) { const std::lock_guard lock {*sell_lock}; @@ -18,20 +39,48 @@ void GoodInstance::add_market_sell_order(const GoodMarketSellOrder market_sell_o } void GoodInstance::execute_orders() { - const fixed_point_t price = get_price(); + fixed_point_t demand_running_total = fixed_point_t::_0(); + for (GoodBuyUpToOrder const& buy_up_to_order : buy_up_to_orders) { + demand_running_total += buy_up_to_order.get_max_quantity(); + } fixed_point_t supply_running_total = fixed_point_t::_0(); - for(GoodMarketSellOrder const& market_sell_order : market_sell_orders) { + for (GoodMarketSellOrder const& market_sell_order : market_sell_orders) { + supply_running_total += market_sell_order.get_quantity(); + } + + fixed_point_t new_price; + if (demand_running_total > supply_running_total) { + new_price = max_next_price; + } else if (demand_running_total < supply_running_total) { + new_price = min_next_price; + } else { + new_price = price; + } + + for (GoodBuyUpToOrder const& buy_up_to_order : buy_up_to_orders) { + const fixed_point_t quantity_bought = buy_up_to_order.get_money_to_spend() / new_price; + buy_up_to_order.get_after_trade()({ + quantity_bought, + buy_up_to_order.get_money_to_spend() - quantity_bought * new_price + }); + } + + for (GoodMarketSellOrder const& market_sell_order : market_sell_orders) { const fixed_point_t market_sell_quantity = market_sell_order.get_quantity(); - supply_running_total += market_sell_quantity; market_sell_order.get_after_trade()({ market_sell_quantity, - market_sell_quantity * price + market_sell_quantity * new_price }); } + total_demand_yesterday = demand_running_total; total_supply_yesterday = supply_running_total; + buy_up_to_orders.clear(); market_sell_orders.clear(); + if (new_price != price) { + update_next_price_limits(); + } } bool GoodInstanceManager::setup(GoodDefinitionManager const& good_definition_manager) { diff --git a/src/openvic-simulation/economy/GoodInstance.hpp b/src/openvic-simulation/economy/GoodInstance.hpp index 5a5bbeac..4d3eb109 100644 --- a/src/openvic-simulation/economy/GoodInstance.hpp +++ b/src/openvic-simulation/economy/GoodInstance.hpp @@ -5,6 +5,7 @@ #include #include "openvic-simulation/economy/GoodDefinition.hpp" +#include "openvic-simulation/economy/trading/BuyUpToOrder.hpp" #include "openvic-simulation/economy/trading/MarketSellOrder.hpp" #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" #include "openvic-simulation/types/HasIdentifier.hpp" @@ -18,19 +19,26 @@ namespace OpenVic { friend struct GoodInstanceManager; private: + std::unique_ptr buy_lock; std::unique_ptr sell_lock; GoodDefinition const& PROPERTY(good_definition); fixed_point_t PROPERTY(price); + fixed_point_t PROPERTY(max_next_price); + fixed_point_t PROPERTY(min_next_price); bool PROPERTY(is_available); + fixed_point_t PROPERTY(total_demand_yesterday); fixed_point_t PROPERTY(total_supply_yesterday); + std::deque buy_up_to_orders; std::deque market_sell_orders; GoodInstance(GoodDefinition const& new_good_definition); + void update_next_price_limits(); public: GoodInstance(GoodInstance&&) = default; //thread safe + void add_buy_up_to_order(const GoodBuyUpToOrder buy_up_to_order); void add_market_sell_order(const GoodMarketSellOrder market_sell_order); //not thread safe diff --git a/src/openvic-simulation/economy/production/ArtisanalProducer.cpp b/src/openvic-simulation/economy/production/ArtisanalProducer.cpp index d5cc3d31..25fb3b9e 100644 --- a/src/openvic-simulation/economy/production/ArtisanalProducer.cpp +++ b/src/openvic-simulation/economy/production/ArtisanalProducer.cpp @@ -1,13 +1,114 @@ #include "ArtisanalProducer.hpp" +#include "openvic-simulation/economy/GoodDefinition.hpp" +#include "openvic-simulation/economy/trading/BuyResult.hpp" +#include "openvic-simulation/economy/trading/SellResult.hpp" + using namespace OpenVic; ArtisanalProducer::ArtisanalProducer( - ProductionType const& new_production_type, + MarketInstance& new_market_instance, + ModifierEffectCache const& new_modifier_effect_cache, + Pop& new_pop, GoodDefinition::good_definition_map_t&& new_stockpile, - fixed_point_t new_current_production, - GoodDefinition::good_definition_map_t&& new_current_needs -) : production_type { new_production_type }, - stockpile { std::move(new_stockpile) }, - current_production { new_current_production }, - current_needs { std::move(new_current_needs) } {} + ProductionType const& new_production_type, + fixed_point_t new_current_production +) : market_instance { new_market_instance }, + modifier_effect_cache { new_modifier_effect_cache }, + pop { new_pop }, + stockpile { new_stockpile }, + production_type { new_production_type }, + current_production { new_current_production } + {} + +void ArtisanalProducer::artisan_tick() { + GoodDefinition::good_definition_map_t goods_to_buy_and_max_price { }; + GoodDefinition::good_definition_map_t demand { }; + fixed_point_t inputs_bought_scalar = fixed_point_t::_1(); + if (production_type.get_input_goods().empty()) { + inputs_bought_scalar = fixed_point_t::_1(); + } else { + GoodInstanceManager const& good_instance_manager = *market_instance.get_good_instance_manager(); + for (auto const& [input_good_ptr, base_desired_quantity] : production_type.get_input_goods()) { + const fixed_point_t desired_quantity = demand[input_good_ptr] = base_desired_quantity * pop.get_size() / production_type.get_base_workforce_size(); + inputs_bought_scalar = std::min(stockpile[input_good_ptr] / desired_quantity, inputs_bought_scalar); + GoodInstance const& good = *good_instance_manager.get_good_instance_by_identifier( + input_good_ptr->get_identifier() + ); + goods_to_buy_and_max_price[input_good_ptr] = good.get_max_next_price(); + } + + if (inputs_bought_scalar > fixed_point_t::_0()) { + for (auto const& [input_good_ptr, base_desired_quantity] : production_type.get_input_goods()) { + const fixed_point_t desired_quantity = demand[input_good_ptr]; + stockpile[input_good_ptr] = std::max( + fixed_point_t::_0(), + stockpile[input_good_ptr] - desired_quantity * inputs_bought_scalar + ); + + if (stockpile[input_good_ptr] >= desired_quantity) { + goods_to_buy_and_max_price.erase(input_good_ptr); + } + } + } + + const fixed_point_t total_cash_to_spend = pop.get_cash(); + if (total_cash_to_spend > 0 && !goods_to_buy_and_max_price.empty()) { + fixed_point_t max_possible_satisfaction = fixed_point_t::_1(); + + bool at_or_below_optimum = false; + while (!at_or_below_optimum) { + at_or_below_optimum = true; + fixed_point_t total_demand_value = fixed_point_t::_0(); + fixed_point_t total_stockpile_value = fixed_point_t::_0(); + for (auto const& [input_good_ptr, max_price] : goods_to_buy_and_max_price) { + total_demand_value += max_price * demand[input_good_ptr]; + total_stockpile_value += max_price * stockpile[input_good_ptr]; + } + + max_possible_satisfaction = std::min( + fixed_point_t::_1(), + (total_stockpile_value + total_cash_to_spend) / total_demand_value + ); + + for (auto const& [input_good_ptr, max_price] : goods_to_buy_and_max_price) { + const fixed_point_t optimal_quantity = demand[input_good_ptr] * max_possible_satisfaction; + if (stockpile[input_good_ptr] >= optimal_quantity) { + goods_to_buy_and_max_price.erase(input_good_ptr); + at_or_below_optimum = false; + } + } + } + + for (auto const& [input_good_ptr, max_price] : goods_to_buy_and_max_price) { + const fixed_point_t optimal_quantity = demand[input_good_ptr] * max_possible_satisfaction; + const fixed_point_t money_to_spend = optimal_quantity * max_price; + //TODO pop cash -= money_to_spend + market_instance.place_buy_up_to_order({ + *input_good_ptr, + optimal_quantity, + money_to_spend, + [this, input_good_ptr](const BuyResult buy_result) -> void { + //TODO pop cash += buy_result.get_money_left() + stockpile[input_good_ptr] += buy_result.get_quantity_bought(); + } + }); + } + } + } + + current_production = production_type.get_base_output_quantity() + * inputs_bought_scalar + * pop.get_size() / production_type.get_base_workforce_size(); + + GoodDefinition const& output_good = production_type.get_output_good(); + if (current_production > 0) { + market_instance.place_market_sell_order({ + output_good, + current_production, + [this](const SellResult sell_result) -> void { + //TODO add artisanal income to pop (part of https://github.com/OpenVicProject/OpenVic-Simulation/issues/225 ) + } + }); + } +} \ No newline at end of file diff --git a/src/openvic-simulation/economy/production/ArtisanalProducer.hpp b/src/openvic-simulation/economy/production/ArtisanalProducer.hpp index 65aa3fa0..d2db1989 100644 --- a/src/openvic-simulation/economy/production/ArtisanalProducer.hpp +++ b/src/openvic-simulation/economy/production/ArtisanalProducer.hpp @@ -1,22 +1,31 @@ #pragma once -#include "openvic-simulation/economy/GoodDefinition.hpp" #include "openvic-simulation/economy/production/ProductionType.hpp" +#include "openvic-simulation/economy/trading/MarketInstance.hpp" +#include "openvic-simulation/modifier/ModifierEffectCache.hpp" +#include "openvic-simulation/pop/Pop.hpp" #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" #include "openvic-simulation/utility/Getters.hpp" namespace OpenVic { struct ArtisanalProducer { private: + MarketInstance& market_instance; + ModifierEffectCache const& modifier_effect_cache; + Pop& pop; + GoodDefinition::good_definition_map_t stockpile; ProductionType const& PROPERTY(production_type); - GoodDefinition::good_definition_map_t PROPERTY(stockpile); fixed_point_t PROPERTY(current_production); - GoodDefinition::good_definition_map_t PROPERTY(current_needs); public: ArtisanalProducer( - ProductionType const& new_production_type, GoodDefinition::good_definition_map_t&& new_stockpile, - fixed_point_t new_current_production, GoodDefinition::good_definition_map_t&& new_current_needs + MarketInstance& new_market_instance, + ModifierEffectCache const& new_modifier_effect_cache, + Pop& new_pop, + GoodDefinition::good_definition_map_t&& new_stockpile, + ProductionType const& new_production_type, + fixed_point_t new_current_production ); + void artisan_tick(); }; } diff --git a/src/openvic-simulation/economy/trading/BuyResult.cpp b/src/openvic-simulation/economy/trading/BuyResult.cpp new file mode 100644 index 00000000..452f3b9d --- /dev/null +++ b/src/openvic-simulation/economy/trading/BuyResult.cpp @@ -0,0 +1,11 @@ +#include "BuyResult.hpp" + +using namespace OpenVic; + +BuyResult::BuyResult( + const fixed_point_t new_quantity_bought, + const fixed_point_t new_money_left +) : + quantity_bought { new_quantity_bought }, + money_left { new_money_left } + {} \ No newline at end of file diff --git a/src/openvic-simulation/economy/trading/BuyResult.hpp b/src/openvic-simulation/economy/trading/BuyResult.hpp new file mode 100644 index 00000000..9eabff7c --- /dev/null +++ b/src/openvic-simulation/economy/trading/BuyResult.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" + +namespace OpenVic { + struct BuyResult { + private: + fixed_point_t PROPERTY(quantity_bought); + fixed_point_t PROPERTY(money_left); + public: + BuyResult( + const fixed_point_t new_quantity_bought, + const fixed_point_t new_money_left + ); + }; +} \ No newline at end of file diff --git a/src/openvic-simulation/economy/trading/BuyUpToOrder.cpp b/src/openvic-simulation/economy/trading/BuyUpToOrder.cpp new file mode 100644 index 00000000..b99c0864 --- /dev/null +++ b/src/openvic-simulation/economy/trading/BuyUpToOrder.cpp @@ -0,0 +1,25 @@ +#include "BuyUpToOrder.hpp" + +using namespace OpenVic; + +GoodBuyUpToOrder::GoodBuyUpToOrder( + const fixed_point_t new_max_quantity, + const fixed_point_t new_money_to_spend, + const std::function new_after_trade +) : max_quantity { new_max_quantity }, + money_to_spend { new_money_to_spend }, + after_trade { new_after_trade } + {} + +BuyUpToOrder::BuyUpToOrder( + GoodDefinition const& new_good, + const fixed_point_t new_max_quantity, + const fixed_point_t new_money_to_spend, + const std::function new_after_trade +) : GoodBuyUpToOrder( + new_max_quantity, + new_money_to_spend, + new_after_trade + ), + good { &new_good } + {} \ No newline at end of file diff --git a/src/openvic-simulation/economy/trading/BuyUpToOrder.hpp b/src/openvic-simulation/economy/trading/BuyUpToOrder.hpp new file mode 100644 index 00000000..b0d00a5b --- /dev/null +++ b/src/openvic-simulation/economy/trading/BuyUpToOrder.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "openvic-simulation/economy/GoodDefinition.hpp" +#include "openvic-simulation/economy/trading/BuyResult.hpp" +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" +#include "openvic-simulation/utility/Getters.hpp" + +namespace OpenVic { + struct GoodBuyUpToOrder { + private: + const fixed_point_t PROPERTY(max_quantity); + const fixed_point_t PROPERTY(money_to_spend); + const std::function PROPERTY(after_trade); + + public: + GoodBuyUpToOrder( + const fixed_point_t new_max_quantity, + const fixed_point_t new_money_to_spend, + const std::function new_after_trade + ); + }; + + struct BuyUpToOrder : GoodBuyUpToOrder { + private: + GoodDefinition const* const PROPERTY(good); + + public: + BuyUpToOrder( + GoodDefinition const& new_good, + const fixed_point_t new_max_quantity, + const fixed_point_t new_money_to_spend, + const std::function new_after_trade + ); + }; +} \ No newline at end of file diff --git a/src/openvic-simulation/economy/trading/MarketInstance.cpp b/src/openvic-simulation/economy/trading/MarketInstance.cpp index 06a470b1..623d3c33 100644 --- a/src/openvic-simulation/economy/trading/MarketInstance.cpp +++ b/src/openvic-simulation/economy/trading/MarketInstance.cpp @@ -9,6 +9,12 @@ bool MarketInstance::setup(GoodInstanceManager& new_good_instance_manager) { return true; } +void MarketInstance::place_buy_up_to_order(const BuyUpToOrder buy_up_to_order) { + GoodDefinition const* const good = buy_up_to_order.get_good(); + GoodInstance* const good_instance = good_instance_manager->get_good_instance_by_identifier(good->get_identifier()); + good_instance->add_buy_up_to_order(buy_up_to_order); +} + void MarketInstance::place_market_sell_order(const MarketSellOrder market_sell_order) { GoodDefinition const* const good = market_sell_order.get_good(); GoodInstance* const good_instance = good_instance_manager->get_good_instance_by_identifier(good->get_identifier()); diff --git a/src/openvic-simulation/economy/trading/MarketInstance.hpp b/src/openvic-simulation/economy/trading/MarketInstance.hpp index 2d8a6511..51b25b49 100644 --- a/src/openvic-simulation/economy/trading/MarketInstance.hpp +++ b/src/openvic-simulation/economy/trading/MarketInstance.hpp @@ -1,6 +1,7 @@ #pragma once #include "openvic-simulation/economy/GoodInstance.hpp" +#include "openvic-simulation/economy/trading/BuyUpToOrder.hpp" #include "openvic-simulation/economy/trading/MarketSellOrder.hpp" namespace OpenVic { @@ -9,6 +10,7 @@ namespace OpenVic { GoodInstanceManager* PROPERTY(good_instance_manager); public: bool setup(GoodInstanceManager& new_good_instance_manager); + void place_buy_up_to_order(const BuyUpToOrder buy_up_to_order); void place_market_sell_order(const MarketSellOrder market_sell_order); void execute_orders(); };