Created
November 3, 2023 15:26
-
-
Save NTICompass/f7692aa8b7ba536666c4dab878efbe97 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/env python3 | |
""" | |
Brute-force solver for a math toy/puzzle | |
©1993 Silver Star | |
""" | |
import operator | |
from itertools import product | |
from collections import deque | |
op_map = { | |
'+': operator.add, | |
'-': operator.sub, | |
'*': operator.mul, | |
'/': operator.truediv # compared to floordiv | |
} | |
def combinations(wheels=5, values=6): | |
# Combinations to exclude | |
excluded = [] | |
for wheelIndexes in product(range(values), repeat=wheels): | |
if wheelIndexes in excluded: | |
continue | |
indexMatrix = [tuple((i + shift) % values for i in indexes) for shift, indexes in zip(range(values), [wheelIndexes] * values)] | |
excluded.extend(indexMatrix) | |
yield indexMatrix | |
def calculate(num1, op1, num2, op2, num3, mode=None): | |
# Question: Is "1 + 2 * 3" read as "1 + (2 * 3)" or "(1 + 2) * 3"? | |
# Guess I'll try both and see which gives the solution | |
specialMode = op1 not in ('/', '*') and op2 in ('/', '*') | |
if mode is not None and not specialMode: | |
mode = 1 | |
if specialMode and (mode is None or mode == 2): | |
# 1 + (2 * 3)" | |
yield (op_map[op1](num1, op_map[op2](num2, num3)), 2) | |
if mode is None or mode == 1: | |
# (1 + 2) * 3" | |
yield (op_map[op2](op_map[op1](num1, num2), num3), 1 if specialMode else None) | |
def solver(wheels, answerWheel): | |
answerWheel = deque(answerWheel) | |
totalWheels = len(wheels) | |
totalVals = len(answerWheel) | |
smallest = min(answerWheel) | |
biggest = max(answerWheel) | |
# Loop over every possible combination of the wheels (except the answer wheel) | |
for wheelIndexes in combinations(totalWheels, totalVals): | |
# Check each side of the toy | |
solution = False | |
args = [wheels[idx][pos] for idx, pos in enumerate(wheelIndexes.pop(0))] | |
for val, mode in calculate(*args): | |
# If the answer exists on the answer wheel, | |
# Then rotate the wheel and check the others | |
if (isinstance(val, int) or val == int(val)) and smallest <= val <= biggest: | |
allSides = True | |
answerWheel.rotate(-answerWheel.index(int(val))) | |
# Now check the other answers | |
for side, sideIdx in enumerate(wheelIndexes, start=1): | |
sideArgs = [wheels[idx][pos] for idx, pos in enumerate(sideIdx)] | |
sideVal = answerWheel[side] | |
vals, modes = zip(*calculate(*sideArgs, mode)) | |
vals = [int(v) if isinstance(v, int) or v == int(v) else 0 for v in vals] | |
# If the side doesn't match the answer, we're done with this combo | |
if sideVal not in vals: | |
allSides = False | |
break | |
else: | |
newMode = modes[vals.index(sideVal)] | |
if mode != newMode: | |
if mode is None: | |
mode = newMode | |
else: | |
allSides = False | |
break | |
# Solution found! | |
if allSides: | |
solution = True | |
break | |
# We're done :-) | |
if solution: | |
print(*args, '=', val) | |
for wheelIdx, wheel in enumerate(wheelIndexes, start=1): | |
wheelArgs = [wheels[idx][pos] for idx, pos in enumerate(wheel)] | |
print(*wheelArgs, '=', answerWheel[wheelIdx]) | |
break | |
if __name__ == '__main__': | |
wheels = ( | |
(1, 2, 3, 4, 5, 6), # red wheel | |
('+', '+', '*', '*', '-', '/'), # pink wheel | |
(1, 2, 6, 3, 4, 5), # orange wheel | |
('-', '-', '+', '/', '+', '*'), # green wheel | |
(1, 3, 2, 5, 4, 6) # teal wheel | |
) | |
# equal sign is blue wheel | |
answerWheel = (1, 3, 4, 2, 5, 6) # purple wheel | |
solver(wheels, answerWheel) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment