Here's my code too. It lists only winning positions on the left and the two positions to the right are the moves you should make in case one gets rejected.
from functools import cache
@cache
def is_winnable(pos):
if sum(pos) == 0:
return False
elif sum(pos) == 1:
return True
lose_psns = []
for i in range(len(pos)):
for dots in range(pos[i]):
child_pos = tuple(sorted(pos[:i] + (dots,) + pos[i+1:], reverse=True))
if not is_winnable(child_pos):
lose_psns.append(child_pos)
if len(lose_psns) >= 2:
print(pos, lose_psns)
return True
return False
for a in range(6):
for b in range(6):
for c in range(6):
is_winnable(tuple(sorted((a, b, c), reverse=True)))