/// <summary> /// Readjust and Recalulate Prices. /// </summary> public void RecalculatePrices() { // TODO Allow for this to be modified by variations // TODO Create a way to allow for faster Price changes, when supply/demand differences are large. // for each product in the market. foreach (var pair in ProductPrices) { // Get the product var product = pair.Item1; double surplus; double shortfall; try { // get surplus product not spent surplus = Surplus.GetProductValue(product); // get product that was desired to buy. shortfall = Shortfall.GetProductValue(product); } catch (KeyNotFoundException) { // if the item does not exist in surplus or shortfal, then it probably was not // sold or desired in the market. Give it a boost to denote it's rarity, and try and encourage it. ProductPrices.AddProducts(product, 0.01); continue; } // the amount of change to make to the good's price. double priceChange = 0; // If any surplus and shortfall exists, price was too high if (surplus > 0 && shortfall > 0) { priceChange += -0.01; } else if (surplus > 0) { // If no shortfall but still surplus, try lowering price to sell it, oversupply is not good. priceChange += -0.01; } else if (shortfall > 0) { // if shortfall but no surplus, price is too low. priceChange += 0.01; } // In no surplus nor shortfall, then we have hit equilibrium. // No change in price. // add the change in price to the new price // TODO make this more flexible and reactive. // going in 0.01 ABS price unit sized steps is too small // and may make prices too stagnant var newPrice = ProductPrices.GetProductValue(product) + priceChange; // update to said price. ProductPrices.SetProductAmount(product, newPrice); } }
/// <summary> /// Gets a price for a good. /// </summary> /// <param name="product">The product we are pricing</param> /// <returns>The price in abstract currency.</returns> /// <exception cref="ArgumentNullException"> /// If Product is null. /// </exception> public double GetPrice(IProduct product) { if (product is null) { throw new ArgumentNullException(nameof(product)); } return(ProductPrices.GetProductValue(product)); }
/// <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); }