|
@@ -435,6 +435,34 @@ class Comparator(object):
|
|
This allows the Comparator to be used as a callable.'''
|
|
This allows the Comparator to be used as a callable.'''
|
|
return self.compare(x)
|
|
return self.compare(x)
|
|
|
|
|
|
|
|
+def validate_comparators(face: DieFaceType, *comparators):
|
|
|
|
+ '''Validate one or more comparators for a die face type.
|
|
|
|
+
|
|
|
|
+ This will test every possible die value, making sure that each
|
|
|
|
+ test succeeds on at least one value and fails on at least one
|
|
|
|
+ value, and it will make sure that at most one test succeeds on any
|
|
|
|
+ given value.
|
|
|
|
+
|
|
|
|
+ '''
|
|
|
|
+ if isinstance(face, str):
|
|
|
|
+ assert face.startswith('F')
|
|
|
|
+ values = range(-1, 2)
|
|
|
|
+ else:
|
|
|
|
+ values = range(1, face+1)
|
|
|
|
+ comparators = list(comparators)
|
|
|
|
+ # Validate individual comparators
|
|
|
|
+ for comp in comparators:
|
|
|
|
+ results = [ comp(val) for val in values ]
|
|
|
|
+ if all(results):
|
|
|
|
+ raise ValueError(f"Test {str(comp)!r} can never fail for d{face}")
|
|
|
|
+ if not any(results):
|
|
|
|
+ raise ValueError(f"Test {str(comp)!r} can never succeed for d{face}")
|
|
|
|
+ # Check for comparator overlap
|
|
|
|
+ for val in values:
|
|
|
|
+ passing_comps = [ comp for comp in comparators if comp(val) ]
|
|
|
|
+ if len(passing_comps) > 1:
|
|
|
|
+ raise ValueError(f"Can't have overlapping tests: {str(passing_comps[0])!r} and {str(passing_comps[1])!r}")
|
|
|
|
+
|
|
def roll_dice(roll_desc: Dict) -> DiceRolled:
|
|
def roll_dice(roll_desc: Dict) -> DiceRolled:
|
|
'''Roll dice based on a roll description.
|
|
'''Roll dice based on a roll description.
|
|
|
|
|
|
@@ -456,12 +484,11 @@ def roll_dice(roll_desc: Dict) -> DiceRolled:
|
|
reroll_type: str = roll_desc['reroll_type']
|
|
reroll_type: str = roll_desc['reroll_type']
|
|
reroll_limit = 1 if reroll_type == 'r' else None
|
|
reroll_limit = 1 if reroll_type == 'r' else None
|
|
reroll_desc: Dict = roll_desc['reroll_desc']
|
|
reroll_desc: Dict = roll_desc['reroll_desc']
|
|
- # TODO: sanity check the reroll comparator. Make sure it
|
|
|
|
- # doesn't pass all values or fail all values.
|
|
|
|
reroll_comparator = Comparator(
|
|
reroll_comparator = Comparator(
|
|
operator = reroll_desc['comparator'],
|
|
operator = reroll_desc['comparator'],
|
|
value = reroll_desc['target'],
|
|
value = reroll_desc['target'],
|
|
)
|
|
)
|
|
|
|
+ validate_comparators(die_face, reroll_comparator)
|
|
for i in range(dice_count):
|
|
for i in range(dice_count):
|
|
current_rolls = roll_die_with_rerolls(die_face, reroll_comparator, reroll_limit)
|
|
current_rolls = roll_die_with_rerolls(die_face, reroll_comparator, reroll_limit)
|
|
if len(current_rolls) == 1:
|
|
if len(current_rolls) == 1:
|
|
@@ -508,7 +535,6 @@ def roll_dice(roll_desc: Dict) -> DiceRolled:
|
|
dropped_rolls = all_rolls
|
|
dropped_rolls = all_rolls
|
|
else:
|
|
else:
|
|
kept_rolls = all_rolls
|
|
kept_rolls = all_rolls
|
|
- # Now we have a list of kept rolls
|
|
|
|
if 'count_success' in roll_desc:
|
|
if 'count_success' in roll_desc:
|
|
die_face = int(die_face) # No fate dice
|
|
die_face = int(die_face) # No fate dice
|
|
success_desc = roll_desc['count_success']
|
|
success_desc = roll_desc['count_success']
|
|
@@ -519,6 +545,7 @@ def roll_dice(roll_desc: Dict) -> DiceRolled:
|
|
# Sanity check: make sure the success test can be met
|
|
# Sanity check: make sure the success test can be met
|
|
if not any(map(success_test, range(1, die_face +1))):
|
|
if not any(map(success_test, range(1, die_face +1))):
|
|
raise ValueError(f"Test {str(success_test)!r} can never succeed for d{die_face}")
|
|
raise ValueError(f"Test {str(success_test)!r} can never succeed for d{die_face}")
|
|
|
|
+ validate_comparators(die_face, success_test)
|
|
success_count = sum(success_test(x) for x in kept_rolls)
|
|
success_count = sum(success_test(x) for x in kept_rolls)
|
|
if 'count_failure' in roll_desc:
|
|
if 'count_failure' in roll_desc:
|
|
failure_desc = roll_desc['count_failure']
|
|
failure_desc = roll_desc['count_failure']
|
|
@@ -526,20 +553,11 @@ def roll_dice(roll_desc: Dict) -> DiceRolled:
|
|
operator = failure_desc['comparator'],
|
|
operator = failure_desc['comparator'],
|
|
value = failure_desc['target'],
|
|
value = failure_desc['target'],
|
|
)
|
|
)
|
|
- # Sanity check: make sure the failure test can be met
|
|
|
|
- if not any(map(failure_test, range(1, die_face +1))):
|
|
|
|
- raise ValueError(f"Test {str(failure_test)!r} can never succeed for d{die_face}")
|
|
|
|
- # Sanity check: make sure the two conditions don't overlap
|
|
|
|
- for i in range(1, die_face + 1):
|
|
|
|
- if success_test(i) and failure_test(i):
|
|
|
|
- raise ValueError(f"Can't use overlapping success and failure conditions: {str(success_test)!r}, {str(failure_test)!r}")
|
|
|
|
|
|
+ validate_comparators(die_face, success_test, failure_test)
|
|
success_count -= sum(failure_test(x) for x in kept_rolls)
|
|
success_count -= sum(failure_test(x) for x in kept_rolls)
|
|
- temp_args = dict(
|
|
|
|
- dice_results = kept_rolls,
|
|
|
|
- dropped_results = dropped_rolls,
|
|
|
|
- success_count = success_count,
|
|
|
|
- roll_text = roll_desc['roll_text'],
|
|
|
|
- )
|
|
|
|
|
|
+ else:
|
|
|
|
+ # TODO: Label crits and critfails here
|
|
|
|
+ pass
|
|
return DiceRolled(
|
|
return DiceRolled(
|
|
dice_results = kept_rolls,
|
|
dice_results = kept_rolls,
|
|
dropped_results = dropped_rolls,
|
|
dropped_results = dropped_rolls,
|