public void SellPhase() { // Get all goods up for sale. _productSupply = Populous.SellPhase(); // Reset Shortfall to zero. Shortfall = new ProductAmountCollection(); // Fill Surplus preemtively, we'll remove bought products later. _surplus = ProductSupply.Copy(); // While we're at it, also get total demand of all products _productDemand = Populous.TotalDemand(); // And the hypothetical total production available. _productionCapacity = Populous.TotalProduction(); }
public void ReturnNecissaryMaintenanceCostsAndMaintenanceMetFromRunMaintenanceWhenStorageIsEmpty() { // Setup storage MaintStorage = new ProductAmountCollection(); // satisfaction Satisfaction var sat = 0.0; // Get Result var result = sut.RunMaintenance(1, MaintStorage, out sat); // Assert Satisfaction is correct. Assert.That(sat, Is.EqualTo(0)); // Assert that Result is correct. AssertCollectionContains(result, MaintenaceProductMock1, 0); AssertCollectionContains(result, MaintenaceProductMock2, 0); }
public void ReturnEmptyCollectionAndFullSatisfactionWhenMaintenanceIsEmpty() { // Setup storage MaintStorage = new ProductAmountCollection(); // setup maintenance sut.Maintenance = new ProductAmountCollection(); // satisfaction Satisfaction var sat = 0.0; // Get Result var result = sut.RunMaintenance(1, MaintStorage, out sat); // Assert Satisfaction is correct. Assert.That(sat, Is.EqualTo(1)); // Assert that Result is correct. Assert.That(result.Count(), Is.EqualTo(0)); }
/// <summary> /// Consumes the given set of goods. /// </summary> /// <param name="goods">The goods to attempt to consume.</param> /// <param name="satisfaction">The satisfaction we'll be filling out as we go.</param> /// <returns>The change in products stored.</returns> private IProductAmountCollection ConsumeGoods(IProductAmountCollection goods, IProductAmountCollection satisfaction) { var result = new ProductAmountCollection(); // for each good to consume. foreach (var pair in goods) { // Get the item and amount of the person. var product = pair.Item1; var amount = pair.Item2; // Assume All items being consumed are in storage already, // if they aren't we have a consistency problem. // get the satisfaction, capping it at 1. var sat = Math.Min(1, Storage.GetProductValue(product) / amount); // If satisfaction can't be met, subtract what you can. if (sat < 1) { result.SubtractProducts(product, Storage.GetProductValue(product)); Storage.SubtractProducts(product, Storage.GetProductValue(product)); } else // If greater than 1, then substract everything needed. { result.SubtractProducts(product, amount); Storage.SubtractProducts(product, amount); } // Finally, set it's satisfaction. satisfaction.SetProductAmount(product, sat); } // Return change in products stored. return(result); }
public void CompleteTransaction(IProductAmountCollection transaction) { if (transaction is null) { throw new ArgumentNullException(nameof(transaction)); } // add and remove the trasaction to storage Storage.AddProducts(transaction); // Add to for sale, rather than recalculating it entirely ForSale.AddProducts(transaction); // quickly remove any products from for sale that are 0 or less. if (ForSale.Any(x => x.Item2 <= 0)) { var gone = ForSale.Where(x => x.Item2 == 0).ToList(); foreach (var product in gone) { ForSale.DeleteProduct(product.Item1); } } }
public void Setup() { other = new Process(); jobMock = new Mock <IJob>(); jobInputs = new ProductAmountCollection(); jobOutputs = new ProductAmountCollection(); jobMock.Setup(x => x.Inputs) .Returns(jobInputs); jobMock.Setup(x => x.Outputs) .Returns(jobOutputs); var id = Guid.NewGuid(); ProductMock1 = new Mock <IProduct>(); ProductMock1.Setup(x => x.Id) .Returns(id); sut = new Process(); }
public void Setup() { #region Needs LifeNeed = new Mock <IProduct>(); LifeNeed.Setup(x => x.Id).Returns(LifeNeedId); LifeNeed.Setup(x => x.Name).Returns("LifeNeed"); DailyNeed = new Mock <IProduct>(); DailyNeed.Setup(x => x.Id).Returns(DailyNeedId); DailyNeed.Setup(x => x.Name).Returns("DailyNeed"); LuxNeed = new Mock <IProduct>(); LuxNeed.Setup(x => x.Id).Returns(LuxNeedId); LuxNeed.Setup(x => x.Name).Returns("LuxNeed"); LifeNeedsMock = new ProductAmountCollection(); LifeNeedsMock.AddProducts(LifeNeed.Object, 1); DailyNeedsMock = new ProductAmountCollection(); DailyNeedsMock.AddProducts(DailyNeed.Object, 1); LuxuryNeedsMock = new ProductAmountCollection(); LuxuryNeedsMock.AddProducts(LuxNeed.Object, 1); #endregion Needs #region JobGoods JobInput = new Mock <IProduct>(); JobInput.Setup(x => x.Id).Returns(JobInputId); JobInput.Setup(x => x.Name).Returns("JobInput"); JobOutput = new Mock <IProduct>(); JobOutput.Setup(x => x.Id).Returns(JobOutputId); JobOutput.Setup(x => x.Name).Returns("JobOutput"); JobCapital = new Mock <IProduct>(); JobCapital.Setup(x => x.Id).Returns(JobCapitalId); JobCapital.Setup(x => x.Name).Returns("JobCapital"); ProducedGood = new Mock <IProduct>(); ProducedGood.Setup(x => x.Id).Returns(ProducedGoodId); JobInputs = new ProductAmountCollection(); JobInputs.AddProducts(JobInput.Object, 1); JobOutputs = new ProductAmountCollection(); JobOutputs.AddProducts(JobOutput.Object, 1); JobCapitals = new ProductAmountCollection(); JobCapitals.AddProducts(JobCapital.Object, 1); ProducedGoods = new ProductAmountCollection(); ProducedGoods.AddProducts(ProducedGood.Object, 1); #endregion JobGoods #region JobSetup JobMock = new Mock <IJob>(); JobMock.Setup(x => x.Inputs) .Returns(JobInputs); JobMock.Setup(x => x.Outputs) .Returns(JobOutputs); JobMock.Setup(x => x.Capital) .Returns(JobCapitals); JobMock.Setup(x => x.LaborRequirements) .Returns(1); JobMock.Setup(x => x.Inputs) .Returns(JobInputs); JobMock.Setup(x => x.Capital) .Returns(JobCapitals); #endregion JobSetup LaborMock = new Mock <IProduct>(); LaborMock.Setup(x => x.Id).Returns(LaborId); currencyValues = new ProductAmountCollection(); CurrencyMock1 = new Mock <IProduct>(); CurrencyMock1.Setup(x => x.Id).Returns(Currency1Id); CurrencyMock1.Setup(x => x.Name).Returns("Currency1"); CurrencyMock2 = new Mock <IProduct>(); CurrencyMock2.Setup(x => x.Id).Returns(Currency2Id); CurrencyMock2.Setup(x => x.Name).Returns("Currency2"); MarketMock = new Mock <IMarket>(); MarketMock.Setup(x => x.GetPrice(LifeNeed.Object)) .Returns(100); MarketMock.Setup(x => x.GetPrice(DailyNeed.Object)) .Returns(100); MarketMock.Setup(x => x.GetPrice(LuxNeed.Object)) .Returns(100); MarketMock.Setup(x => x.GetPrice(JobInput.Object)) .Returns(100); MarketMock.Setup(x => x.GetPrice(JobOutput.Object)) .Returns(100); MarketMock.Setup(x => x.GetPrice(JobCapital.Object)) .Returns(100); MarketMock.Setup(x => x.GetPrice(ProducedGood.Object)) .Returns(100); MarketMock.Setup(x => x.GetPrice(CurrencyMock1.Object)) .Returns(100); MarketMock.Setup(x => x.GetPrice(CurrencyMock2.Object)) .Returns(100); MarketMock.Setup(x => x.AcceptedCurrencies) .Returns(new List <IProduct> { CurrencyMock1.Object, CurrencyMock2.Object }); sut = new PopulationGroup { Id = TestId, Name = TestName, Count = PopCount, JobLabor = LaborMock.Object, DailyNeeds = DailyNeedsMock, LifeNeeds = LifeNeedsMock, LuxuryNeeds = LuxuryNeedsMock, PrimaryJob = JobMock.Object, Priority = Priority1, Storage = new ProductAmountCollection(), ForSale = new ProductAmountCollection(), SecondaryJobs = new List <IJob>(), SkillLevel = SkillLevel, SkillName = SkillName, }; sut.InitializeStorage(); }
// A helper function for avsserting products in the collection are correct. private void AssertProductAmountIsEqual(IProductAmountCollection collection, Mock <IProduct> product, double value) { Assert.That(collection.GetProductValue(product.Object), Is.EqualTo(value)); }
/// <summary> /// Buys good via barter. /// </summary> /// <param name="buyerStock">The buyer's goods up for trade.</param> /// <param name="good">The good being traded for.</param> /// <param name="amount">The amount of the good being bought.</param> /// <param name="market">The market that the barter is taking place in.</param> /// <returns>The resulting change in goods for the buyer.</returns> public IProductAmountCollection BarterGood(IProductAmountCollection buyerStock, IProduct good, double amount, IMarket market) { // TODO, update to use the coincidence of wants, // it will discourage barter more than anything. if (buyerStock == null) { throw new ArgumentNullException(nameof(buyerStock)); } if (good == null) { throw new ArgumentNullException(nameof(good)); } if (market == null) { throw new ArgumentNullException(nameof(market)); } if (amount <= 0) { throw new ArgumentOutOfRangeException(nameof(amount)); } // the return result of the barter var result = new ProductAmountCollection(); // TODO a barter modifier, to discourage or encourage bartering. // This should become more flexible later. var BarterMod = 1; // Get how much we can or want to get amount = Math.Min(amount, ForSale.GetProductValue(good)); // get the price of the good. var totalPrice = GetPrice(good, market.GetPrice(good)) * amount * BarterMod; // get the total price of what's offered. double barterVal = 0; foreach (var product in buyerStock) { barterVal += market.GetPrice(product.Item1) * product.Item2; } // the barter we're trading for the goods. IProductAmountCollection barter; // if the available barter is greater than the price, begin bartering if (barterVal >= totalPrice) { // Use get change for the easiest option. barter = market.ChangeForPrice(buyerStock, totalPrice); // don't go more accurate, barter isn't supposed to be more accurate to coins, // no one would accept change in bits of wheat. // Add the goods being bought result.AddProducts(good, amount); // Remove the goods being traded. result.AddProducts(barter.Multiply(-1)); } else { // if it's not enough, throw it all in, and buy what you can. double buyableUnits = 0; // if the good is fractional if (good.Fractional) { // just divide buyableUnits = barterVal / (market.GetPrice(good) * BarterMod); } else { // round down buyableUnits = Math.Floor(barterVal / (market.GetPrice(good) * BarterMod)); } // If we can buy anything, do so. if (buyableUnits > 0) { // add the goods result.AddProducts(good, buyableUnits); // subtract the goods result.AddProducts(buyerStock.Multiply(-1)); } } // No change, this is bartering. // We've done what we can, move on. return(result); }
public IProductAmountCollection BuyGood(IProductAmountCollection cash, IProduct good, double amount, IMarket market) { // Sanity check for nulls. if (cash is null) { throw new ArgumentNullException(nameof(cash)); } if (good is null) { throw new ArgumentNullException(nameof(good)); } if (market is null) { throw new ArgumentNullException(nameof(market)); } if (amount <= 0) { throw new ArgumentOutOfRangeException(nameof(amount)); } // The collection we're returning. var result = new ProductAmountCollection(); // get how much to buy, what's available, or what's desired. var available = Math.Min(amount, ForSale.GetProductValue(good)); // get the price of that good var totalPrice = GetPrice(good, market.GetPrice(good)) * available; // get the cash needed for the goods var money = market.ChangeForPrice(cash, totalPrice); // get our available money total var totalMoney = money.Sum(x => market.GetPrice(x.Item1, x.Item2)); // if the money is enough, just buy outright if (totalMoney >= totalPrice) { // Add what they're buying. result.AddProducts(good, available); // remove what they spent result.AddProducts(money.Multiply(-1)); } else if (!good.Fractional && totalMoney < market.GetPrice(good)) {// If the total money is not enough for a single unit, and the good can't be divided // we can't actually buy anything, so just return an empty transaction return(new ProductAmountCollection()); } else // if it's not enough { // get the buyable units of the good double buyableUnits = 0; // if we can buy fractionally if (good.Fractional) { // just do the math straight. buyableUnits = totalMoney / market.GetPrice(good); } else // if we can't { // take what we can and round down, seller should always make more than the buyer here. buyableUnits = Math.Floor(totalMoney / market.GetPrice(good)); } // if we can buy any units if (buyableUnits > 0) { // buy them add the units to the results result.AddProducts(good, buyableUnits); // subtract the cash. result.AddProducts(money.Multiply(-1)); } } // Get change, if any. var change = totalMoney - market.GetPrice(good) * result.GetProductValue(good); // get the change, if any IProductAmountCollection buyersChange = new ProductAmountCollection(); // if change is greater than 0. if (change > 0) // Make said change. { buyersChange = market.ChangeForPrice(GetCash(market.AcceptedCurrencies), change); } // add back the buyer's change result.AddProducts(buyersChange); // TODO, maybe add check to see if the seller shortchanged the buyer. // complete the transaction for the seller, and subtract the result. CompleteTransaction(result.Multiply(-1)); // we're done, return the change in the buyer's goods. return(result); }
public Culture() { Id = Guid.NewGuid(); _needs = new ProductAmountCollection(); _needTypes = new Dictionary <Guid, NeedType>(); }
/// <summary> /// The external option to buy from the market. /// </summary> /// <param name="product">The product to buy.</param> /// <param name="amount">How many units to buy.</param> /// <param name="sellable">The available products to trade for the product.</param> /// <returns>The change in <paramref name="sellable"/> items, plus the amount of good bought.</returns> /// <remarks>Buying goods prioritizes using currency to buy over barter of goods.</remarks> public IProductAmountCollection BuyGood(IProduct product, double amount, IProductAmountCollection cash) { throw new NotImplementedException(); }
/// <summary> /// Given a set of cash and a desired price, get the amount of the cash /// to meet the price (rounded up for safety) /// </summary> /// <param name="AvailableCash">The amount of cash avaliable.</param> /// <param name="price">The price to meet (roughly)</param> /// <returns>The appropriate cash for the price.</returns> /// <exception cref="ArgumentNullException">If <paramref name="AvailableCash"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException">If <paramref name="price"/> is less than or equal to 0.</exception> public IProductAmountCollection ChangeForPrice(IProductAmountCollection AvailableCash, double price) { // ensure cash is not null. if (AvailableCash is null) { throw new ArgumentNullException(nameof(AvailableCash)); } // ensure that the price is greater than 0 if (price <= 0) // TODO, allow this to savely give change for 0. It's not that hard. { throw new ArgumentOutOfRangeException("Price must be greater than 0."); } // first, check that all available cash can meet the price. var totalCash = AvailableCash.Sum(x => x.Item2 * ProductPrices.GetProductValue(x.Item1)); // If the total cash available is less than the price sought, return a copy of the available cash. if (totalCash < price) { return(AvailableCash.Copy()); } // since we have more than we need, var result = new ProductAmountCollection(); // start from the best and start making change, highest to lowest value. foreach (var coin in AvailableCash.OrderByDescending(x => ProductPrices.GetProductValue(x.Item1))) { // if none of that coin exist if (coin.Item2 == 0) { // add it as zero result.AddProducts(coin.Item1, 0); // and skip to the next loop continue; } // coin type var curr = coin.Item1; // coin value var val = ProductPrices.GetProductValue(curr); // if value is higher then remaining price. if (val > price) { // add it as zero result.AddProducts(curr, 0); // and skip continue; } // coin amount var amt = coin.Item2; // how many whole coins can fit into the price. var count = Math.Floor(price / val); // select cap coins at the available level. count = Math.Min(count, amt); // add to our change result.AddProducts(curr, count); // subtract the value we took out price -= val * count; // then go to the next coin. } // if there is a remainder if (price > 0) { // find the smallest coin available foreach (var coin in AvailableCash.OrderBy(x => ProductPrices.GetProductValue(x.Item1))) { // if we have any available if (result.GetProductValue(coin.Item1) < AvailableCash.GetProductValue(coin.Item1)) { // add one result.AddProducts(coin.Item1, 1); // and move on. break; // by logic, only one should be needed as we guaranteed have // more value in coins then the requested price and the // remainder should be smaller than the smallest coin. } } } // return the change. return(result); }