Quellcode durchsuchen

Properly validate all comparators

Now it is an error to use a comparator that will always be true or
always be false, as well as success and failure criteria that aren't
mutually exclusive.
Ryan C. Thompson vor 6 Jahren
Ursprung
Commit
1ec4cf0a25
1 geänderte Dateien mit 34 neuen und 16 gelöschten Zeilen
  1. 34 16
      roll.py

+ 34 - 16
roll.py

@@ -435,6 +435,34 @@ class Comparator(object):
         This allows the Comparator to be used as a callable.'''
         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:
     '''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_limit = 1 if reroll_type == 'r' else None
         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(
             operator = reroll_desc['comparator'],
             value = reroll_desc['target'],
         )
+        validate_comparators(die_face, reroll_comparator)
         for i in range(dice_count):
             current_rolls = roll_die_with_rerolls(die_face, reroll_comparator, reroll_limit)
             if len(current_rolls) == 1:
@@ -508,7 +535,6 @@ def roll_dice(roll_desc: Dict) -> DiceRolled:
             dropped_rolls = all_rolls
         else:
             kept_rolls = all_rolls
-    # Now we have a list of kept rolls
     if 'count_success' in roll_desc:
         die_face = int(die_face) # No fate dice
         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
         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}")
+        validate_comparators(die_face, success_test)
         success_count = sum(success_test(x) for x in kept_rolls)
         if 'count_failure' in roll_desc:
             failure_desc = roll_desc['count_failure']
@@ -526,20 +553,11 @@ def roll_dice(roll_desc: Dict) -> DiceRolled:
                 operator = failure_desc['comparator'],
                 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)
-    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(
         dice_results = kept_rolls,
         dropped_results = dropped_rolls,