packages feed

hstzaar-0.4: src/AI/Minimax.hs

module AI.Minimax( greedy
                 , plyN
                 , dynamic
                 , minimax
                 , minimax_ab
                 , minimaxMove
                 , minimaxMove_ab
                 , prunedepth
                 , prunebreadth_asc
                 ) where

import Data.List (sort, sortBy, maximumBy, minimumBy, nub, nubBy)
import AI.Utils
import AI.Eval
import Board
import Debug.Trace


-- A greedy strategy
-- chooses the move with highest static evaluation score
greedy :: AI
greedy = AI { name = "greedy"
            , description = "Maximize the static evaluation function"
            , strategy = (ifPieces (==60)
                          greedyStrategy
                          (winOrPreventLoss (singleCaptures greedyStrategy))
                         )
            }



greedyStrategy :: Strategy
greedyStrategy (GameTree _ branches) rndgen 
    = trace ("[greedy score: " ++ show bestscore ++ "]") (bestmove, rndgen)
    where 
      choices = [(m, score t) | (m,t)<-branches]
      (bestmove,bestscore) = maximumBy cmp choices
      cmp (_,x) (_,y) = compare x y 
      score (GameTree _ []) = inf     -- opponent loses
      score (GameTree b _)  = -eval b   -- valued by the opponent



-- straight minimaxing strategies with fixed depth
plyN :: Int -> AI
plyN n = AI { name = "ply" ++ show n
            , description = "Minimax with depth " ++ show n
            , strategy = (ifPieces (==60)
                          greedyStrategy
                          (winOrPreventLoss
                           (singleCaptures 
                            (minimaxStrategy n 5))))
            }
         

-- dynamic strategy
-- use greedy algorithm for opening then switching to maximaxing 
dynamic :: Int -> AI
dynamic n = AI { name = "dyn" ++ show n 
               , description = "Minimax with dynamic depth " ++ show n
               , strategy = (ifPieces (==60) 
                             greedyStrategy
                             (winOrPreventLoss                       
                              (singleCaptures
                               (ifPieces (>40)
                                greedyStrategy
                                (minimaxStrategy n 5)
                               )
                              )
                             )
                            )
               }


-- Minimaxing strategy to ply depth `n' and breadth `m'
-- FIXME: for some reason alpha-beta prunning gives 
-- worst results than plain minimaxing against the greedy strategy 
minimaxStrategy :: Int -> Int -> Strategy
minimaxStrategy n m (GameTree _ []) rndgen = error "minimaxStrategy: empty tree"
minimaxStrategy n m g rndgen 
    = trace ("[minimax score: "++ show bestscore ++"]") (bestmove, rndgen)
    where (bestmove,bestscore) = minimaxMove g' -- minimaxMove_ab (-inf) inf g'
          g'  = prunebreadth_asc m $  -- ^ cut to breadth `m'
                prunedepth n $    -- ^ prune to depth `n'
                mapTree eval g   -- ^ apply evaluation function


-- Naive minimax algorithm (not used)
-- nodes should contain the static evaluation scores
minimax :: (Num a, Ord a) => GameTree a m -> a 
minimax (GameTree x []) = x
minimax (GameTree _ branches) = - minimum (map (minimax.snd) branches)

-- auxiliary function that returns the best first move
minimaxMove :: (Num a, Ord a) => GameTree a m -> (m,a)
minimaxMove (GameTree _ branches) = (m,x)
    where (m,x) = maximumBy cmp [(m, -minimax t) | (m,t)<-branches]
          cmp (_, x) (_, y) = compare x y



-- Minimax with alpha-beta prunning
minimax_ab :: (Num a, Ord a) => a -> a -> GameTree a m -> a
minimax_ab a b (GameTree x []) = a `max` x `min` b
minimax_ab a b (GameTree _ branches) = cmx a b (map snd branches)
    where cmx a b []  = a
          cmx a b (t:ts) | a'>=b = b
                         | otherwise = cmx a' b ts
                         where a' = - minimax_ab (-b) (-a) t


-- This variant also returns the best initial move
minimaxMove_ab :: (Num a, Ord a) => a -> a -> GameTree a m -> (m,a)
minimaxMove_ab a b (GameTree _ []) = error "minimaxMove_ab: empty tree"
minimaxMove_ab a b (GameTree _ branches@((m,_):_)) = cmx m a b branches
    where cmx m a b []  = (m,a)
          cmx m a b ((m',t):branches) 
              | a'>=b = (m',b)
              | otherwise = cmx m' a' b branches
              where a' = - minimax_ab (-b) (-a) t






{-
-- | eliminate double-captures that lead to the same board
nubCaptures :: BoardTree -> BoardTree
nubCaptures (GameTree node branches) 
    = GameTree node $ nubBy equiv [(t, nubCaptures g) | (t,g)<-branches]
    where
      equiv :: (Turn,BoardTree) -> (Turn,BoardTree) -> Bool
      equiv ((m1,Just m2),_) ((m2', Just m1'),_)
          = fst m1/=fst m2 && m1==m1' && m2==m2'
      equiv _ _ = False
                            
-}