Skip to content

Instantly share code, notes, and snippets.

@NTICompass
Created November 3, 2023 15:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NTICompass/f7692aa8b7ba536666c4dab878efbe97 to your computer and use it in GitHub Desktop.
Save NTICompass/f7692aa8b7ba536666c4dab878efbe97 to your computer and use it in GitHub Desktop.
#!/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