import os import yaml from typing import Any, Callable import BaseClasses from worlds.generic.Rules import set_rule from .util import load_resource GAME = "SICPelago" OriginRegionName = "REPL" class Location(BaseClasses.Location): game = GAME class LocationData: def __init__(self, data: dict[str, Any]): self.data = data def __str__(self): return self.data['name'] def id(self): (chapter, exercise) = str(self).split(' ')[0].split('.') return int(chapter) * 100 + int(exercise) all_locations = [LocationData(data) for data in yaml.safe_load(load_resource('locations.yml'))] def set_up_rules(loc: LocationData, root: BaseClasses.Region, region: BaseClasses.Region, player: int): # Ideally this would be automatically generated from locations.yml and items.yml! # But with only 40 puzzles, this is a more guaranteed approach: rules = { 103: ('+', '*', '<', 'if'), 111: ('#recursion', '<', '+'), 116: ('#recursion', 'zero?', 'decr', '*', '/'), 119: ('even?', 'zero?', '/', '*', '+', '#recursion'), 130: ('#recursion', '+', '='), 133: ('#recursion', '='), 140: ('+', '*'), 141: (), 142: (), 143: ('#recursion', 'decr', 'zero?'), 146: ('#recursion',), 201: ('cons', 'if', '<', '-'), 202: ('/', '+', 'car', 'cdr'), 204: (), 206: (), 208: ('-',), 212: ('-', '*'), 217: ('#recursion', '#null-or-pair-check', 'cdr'), 218: ('#recursion', '#null-or-pair-check', 'car', 'cdr', 'cons'), 220: ('if', 'filter', '#null-or-pair-check', 'even?'), 221: ('map', '*'), 225: ('car', 'cdr'), 227: ('reverse', 'map', '#number-or-pair-or-list-check', '#recursion'), 228: ('#number-or-pair-or-list-check', '#recursion', 'flat-map', '#list-or-cons'), 229: ('#recursion', '#number-or-pair-or-list-check', '+', '*', '='), 230: ('#tree-map', '*'), 231: ('#tree-map',), 232: ('cons', 'car', 'cdr', 'map', 'append', '#recursion', '#null-or-pair-check'), 236: ('map', 'car', 'cdr', '#recursion', 'accumulate', '#null-or-pair-check'), 240: ('#recursion', '=', 'incr', '#list-or-cons', 'append'), 241: ('#recursion', '=', '+', '#list-or-cons', 'append'), 254: ('car', 'cdr', 'if', 'eq?', '#pair-or-list-check'), 259: ('append', 'filter'), 261: ('#recursion', '#null-or-pair-check', '<', 'car', 'cdr', 'cons'), 262: ('#recursion', '#null-or-pair-check', '<', 'car', 'cdr', 'cons'), 354: ('stream-map', 'cons-stream', 'define', '*'), 355: ('stream-map', 'cons-stream', 'define', '+'), 356: ('stream-map', 'cons-stream', 'define', '*'), 364: ('stream-car', 'stream-cdr', '<', '-', '#recursion'), 370: ('null?', '<', 'stream-car', 'stream-cdr', '#recursion', 'cons-stream'), } c = root.connect(region, name=str(loc)) if loc.id() in rules: reqs = rules[loc.id()] set_rule(c, lambda state: StateChecker(state, player).check(*reqs)) else: raise AssertionError(f'missing rules for {loc}') class StateChecker: state: BaseClasses.CollectionState player: int checks: dict[str, Callable[[], bool]] checked: dict[str, bool] def __init__(self, state: BaseClasses.CollectionState, player: int): self.state = state self.player = player self.checks = { '+': self.has_plus, '<': self.has_less, '=': self.has_numeric_eq, 'and': self.has_and, 'append': self.has_append, 'car': self.has_car, 'cons-stream': self.has_cons_stream, 'decr': self.has_decr, 'even?': self.has_even, 'filter': self.has_filter, 'flat-map': self.has_flat_map, 'if': self.has_if, 'incr': self.has_incr, 'map': self.has_map, 'min': self.has_min, 'null?': self.has_null, 'or': self.has_or, 'reverse': self.has_reverse, 'stream-car': self.has_car, 'stream-cdr': self.has_stream_cdr, 'stream-map': self.has_stream_map, 'zero?': self.has_zero, '#list-or-cons': self.has_list_or_cons, '#null-or-pair-check': self.has_null_or_pair_check, '#number-or-pair-or-list-check': self.has_number_or_pair_or_list_check, '#pair-or-list-check': self.has_pair_or_list_check, '#recursion': self.has_recursion, '#tree-map': self.has_tree_map, } self.checked = {} def check(self, *reqs: str): for req in reqs: if req in self.checked: if self.checked[req]: continue else: return False self.checked[req] = False check = self.checks.get(req, lambda: self.state.has(req, self.player)) if not check(): return False self.checked[req] = True return True def has_plus(self): return self.state.has_any(["+", "-"], self.player) def has_less(self): return self.state.has_any(["<", ">", "<=", ">="], self.player) or self.check('if', 'min', '=') def has_numeric_eq(self): return self.state.has_any(["=", "eq?", "equal?"], self.player) or self.check('if', '<') def has_and(self): return self.state.has("and", self.player) or self.check('if') or self.check('or', 'not') def has_accumulate(self): return self.state.has("accumulate", self.player) or self.check('#recursion', '#null-or-pair-check', 'car', 'cdr') def has_append(self): return self.state.has("append", self.player) or self.check('#recursion', '#null-or-pair-check', 'cons', 'car', 'cdr') def has_car(self): return self.state.has_any(["car", "stream-car", "list-ref", "stream-ref"], self.player) def has_cons_stream(self): return self.state.has("cons-stream", self.player) or self.check('cons', 'delay') def has_decr(self): return self.state.has("decr", self.player) or self.check('+') def has_even(self): return self.state.has_any(["even?", "odd?"], self.player) or self.check('=', 'remainder') def has_filter(self): return self.state.has("filter", self.player) or self.check('#recursion', '#null-or-pair-check', 'cons', 'car', 'cdr') or self.check('accumulate', 'if', 'cons') or self.check('flat-map', 'if', '#list-or-cons') def has_flat_map(self): return self.state.has("flat-map", self.player) or self.check('accumulate', 'append') def has_if(self): return self.state.has_any(["if", "cond"], self.player) or self.check('and', 'or') def has_incr(self): return self.state.has("decr", self.player) or self.check('+') def has_map(self): return self.state.has("map", self.player) or self.check('#recursion', '#null-or-pair-check', 'cons', 'car', 'cdr') or self.check('accumulate', 'cons') or self.check('flat-map', '#list-or-cons') def has_min(self): return self.state.has_any(["min", "max"], self.player) or self.check('if', '<') def has_null(self): return self.state.has_any(["null?", "eq?", "equal?"], self.player) def has_or(self): return self.state.has("or", self.player) or self.check('if') or self.check('and', 'not') def has_reverse(self): return self.state.has("reverse", self.player) or self.check('accumulate', 'cons') def has_stream_cdr(self): return self.state.has("stream-cdr", self.player) or self.check('cdr', 'force') def has_stream_map(self): return self.state.has("stream-map", self.player) or self.check('#recursion', '#null-or-pair-check', 'stream-cons', 'stream-car', 'stream-cdr') def has_zero(self): return self.state.has("zero?", self.player) or self.check('=') or (self.check('if') and self.state.has_all(['positive?', 'negative?'], self.player)) def has_list_or_cons(self): return self.state.has_any(["list", "cons"], self.player) def has_null_or_pair_check(self): return self.check('null?') or self.state.has("pair?", self.player) def has_number_or_pair_or_list_check(self): return self.state.has_any(["number?", "pair?", "list?", "atom?"], self.player) def has_pair_or_list_check(self): return self.state.has_any(["pair?", "list?", "atom?"], self.player) def has_recursion(self): return self.check('if', 'define') # Y combinator is out of logic def has_tree_map(self): return self.check('#recursion', '#pair-or-list-check', 'map')