From d62011fc81e36aea3fa1db69f1b742c02467330b Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 5 Feb 2019 14:35:29 -0500 Subject: [PATCH 01/23] Updated gaming plugin docstrings Updates the docstrings in the gaming plugin to be more consistent with the rest of the project. --- plugins/gaming.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index 0a6d1e699..83719fa4d 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -24,6 +24,7 @@ def clamp(n, min_value, max_value): """Restricts a number to a certain range of values, returning the min or max value if the value is too small or large, respectively + :param n: The value to clamp :param min_value: The minimum possible value :param max_value: The maximum possible value @@ -34,6 +35,7 @@ def clamp(n, min_value, max_value): def n_rolls(count, n): """roll an n-sided die count times + :type count: int :type n: int | str """ @@ -58,7 +60,6 @@ def dice(text, notice): :type text: str """ - if hasattr(text, "groups"): text, desc = text.groups() else: # type(text) == str @@ -138,7 +139,6 @@ def coin(text, notice, action): :type text: str """ - if text: try: amount = int(text) From 716ac8754cc4cbc5d43bed8a0d1eda3ac81d7a28 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 5 Feb 2019 14:42:21 -0500 Subject: [PATCH 02/23] Refactored coin function to use string constants The user facing strings were hardcoded into the function rather than being constants. Having them as constants improves readability and maintainability. --- plugins/gaming.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index 83719fa4d..3c9680530 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -15,6 +15,12 @@ from cloudbot import hook +# String constants for the coin flip function +INVALID_NUMBER = "Invalid input '{}': not a number" +NO_COIN = "makes a coin flipping motion" +SINGLE_COIN = "flips a coin and gets {}." +MANY_COINS = "flips {} coins and gets {} heads and {} tails." + whitespace_re = re.compile(r'\s+') valid_diceroll = re.compile(r'^([+-]?(?:\d+|\d*d(?:\d+|F))(?:[+-](?:\d+|\d*d(?:\d+|F)))*)( .+)?$', re.I) sign_re = re.compile(r'[+-]?(?:\d*d)?(?:\d+|F)', re.I) @@ -143,19 +149,19 @@ def coin(text, notice, action): try: amount = int(text) except (ValueError, TypeError): - notice("Invalid input '{}': not a number".format(text)) + notice(INVALID_NUMBER.format(text)) return else: amount = 1 if amount == 1: - action("flips a coin and gets {}.".format(random.choice(["heads", "tails"]))) + action(SINGLE_COIN.format(random.choice(["heads", "tails"]))) elif amount == 0: - action("makes a coin flipping motion") + action(NO_COIN) else: mu = .5 * amount sigma = (.75 * amount) ** .5 n = random.normalvariate(mu, sigma) heads = clamp(int(round(n)), 0, amount) tails = amount - heads - action("flips {} coins and gets {} heads and {} tails.".format(amount, heads, tails)) + action(MANY_COINS.format(amount, heads, tails)) From b03af565bb8dd02cb9451a584cd3ccd9c632c7ec Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 5 Feb 2019 14:51:29 -0500 Subject: [PATCH 03/23] Rewrote coin flip to use a cleaner method The original way of calculating coin flips was more complex and harder to read than needed. This changes it to use a simpler and cleaner method that's still statistically close enough to actual coin flip results. --- plugins/gaming.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index 3c9680530..085ba3edb 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -145,23 +145,23 @@ def coin(text, notice, action): :type text: str """ + amount = 1 if text: try: amount = int(text) except (ValueError, TypeError): notice(INVALID_NUMBER.format(text)) return - else: - amount = 1 - if amount == 1: - action(SINGLE_COIN.format(random.choice(["heads", "tails"]))) - elif amount == 0: + if amount == 0: action(NO_COIN) + elif amount == 1: + side = random.choice(['heads', 'tails']) + action(SINGLE_COIN.format(side)) else: - mu = .5 * amount - sigma = (.75 * amount) ** .5 - n = random.normalvariate(mu, sigma) - heads = clamp(int(round(n)), 0, amount) + if amount > 100: + heads = sum(random.randint(0, 1) for _ in range(amount)) + else: + heads = int(amount * random.uniform(0.45, 0.55)) tails = amount - heads action(MANY_COINS.format(amount, heads, tails)) From 81a0d70192c22bc2b667b48bd4ef95b12b6e7301 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 5 Feb 2019 15:04:16 -0500 Subject: [PATCH 04/23] Removed clamp function from gaming plugin The clamp function was originally necessary to deal with some issues with the coin flip function. The new logic for coin flip makes this no longer necessary and therefore it can be removed. --- plugins/gaming.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index 085ba3edb..00e8c343e 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -27,18 +27,6 @@ split_re = re.compile(r'([\d+-]*)d?(F|\d*)', re.I) -def clamp(n, min_value, max_value): - """Restricts a number to a certain range of values, - returning the min or max value if the value is too small or large, respectively - - :param n: The value to clamp - :param min_value: The minimum possible value - :param max_value: The maximum possible value - :return: The clamped value - """ - return min(max(n, min_value), max_value) - - def n_rolls(count, n): """roll an n-sided die count times From f94fed12655386b09049adcf37c25ab0f577cc1f Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 5 Feb 2019 15:09:05 -0500 Subject: [PATCH 05/23] Added leonthemisfit to modified by Added my name (leonthemisfit) to the modified by comment because of making significant changes. --- plugins/gaming.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/gaming.py b/plugins/gaming.py index 00e8c343e..dc3808df5 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -5,6 +5,7 @@ Modified By: - Luke Rogers + - leonthemisfit License: GPL v3 From 8dd899f2a2af8e58f62e45914e6fc485508d1c62 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 5 Feb 2019 15:15:47 -0500 Subject: [PATCH 06/23] Fixed comparison typo and added roll limit const to gaming plugin There was a typo in the new coin flip logic where it was supposed to run a proper simulation if flips were under the roll limit but instead would only run if they were over. Also moved hardcoded roll limit to a constant because it is used in multiple places. --- plugins/gaming.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index dc3808df5..b67c1f931 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -22,6 +22,8 @@ SINGLE_COIN = "flips a coin and gets {}." MANY_COINS = "flips {} coins and gets {} heads and {} tails." +ROLL_LIMIT = 100 # The maximum number of times to roll or flip before approximating results + whitespace_re = re.compile(r'\s+') valid_diceroll = re.compile(r'^([+-]?(?:\d+|\d*d(?:\d+|F))(?:[+-](?:\d+|\d*d(?:\d+|F)))*)( .+)?$', re.I) sign_re = re.compile(r'[+-]?(?:\d*d)?(?:\d+|F)', re.I) @@ -35,9 +37,9 @@ def n_rolls(count, n): :type n: int | str """ if n in ('f', 'F'): - return [random.randint(-1, 1) for _ in range(min(count, 100))] + return [random.randint(-1, 1) for _ in range(min(count, ROLL_LIMIT))] - if count < 100: + if n < ROLL_LIMIT: return [random.randint(1, n) for _ in range(count)] # Calculate a random sum approximated using a randomized normal variate with the midpoint used as the mu @@ -148,7 +150,7 @@ def coin(text, notice, action): side = random.choice(['heads', 'tails']) action(SINGLE_COIN.format(side)) else: - if amount > 100: + if amount < ROLL_LIMIT: heads = sum(random.randint(0, 1) for _ in range(amount)) else: heads = int(amount * random.uniform(0.45, 0.55)) From 87c029f5a336a64944ed8515acc2a4c677a82244 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 5 Feb 2019 15:33:53 -0500 Subject: [PATCH 07/23] Updated format strings to remove ' literals and added invalid roll const As per request the format strings utilizing a single quote within the string have been modified to remove the quotes and use the {!r} formatter. The string for invalid rolls has also been moved to a constant for consistency with the rest of the plugin as well as improving readability. --- plugins/gaming.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index b67c1f931..ff8be8e52 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -17,11 +17,13 @@ from cloudbot import hook # String constants for the coin flip function -INVALID_NUMBER = "Invalid input '{}': not a number" +INVALID_NUMBER = "Invalid input {!r}: not a number" NO_COIN = "makes a coin flipping motion" SINGLE_COIN = "flips a coin and gets {}." MANY_COINS = "flips {} coins and gets {} heads and {} tails." +INVALID_ROLL = "Invalid dice roll {!r}" + ROLL_LIMIT = 100 # The maximum number of times to roll or flip before approximating results whitespace_re = re.compile(r'\s+') @@ -64,7 +66,7 @@ def dice(text, notice): if match: text, desc = match.groups() else: - notice("Invalid dice roll '{}'".format(text)) + notice(INVALID_ROLL.format(text)) return if "d" not in text: @@ -72,7 +74,7 @@ def dice(text, notice): spec = whitespace_re.sub('', text) if not valid_diceroll.match(spec): - notice("Invalid dice roll '{}'".format(text)) + notice(INVALID_ROLL.format(text)) return groups = sign_re.findall(spec) From 530615125869640bd4aff9933644c5d3e234f10f Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 5 Feb 2019 22:10:28 -0500 Subject: [PATCH 08/23] Fixed incorrect roll count comparison The n_rolls function was incorrectly checking if the number of sides was less than the roll limit rather than the number of rolls to perform. --- plugins/gaming.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index ff8be8e52..00355d7e2 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -41,7 +41,7 @@ def n_rolls(count, n): if n in ('f', 'F'): return [random.randint(-1, 1) for _ in range(min(count, ROLL_LIMIT))] - if n < ROLL_LIMIT: + if count < ROLL_LIMIT: return [random.randint(1, n) for _ in range(count)] # Calculate a random sum approximated using a randomized normal variate with the midpoint used as the mu From 46702b9c0b868c9ad8434ff166037fc25bd087fb Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 5 Feb 2019 22:24:09 -0500 Subject: [PATCH 09/23] Added fudge approximation and refactored to match change This adds the ability to approximate fudge rolls greater than the roll limit in n_rolls. This also majorly refactors n_rolls because the changes to fudge dice make it possible to use the approximation calculations in a more generic way that is applicable to both fudge and non fudge dice. --- plugins/gaming.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index 00355d7e2..303c74909 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -22,6 +22,10 @@ SINGLE_COIN = "flips a coin and gets {}." MANY_COINS = "flips {} coins and gets {} heads and {} tails." +# Pregenerated mean and variance for fudge dice +FUDGE_MEAN = 0 +FUDGE_VAR = 0.6667 + INVALID_ROLL = "Invalid dice roll {!r}" ROLL_LIMIT = 100 # The maximum number of times to roll or flip before approximating results @@ -38,16 +42,25 @@ def n_rolls(count, n): :type count: int :type n: int | str """ - if n in ('f', 'F'): - return [random.randint(-1, 1) for _ in range(min(count, ROLL_LIMIT))] + fudge = n in ('f', 'F') if count < ROLL_LIMIT: - return [random.randint(1, n) for _ in range(count)] + low = 1 + high = n + if fudge: + low = -1 + high = 1 + return [random.randint(low, high) for _ in range(count)] + + if fudge: + mid = FUDGE_MEAN + var = FUDGE_VAR + else: + mid = 0.5 * (n + 1) * count + var = (n ** 2 - 1) / 12 # Calculate a random sum approximated using a randomized normal variate with the midpoint used as the mu # and an approximated standard deviation based on variance as the sigma - mid = .5 * (n + 1) * count - var = (n ** 2 - 1) / 12 adj_var = (var * count) ** 0.5 return [int(random.normalvariate(mid, adj_var))] From 7d40650bfcabb5f3eeffd24fc5172dbd2d9f37f1 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 6 Feb 2019 10:22:44 -0500 Subject: [PATCH 10/23] Patches bug with bounds checking in .dice command The wrong variable was being checked resulting in simulating very large numbers rather than approximating them. This patch fixes that. --- plugins/gaming.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index 303c74909..64c85b56c 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -44,13 +44,8 @@ def n_rolls(count, n): """ fudge = n in ('f', 'F') - if count < ROLL_LIMIT: - low = 1 - high = n - if fudge: - low = -1 - high = 1 - return [random.randint(low, high) for _ in range(count)] + if count < 100: + return [random.randint(1, n) for _ in range(count)] if fudge: mid = FUDGE_MEAN From 5051659a13e992a31b722d2ba6debf1b8fc72c52 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 18 Mar 2019 16:58:23 -0400 Subject: [PATCH 11/23] Fixed fudge dice issues with simulated rolls Because 'F' is a letter and not a number if a person were to roll a fudge die less than the roll limit the call to random.randint would fail. This has been replaced with a correct checking of fudge dice and ensuring the correct values are passed. "Uh, Big Bird, F is not a number... F is a letter" --Kramit the Frog, Sensimilla Street --- plugins/gaming.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index 64c85b56c..548d79e57 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -44,8 +44,14 @@ def n_rolls(count, n): """ fudge = n in ('f', 'F') - if count < 100: - return [random.randint(1, n) for _ in range(count)] + if count < ROLL_LIMIT: + if fudge: + lower = -1 + upper = 1 + else: + lower, upper = sorted((n, 1)) + + return [random.randint(lower, upper) for _ in range(count)] if fudge: mid = FUDGE_MEAN From 29fd437974bb159c98411d8ab8c34c35906b376c Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 18 Mar 2019 16:59:58 -0400 Subject: [PATCH 12/23] Moved approximation explanation to a clearer location The current location of the explanatory comment is actually beneath a large chunk of where the approximation happens. This is remedied by moving the comment above those approximations. --- plugins/gaming.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index 548d79e57..b472e0496 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -53,6 +53,8 @@ def n_rolls(count, n): return [random.randint(lower, upper) for _ in range(count)] + # Calculate a random sum approximated using a randomized normal variate with the midpoint used as the mu + # and an approximated standard deviation based on variance as the sigma if fudge: mid = FUDGE_MEAN var = FUDGE_VAR @@ -60,8 +62,6 @@ def n_rolls(count, n): mid = 0.5 * (n + 1) * count var = (n ** 2 - 1) / 12 - # Calculate a random sum approximated using a randomized normal variate with the midpoint used as the mu - # and an approximated standard deviation based on variance as the sigma adj_var = (var * count) ** 0.5 return [int(random.normalvariate(mid, adj_var))] From a6ed38c20059259abd57ba6e289c003d643c8c16 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 18 Mar 2019 17:32:50 -0400 Subject: [PATCH 13/23] Refactored n_rolls to have clearer param names The original parameter names were unclear and has already caused confusion over the process of cleaning this up, so a quick refactor has been done to clear up confusion. --- plugins/gaming.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index b472e0496..530756eb6 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -36,22 +36,22 @@ split_re = re.compile(r'([\d+-]*)d?(F|\d*)', re.I) -def n_rolls(count, n): +def n_rolls(roll_cnt, sides): """roll an n-sided die count times - :type count: int - :type n: int | str + :type roll_cnt: int + :type sides: int | str """ - fudge = n in ('f', 'F') + fudge = sides in ('f', 'F') - if count < ROLL_LIMIT: + if roll_cnt < ROLL_LIMIT: if fudge: lower = -1 upper = 1 else: - lower, upper = sorted((n, 1)) + lower, upper = sorted((sides, 1)) - return [random.randint(lower, upper) for _ in range(count)] + return [random.randint(lower, upper) for _ in range(roll_cnt)] # Calculate a random sum approximated using a randomized normal variate with the midpoint used as the mu # and an approximated standard deviation based on variance as the sigma @@ -59,10 +59,10 @@ def n_rolls(count, n): mid = FUDGE_MEAN var = FUDGE_VAR else: - mid = 0.5 * (n + 1) * count - var = (n ** 2 - 1) / 12 + mid = 0.5 * (sides + 1) * roll_cnt + var = (sides ** 2 - 1) / 12 - adj_var = (var * count) ** 0.5 + adj_var = (var * roll_cnt) ** 0.5 return [int(random.normalvariate(mid, adj_var))] From fcb5ce2ea9ecb0a02d951f206e6f727bf6dd5193 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 20 Mar 2019 07:06:36 -0400 Subject: [PATCH 14/23] Changed calls to int to calls to round The two places that int() was used to get the floor of a number has been replaced with round because linuxdaemon. --- plugins/gaming.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index 530756eb6..897416cd8 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -64,7 +64,7 @@ def n_rolls(roll_cnt, sides): adj_var = (var * roll_cnt) ** 0.5 - return [int(random.normalvariate(mid, adj_var))] + return [round(random.normalvariate(mid, adj_var))] @hook.command("roll", "dice") @@ -169,6 +169,6 @@ def coin(text, notice, action): if amount < ROLL_LIMIT: heads = sum(random.randint(0, 1) for _ in range(amount)) else: - heads = int(amount * random.uniform(0.45, 0.55)) + heads = round(amount * random.uniform(0.45, 0.55)) tails = amount - heads action(MANY_COINS.format(amount, heads, tails)) From c32a1c3ea427b093e6e150b85febc2fa95fe6a66 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 20 Mar 2019 07:11:03 -0400 Subject: [PATCH 15/23] Added returned type to nrolls This simply adds a return type in the docstring of the nrolls function in gaming.py. --- plugins/gaming.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/gaming.py b/plugins/gaming.py index 897416cd8..ffcfd2568 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -41,6 +41,7 @@ def n_rolls(roll_cnt, sides): :type roll_cnt: int :type sides: int | str + :rtype: list[int] """ fudge = sides in ('f', 'F') From 1c711769f58865f745ef79eaf4a402bc8a9c22fd Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 20 Mar 2019 08:01:56 -0400 Subject: [PATCH 16/23] Moved approximation formulas to separate functions In order to provide better documentation and readability as to how rolls are approximated the formulas have been moved to their own functions. This allows each formula to be better documented in docstrings as well as giving them concise names so that there's no reliance on just variable names to understand how the values are being found. --- plugins/gaming.py | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index ffcfd2568..78bf7edbc 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -36,6 +36,45 @@ split_re = re.compile(r'([\d+-]*)d?(F|\d*)', re.I) +def find_midpoint(sides, roll_cnt): + """find the midpoint for a die with n rolls + + :type sides: int + :type roll_cnt: int + :rtype: float + """ + return 0.5 * (sides + 1) * roll_cnt + + +def find_variance(sides): + """find the variance for a die with n sides + + :type sides: int + :rtype: float + """ + return (sides ** 2 - 1) / 12 + + +def find_mid_var(sides, roll_cnt): + """find the midpoint and variance for a die with x sides and y rolls + + :type sides: int + :type roll_cnt: int + :rtype: (float, float) + """ + return find_midpoint(sides, roll_cnt), find_variance(sides) + + +def find_adjusted_variance(variance, roll_cnt): + """find the variance adjusted for the number of rolls + + :type variance: float + :type roll_cnt: int + :rtype: float + """ + return (variance * roll_cnt) ** 0.5 + + def n_rolls(roll_cnt, sides): """roll an n-sided die count times @@ -60,10 +99,9 @@ def n_rolls(roll_cnt, sides): mid = FUDGE_MEAN var = FUDGE_VAR else: - mid = 0.5 * (sides + 1) * roll_cnt - var = (sides ** 2 - 1) / 12 + mid, var = find_mid_var(sides, roll_cnt) - adj_var = (var * roll_cnt) ** 0.5 + adj_var = find_adjusted_variance(var, roll_cnt) return [round(random.normalvariate(mid, adj_var))] From 7dc404e3124a9d908e6b2f438c8efa3c6c243bf8 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 20 Mar 2019 08:13:15 -0400 Subject: [PATCH 17/23] Moved approximation to separate function In order to continue to improve readability and testability the approximation of rolls has been moved to a separate function. This allows n_rolls to be shorter and more readable. --- plugins/gaming.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index 78bf7edbc..b83f32a39 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -75,6 +75,24 @@ def find_adjusted_variance(variance, roll_cnt): return (variance * roll_cnt) ** 0.5 +def approximate_rolls(roll_cnt, sides, fudge): + """approximate a sum based on a random normal variate using the midpoint as the mu and variance as the sigma + + :type roll_cnt: int + :type sides: int | str + :type fudge: bool + :rtype: list(int)""" + if fudge: + mid = FUDGE_MEAN + var = FUDGE_VAR + else: + mid, var = find_mid_var(sides, roll_cnt) + + adj_var = find_adjusted_variance(var, roll_cnt) + + return [round(random.normalvariate(mid, adj_var))] + + def n_rolls(roll_cnt, sides): """roll an n-sided die count times @@ -93,17 +111,7 @@ def n_rolls(roll_cnt, sides): return [random.randint(lower, upper) for _ in range(roll_cnt)] - # Calculate a random sum approximated using a randomized normal variate with the midpoint used as the mu - # and an approximated standard deviation based on variance as the sigma - if fudge: - mid = FUDGE_MEAN - var = FUDGE_VAR - else: - mid, var = find_mid_var(sides, roll_cnt) - - adj_var = find_adjusted_variance(var, roll_cnt) - - return [round(random.normalvariate(mid, adj_var))] + return approximate_rolls(roll_cnt, sides, fudge) @hook.command("roll", "dice") From 1036375d81c778a46146802f6d3a2a9184de0407 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 20 Mar 2019 08:18:38 -0400 Subject: [PATCH 18/23] Moved dice roll simulation to its own function The last step in this refactor is to move dice roll simulation to a separate function. This final piece of refactoring represents all logic being separated into logical units. This provides the best means to document and test the various functions. This also provides better maintainability. --- plugins/gaming.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index b83f32a39..99832bc99 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -93,6 +93,23 @@ def approximate_rolls(roll_cnt, sides, fudge): return [round(random.normalvariate(mid, adj_var))] +def simulate_rolls(roll_cnt, sides, fudge): + """simulate rolling a dice + + :type roll_cnt: int + :type sides: int | str + :type fudge: bool + :rtype: list(int) + """ + if fudge: + lower = -1 + upper = 1 + else: + lower, upper = sorted((sides, 1)) + + return [random.randint(lower, upper) for _ in range(roll_cnt)] + + def n_rolls(roll_cnt, sides): """roll an n-sided die count times @@ -103,15 +120,9 @@ def n_rolls(roll_cnt, sides): fudge = sides in ('f', 'F') if roll_cnt < ROLL_LIMIT: - if fudge: - lower = -1 - upper = 1 - else: - lower, upper = sorted((sides, 1)) - - return [random.randint(lower, upper) for _ in range(roll_cnt)] - - return approximate_rolls(roll_cnt, sides, fudge) + return simulate_rolls(roll_cnt, sides, fudge) + else: + return approximate_rolls(roll_cnt, sides, fudge) @hook.command("roll", "dice") From a0c49845f4bfd0ca09d5f941722ab7f16bcfad33 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 20 Mar 2019 08:23:48 -0400 Subject: [PATCH 19/23] Refactored n_rolls to follow SESE Rather than having multiple return statements which can often cause confusion, the results of the two function calls are stored and then returned at the end of the code block. This can also help future changes where the returned values may be manipulated for additional features or other unforeseen reasons. --- plugins/gaming.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index 99832bc99..d49bb3044 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -120,9 +120,11 @@ def n_rolls(roll_cnt, sides): fudge = sides in ('f', 'F') if roll_cnt < ROLL_LIMIT: - return simulate_rolls(roll_cnt, sides, fudge) + values = simulate_rolls(roll_cnt, sides, fudge) else: - return approximate_rolls(roll_cnt, sides, fudge) + values = approximate_rolls(roll_cnt, sides, fudge) + + return values @hook.command("roll", "dice") From cc2dfa91448d000b3c980e8dfd7e63d58b1b04a3 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 20 Mar 2019 08:53:53 -0400 Subject: [PATCH 20/23] Rounded results of find_variance function 4 digits is plenty of precision for the calculations that use this to be accurate. This is also done so that tests in docstrings will be easier to read. --- plugins/gaming.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index d49bb3044..9bb3248f2 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -52,7 +52,7 @@ def find_variance(sides): :type sides: int :rtype: float """ - return (sides ** 2 - 1) / 12 + return round((sides ** 2 - 1) / 12, 4) def find_mid_var(sides, roll_cnt): From 685be6aa3dc23eb8c013b656d2c47d0665d22215 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 20 Mar 2019 09:00:28 -0400 Subject: [PATCH 21/23] Rounded the result of find_adjusted_variance() Like find_variance 4 digits of precision is plenty for its use here and it improves readability of test results. --- plugins/gaming.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index 9bb3248f2..f1545ff76 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -72,7 +72,7 @@ def find_adjusted_variance(variance, roll_cnt): :type roll_cnt: int :rtype: float """ - return (variance * roll_cnt) ** 0.5 + return round((variance * roll_cnt) ** 0.5, 4) def approximate_rolls(roll_cnt, sides, fudge): From f4834c0d32ea1ca6f56bb743f48a5a2ef188076a Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 20 Mar 2019 09:03:27 -0400 Subject: [PATCH 22/23] Added tests to utility function docstrings This commit adds simple tests/examples to the docstrings of util functions that are non-random (for simplicity) so that you can get an idea of what kind of data they return. --- plugins/gaming.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/gaming.py b/plugins/gaming.py index f1545ff76..ee1948245 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -42,6 +42,9 @@ def find_midpoint(sides, roll_cnt): :type sides: int :type roll_cnt: int :rtype: float + + >>> [find_midpoint(s, r) for s, r in [(6, 1), (6, 2), (12, 1), (12, 2)]] + [3.5, 7, 6.5, 13] """ return 0.5 * (sides + 1) * roll_cnt @@ -51,6 +54,9 @@ def find_variance(sides): :type sides: int :rtype: float + + >>> [find_variance(s) for s in [3, 4, 5, 6, 20, 50]] + [0.6667, 1.25, 2.0, 2.9167, 33.25, 208.25] """ return round((sides ** 2 - 1) / 12, 4) @@ -61,6 +67,9 @@ def find_mid_var(sides, roll_cnt): :type sides: int :type roll_cnt: int :rtype: (float, float) + + >>> [find_mid_var(s, r) for s, r in [(6, 1), (6, 2), (12, 1), (12, 2)]] + [(3.5, 2.9167), (7.0, 2.9167), (6.5, 11.9167), (13.0, 11.9167)] """ return find_midpoint(sides, roll_cnt), find_variance(sides) @@ -71,6 +80,9 @@ def find_adjusted_variance(variance, roll_cnt): :type variance: float :type roll_cnt: int :rtype: float + + >>> [find_adjusted_variance(find_variance(s), r) for s, r in [(6, 1), (6, 2), (12, 1), (12, 2)]] + [1.7078, 2.4152, 3.4521, 4.8819] """ return round((variance * roll_cnt) ** 0.5, 4) From 46245e945f177f8b36873f1405c04c24613bfb24 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 20 Mar 2019 09:26:26 -0400 Subject: [PATCH 23/23] Updated tests so they correct pass in travis There was a discrepancy between my local test results and the test results from travis with the return type from find_midpoint. The tests in the plugin have been updated to reflect the results travis expects rather than my local machine. --- plugins/gaming.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/gaming.py b/plugins/gaming.py index ee1948245..5cdf77636 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -44,7 +44,7 @@ def find_midpoint(sides, roll_cnt): :rtype: float >>> [find_midpoint(s, r) for s, r in [(6, 1), (6, 2), (12, 1), (12, 2)]] - [3.5, 7, 6.5, 13] + [3.5, 7.0, 6.5, 13.0] """ return 0.5 * (sides + 1) * roll_cnt