Haskell - how to generate next move in tic tac toe game with list monad

with

picks :: [x] -> [([x], x, [x])]
picks [] = []
picks (x : xs) = ([] , x, xs) : [(x : sy, y, ys) | (sy, y, ys) <- picks xs]

(which is a tweaked version of this), all possible next boards are

import Data.List.Split (chunksOf)

next :: Int -> Cell -> Board -> [Board]
next n who b =  
    picks (concat b) >>= \(sy, y, ys) ->
             case y of E -> [chunksOf n $ sy ++ [who] ++ ys] ;
                       _ -> []

where who is one of X or O, or course.

This is nothing more than a filter to keep the empties, and a map over those that have filtered through, at the same time. It is even simpler with list comprehensions,

next n who b = [ chunksOf n $ sy ++ [who] ++ ys
                 | (sy, E, ys) <- picks $ concat b ]

The picks function picks all possible cells, one after another, in the concatenated rows, while preserving also a prefix and a suffix; chunksOf n rebuilds the board from one long row of cells, in chunks of n cells in a row. So the overall effect is a list of all possible boards where E got replaced with who.

More efficient picks would build its prefixes (sy) in reversed order; creating a list of what is known as "zippers". Then on rebuilding they would have to be correspondingly reversed.

edit: as the list comprehension shows, it could've been written with do notation in the first place:

next n who b =  do 
    (sy, E, ys) <- picks $ concat b
    return (chunksOf n $ sy ++ [who] ++ ys])

In do notation a pattern mismatch is translated into a call to fail, which, in list monad, causes an element to be skipped while the computation as a whole continues without failing.

edit2: a Data.List-based code which does it in one pass over the input, is

import Data.List (mapAccumL)

-- mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
next who b = concat . snd $ mapAccumL f (id, drop 1 xs) xs 
  where
    xs = concat b
    n = length b
    f (k,r) x = ( (k.(x:), drop 1 r) , [chunksOf n $ k (who:r) | x==E] ) 

Thanks to גלעד ברקן for the discussion.


If we look at the type signature for >>= we see that it is

(>>=) :: Monad m => m a -> (a -> m b) -> m b

If you want to be able to "chain" your nxt function, the entire type signature for the bind must be:

[Board] -> (Board -> [Board]) -> [Board]

so nxt must have the type Board -> [Board]. Now we must ask ourselves what exactly nxt does: It takes a board and returns all possible moves from the current board. Coincidentially, the type for nxt is exactly what >>= needs: Board -> [Board]. But wait. How do we know whose turn it is? Like you already did, we can pass the current mark to place as parameter, but this also alters the type signature: Cell -> Board -> [Board]. Can we still chain this function? Yes we can. Using partial application, we can already apply the next marker to place by already passing it and then binding the resulting function:

nxt   :: Cell -> Board -> [Board]
nxt X ::         Board -> [Board]

Now all we have to do is traverse every field and check whether it is empty. If it is, then we replace it with our mark and traverse the other fields. :

nxt :: Cell -> Board -> [Board]
nxt _    []         = []
nxt mark (row:rest) = map (:rest) (replaceAll mark row) ++ (map (row:) $ nxt mark rest)
  where
    replaceAll _ [] = []
    replaceAll m (x:xs)
      | x == E = (m:xs) : (map (x:) $ replaceAll m xs)
      | otherwise = map (x:) $ replaceAll m xs

Now you can chain moves like this:

iniState 3 >>= nxt X >>= nxt O

I would advise to separate the simulating function and the actual move finding function for greater usage purposes. For example, like this you could easily write a function which returns all boards which will win for a specific size and a specific player:

winner :: Cell -> Int -> [Board]
winner who size = filter (win who)
                $ foldr (>=>) return (take (n*n) $ cycle [nxt O, nxt X])
                $ initBoard n

I will leave it to you to implement the game playing part as an exercise.


The other answers covered the straightforward solutions. Here I present a lens solution, because it's nicely applicable for the task.

With lens we can separately specify the following two things:

  • Which parts of a data structure we want to operate on.
  • What operations we'd like to do on those parts.

We'd like to point to the empty cells of the board as targets. Traversal' Board Cell indicates that the overall data structure has type Board, while the targets have type Cell.

import Control.Lens

emptyCells :: Traversal' Board Cell
emptyCells = each . each . filtered (==E)

Now we can do a variety of operations with emptyCells.

board = iniBoard 3

-- get the number of targets:
lengthOf emptyCells board -- 9

-- return a flat list of the targets
toListOf emptyCells board -- [E,E,E,E,E,E,E,E,E]

-- set all targets to a value
set emptyCells X board -- [[X,X,X],[X,X,X],[X,X,X]]

-- set the nth target to a value
set (elementOf emptyCells 2) X board -- [[E,E,X],[E,E,E],[E,E,E]]

-- get the nth target, if it exists
preview (elementOf emptyCells 2) board -- Just E

We can also neatly implement next using emptyCells and the holesOf function. holesOf emptyCells returns a lists of "holes" of the board. Each hole essentially contains a Cell and a function which takes a Cell argument and returns a new Board with the supplied Cell plugged into a certain position.

Unfortunately, the holes are implemented rather abstractly, and holesOf emptyCells has an uninformative Board ->[Control.Lens.Internal.Context.Pretext (->) Cell Cell Board] type. We should just remember that Control.Comonad.Store provides an interface for working with holes. pos returns the focus element of a hole (here it's a Cell), while peek plugs a new element in the hole and returns the resulting data structure.

For nxt x board, we need to plug in x into every position with an empty cell. With this in mind, nxt simply becomes:

import Control.Comonad.Store

nxt :: Cell -> Board -> [Board]
nxt x = map (peek x) . holesOf emptyCells