Solution (source code)

= Solution

Assume the board satisfies the <representation invariant>. Reject completed boards and occupied or out-of-range coordinates; testing membership in the empty component covers both coordinate failures.

``
finished :: Board -> Bool
finished (os,xs,es) = won os || won xs || null es

play :: Board -> Place -> Board
play b@(os,xs,es) q
  | finished b     = error "play: completed game"
  | q `notElem` es = error "play: unavailable place"
  | length os == length xs = (q:os,xs,rest)
  | otherwise             = (os,q:xs,rest)
  where rest = filter (/=q) es
``

Here `won` is defined in part d. Moving one available coordinate into the appropriate occupied component preserves the <set partition> and the alternating-count rule. Checking completion first preserves the no-moves-after-a-win invariant.