-
Notifications
You must be signed in to change notification settings - Fork 0
/
levels.py
232 lines (175 loc) · 6.98 KB
/
levels.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
import json
from copy import deepcopy
from dataclasses import dataclass
import sympy
from sympy import sympify
# VALID_CARD_VALUES = ['+', '-', '*', '/', '!', '(', ')', '^', 'exp', 'log',
# 'ln', 'sqrt', 'pi', 'e', 'sin', 'cos', 'tan']
@dataclass
class Card:
value: str
@dataclass
class Level:
nb_bits_to_overflow: int
cards: list[Card]
min_cards: int
max_cards: int
hint: str
def is_float(string):
try:
float(eval(string))
return True
except:
return False
def load_level(level_path: str) -> Level:
with open(level_path) as f:
level_json = json.load(f)
if 'nbBitsToOverflow' not in level_json:
raise ValueError(f'nbBitsToOverflow not found in level {level_path}')
if 'cards' not in level_json:
raise ValueError(f'cards not found in level {level_path}')
nb_bits_to_overflow = int(level_json['nbBitsToOverflow'])
if nb_bits_to_overflow < 1:
raise ValueError(f'Invalid nbBitsToOverflow for level {level_path}: {nb_bits_to_overflow}')
raw_cards = level_json['cards']
# FUCK THIS VALIDATION IM TIRED
# for raw_card in raw_cards:
# valid = False
# for valid_card in VALID_CARD_VALUES:
# if raw_card.startswith(valid_card):
# valid = True
# break
# if not is_float(raw_card): # and not valid:
# raise ValueError(f'Invalid card found for level {level_path}: {raw_card}')
cards = [Card(raw_card) for raw_card in raw_cards]
min_cards = 0
max_cards = len(cards)
if 'minCards' in level_json:
min_cards = int(level_json['minCards'])
if 'maxCards' in level_json:
max_cards = int(level_json['maxCards'])
if min_cards < 0 or min_cards > len(cards):
raise ValueError(f'Invalid minCards for level {level_path}: {min_cards}')
if max_cards <= 0 or max_cards > len(cards):
raise ValueError(f'Invalid maxCards for level {level_path}: {max_cards}')
if min_cards > max_cards:
raise ValueError(f'minCards is greater than maxCards for level {level_path}')
hint = ''
if 'hint' in level_json:
hint = level_json['hint']
level = Level(nb_bits_to_overflow, cards, min_cards, max_cards, hint)
return level
def validate_solution(level: Level, solution: list[Card]) -> bool:
# Validate length of expression
if len(solution) < level.min_cards or len(solution) > level.max_cards:
print(f'Invalid length of solution: {len(solution)}')
return False
# Validate cards in expression
solution_cards = [card.value for card in level.cards]
for card in solution:
if card.value not in solution_cards:
print(f'Invalid card found in solution: {card}')
return False
solution_cards.remove(card.value)
# Check that we don't have two consecutive numerics
for i in range(len(solution) - 1):
a = solution[i].value
b = solution[i + 1].value
# This is to prevent two consecutive numerics which would lead to 1,1 => 11
if is_float(a) and is_float(b):
print(f'Two consecutive numerics found in solution: {solution[i].value}, {solution[i + 1].value}')
return False
# This is to prevent integer division
if a == '/' and b == '/':
print(f'Two consecutive division found in solution: {solution[i].value}, {solution[i + 1].value}')
return False
# This is to prevent multiplication becoming exposant
if a == '*' and b == '*':
print(f'Two consecutive division found in solution: {solution[i].value}, {solution[i + 1].value}')
return False
return True
def preprocess_solution(solution: list[Card]) -> list[Card]:
solution_copy = deepcopy(solution)
# Sympy preprocess
for card in solution_copy:
card.value = card.value.replace('e', 'E').replace('Exp', 'exp')
card.value = card.value.replace('mod', '%')
# For cards that are functions, assume next card is the argument LGTM for now
# e.g. 'sqrt' -> 'sqrt(' + next_card + ')'
processed_solution = []
i = 0
while i < len(solution_copy):
card = solution_copy[i]
if i >= len(solution_copy) - 1:
processed_solution.append(card)
i += 1
continue
next_card = solution_copy[i + 1]
if next_card.value == '(':
processed_solution.append(card)
processed_solution.append(next_card)
i += 2
continue
if card.value == 'sqrt':
card.value = 'sqrt('
elif card.value == 'exp':
card.value = 'exp('
elif card.value == 'log':
card.value = 'log(10,'
elif card.value == 'ln':
card.value = 'ln('
elif card.value == 'abs':
card.value = 'abs('
elif card.value == 'sin':
card.value = 'sin('
elif card.value == 'cos':
card.value = 'cos('
elif card.value == 'tan':
card.value = 'tan('
else:
processed_solution.append(card)
i += 1
continue
processed_solution.append(card)
processed_solution.append(next_card)
processed_solution.append(Card(')'))
i += 2
return processed_solution
def evaluate_solution(level: Level, solution: list[Card], world, game) -> float:
is_valid = validate_solution(level, solution)
if not is_valid:
raise ValueError('Solution validation failed')
if world == 4 and solution.__len__() > 10:
game.switchState('BlueScreenState')
return 0
# Preprocess solution
preprocessed_solution = preprocess_solution(solution)
expression = ''.join([card.value for card in preprocessed_solution])
try:
value = sympify(expression).evalf()
except:
raise ValueError(f'Sympy evaluation failed for expression: {expression}')
if value == sympy.zoo:
raise ValueError('Division by zero')
if sympy.im(value) != 0:
raise ValueError('Imaginary number')
return value
if __name__ == '__main__':
level = load_level(f'res/worlds/1/7.json')
cards_str = ', '.join([card.value for card in level.cards])
print(f'Number to overflow: {2**level.nb_bits_to_overflow-1}')
print(f'Cards: {cards_str}')
print(f'Nb cards constraints: [{level.min_cards},{level.max_cards}]')
running = True
while running:
solution_str = input('Enter your solution with each card separated by a comma : ')
card_values = solution_str.replace(' ', '').split(',')
cards = [Card(value) for value in card_values]
# try:
# value = evaluate_solution(level, cards)
# except Exception as e:
# print(e)
# continue
# print(f'Result is: {value}')
# running = value <= 2**level.nb_bits_to_overflow-1
print('You did it, gg!')