packages feed

hstzaar-0.5: src/AI/Minimax.hs

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

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

-- greedy AI player
greedy :: AI
greedy = AI { name = "greedy"
            , description = "Maximize the static evaluation function"
            , strategy = (withPieces $ \n -> 
                          if n==60 then singleCaptures greedyStrategy
                          else winOrPreventLoss $ 
                               -- nubDoubleCaptures $ 
                               dontPass greedyStrategy
                         )
            }

-- greedy strategy
-- lookup one move ahed and choose the highest static evaluation score
greedyStrategy :: Strategy
greedyStrategy (GameTree _ branches) rndgen 
    | null branches = error "greedyStrategy: empty branches"
    | otherwise     = (bestmove, rndgen)
    where 
      choices = [(m, score t) | (m,t)<-branches]
      (bestmove,bestscore) = minimumBy cmp choices
      cmp (_,x) (_,y) = compare x y 
      score (GameTree b _)  = static_eval b   -- valued the opponent



-- minimaxing AI player with alpha-beta prunning and fixed depth and breadth
plyN :: Int -> Int -> AI
plyN depth breadth 
  = AI { name = "ply_" ++ show depth ++ "_" ++ show breadth
       , description = "Minimaxing with depth " ++ show depth ++ 
                       " and breadth " ++ show breadth
       , strategy = (withPieces $ \n -> 
                      if n==60 then singleCaptures greedyStrategy
                      else winOrPreventLoss $ 
                           --nubDoubleCaptures $
                           dontPass $
                           minimaxStrategy depth breadth
                    )
       }
         

{-
-- 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 with alpha-beta and static prunning 
-- n is the ply depth, m is the tree breadth 
minimaxStrategy :: Int -> Int -> Strategy
minimaxStrategy n m (GameTree _ []) rndgen 
    = error "minimaxStrategy: empty tree"
minimaxStrategy n m bt rndgen 
    = (bestmove, rndgen)
    where (bestmove,bestscore) = minimaxMove_ab (-infinity) infinity bt'
          bt' = pruneDepth n $        -- ^ prune to depth `n'
                pruneBreadth m $      -- ^ cut to breadth `m'
                lowFirst $            -- ^ order moves acording to static valuation
                mapTree static_eval bt  -- ^ apply static 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