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)))