{-# LANGUAGE DeriveFunctor #-} import Data.Maybe import Data.Sequence hiding (zip, filter, length) import Data.Foldable import Data.Monoid import Control.Arrow newtype Fix f = Fix { unFix :: f (Fix f) } cata :: Functor f => (f a -> a) -> Fix f -> a cata f = f . fmap (cata f) . unFix ana :: Functor f => (a -> f a) -> a -> Fix f ana f = Fix . fmap (ana f) . f data Player = Cross | Nought deriving (Eq, Show) type Cell = Maybe Player e :: Cell e = Nothing -- always 9 cells data Board = Board { getPlayer :: Player, getCells :: Seq Cell } type Move = Int (!?) :: Seq a -> Move -> a s !? m = index s m showCell :: Cell -> String showCell Nothing = " " showCell (Just Cross) = "x" showCell (Just Nought) = "o" instance Show Board where show (Board p cells) = "+---+---+---+\n" ++ "| " ++ showCell (cells !? 0) ++ " | " ++ showCell (cells !? 1) ++ " | " ++ showCell (cells !? 2) ++ " |\n" ++ "+---+---+---+\n" ++ "| " ++ showCell (cells !? 3) ++ " | " ++ showCell (cells !? 4) ++ " | " ++ showCell (cells !? 5) ++ " |\n" ++ "+---+---+---+\n" ++ "| " ++ showCell (cells !? 6) ++ " | " ++ showCell (cells !? 7) ++ " | " ++ showCell (cells !? 8) ++ " |\n" ++ "+---+---+---+\n" ++ "It's " ++ show p ++ "'s turn\n" -- a game tree is a tree with a current board -- and a list of moves and their outcomes data GameTreeF b m f = Tree b [(m, f)] deriving (Functor) type GameTree b m = Fix (GameTreeF b m) other :: Player -> Player other Cross = Nought other Nought = Cross -- decide on a winner. The first found winner is taken, no matter if more exist decide :: Board -> Maybe Player decide (Board p cells) = getAlt $ isWinner 0 1 2 <> isWinner 3 4 5 <> isWinner 6 7 8 <> isWinner 0 3 6 <> isWinner 1 4 7 <> isWinner 2 5 8 <> isWinner 0 4 8 <> isWinner 2 4 6 where sameAs :: Cell -> Cell -> Cell sameAs (Just Cross) (Just Cross) = Just Cross sameAs (Just Nought) (Just Nought) = Just Nought sameAs _ _ = Nothing isWinner a b c = Alt $ (cells !? a) `sameAs` (cells !? b) `sameAs` (cells !? c) initialState :: Board initialState = (Board Cross (fromList $ map (const Nothing) [0..8])) findMoves :: Board -> [Move] findMoves (Board p cells) = map fst $ filter (isNothing . snd) $ zip [0..] $ toList cells applyMove :: Board -> Move -> Board applyMove (Board player cells) move = Board (other player) (update move (Just player) cells) fullGameTree :: Board -> GameTree Board Move fullGameTree = ana singleStep where singleStep board = Tree board $ map (id &&& applyMove board) (findMoves board) -- TTT is decidable, so either -1 | 0 | 1 data MinimaxRating = Loss | Draw | Win deriving (Eq, Ord, Show) invertRating Win = Loss invertRating Draw = Draw invertRating Loss = Win minimax :: GameTree Board Move -> (MinimaxRating, [Move]) minimax = cata singleStep where mergeInMove (m, (r, ms)) = (invertRating r, m:ms) compareMoves (m, ms) (n, ns) = compare m n <> compare (length ns) (length ms) singleStep (Tree board []) = (Draw, []) singleStep (Tree board moves) = case decide board of Just winner | winner == getPlayer board -> (Win, []) Just winner -> (Loss, []) Nothing -> maximumBy compareMoves $ map mergeInMove moves --computeKITree :: GameTree Board Move -> GameTree (Board, MinimaxRating, [Move]) Move --computeKITree = _ playMove = flip applyMove crossWin :: Board -> Board crossWin = playMove 6 . playMove 8 main = putStrLn $ show $ minimax . fullGameTree $ crossWin $ initialState