Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Artisanal production + buying from market #230

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/openvic-simulation/InstanceManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,17 @@ bool InstanceManager::load_bookmark(Bookmark const* new_bookmark) {

today = bookmark->get_date();

ArtisanalProducerFactoryPattern artisanal_producer_factory_pattern {
market_instance,
definition_manager.get_modifier_manager().get_modifier_effect_cache(),
definition_manager.get_economy_manager().get_production_type_manager()
};
bool ret = map_instance.apply_history_to_provinces(
definition_manager.get_history_manager().get_province_manager(), today,
country_instance_manager,
// TODO - the following argument is for generating test pop attributes
definition_manager.get_politics_manager().get_issue_manager()
definition_manager.get_politics_manager().get_issue_manager(),
artisanal_producer_factory_pattern
);

ret &= country_instance_manager.apply_history_to_countries(
Expand Down
67 changes: 60 additions & 7 deletions src/openvic-simulation/economy/GoodInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,87 @@ using namespace OpenVic;

GoodInstance::GoodInstance(GoodDefinition const& new_good_definition)
: HasIdentifierAndColour { new_good_definition },
buy_lock { std::make_unique<std::mutex>() },
sell_lock { std::make_unique<std::mutex>() },
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(GoodBuyUpToOrder&& buy_up_to_order) {
const std::lock_guard<std::mutex> lock {*buy_lock};
buy_up_to_orders.push_back(std::move(buy_up_to_order));
}

void GoodInstance::add_market_sell_order(GoodMarketSellOrder&& market_sell_order) {
const std::lock_guard<std::mutex> lock {*sell_lock};
market_sell_orders.push_back(std::move(market_sell_order));
}

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) {
const fixed_point_t market_sell_quantity = market_sell_order.get_quantity();
supply_running_total += market_sell_quantity;
for (GoodMarketSellOrder const& market_sell_order : market_sell_orders) {
supply_running_total += market_sell_order.get_quantity();
}

fixed_point_t new_price;
fixed_point_t fraction_bought = fixed_point_t::_1();
fixed_point_t fraction_sold = fixed_point_t::_1();
if (demand_running_total > supply_running_total) {
new_price = max_next_price;
fraction_bought = supply_running_total / demand_running_total;
} else if (demand_running_total < supply_running_total) {
new_price = min_next_price;
fraction_sold = demand_running_total / supply_running_total;
} else {
new_price = price;
}

for (GoodBuyUpToOrder const& buy_up_to_order : buy_up_to_orders) {
const fixed_point_t quantity_bought = fraction_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 quantity_sold = fraction_sold * market_sell_order.get_quantity();
market_sell_order.get_after_trade()({
market_sell_quantity,
market_sell_quantity * price
quantity_sold,
quantity_sold * 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) {
Expand Down
8 changes: 8 additions & 0 deletions src/openvic-simulation/economy/GoodInstance.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <mutex>

#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"
Expand All @@ -18,19 +19,26 @@ namespace OpenVic {
friend struct GoodInstanceManager;

private:
std::unique_ptr<std::mutex> buy_lock;
std::unique_ptr<std::mutex> 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<GoodBuyUpToOrder> buy_up_to_orders;
std::deque<GoodMarketSellOrder> 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(GoodBuyUpToOrder&& buy_up_to_order);
void add_market_sell_order(GoodMarketSellOrder&& market_sell_order);

//not thread safe
Expand Down
118 changes: 111 additions & 7 deletions src/openvic-simulation/economy/production/ArtisanalProducer.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,117 @@
#include "ArtisanalProducer.hpp"

#include "openvic-simulation/economy/GoodDefinition.hpp"
#include "openvic-simulation/economy/trading/BuyResult.hpp"
#include "openvic-simulation/economy/trading/SellResult.hpp"
#include "openvic-simulation/pop/Pop.hpp"

using namespace OpenVic;

ArtisanalProducer::ArtisanalProducer(
ProductionType const& new_production_type,
MarketInstance& new_market_instance,
ModifierEffectCache const& new_modifier_effect_cache,
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 },
stockpile { new_stockpile },
production_type { new_production_type },
current_production { new_current_production }
{}

void ArtisanalProducer::artisan_tick(Pop& pop) {
GoodDefinition::good_definition_map_t goods_to_buy_and_max_price { };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we merge artisanal inputs and pop needs?
Can pops consume their own produce?
How is money allocated between inputs and needs?

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();
if (desired_quantity == fixed_point_t::_0()) {
continue;
}
inputs_bought_scalar = std::min(stockpile[input_good_ptr] / desired_quantity, inputs_bought_scalar);
GoodInstance const& good = good_instance_manager.get_good_instance_from_definition(*input_good_ptr);
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 = total_demand_value == fixed_point_t::_0()
? fixed_point_t::_1()
: 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 max_quantity_to_buy = demand[input_good_ptr] - stockpile[input_good_ptr];
const fixed_point_t money_to_spend = optimal_quantity * max_price;
pop.add_artisan_inputs_expense(money_to_spend);
market_instance.place_buy_up_to_order({
*input_good_ptr,
max_quantity_to_buy,
money_to_spend,
[this, &pop, input_good_ptr](const BuyResult buy_result) -> void {
pop.add_artisan_inputs_expense(-buy_result.get_money_left());
stockpile[input_good_ptr] += buy_result.get_quantity_bought();
}
});
}
}
}

current_production = production_type.get_base_output_quantity()
wvpm marked this conversation as resolved.
Show resolved Hide resolved
* 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,
[&pop](const SellResult sell_result) -> void {
pop.add_artisanal_income(sell_result.get_money_gained());
}
});
}
}
18 changes: 13 additions & 5 deletions src/openvic-simulation/economy/production/ArtisanalProducer.hpp
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
#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/types/fixed_point/FixedPoint.hpp"
#include "openvic-simulation/utility/Getters.hpp"

