1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-23 22:37:55 +02:00

ResourceTrader improvements and testing finished

This commit is contained in:
Mircea TheHonestCTO
2025-09-12 08:08:18 +02:00
parent 1b5cacfc13
commit 6df299d892
5 changed files with 82 additions and 206 deletions

View File

@@ -458,6 +458,7 @@ TResource BuildAnalyzer::goldApproximate(const TResources & res)
TResource BuildAnalyzer::goldApproximate(const TResource & res, EGameResID resId) TResource BuildAnalyzer::goldApproximate(const TResource & res, EGameResID resId)
{ {
// TODO: Mircea: replace with LIBRARY->objh->resVals[resId]
switch(resId) switch(resId)
{ {
case EGameResID::WOOD: case EGameResID::WOOD:
@@ -467,7 +468,7 @@ TResource BuildAnalyzer::goldApproximate(const TResource & res, EGameResID resId
case EGameResID::SULFUR: case EGameResID::SULFUR:
case EGameResID::CRYSTAL: case EGameResID::CRYSTAL:
case EGameResID::GEMS: case EGameResID::GEMS:
return res * 125; return res * 150;
case EGameResID::GOLD: case EGameResID::GOLD:
return res; return res;
case EGameResID::MITHRIL: case EGameResID::MITHRIL:

View File

@@ -79,6 +79,7 @@ class DLL_EXPORT BuildAnalyzer
Nullkiller * aiNk; Nullkiller * aiNk;
public: public:
virtual ~BuildAnalyzer() = default;
explicit BuildAnalyzer(Nullkiller * aiNk) : aiNk(aiNk) {} explicit BuildAnalyzer(Nullkiller * aiNk) : aiNk(aiNk) {}
void update(); void update();
@@ -88,7 +89,7 @@ public:
const std::vector<TownDevelopmentInfo> & getDevelopmentInfo() const { return developmentInfos; } const std::vector<TownDevelopmentInfo> & getDevelopmentInfo() const { return developmentInfos; }
TResources getDailyIncome() const { return dailyIncome; } TResources getDailyIncome() const { return dailyIncome; }
float getGoldPressure() const { return goldPressure; } float getGoldPressure() const { return goldPressure; }
bool isGoldPressureOverMax() const; virtual bool isGoldPressureOverMax() const;
bool isBuilt(FactionID alignment, BuildingID bid) const; bool isBuilt(FactionID alignment, BuildingID bid) const;
void reset(); void reset();

View File

@@ -18,16 +18,16 @@ bool ResourceTrader::trade(BuildAnalyzer & buildAnalyzer, CCallback & cc, const
// TODO: Mircea: What about outside town markets that have better rates than a single town for example? // TODO: Mircea: What about outside town markets that have better rates than a single town for example?
// Are those used anywhere? To inspect. // Are those used anywhere? To inspect.
for (const auto * const town : cc.getTownsInfo()) for(const auto * const town : cc.getTownsInfo())
{ {
if (town->hasBuiltSomeTradeBuilding()) if(town->hasBuiltSomeTradeBuilding())
{ {
marketId = town->id; marketId = town->id;
break; break;
} }
} }
if (!marketId.hasValue()) if(!marketId.hasValue())
return false; return false;
const CGObjectInstance * obj = cc.getObj(marketId, false); const CGObjectInstance * obj = cc.getObj(marketId, false);
@@ -55,9 +55,12 @@ bool ResourceTrader::trade(BuildAnalyzer & buildAnalyzer, CCallback & cc, const
// We don't want to sell something that's necessary later on, though that could make short term a bit harder sometimes // We don't want to sell something that's necessary later on, though that could make short term a bit harder sometimes
TResources freeAfterMissingTotal = buildAnalyzer.getFreeResourcesAfterMissingTotal(ARMY_GOLD_RATIO_PER_MAKE_TURN_PASS); TResources freeAfterMissingTotal = buildAnalyzer.getFreeResourcesAfterMissingTotal(ARMY_GOLD_RATIO_PER_MAKE_TURN_PASS);
#if NK2AI_TRACE_LEVEL >= 2 logAi->info(
logAi->info("ResourceTrader: Free %s. FreeAfterMissingTotal %s. MissingNow %s", freeResources.toString(), freeAfterMissingTotal.toString(), missingNow.toString()); "ResourceTrader: Free %s. FreeAfterMissingTotal %s. MissingNow %s",
#endif freeResources.toString(),
freeAfterMissingTotal.toString(),
missingNow.toString()
);
if(ResourceTrader::tradeHelper(EXPENDABLE_BULK_RATIO, *market, missingNow, income, freeAfterMissingTotal, buildAnalyzer, cc)) if(ResourceTrader::tradeHelper(EXPENDABLE_BULK_RATIO, *market, missingNow, income, freeAfterMissingTotal, buildAnalyzer, cc))
{ {
@@ -90,7 +93,16 @@ bool ResourceTrader::tradeHelper(
if(missingNow[i] == 0) if(missingNow[i] == 0)
continue; continue;
const TResource score = BuildAnalyzer::goldApproximate(income[i] - missingNow[i], GameResID(i)); TResource score = income[i] - missingNow[i];
if(i != GameResID::GOLD)
{
int givenPerUnit;
int receivedPerUnit;
market.getOffer(i, GameResID::GOLD, givenPerUnit, receivedPerUnit, EMarketMode::RESOURCE_RESOURCE);
score *= receivedPerUnit;
logAi->trace("ResourceTrader: mostWantedScoreNeg %d for %d with market receivedPerUnit %d", mostWantedScoreNeg, i, receivedPerUnit);
}
if(score < mostWantedScoreNeg) if(score < mostWantedScoreNeg)
{ {
mostWanted = i; mostWanted = i;
@@ -124,7 +136,6 @@ bool ResourceTrader::tradeHelper(
} }
} }
#if NK2AI_TRACE_LEVEL >= 2
logAi->trace( logAi->trace(
"ResourceTrader: mostWanted: %d, mostWantedScoreNeg %d, mostExpendable: %d, mostExpendableAmountPos %d", "ResourceTrader: mostWanted: %d, mostWantedScoreNeg %d, mostExpendable: %d, mostExpendableAmountPos %d",
mostWanted, mostWanted,
@@ -132,7 +143,6 @@ bool ResourceTrader::tradeHelper(
mostExpendable, mostExpendable,
mostExpendableAmountPos mostExpendableAmountPos
); );
#endif
if(mostExpendable == mostWanted || mostWanted == EMPTY || mostExpendable == EMPTY) if(mostExpendable == mostWanted || mostWanted == EMPTY || mostExpendable == EMPTY)
return false; return false;
@@ -140,18 +150,12 @@ bool ResourceTrader::tradeHelper(
int givenPerUnit; int givenPerUnit;
int receivedPerUnit; int receivedPerUnit;
market.getOffer(mostExpendable, mostWanted, givenPerUnit, receivedPerUnit, EMarketMode::RESOURCE_RESOURCE); market.getOffer(mostExpendable, mostWanted, givenPerUnit, receivedPerUnit, EMarketMode::RESOURCE_RESOURCE);
#if NK2AI_TRACE_LEVEL >= 2
logAi->info("ResourceTrader: Offer: %d of %d for %d of %d", givenPerUnit, mostExpendable, receivedPerUnit, mostWanted); logAi->info("ResourceTrader: Offer: %d of %d for %d of %d", givenPerUnit, mostExpendable, receivedPerUnit, mostWanted);
#endif
if(!givenPerUnit || !receivedPerUnit) if(!givenPerUnit || !receivedPerUnit)
{ {
logGlobal->error( logGlobal->error(
"ResourceTrader: No offer for %d of %d, given %d, received %d. Should never happen", "ResourceTrader: No offer for %d of %d, given %d, received %d. Should never happen", mostExpendable, mostWanted, givenPerUnit, receivedPerUnit
mostExpendable,
mostWanted,
givenPerUnit,
receivedPerUnit
); );
return false; return false;
} }
@@ -175,9 +179,7 @@ bool ResourceTrader::tradeHelper(
} }
cc.trade(market.getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), givenMultiplied); cc.trade(market.getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), givenMultiplied);
#if NK2AI_TRACE_LEVEL >= 2
logAi->info("ResourceTrader: Traded %d of %s for %d receivedPerUnit of %s", givenMultiplied, mostExpendable, receivedPerUnit, mostWanted); logAi->info("ResourceTrader: Traded %d of %s for %d receivedPerUnit of %s", givenMultiplied, mostExpendable, receivedPerUnit, mostWanted);
#endif
return true; return true;
} }
} }

View File

@@ -18,7 +18,7 @@ class ResourceTrader
public: public:
// TODO: Mircea: Maybe include based on how close danger is: X as default + proportion of close danger or something around that // TODO: Mircea: Maybe include based on how close danger is: X as default + proportion of close danger or something around that
static constexpr float ARMY_GOLD_RATIO_PER_MAKE_TURN_PASS = 0.1f; static constexpr float ARMY_GOLD_RATIO_PER_MAKE_TURN_PASS = 0.1f;
static constexpr float EXPENDABLE_BULK_RATIO = 0.3f; static constexpr float EXPENDABLE_BULK_RATIO = 0.5f;
static bool trade(BuildAnalyzer & buildAnalyzer, CCallback & cc, const TResources & freeResources); static bool trade(BuildAnalyzer & buildAnalyzer, CCallback & cc, const TResources & freeResources);
static bool tradeHelper( static bool tradeHelper(

View File

@@ -15,19 +15,21 @@
class MockMarket final : public IMarket class MockMarket final : public IMarket
{ {
ObjectInstanceID id;
int marketEfficiency;
public: public:
explicit MockMarket(IGameInfoCallback * cb) : IMarket(cb) {} explicit MockMarket(const int marketEfficiency) : marketEfficiency(marketEfficiency), IMarket(nullptr) {}
~MockMarket() override = default; ~MockMarket() override = default;
MOCK_METHOD(bool, allowsTrade, (const EMarketMode mode), (const, override));
MOCK_METHOD(bool, getOffer, (int id1, int id2, int & val1, int & val2, EMarketMode mode), (const, override));
MOCK_METHOD(int, availableUnits, (const EMarketMode mode, const int marketItemSerial), (const, override));
MOCK_METHOD(std::vector<TradeItemBuy>, availableItemsIds, (const EMarketMode mode), (const, override));
MOCK_METHOD(std::set<EMarketMode>, availableModes, (), (const, override)); MOCK_METHOD(std::set<EMarketMode>, availableModes, (), (const, override));
MOCK_METHOD(int, getMarketEfficiency, (), (const, override));
ObjectInstanceID getObjInstanceID() const override ObjectInstanceID getObjInstanceID() const override
{ {
return ObjectInstanceID(); return id;
}
int getMarketEfficiency() const override
{
return marketEfficiency;
} }
}; };
@@ -35,7 +37,7 @@ class MockBuildAnalyzer final : public NK2AI::BuildAnalyzer
{ {
public: public:
explicit MockBuildAnalyzer() : BuildAnalyzer(nullptr) {} explicit MockBuildAnalyzer() : BuildAnalyzer(nullptr) {}
MOCK_METHOD(bool, getGoldPressure, (), (const)); MOCK_METHOD(bool, isGoldPressureOverMax, (), (const, override));
}; };
class MockCCallback final : public CCallback class MockCCallback final : public CCallback
@@ -50,184 +52,54 @@ public:
); );
}; };
TEST(Nullkiller2_Engine_ResourceTrader, tradeHelper) TEST(Nullkiller2_Engine_ResourceTrader, tradeHelper_crystalsToGold)
{ {
const TResources missingNow = Nulkiller2TestUtils::res(0, 0, 0, 0, 0, 0, 193445, 0); const TResources missingNow01 = Nulkiller2TestUtils::res(0, 0, 0, 4000, 0, 0, 75000, 0);
const TResources income = Nulkiller2TestUtils::res(2, 2, 2, 2, 2, 2, 1000, 0); const TResources missingNow02 = Nulkiller2TestUtils::res(0, 0, 0, 0, 0, 0, 10000, 0);
const TResources freeAfterMissingTotal = Nulkiller2TestUtils::res(1000, 1000, 1000, 1001, 1002, 1000, 0, 0); const TResources income = Nulkiller2TestUtils::res(2, 2, 2, 4000, 2, 2, 1000, 0);
const TResources freeAfterMissingTotal = Nulkiller2TestUtils::res(1000, 1000, 1000, 0, 1002, 1000, 0, 0);
MockMarket market(nullptr); MockBuildAnalyzer ba;
EXPECT_CALL(market, getOffer(4, 6, testing::_, testing::_, EMarketMode::RESOURCE_RESOURCE)).Times(1); EXPECT_CALL(ba, isGoldPressureOverMax()).Times(0);
MockBuildAnalyzer buildAnalyzer; MockMarket m01(1);
MockCCallback callback; MockMarket m20(20); // maxes out at 0.5 effectiveness anyway after 9 castles
NK2AI::ResourceTrader::tradeHelper(NK2AI::ResourceTrader::EXPENDABLE_BULK_RATIO, market, missingNow, income, freeAfterMissingTotal, buildAnalyzer, callback); MockCCallback cc;
// Case: sells 50% of crystals
EXPECT_CALL(cc, trade(m01.getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, TradeItemSell(GameResID(4)), TradeItemBuy(GameResID(6)), 501, nullptr)).Times(1);
NK2AI::ResourceTrader::tradeHelper(NK2AI::ResourceTrader::EXPENDABLE_BULK_RATIO, m01, missingNow01, income, freeAfterMissingTotal, ba, cc);
// Case: only 200 crystals because that's enough for getting the desired goal
EXPECT_CALL(cc, trade(m01.getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, TradeItemSell(GameResID(4)), TradeItemBuy(GameResID(6)), 200, nullptr)).Times(1);
NK2AI::ResourceTrader::tradeHelper(NK2AI::ResourceTrader::EXPENDABLE_BULK_RATIO, m01, missingNow02, income, freeAfterMissingTotal, ba, cc);
// Case: only 40 crystals because that's enough for getting the desired goal
EXPECT_CALL(cc, trade(m20.getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, TradeItemSell(GameResID(4)), TradeItemBuy(GameResID(6)), 40, nullptr)).Times(1);
NK2AI::ResourceTrader::tradeHelper(NK2AI::ResourceTrader::EXPENDABLE_BULK_RATIO, m20, missingNow02, income, freeAfterMissingTotal, ba, cc);
} }
// ResourceTrader: Free [13919, 13883, 13921, 13857, 13792, 13883, 14, 0]. FreeAfterMissingTotal [13859, 13819, 13891, 13833, 13718, 13763, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 193445, 0] TEST(Nullkiller2_Engine_ResourceTrader, tradeHelper_goldToGems)
// ResourceTrader: got offer: 1 of mostExpendable 2 for 125 of mostWanted: 6 {
// ResourceTrader: Traded 1547 of 2 for 125 receivedPerUnit of 6 const TResources missingNow01 = Nulkiller2TestUtils::res(0, 0, 0, 4000, 0, 200, 0, 0);
// ResourceTrader: Free [13919, 13883, 13921, 13857, 13792, 13883, 14, 0]. FreeAfterMissingTotal [13859, 13819, 12344, 13833, 13718, 13763, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 70, 0] const TResources missingNow02 = Nulkiller2TestUtils::res(0, 0, 0, 4000, 0, 4, 0, 0);
// ResourceTrader: got offer: 1 of mostExpendable 0 for 125 of mostWanted: 6 const TResources income = Nulkiller2TestUtils::res(2, 2, 2, 4000, 2, 2, 1000, 0);
// ResourceTrader: Traded 1 of 0 for 125 receivedPerUnit of 6 const TResources freeAfterMissingTotal = Nulkiller2TestUtils::res(100, 100, 100, 0, 0, 0, 100000, 0);
// ResourceTrader: Free [13908, 13883, 12374, 13857, 13722, 13883, 414, 0]. FreeAfterMissingTotal [13848, 13819, 12344, 13833, 13648, 13763, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 193075, 0]
// ResourceTrader: got offer: 1 of mostExpendable 0 for 125 of mostWanted: 6 MockBuildAnalyzer ba;
// ResourceTrader: Traded 1544 of 0 for 125 receivedPerUnit of 6 EXPECT_CALL(ba, isGoldPressureOverMax()).Times(2).WillRepeatedly(testing::Return(false));
// ResourceTrader: Free [13908, 13883, 12374, 13857, 13722, 13883, 414, 0]. FreeAfterMissingTotal [12304, 13819, 12344, 13833, 13648, 13763, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 75, 0] MockMarket m(1);
// ResourceTrader: got offer: 1 of mostExpendable 3 for 250 of mostWanted: 6 MockMarket m20(20); // maxes out at 0.5 effectiveness anyway after 9 castles
// ResourceTrader: Traded 1 of 3 for 250 receivedPerUnit of 6 MockCCallback cc;
// ResourceTrader: Free [12364, 13883, 12374, 13841, 13722, 13883, 24, 0]. FreeAfterMissingTotal [12304, 13819, 12344, 13817, 13648, 13763, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 193465, 0]
// ResourceTrader: got offer: 1 of mostExpendable 1 for 250 of mostWanted: 6 // Case: sells 50% of gold
// ResourceTrader: Traded 773 of 1 for 250 receivedPerUnit of 6 EXPECT_CALL(cc, trade(m.getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, TradeItemSell(GameResID(6)), TradeItemBuy(GameResID(5)), 50000, nullptr)).Times(1);
// ResourceTrader: Free [12364, 13883, 12374, 13841, 13722, 13883, 24, 0]. FreeAfterMissingTotal [12304, 13046, 12344, 13817, 13648, 13763, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 215, 0] NK2AI::ResourceTrader::tradeHelper(NK2AI::ResourceTrader::EXPENDABLE_BULK_RATIO, m, missingNow01, income, freeAfterMissingTotal, ba, cc);
// ResourceTrader: got offer: 1 of mostExpendable 3 for 250 of mostWanted: 6
// ResourceTrader: Traded 1 of 3 for 250 receivedPerUnit of 6 // Case: only 20,000 gold because that's enough for getting the desired goal
// ResourceTrader: Free [12364, 13110, 12374, 13837, 13722, 13883, 52524, 0]. FreeAfterMissingTotal [12304, 13046, 12344, 13813, 13648, 13763, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 140965, 0] EXPECT_CALL(cc, trade(m.getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, TradeItemSell(GameResID(6)), TradeItemBuy(GameResID(5)), 20000, nullptr)).Times(1);
// ResourceTrader: got offer: 1 of mostExpendable 3 for 250 of mostWanted: 6 NK2AI::ResourceTrader::tradeHelper(NK2AI::ResourceTrader::EXPENDABLE_BULK_RATIO, m, missingNow02, income, freeAfterMissingTotal, ba, cc);
// ResourceTrader: Traded 563 of 3 for 250 receivedPerUnit of 6
// ResourceTrader: Free [12364, 13110, 12374, 13837, 13722, 13883, 52524, 0]. FreeAfterMissingTotal [12304, 13046, 12344, 13250, 13648, 13763, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 215, 0] // Case: only 4,000 gold because that's enough for getting the desired goal
// ResourceTrader: got offer: 1 of mostExpendable 5 for 250 of mostWanted: 6 EXPECT_CALL(cc, trade(m20.getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, TradeItemSell(GameResID(6)), TradeItemBuy(GameResID(5)), 4000, nullptr)).Times(1);
// ResourceTrader: Traded 1 of 5 for 250 receivedPerUnit of 6 NK2AI::ResourceTrader::tradeHelper(NK2AI::ResourceTrader::EXPENDABLE_BULK_RATIO, m20, missingNow02, income, freeAfterMissingTotal, ba, cc);
// ResourceTrader: Free [2097, 268, 1168, 10930, 10925, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10900, 10925, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17493, 0] }
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10930, 10925, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10900, 10809, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 93, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10929, 10809, 3, 47, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10899, 10809, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17453, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10929, 10809, 3, 47, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10783, 10809, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 53, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10813, 10808, 3, 22, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10783, 10808, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17478, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10813, 10808, 3, 22, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10783, 10692, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 78, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10812, 10692, 3, 27, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10782, 10692, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17473, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10812, 10692, 3, 27, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10666, 10692, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 73, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10696, 10691, 3, 2, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10666, 10691, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17498, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10696, 10691, 3, 2, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10666, 10575, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 98, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10695, 10575, 3, 22, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10665, 10575, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17478, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10695, 10575, 3, 22, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10549, 10575, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 78, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10579, 10574, 3, 42, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10549, 10574, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17458, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10579, 10574, 3, 42, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10549, 10458, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 58, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10578, 10458, 3, 62, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10548, 10458, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17438, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10578, 10458, 3, 62, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10432, 10458, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 38, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10462, 10457, 3, 17, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10432, 10457, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17483, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10462, 10457, 3, 17, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10432, 10341, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 83, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10461, 10341, 3, 27, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10431, 10341, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17473, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10461, 10341, 3, 27, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10315, 10341, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 73, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10345, 10340, 3, 2, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10315, 10340, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17498, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10345, 10340, 3, 2, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10315, 10224, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 98, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10344, 10224, 3, 42, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10314, 10224, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17458, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10344, 10224, 3, 42, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10198, 10224, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 58, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10228, 10223, 3, 17, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10198, 10223, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17483, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10228, 10223, 3, 17, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10198, 10107, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 83, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10227, 10107, 3, 57, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10197, 10107, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17443, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10227, 10107, 3, 57, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10081, 10107, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 43, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10111, 10106, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10081, 10106, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17493, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10111, 10106, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10081, 9990, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 93, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10110, 9990, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 10080, 9990, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17493, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 10110, 9990, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9964, 9990, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 93, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9994, 9989, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9964, 9989, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17493, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9994, 9989, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9964, 9873, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 93, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9993, 9873, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9963, 9873, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17493, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9993, 9873, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9847, 9873, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 93, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9877, 9872, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9847, 9872, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17493, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9877, 9872, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9847, 9756, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 93, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9876, 9756, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9846, 9756, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17493, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9876, 9756, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9730, 9756, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 93, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9760, 9755, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9730, 9755, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17493, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9760, 9755, 3, 7, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9730, 9639, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 93, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9759, 9639, 3, 57, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9729, 9639, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17443, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9759, 9639, 3, 57, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9613, 9639, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 43, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9643, 9638, 3, 47, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9613, 9638, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 17453, 0]
// ResourceTrader: got offer: 1 of mostExpendable 4 for 150 of mostWanted: 6
// ResourceTrader: Traded 116 of 4 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9643, 9638, 3, 47, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9613, 9522, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 53, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9642, 9522, 3, 5232, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9612, 9522, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 12268, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 81 of 3 for 150 receivedPerUnit of 6
// ResourceTrader: Free [2097, 268, 1168, 9642, 9522, 3, 5232, 0]. FreeAfterMissingTotal [2097, 268, 1168, 9531, 9522, 3, 0, 0]. MissingNow [0, 0, 0, 0, 0, 0, 118, 0]
// ResourceTrader: got offer: 1 of mostExpendable 3 for 150 of mostWanted: 6
// ResourceTrader: Traded 1 of 3 for 150 receivedPerUnit of 6