|
6 | 6 |
|
7 | 7 | from CharacterCreator.models import * |
8 | 8 |
|
| 9 | + |
| 10 | +# class HistoryEventError(Exception): |
| 11 | +# def __init__(self, name): |
| 12 | +# self.name = name |
| 13 | +# print(self.name + ' not present') |
| 14 | +# raise |
| 15 | + |
| 16 | + |
| 17 | +def validate_skillstats(yaml_file): |
| 18 | + print('Validating Skills & Stats YAML: ' + yaml_file) |
| 19 | + |
| 20 | + with open(yaml_file, 'r') as yamlfile: |
| 21 | + tree = yaml.load(yamlfile) |
| 22 | + |
| 23 | + error_flag = False |
| 24 | + |
| 25 | + if 'stats' not in tree.keys(): |
| 26 | + print('ERROR: Missing required stats keyword up top') |
| 27 | + error_flag = True |
| 28 | + |
| 29 | + skill_collection = [] |
| 30 | + for stat in tree['stats'].keys(): |
| 31 | + substat = tree['stats'][stat] |
| 32 | + |
| 33 | + if 'stat' not in substat: |
| 34 | + print('ERROR: stat keyword not in [stats > ' + stat + ']') |
| 35 | + error_flag = True |
| 36 | + else: |
| 37 | + # improve with re non-hyphen + non-numeric tests |
| 38 | + value_range = [int(value) for value in substat['stat'].split('-')] |
| 39 | + prefix = 'ERROR: stat range (' + substat['stat'] + ') in [stats > ' + stat + ' > stat]' |
| 40 | + if len(value_range) < 2: |
| 41 | + print(prefix + ' has less than two hyphenated values') |
| 42 | + error_flag = True |
| 43 | + elif len(value_range) > 2: |
| 44 | + print(prefix + ' has greater than two hyphenated values') |
| 45 | + error_flag = True |
| 46 | + elif value_range[0] > value_range[1]: |
| 47 | + print(prefix + ' contains a larger first number (' + str(value_range[0]) + ') than second number (' + str(value_range[1]) + ')') |
| 48 | + error_flag = True |
| 49 | + |
| 50 | + if 'skills' not in substat: |
| 51 | + print('WARNING: skills keyword not in [stats > ' + stat + ']') |
| 52 | + else: |
| 53 | + for skill in substat['skills'].keys(): |
| 54 | + subskill = substat['skills'][skill] |
| 55 | + value_range = [int(value) for value in subskill.split('-')] |
| 56 | + prefix = 'ERROR: skill range (' + subskill + ') in [stats > ' + stat + ' > skills > ' + skill + ']' |
| 57 | + if len(value_range) < 2: |
| 58 | + print(prefix + ' has less than two hyphenated values') |
| 59 | + error_flag = True |
| 60 | + elif len(value_range) > 2: |
| 61 | + print(prefix + ' has greater than two hyphenated values') |
| 62 | + error_flag = True |
| 63 | + elif value_range[0] > value_range[1]: |
| 64 | + print(prefix + ' contains a larger first number (' + str(value_range[0]) + ') than second number (' + str(value_range[1]) + ')') |
| 65 | + error_flag = True |
| 66 | + skill_collection.append(skill) |
| 67 | + |
| 68 | + if 'roles' in tree.keys(): |
| 69 | + for role in tree['roles'].keys(): |
| 70 | + subrole = tree['roles'][role] |
| 71 | + if 'special' not in subrole: |
| 72 | + print('ERROR: special keyword not in [roles > ' + role + ']') |
| 73 | + error_flag = True |
| 74 | + if 'common' not in subrole: |
| 75 | + print('ERROR: common keyword not in [roles > ' + role + ']') |
| 76 | + error_flag = True |
| 77 | + |
| 78 | + for special in subrole['special'].keys(): |
| 79 | + subspecial = subrole['special'][special] |
| 80 | + value_range = [int(value) for value in subspecial.split('-')] |
| 81 | + prefix = 'ERROR: special range (' + subspecial + ') in [roles > ' + role + ' > special > ' + special + ']' |
| 82 | + if len(value_range) < 2: |
| 83 | + print(prefix + ' has less than two hyphenated values') |
| 84 | + error_flag = True |
| 85 | + elif len(value_range) > 2: |
| 86 | + print(prefix + ' has greater than two hyphenated values') |
| 87 | + error_flag = True |
| 88 | + elif value_range[0] > value_range[1]: |
| 89 | + print(prefix + ' contains a larger first number (' + str(value_range[0]) + ') than second number (' + str(value_range[1]) + ')') |
| 90 | + error_flag = True |
| 91 | + |
| 92 | + for common in subrole['common']: |
| 93 | + if common not in skill_collection: |
| 94 | + print('ERROR: [roles > ' + role + ' > common > ' + common + '] not among all common skills:') |
| 95 | + print(skill_collection) |
| 96 | + error_flag = True |
| 97 | + |
| 98 | + if error_flag: |
| 99 | + return False |
| 100 | + else: |
| 101 | + print('Valid Skills & Stats YAML!') |
| 102 | + return True |
| 103 | + |
| 104 | + |
| 105 | +def validate_history(yaml_file): |
| 106 | + print('Validating History YAML: ' + yaml_file) |
| 107 | + |
| 108 | + with open(yaml_file, 'r') as yamlfile: |
| 109 | + tree = yaml.load(yamlfile) |
| 110 | + |
| 111 | + events = list(tree.keys()) |
| 112 | + error_flag = False |
| 113 | + |
| 114 | + if 'START' not in events: |
| 115 | + print('ERROR: Missing required START keyword up top') |
| 116 | + error_flag = True |
| 117 | + |
| 118 | + if 'NPC' not in events: |
| 119 | + print('ERROR: Missing required NPC keyword up top') |
| 120 | + error_flag = True |
| 121 | + |
| 122 | + events.remove('START') |
| 123 | + events.remove('NPC') |
| 124 | + |
| 125 | + reroll_list = [] |
| 126 | + |
| 127 | + for event in events: |
| 128 | + event_dict = tree[event] |
| 129 | + |
| 130 | + if 'dice' not in event_dict: |
| 131 | + print('ERROR: dice keyword not in event ' + event) |
| 132 | + error_flag = True |
| 133 | + if 'roll' not in event_dict: |
| 134 | + print('ERROR: roll keyword not in event ' + event) |
| 135 | + error_flag = True |
| 136 | + |
| 137 | + dice = event_dict['dice'] |
| 138 | + search = re.match(r'([0-9]+)d([0-9]+)\ *(\+|-)*\ *([0-9]*)', dice) |
| 139 | + if search.group(1) == None and search.group(2) == None: |
| 140 | + print('ERROR: Invalid dice (' + dice + ') for event ' + event) |
| 141 | + error_flag = True |
| 142 | + |
| 143 | + quantity = int(search.group(1)) |
| 144 | + sides = int(search.group(2)) |
| 145 | + if search.group(3) == None: |
| 146 | + offset = 0 |
| 147 | + else: |
| 148 | + offset = int(search.group(3)+search.group(4)) |
| 149 | + |
| 150 | + dice_min = quantity + offset |
| 151 | + dice_max = quantity * sides + offset |
| 152 | + dice_span = list(range(dice_min, dice_max+1)) |
| 153 | + |
| 154 | + rolls = event_dict['roll'] |
| 155 | + |
| 156 | + if 'EVEN' in rolls and 'ODD' not in rolls and len(rolls) == 2: |
| 157 | + print('ERROR: EVEN in roll, but no ODD in roll for event ' + event) |
| 158 | + error_flag = True |
| 159 | + elif 'EVEN' not in rolls and 'ODD' in rolls and len(rolls) == 2: |
| 160 | + print('ERROR: ODD in roll, but no EVEN in roll for event ' + event) |
| 161 | + error_flag = True |
| 162 | + elif 'EVEN' in rolls and 'ODD' in rolls and len(rolls) > 2: |
| 163 | + print('ERROR: EVEN and ODD keywords used, but there are more than two keywords in event ' + event) |
| 164 | + error_flag = True |
| 165 | + elif 'EVEN' not in rolls and 'ODD' not in rolls: |
| 166 | + possible_rolls = [] |
| 167 | + # a dictionary of roll value lists whose keys are roll strings |
| 168 | + for element in rolls.keys(): |
| 169 | + roll_str = str(element) |
| 170 | + |
| 171 | + if '-' not in roll_str and ',' not in roll_str: |
| 172 | + # a single value |
| 173 | + values = int(roll_str) |
| 174 | + possible_rolls.append(values) |
| 175 | + elif ',' in roll_str: |
| 176 | + # multiple values |
| 177 | + for partial in roll_str.split(','): |
| 178 | + if '-' in partial: |
| 179 | + # a span of comma-separated values |
| 180 | + minimum,maximum = (int(number) for number in partial.split('-')) |
| 181 | + values = list(range(minimum,maximum+1)) |
| 182 | + possible_rolls += values |
| 183 | + else: |
| 184 | + # a single comma-separated value |
| 185 | + values = int(partial) |
| 186 | + possible_rolls.append(values) |
| 187 | + elif '-' in roll_str: |
| 188 | + # a span of values |
| 189 | + minimum,maximum = (int(number) for number in roll_str.split('-')) |
| 190 | + values = list(range(minimum,maximum+1)) |
| 191 | + possible_rolls += values |
| 192 | + |
| 193 | + if len(possible_rolls) < len(dice_span): |
| 194 | + print('ERROR: Too few possible rolls in ' + str(rolls) + ' for event ' + event) |
| 195 | + error_flag = True |
| 196 | + elif len(possible_rolls) > len(dice_span): |
| 197 | + print('ERROR: Too many possible rolls in ' + str(rolls) + ' for event ' + event) |
| 198 | + error_flag = True |
| 199 | + elif sorted(possible_rolls) != dice_span: |
| 200 | + print('ERROR: possible rolls (' + possible_rolls + ') does not match dice span (' + dice_span + ') for event ' + event) |
| 201 | + error_flag = True |
| 202 | + |
| 203 | + if 'next' in event_dict: |
| 204 | + if event_dict['next'] not in events: |
| 205 | + print('ERROR: next event (' + event_dict['next'] + ') does not exist for event ' + event) |
| 206 | + error_flag = True |
| 207 | + |
| 208 | + for roll in rolls.keys(): |
| 209 | + outcome = rolls[roll] |
| 210 | + if type(outcome) is dict: |
| 211 | + for key in outcome.keys(): |
| 212 | + if key != 'next': |
| 213 | + print('ERROR: Invalid keyword (' + key + ') for roll ' + str(roll) + ' in event ' + event) |
| 214 | + error_flag = True |
| 215 | + else: |
| 216 | + outcome = outcome['next'] |
| 217 | + |
| 218 | + if '<NPC' in outcome: |
| 219 | + npc_match = re.match(r'(.*)<NPC\ *(.+)>\ *(.+)', outcome) |
| 220 | + outcome = npc_match.group(1) + npc_match.group(3) |
| 221 | + |
| 222 | + if '<ROLL X' in outcome: |
| 223 | + roll_x = re.match(r'(.*)<ROLL\ *X([0-9]+)>\ *(.+)', outcome) |
| 224 | + outcome = roll_x.group(1) + roll_x.group(3) |
| 225 | + reroll_list.append(outcome) |
| 226 | + |
| 227 | + if outcome not in events: |
| 228 | + print('ERROR: next event (' + outcome + ') does not exist for roll ' + str(roll) + ' in event ' + event) |
| 229 | + error_flag = True |
| 230 | + |
| 231 | + elif type(outcome) is str: |
| 232 | + if '<NPC' in outcome: |
| 233 | + npc_match = re.match(r'(.*)<NPC\ *(.+)>\ *(.+)', outcome) |
| 234 | + outcome = npc_match.group(1) + npc_match.group(3) |
| 235 | + |
| 236 | + if '<ROLL X' in outcome: |
| 237 | + roll_x = re.match(r'(.*)<ROLL\ *X([0-9]+)>\ *(.+)', outcome) |
| 238 | + outcome = roll_x.group(1) + roll_x.group(3) |
| 239 | + reroll_list.append(outcome) |
| 240 | + |
| 241 | + else: |
| 242 | + print('ERROR: Outcome is neither a next key-value pair nor a string for roll ' + str(roll) + ' in event ' + event) |
| 243 | + error_flag = True |
| 244 | + |
| 245 | + reroll_set = set(reroll_list) |
| 246 | + for event in events: |
| 247 | + event_dict = tree[event] |
| 248 | + if 'reroll' in event_dict: |
| 249 | + reroll_event = event_dict['reroll'] |
| 250 | + if reroll_event in reroll_set: |
| 251 | + reroll_set.remove(reroll_event) |
| 252 | + else: |
| 253 | + print('ERROR: reroll event (' + reroll_event + ') does not exist in any <ROLL X#> tag for event ' + event) |
| 254 | + error_flag = True |
| 255 | + |
| 256 | + if len(reroll_set) != 0: |
| 257 | + print('ERROR: Unmatched reroll(s) among all <ROLL X#> tags: ' + str(reroll_set)) |
| 258 | + error_flag = True |
| 259 | + |
| 260 | + if error_flag: |
| 261 | + return False |
| 262 | + else: |
| 263 | + print('Valid History YAML!') |
| 264 | + return True |
| 265 | + |
| 266 | + |
9 | 267 | def setup_skillstats(yaml_file): |
10 | 268 | # needs error handling |
11 | 269 | with open(yaml_file,'r') as yamlfile: |
@@ -238,15 +496,29 @@ def setup_history(yaml_file): |
238 | 496 |
|
239 | 497 |
|
240 | 498 | if __name__ == '__main__': |
241 | | - skillstats_yaml = 'Examples/classless_cyberpunk_test/classless_cyberpunk_test_stats_skills.yaml' |
242 | | - history_yaml = 'Examples/classless_cyberpunk_test/classless_cyberpunk_test_history.yaml' |
243 | | - |
244 | | - # ONE-TIME roles, stats, and skills definitions ONLY HAPPENS ONCE |
245 | | - # This should only be run once |
246 | | - # If it fails somehow, you should empty your database, adjust your YAML's, and try again |
247 | | - setup_skillstats(skillstats_yaml) # comment this out when you're done with it |
248 | | - |
249 | | - # ONE-TIME history events and rolls definitions ONLY HAPPENS ONCE |
250 | | - # This should only be run once |
251 | | - # If it fails somehow, you should empty your database, adjust your YAML's, and try again |
252 | | - setup_history(history_yaml) # comment this out when you're done with it |
| 499 | + skillstats_yaml = 'Examples/system_stats_skills.yaml' |
| 500 | + history_yaml = 'Examples/system_history.yaml' |
| 501 | + |
| 502 | + valid_skillstats = validate_skillstats(skillstats_yaml) |
| 503 | + valid_history = validate_history(history_yaml) |
| 504 | + |
| 505 | + if not valid_skillstats: |
| 506 | + print('Skils & Stats YAML had at least one ERROR, review this output and correct all errors') |
| 507 | + |
| 508 | + if not valid_history: |
| 509 | + print('History YAML had at least one ERROR, review this output and correct all errors') |
| 510 | + |
| 511 | + if not valid_skillstats or not valid_history: |
| 512 | + print('Quitting without setting up game system database') |
| 513 | + else: |
| 514 | + print('Setting up game system database') |
| 515 | + # ONE-TIME roles, stats, and skills definitions ONLY HAPPENS ONCE |
| 516 | + # This should only be run once |
| 517 | + # If it fails somehow, you should empty your database, adjust your YAML's, and try again |
| 518 | + setup_skillstats(skillstats_yaml) # comment this out when you're done with it |
| 519 | + |
| 520 | + # ONE-TIME history events and rolls definitions ONLY HAPPENS ONCE |
| 521 | + # This should only be run once |
| 522 | + # If it fails somehow, you should empty your database, adjust your YAML's, and try again |
| 523 | + setup_history(history_yaml) # comment this out when you're done with it |
| 524 | + print('Successfully completed setup.py! Proceed to makecharacter.py...') |
0 commit comments