namespace OpenVic {
struct Pop;

struct ArtisanalProducer {
private:
MarketInstance& market_instance;
ModifierEffectCache const& modifier_effect_cache;
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,
GoodDefinition::good_definition_map_t&& new_stockpile,
ProductionType const& new_production_type,
fixed_point_t new_current_production
);
void artisan_tick(Pop& pop);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include "ArtisanalProducerFactoryPattern.hpp"

#include "openvic-simulation/economy/GoodInstance.hpp"

using namespace OpenVic;

ArtisanalProducerFactoryPattern::ArtisanalProducerFactoryPattern(
MarketInstance& new_market_instance,
ModifierEffectCache const& new_modifier_effect_cache,
ProductionTypeManager const& new_production_type_manager
) : index { -1 },
unlocked_artisanal_production_types { },
market_instance { new_market_instance },
modifier_effect_cache { new_modifier_effect_cache },
production_type_manager { new_production_type_manager }
{ }

std::unique_ptr<ArtisanalProducer> ArtisanalProducerFactoryPattern::CreateNewArtisanalProducer() {
//TODO update unlocked_artisanal_production_types when goods are unlocked
if (index == -1) {
for (ProductionType const& production_type : production_type_manager.get_production_types()) {
if (production_type.get_template_type() == ProductionType::template_type_t::ARTISAN) {
GoodInstance const& good_instance = market_instance.get_good_instance_manager().get_good_instance_from_definition(
production_type.get_output_good()
);
if (!good_instance.get_is_available()) {
continue;
}

unlocked_artisanal_production_types.push_back(&production_type);
}
}
}

if (unlocked_artisanal_production_types.size() == 0) {
Logger::error("CreateNewArtisanalProducer was called but there are no artisanal production types.");
}

//TODO select production type the way Victoria 2 does it (random?)
index = (index+1) % unlocked_artisanal_production_types.size();
ProductionType const* random_artisanal_production_type = unlocked_artisanal_production_types[index];

return std::make_unique<ArtisanalProducer>(
market_instance,
modifier_effect_cache,
GoodDefinition::good_definition_map_t{},
*random_artisanal_production_type,
fixed_point_t::_0()
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include <memory>

#include "openvic-simulation/economy/production/ArtisanalProducer.hpp"
#include "openvic-simulation/economy/production/ProductionType.hpp"
#include "openvic-simulation/economy/trading/MarketInstance.hpp"
#include "openvic-simulation/modifier/ModifierEffectCache.hpp"

namespace OpenVic {
struct Pop;

struct ArtisanalProducerFactoryPattern {
private:
int index;
std::vector<ProductionType const*> unlocked_artisanal_production_types;
MarketInstance& market_instance;
ModifierEffectCache const& modifier_effect_cache;
ProductionTypeManager const& production_type_manager;

public:
ArtisanalProducerFactoryPattern(
MarketInstance& new_market_instance,
ModifierEffectCache const& new_modifier_effect_cache,
ProductionTypeManager const& new_production_type_manager
);

std::unique_ptr<ArtisanalProducer> CreateNewArtisanalProducer();
};
}
Loading
Loading