diff --git a/javascript/features/economy/economy.js b/javascript/features/economy/economy.js index 58e58834c..802dc26d5 100644 --- a/javascript/features/economy/economy.js +++ b/javascript/features/economy/economy.js @@ -32,18 +32,29 @@ class Economy extends Feature { } // Calculates the value of a house after it has been in possession of a particular player for - // |ownershipSeconds| seconds. Different from normal economics, the value of a house increases + // |ownershipDuration| seconds. Different from normal economics, the value of a house increases // as the player owns it for a longer amount of time, to discourage players from switching // houses very frequently (which will turn out expensive for them). - calculateHouseValue(position, parkingLotCount, interiorValue, ownershipSeconds) { - // TODO: Implement this calculation. - return 25; + calculateHouseValue(position, parkingLotCount, interiorValue, ownershipDuration) { + return this.economyCalculator_.calculateHouseValue( + /* purchasePrice */ this.calculateHousePrice( + position, parkingLotCount, interiorValue), + /* ownershipDuration */ ownershipDuration); } // Calculates the price for the given |feature| for a house at |position|. calculateHouseFeaturePrice(position, feature) { - // TODO: Enable having different prices for different |feature|s. - const featureValue = 2; // [0, 5] + let featureValue = null; + + switch (feature) { + case 'health': // health pickup + case 'armour': // armour pickup + featureValue = 2; + break; + + default: + throw new Error('Unrecognized house feature: ' + feature); + } return this.economyCalculator_.calculateHouseFeaturePrice( /* residentialValue */ this.residentialValueMap_.query(position), diff --git a/javascript/features/economy/economy_calculator.js b/javascript/features/economy/economy_calculator.js index 2b88d4fac..c3846fc20 100644 --- a/javascript/features/economy/economy_calculator.js +++ b/javascript/features/economy/economy_calculator.js @@ -28,7 +28,7 @@ class EconomyCalculator { // |interiorValue| must be in range of [0, 9]. The variance factor will be included. This value // is being calculated based on the following spreadsheet: // - // https://docs.google.com/spreadsheets/d/1rgydvkX5DEX7tXH8pvQggIOTg2RQpvfqzJEH6ZsKbyE/edit + // https://docs.google.com/spreadsheets/d/1R01tp9WF_lHS3JP2DDqsLKhZOM7illJa3DF4gK3t3yY/edit // calculateHousePrice(residentialValue, parkingLotCount, interiorValue) { if (residentialValue < 0 || residentialValue > 5) { @@ -78,11 +78,37 @@ class EconomyCalculator { return fixedPrice * varianceFactor; } + // Calculates the current house value based on the |purchasePrice| and the |ownershipDuration|. + // The value will be influenced by the variance factor by up to 5%. + // + // https://docs.google.com/spreadsheets/d/1R01tp9WF_lHS3JP2DDqsLKhZOM7illJa3DF4gK3t3yY/edit + // + calculateHouseValue(purchasePrice, ownershipDuration) { + const BaseRefundPercentage = 35; + const MaximumRefundPercentage = 70; + + const MaximizeRefundPeriod = 30 /* days */ * 86400; + + // Determine the refund percentage based on the |ownershipDuration|. + const refundPercentage = + Math.min(MaximumRefundPercentage, + BaseRefundPercentage + ((MaximumRefundPercentage - BaseRefundPercentage) * + (ownershipDuration / MaximizeRefundPeriod))); + + // Calculate the refund based on the |purchasePrice| and the |refundPercentage|. + const refund = purchasePrice * (refundPercentage / 100); + + // The variance will either decrease or increase the refund by, at most, 5%. + const varianceFactor = 1 + ((-50 + this.varianceValue_) / 1000); + + return refund * varianceFactor; + } + // Calculates the price for a feature of value |featureValue|, which must be in range of [0, 5], // at a location having |residentialValue|, which must be in range of [0, 5] as well. The // variance factor will be included in the feature's price as well. // - // https://docs.google.com/spreadsheets/d/1rgydvkX5DEX7tXH8pvQggIOTg2RQpvfqzJEH6ZsKbyE/edit + // https://docs.google.com/spreadsheets/d/1R01tp9WF_lHS3JP2DDqsLKhZOM7illJa3DF4gK3t3yY/edit // calculateHouseFeaturePrice(residentialValue, featureValue) { if (residentialValue < 0 || residentialValue > 5) { diff --git a/javascript/features/economy/economy_calculator.test.js b/javascript/features/economy/economy_calculator.test.js index 8dc3b7387..062389f70 100644 --- a/javascript/features/economy/economy_calculator.test.js +++ b/javascript/features/economy/economy_calculator.test.js @@ -82,6 +82,33 @@ describe('EconomyCalculator', (it, beforeEach, afterEach) => { }); }); + it('should be able to determine the value for a house appropriately', assert => { + // Returns the value for a house based on its purchase price and ownership days, + const calculateHousePrice = (purchasePrice, ownershipDays, varianceValue) => { + calculator.setVarianceValueForTests(varianceValue); + return calculator.calculateHouseValue(purchasePrice, ownershipDays * 86400); + }; + + const PurchasePrice = 80643816.73; + const MaximumRefundValue = 0.7 * PurchasePrice; + + // Change detector tests against the spreadsheet. + assert.closeTo(calculateHousePrice(PurchasePrice, 0.5, 50), 28695758.12, 1); + assert.closeTo(calculateHousePrice(PurchasePrice, 1, 50), 29166180.38, 1); + assert.closeTo(calculateHousePrice(PurchasePrice, 3, 50), 31047869.44, 1); + assert.closeTo(calculateHousePrice(PurchasePrice, 7, 50), 34811247.56, 1); + assert.closeTo(calculateHousePrice(PurchasePrice, 14, 50), 41397159.25, 1); + assert.closeTo(calculateHousePrice(PurchasePrice, 30.25, 50), 56450671.71, 1); + assert.closeTo(calculateHousePrice(PurchasePrice, 60.50, 50), 56450671.71, 1); + assert.closeTo(calculateHousePrice(PurchasePrice, 90.75, 50), 56450671.71, 1); + + // Verify that the variance is no more than 5% of the total house price. + assert.closeTo(calculateHousePrice(PurchasePrice, 100, 0), 0.95 * MaximumRefundValue, 1); + assert.closeTo(calculateHousePrice(PurchasePrice, 100, 50), MaximumRefundValue, 1); + assert.closeTo(calculateHousePrice(PurchasePrice, 100, 100), 1.05 * MaximumRefundValue, 1); + + }); + it('should be able to price features for houses appropriately', assert => { // Returns the house price that has been determined for the three input values. const calculateFeaturePrice = (residentialValue, featureValue, varianceValue) => {