Beispiel #1
0
        /// <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);
        }
        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);
        }
Beispiel #3
0
        /// <summary>
        /// Buys good from the market.
        /// </summary>
        /// <param name="buyer">The one buying the good.</param>
        /// <param name="good">The good they are trying to buy.</param>
        /// <param name="amount">How much they are trying to buy.</param>
        /// <returns>The Receipt of purchases</returns>
        public IProductAmountCollection BuyGoodsFromMarket(IPopulationGroup buyer,
                                                           IProduct good, double amount)
        {
            if (buyer is null)
            {
                throw new ArgumentNullException(nameof(buyer));
            }
            if (good is null)
            {
                throw new ArgumentNullException(nameof(good));
            }
            if (amount <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(amount));
            }

            // The result of the purchases.
            IProductAmountCollection result = new ProductAmountCollection();

            // First buy from local merchants, they only accept cash.
            result = Populous.Merchants
                     .BuyGood(buyer.GetCash(AcceptedCurrencies), good, amount, this);

            // see how much was bought.
            double remainder = 0;

            // if any was bought, update what we are seeking.
            if (result.Contains(good))
            {
                remainder = amount - result.GetProductValue(good);
                // and complete the transaction
                buyer.CompleteTransaction(result);
            }

            // if no remainder, return
            if (remainder <= 0)
            {
                return(result);
            }

            // Then buy from everyone else via both cash and barter.
            foreach (var seller in Populous.GetPopsSellingProduct(good))
            {
                // If someone is selling the good, buy or barter with them.
                var reciept = BuyGoods(buyer, good, remainder, seller);

                // if something was bought
                if (reciept.Count() > 0)
                {
                    // remove it from the remainder
                    remainder -= reciept.GetProductValue(good);

                    // add it to our result
                    result.AddProducts(reciept);
                }

                // if nothing remains, we're done.
                if (remainder <= 0)
                {
                    return(result);
                }
            }

            // Finish buy going to the travelling merchants, if all else fails.
            foreach (var travSeller in TravellingMerchantsSelling(good))
            {
                var reciept = travSeller.BuyGood(buyer.GetCash(AcceptedCurrencies), good, remainder, this);

                // if something was bought
                if (reciept.Count() > 0)
                {
                    // remove it from remainder
                    remainder -= reciept.GetProductValue(good);

                    // Complete the transaction for the buyer
                    buyer.CompleteTransaction(reciept);

                    // add it to the result
                    result.AddProducts(reciept);
                }

                // if nothing remains, return
                if (remainder <= 0)
                {
                    return(result);
                }
            }

            // return the ultimate receipt.
            return(result);
        }