{-# LANGUAGE DeriveFunctor #-}
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
e :: Cell
e = Nothing
-- always 9 cells
data Board = Board { getPlayer :: Player, getCells :: Seq Cell }
(!?) :: Seq a -> Move -> a
s !? m = index s m
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)]
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]
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)
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