packages feed

hstzaar-0.9.2: src/AI/Minimax.hs

{-# LANGUAGE BangPatterns #-}
module AI.Minimax ( negamax
                  , negamax_alpha_beta
                  , negascout
                  , jamboree
                  ) where

import Board
import AI.Gametree
import Control.Parallel.Strategies 


-- | Naive negamax algorithm (no prunning)
-- wrapper
negamax :: Gametree p => Valuation p -> Int -> p -> Valued p
negamax node_value depth p = negamax' depth p
  where
    -- worker
    negamax' d p
      | d==0 || is_terminal p = valued node_value p
      | otherwise = negate $ minimum [negamax' d  p' | p'<-children p]
        where d' = d-1

-- | Negamax with alpha-beta prunning 
-- wrapper
negamax_alpha_beta :: Gametree p => Valuation p -> Int -> p -> Valued p
negamax_alpha_beta node_value depth p
  = let a = fromIntegral (minBound+1 :: Int) 
        b = fromIntegral (maxBound :: Int) 
    in alpha_beta' depth a b p
  where
    -- worker
    alpha_beta' d alfa beta p
        | d==0 || is_terminal p = valued node_value p 
        | otherwise = cmx alfa (children p)
          where 
            d' = d-1
            cmx alfa [] = alfa
            cmx alfa (p:ps) 
              | a'>=beta = a'
              | otherwise = cmx (max a' alfa) ps
                where a' = negate $ alpha_beta' d' (negate beta) (negate alfa) p



-- | Negascout search
-- wrapper
negascout :: Gametree p => Valuation p -> Int -> p -> Valued p
negascout node_value depth p
  = let a = fromIntegral (minBound+1 :: Int) 
        b = fromIntegral (maxBound :: Int) 
    in negascout' node_value depth a b p

-- worker      
negascout' node_value d alfa beta p
  | d==0 || is_terminal p = valued node_value p 
  | d==1  = valued (negate . node_value) p0       -- short-circuit for depth 1
  | b >= beta = b
  | otherwise = scout (max alfa b) b ps
    where
      d' = d-1
      ps = children p
      p0 = unvalued $ minimum $ map (valued node_value) ps
      -- p0 = estimate_best node_value ps  -- child with best static score
      b = negate $ negascout' node_value d' (negate beta) (negate alfa) p0 -- full search estimate
    
      scout !alfa !b [] = b
      scout !alfa !b (p:ps)
        | s>=beta = s
        | otherwise = scout alfa' b' ps
          where s = negate $ negascout' node_value d' (negate (1$+alfa)) (negate alfa) p
                s' | s>alfa = negate $ 
                              negascout' node_value d' (negate beta) (negate alfa) p
                   | otherwise = s
                alfa' = max alfa s'
                b' = max b s'
      

-- | Parallel negascout, aka "Jamboree"
-- | result of each scout test
data Result a b = Cutoff a   -- beta cutoff found
                | Search b   -- do a full search
                | OK          -- test suceeded
                      
                      
jamboree :: Gametree p => Valuation p -> Int -> p -> Valued p
jamboree node_value depth p 
  = let a = fromIntegral (minBound+1 :: Int) 
        b = fromIntegral (maxBound :: Int) 
    in jamboree' node_value depth a b p 

-- worker
jamboree' node_value d alfa beta p
  | d<=1  = negascout' node_value d alfa beta p
            -- use sequencial version for low depth
  | is_terminal p = valued node_value p   -- terminal node?
  | b >= beta = b   -- 1st child failed high
  | otherwise = cutoff [] (map scout ps `using` parList rseq)
    where    
      d' = d-1
      ps = children p            
      p0 = unvalued $ minimum $ map (valued node_value) ps -- best child
    
      b = negate $ jamboree' node_value d' (negate beta) (negate alfa) p0 -- full search estimate
      alfa' = max alfa b

      scout p 
        | s >= beta = Cutoff s  
        | s > alfa' = Search p
        | otherwise = OK
          where s = negate $ jamboree' node_value d' (negate (1$+alfa')) (negate alfa') p
                    -- null window search

      -- join results of parallel scouts
      cutoff _  (Cutoff s : rs) = s
      cutoff ps (Search p : rs) = cutoff (p:ps) rs
      cutoff ps (OK : rs)       = cutoff ps rs
      cutoff ps []              = search alfa' b ps

      -- sequential full search for scout failures
      search !alfa !b [] = b
      search !alfa !b (p : ps) 
        | s >= beta = s
        | otherwise = search (max s alfa) (max s b) ps
          where s = negate $ jamboree' node_value d' (negate beta) (negate alfa) p



-- | estimate best move using static evaluation
-- estimate_best :: Valuation p -> [p] -> p            
-- estimate_best node_value = minimumBy cmp 
--  where cmp p p' = compare (node_value p) (node_value p')