packages feed

hstzaar-0.6: src/Tests.hs

{-
  Quickcheck properties for board & AI code
  Pedro Vasconcelos, 2010, 2011
-}
module Tests (run_tests) where
import Board 
import AI.Minimax
import AI.Utils
import AI.Eval
import Test.QuickCheck
import qualified Data.IntMap as IntMap
import qualified Data.IntSet as IntSet
import List (delete, nub, sort)


---------------------------------------------------------------------------
-- Quickcheck properties 
---------------------------------------------------------------------------

-- a capture reduces the number of pieces by one
prop_capture_moves :: Board -> Bool
prop_capture_moves b
    = and [1+boardSize b' == boardSize b |
           m<-nextCaptureMoves b, let b' = applyMove b m]

-- a stacking reduces the number of pieces by one
prop_stacking_moves1 :: Board -> Bool
prop_stacking_moves1 b
    = and [1+boardSize b' == boardSize b |
           m<-nextStackingMoves b, let b' = applyMove b m]

-- stacking mantains the sum of pieces heights of the active player
-- and does not change the pieces of the other player
prop_stacking_moves2 :: Board -> Bool
prop_stacking_moves2 b
    = and [ heights (active b') == heights (active b) && inactive b' == inactive b | 
             m <- nextStackingMoves b, let b'=applyMove b m]
    where heights b = sum [h | (_,h)<-IntMap.elems b]


---------------------------------------------------------------------------
-- some properties of the AI code
---------------------------------------------------------------------------


-- upper and lower bounds for the evaluation function
prop_value_bounds :: Board -> Property
prop_value_bounds board
    = not (active_lost board) && not (inactive_lost board) ==> abs value < infinity
    where value = eval board


-- end game positions give plus/minus infinityinity scores
prop_inactive_lost :: Board -> Property
prop_inactive_lost b
    = not (active_lost b) && inactive_lost b ==> eval b == infinity 

prop_active_lost :: Board -> Property
prop_active_lost b
    = not (inactive_lost b) && active_lost b ==> eval b == (-infinity)


-- correcteness of alpha-beta pruning against plain minimax 
-- parameters: number of pieces, pruning depth 
prop_alpha_beta :: Int -> Int  -> Property
prop_alpha_beta npieces depth 
    = forAllShrink (resize npieces arbitrary) shrink $ \b ->
      admissible b ==>
          let bt = mkTree depth b
          in minimax_ab (-infinity) infinity bt == minimax bt
    

-- correctness of alpha-beta minimax extended with principal variation
-- parameters: number of pieces, pruning depth 
prop_alpha_beta_pv :: Int -> Int -> Property
prop_alpha_beta_pv npieces depth 
    | depth`mod`4 == 0
        = forAllShrink (resize npieces arbitrary) shrink $ \b ->
          admissible b  ==> 
          let bt = mkTree depth b
              (v,ms)= minimaxPV bt
              (GameTree v' _) = foldl treeMove bt ms
          in neg (length ms) v'==v
    where neg n x | n`mod`4==0 = x
                  | n`mod`4==2 = -x


mkTree :: Int -> Board -> GameTree Int Move
mkTree depth board = pruneDepth depth $ mapTree eval $ boardTree board


treeMove :: Eq m => GameTree s m -> m -> GameTree s m
treeMove (GameTree _ branches) m = head [t | (m',t)<-branches, m'==m]




 
-- correctness of the zone of control computation
-- the zone of control is the set of pieces
-- that can be captured in a turn (one or two moves)
prop_zoc_correct :: Board -> Bool
prop_zoc_correct b = pos == pos'
    where
      moves1 = nextCaptureMoves b
      moves2 = concat [nextCaptureMoves (applyMove b m) | m<-moves1]
      pos = IntSet.fromList [dest | Capture _ dest<-(moves1++moves2)]
      pos'= IntMap.keysSet (zoneOfControl b)


-- helper functions to filter boards, etc.
-- "admissible" boards: no winner yet
admissible :: Board -> Bool
admissible b = not (active_lost b) && not (inactive_lost b)

active_lost, inactive_lost :: Board -> Bool
active_lost b 
    = (move b==1 && null (nextCaptureMoves b)) || 
      any (==0) (countStacks $ active b)

inactive_lost b = any (==0) (countStacks $ inactive b)


-- number of piece types in a half-board
--pieceTypes :: HalfBoard -> Int
--pieceTypes b = length $ nub $ map fst $ IntMap.elems b


-- run all tests
run_tests :: IO ()
run_tests = mapM_ run_test all_tests
    where run_test (name, test) = putStrLn (">>> " ++ name) >> test

all_tests = [ ("prop_capture_moves", quickCheck prop_capture_moves)
            , ("prop_stacking_moves1", quickCheck prop_stacking_moves1)
            , ("prop_stacking_moves2", quickCheck prop_stacking_moves2)
            --, ("prop_zero_sum", quickCheck prop_zero_sum)
            , ("prop_value_bounds", quickCheck prop_value_bounds)
            , ("prop_inactive_lost", quickCheck prop_inactive_lost)
            , ("prop_active_lost", quickCheck prop_active_lost)
            , ("prop_zoc_correct", quickCheck prop_zoc_correct)
            --, ("prop_zoc_correct2", quickCheck prop_zoc_correct2)
            , ("prop_alpha_beta 10 4",
               quickCheck (prop_alpha_beta 10 4))
            , ("prop_alpha_beta 15 6",
               quickCheck (prop_alpha_beta 15 6))
            , ("prop_alpha_beta_pv 10 4",
               quickCheck (prop_alpha_beta_pv 10 4))
            , ("prop_alpha_beta_pv 15 6",
               quickCheck (prop_alpha_beta_pv 15 6))
            ]



quickCheckN n = quickCheckWith (stdArgs{maxSuccess=n})