117ab57e95b5658066ee88cd53054ce4bb491837
[sicpelago] / apworld / sicp / locations.py
1 import os
2 import yaml
3
4 from typing import Any, Callable
5
6 import BaseClasses
7 from worlds.generic.Rules import set_rule
8
9 GAME = "SICPelago"
10 OriginRegionName = "REPL"
11
12 class Location(BaseClasses.Location):
13     game = GAME
14
15 class LocationData:
16     def __init__(self, data: dict[str, Any]):
17         self.data = data
18     def __str__(self):
19         return self.data['name']
20     def id(self):
21         (chapter, exercise) = str(self).split(' ')[0].split('.')
22         return int(chapter) * 100 + int(exercise)
23
24 with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'locations.yml'), 'r') as input:
25     all_locations = [LocationData(data) for data in yaml.safe_load(input)]
26
27
28 def set_up_rules(loc: LocationData, root: BaseClasses.Region, region: BaseClasses.Region, player: int):
29     # Ideally this would be automatically generated from locations.yml and items.yml!
30     # But with only 40 puzzles, this is a more guaranteed approach:
31
32     rules = {
33         103: ('+', '*', '<', 'if'),
34         111: ('#recursion', '<', '+'),
35         116: ('#recursion', 'zero?', 'decr', '*', '/'),
36         119: ('even?', 'zero?', '/', '*', '+', '#recursion'),
37         130: ('#recursion', '+', '='),
38         133: ('#recursion', '='),
39         140: ('+', '*'),
40         141: (),
41         142: (),
42         143: ('#recursion', 'decr', 'zero?'),
43         146: ('#recursion',),
44         201: ('cons', 'if', '<', '-'),
45         202: ('/', '+', 'car', 'cdr'),
46         204: (),
47         206: (),
48         208: ('-',),
49         212: ('-', '*'),
50         217: ('#recursion', '#null-or-pair-check', 'cdr'),
51         218: ('#recursion', '#null-or-pair-check', 'car', 'cdr', 'cons'),
52         220: ('if', 'filter', '#null-or-pair-check', 'even?'),
53         221: ('map', '*'),
54         225: ('car', 'cdr'),
55         227: ('reverse', 'map', '#number-or-pair-or-list-check', '#recursion'),
56         228: ('#number-or-pair-or-list-check', '#recursion', 'flat-map', '#list-or-cons'),
57         229: ('#recursion', '#number-or-pair-or-list-check', '+', '*', '='),
58         230: ('#tree-map', '*'),
59         231: ('#tree-map',),
60         232: ('cons', 'car', 'cdr', 'map', 'append', '#recursion', '#null-or-pair-check'),
61         236: ('map', 'car', 'cdr', '#recursion', 'accumulate', '#null-or-pair-check'),
62         240: ('#recursion', '=', 'incr', '#list-or-cons', 'append'),
63         241: ('#recursion', '=', '+', '#list-or-cons', 'append'),
64         254: ('car', 'cdr', 'if', 'eq?', '#pair-or-list-check'),
65         259: ('append', 'filter'),
66         261: ('#recursion', '#null-or-pair-check', '<', 'car', 'cdr', 'cons'),
67         262: ('#recursion', '#null-or-pair-check', '<', 'car', 'cdr', 'cons'),
68         354: ('stream-map', 'cons-stream', 'define', '*'),
69         355: ('stream-map', 'cons-stream', 'define', '+'),
70         356: ('stream-map', 'cons-stream', 'define', '*'),
71         364: ('stream-car', 'stream-cdr', '<', '-', '#recursion'),
72         370: ('null?', '<', 'stream-car', 'stream-cdr', '#recursion', 'cons-stream'),
73     }
74
75     c = root.connect(region, name=str(loc))
76     if loc.id() in rules:
77         reqs = rules[loc.id()]
78         set_rule(c, lambda state: StateChecker(state, player).check(*reqs))
79     else:
80         raise AssertionError(f'missing rules for {loc}')
81
82 class StateChecker:
83     state: BaseClasses.CollectionState
84     player: int
85     checks: dict[str, Callable[[], bool]]
86     checked: dict[str, bool]
87
88     def __init__(self, state: BaseClasses.CollectionState, player: int):
89         self.state = state
90         self.player = player
91         self.checks = {
92             '+': self.has_plus,
93             '<': self.has_less,
94             '=': self.has_numeric_eq,
95             'and': self.has_and,
96             'append': self.has_append,
97             'car': self.has_car,
98             'cons-stream': self.has_cons_stream,
99             'decr': self.has_decr,
100             'even?': self.has_even,
101             'filter': self.has_filter,
102             'flat-map': self.has_flat_map,
103             'if': self.has_if,
104             'incr': self.has_incr,
105             'map': self.has_map,
106             'min': self.has_min,
107             'null?': self.has_null,
108             'or': self.has_or,
109             'reverse': self.has_reverse,
110             'stream-car': self.has_car,
111             'stream-cdr': self.has_stream_cdr,
112             'stream-map': self.has_stream_map,
113             'zero?': self.has_zero,
114             '#list-or-cons': self.has_list_or_cons,
115             '#null-or-pair-check': self.has_null_or_pair_check,
116             '#number-or-pair-or-list-check': self.has_number_or_pair_or_list_check,
117             '#pair-or-list-check': self.has_pair_or_list_check,
118             '#recursion': self.has_recursion,
119             '#tree-map': self.has_tree_map,
120         }
121         self.checked = {}
122
123     def check(self, *reqs: str):
124         for req in reqs:
125             if req in self.checked:
126                 if self.checked[req]:
127                     continue
128                 else:
129                     return False
130             self.checked[req] = False
131             check = self.checks.get(req, lambda: self.state.has(req, self.player))
132             if not check():
133                 return False
134             self.checked[req] = True
135         return True
136
137     def has_plus(self):
138         return self.state.has_any(["+", "-"], self.player)
139
140     def has_less(self):
141         return self.state.has_any(["<", ">", "<=", ">="], self.player) or self.check('if', 'min', '=')
142
143     def has_numeric_eq(self):
144         return self.state.has_any(["=", "eq?", "equal?"], self.player) or self.check('if', '<')
145
146     def has_and(self):
147         return self.state.has("and", self.player) or self.check('if') or self.check('or', 'not')
148
149     def has_accumulate(self):
150         return self.state.has("accumulate", self.player) or self.check('#recursion', '#null-or-pair-check', 'car', 'cdr')
151
152     def has_append(self):
153         return self.state.has("append", self.player) or self.check('#recursion', '#null-or-pair-check', 'cons', 'car', 'cdr')
154
155     def has_car(self):
156         return self.state.has_any(["car", "stream-car", "list-ref", "stream-ref"], self.player)
157
158     def has_cons_stream(self):
159         return self.state.has("cons-stream", self.player) or self.check('cons', 'delay')
160
161     def has_decr(self):
162         return self.state.has("decr", self.player) or self.check('+')
163
164     def has_even(self):
165         return self.state.has_any(["even?", "odd?"], self.player) or self.check('=', 'remainder')
166
167     def has_filter(self):
168         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')
169
170     def has_flat_map(self):
171         return self.state.has("flat-map", self.player) or self.check('accumulate', 'append')
172
173     def has_if(self):
174         return self.state.has_any(["if", "cond"], self.player) or self.check('and', 'or')
175
176     def has_incr(self):
177         return self.state.has("decr", self.player) or self.check('+')
178
179     def has_map(self):
180         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')
181
182     def has_min(self):
183         return self.state.has_any(["min", "max"], self.player) or self.check('if', '<')
184
185     def has_null(self):
186         return self.state.has_any(["null?", "eq?", "equal?"], self.player)
187
188     def has_or(self):
189         return self.state.has("or", self.player) or self.check('if') or self.check('and', 'not')
190
191     def has_reverse(self):
192         return self.state.has("reverse", self.player) or self.check('accumulate', 'cons')
193
194     def has_stream_cdr(self):
195         return self.state.has("stream-cdr", self.player) or self.check('cdr', 'force')
196
197     def has_stream_map(self):
198         return self.state.has("stream-map", self.player) or self.check('#recursion', '#null-or-pair-check', 'stream-cons', 'stream-car', 'stream-cdr')
199
200     def has_zero(self):
201         return self.state.has("zero?", self.player) or self.check('=') or (self.check('if') and self.state.has_all(['positive?', 'negative?'], self.player))
202
203     def has_list_or_cons(self):
204         return self.state.has_any(["list", "cons"], self.player)
205
206     def has_null_or_pair_check(self):
207         return self.check('null?') or self.state.has("pair?", self.player)
208
209     def has_number_or_pair_or_list_check(self):
210         return self.state.has_any(["number?", "pair?", "list?", "atom?"], self.player)
211
212     def has_pair_or_list_check(self):
213         return self.state.has_any(["pair?", "list?", "atom?"], self.player)
214
215     def has_recursion(self):
216         return self.check('if', 'define') # Y combinator is out of logic
217
218     def has_tree_map(self):
219         return self.check('#recursion', '#pair-or-list-check', 'map')
220