Last active
January 8, 2023 12:08
-
-
Save jessejputnam/2e81fbd676085618c28cd07458ad5d19 to your computer and use it in GitHub Desktop.
Snippet that simulates DnD 5e tavern marketing budget scenarios to determine optimal marketing budget from players
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* DnD 5e Waterdeep campaign includes a Tavern controlled by players, that for | |
* every 10-day cycle, must have upkeep given but also can earn income from | |
* the business. That net income is determined by dice roll. However, players | |
* may opt to add a --marketing budget-- which adds on to the dice roll... | |
* but at what (literal) cost? | |
* | |
* To answer the question of which amount of gold would grant best return on | |
* investment, I wrote this to run scenarios to determine, over many iterations, | |
* what the best choice of marketing budget would be. | |
*/ | |
"use strict"; | |
// Helper Functions | |
/** | |
* Gets dice roll value based on variably sided die | |
* @param {number} sides - Number of sides on the die | |
* @returns {number} - Roll value | |
*/ | |
function roll(sides) { | |
return Math.floor(Math.random() * sides) + 1; | |
} | |
/** | |
* Determines gross income based on dice roll | |
* @param {number} value - | |
* @returns {number} | |
*/ | |
function getGrossIncome(value) { | |
const scenario = Math.trunc((value - 1) * 0.1); | |
if (scenario < 2) return -90; | |
else if (scenario === 2) return -60; | |
else if (scenario === 3) return -30; | |
else if (scenario === 4 || scenario === 5) return 0; | |
else if (scenario === 6 || scenario === 7) return 5 * roll(6); | |
else if (scenario === 8) return 5 * (roll(8) + roll(8)); | |
else return 5 * (roll(10) + roll(10) + roll(10)); | |
} | |
/** | |
* Calculates net income for the week based on marketing budget | |
* @param {number} budget - Total gold given by players as marketing budget | |
* @returns {number} - Net income from 10-day cycle | |
*/ | |
function getNetIncome(budget) { | |
const value = roll(100) + 10 + budget; | |
return getGrossIncome(value) - budget; | |
} | |
// MAIN FUNCTION | |
/** | |
* Calculates the optimal marketing budget by running sample_size | |
* trials. Marketing budgets checked from 0gp to 90gp as beyond | |
* guarantees money lost. | |
* @returns {{max_roi: number, budget: number, all_roi: number[]}} | |
* max_roi: optimal return of interest | |
* budget: optimal budget | |
* all_roi: avg roi per budget | |
*/ | |
function findOptimalMarketingBudget() { | |
const sample_size = 1_000_000; | |
// Indices correspond to marketing budget (arr[0] = 0gp; arr[90] = 90gp) | |
const trial_sums = new Array(91).fill(0); | |
// Run experiment sample_size times | |
for (let i = 0; i < sample_size; i++) { | |
// For each trial, add a cycle to arr[index] where the index is the marketing budget | |
trial_sums.forEach( | |
(_, budget) => (trial_sums[budget] += getNetIncome(budget)) | |
); | |
} | |
// Initialize variables for storing return of investment max and its corresponding budget | |
let max_roi = -Infinity; | |
let max_roi_budget; | |
// Get mean averages and find max_roi and budget | |
const all_roi = trial_sums.map((trial_sum, budget) => { | |
const avg_roi = Number((trial_sum / sample_size).toFixed(2)); | |
if (avg_roi > max_roi) { | |
max_roi = avg_roi; | |
max_roi_budget = budget; | |
} | |
return avg_roi; | |
}); | |
return { | |
max_roi, | |
budget: max_roi_budget, | |
all_roi | |
}; | |
} | |
console.log(findOptimalMarketingBudget()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment