{-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} module Game.Halma.Board.Tests (tests) where import Control.Monad (forM_) import Data.List (permutations, sortBy) import Data.Maybe (isJust, fromJust) import Data.Function (on) import Game.Halma.Board import Math.Geometry.Grid import Test.HUnit hiding (Test) import Test.QuickCheck hiding (Result) import Test.Framework import Test.Framework.Providers.HUnit (testCase) import Test.Framework.Providers.QuickCheck2 (testProperty) import qualified Data.Map.Strict as M import qualified Math.Geometry.Grid.HexagonalInternal as HexGrid testRowsInDirection :: Assertion testRowsInDirection = forM_ [minBound..maxBound] $ \halmaDir -> do let fieldsAtRow i = filter ((== i) . rowsInDirection halmaDir) (indices SmallGrid) expected = let xs = [0,1,2,3,4,13,12,11,10] in xs ++ [9] ++ reverse xs expected @=? map (length . fieldsAtRow) [-9..9] 8 @=? rowsInDirection halmaDir (corner SmallGrid halmaDir) (-8) @=? rowsInDirection halmaDir (corner SmallGrid (oppositeDirection halmaDir)) -- | Cutoff equality comparison (@=?*) :: (Eq a, Show a) => [a] -> [a] -> Assertion expected @=?* actual = expected @=? take (length expected) actual testDistancesFromCenter :: Assertion testDistancesFromCenter = do [1,6,12,18,24,24,18,12,6,0,0,0] @=?* actual SmallGrid [1,6,12,18,24,30,30,24,18,12,6,0,0,0] @=?* actual LargeGrid where distCenter :: HalmaGrid -> (Int, Int) -> Int distCenter hg = distance hg (0, 0) fieldsAtDistance :: HalmaGrid -> Int -> [(Int, Int)] fieldsAtDistance hg d = filter ((== d) . distCenter hg) (indices hg) actual :: HalmaGrid -> [Int] actual hg = map (length . fieldsAtDistance hg) [0,1..] testBoundaryLength :: Assertion testBoundaryLength = do 48 @=? length (boundary SmallGrid) 60 @=? length (boundary LargeGrid) testDirectionTo :: Assertion testDirectionTo = do let c = corner SmallGrid [HexGrid.Northeast] @=? directionTo SmallGrid (c South) (c Southeast) [HexGrid.Northeast] @=? directionTo SmallGrid (c South) (c Northeast) assertOneOf (directionTo SmallGrid (c South) (c North)) (permutations [HexGrid.Northeast, HexGrid.Northwest]) assertOneOf :: (Eq a, Show a) => a -> [a] -> Assertion assertOneOf actual validResults = assertBool msg (actual `elem` validResults) where msg = "Expected '" ++ show actual ++ "' to be one of '" ++ show validResults ++ "'" numbersCorrect :: HalmaGrid -> [Maybe Piece] -> Bool numbersCorrect grid = go 15 15 (numberOfFields grid - 2*15) where go :: Int -> Int -> Int -> [Maybe Piece] -> Bool go 0 0 0 [] = True go _ _ _ [] = False go !n !s !e (piece : pieces) = case pieceTeam <$> piece of Just North -> go (n-1) s e pieces Just South -> go n (s-1) e pieces Just _otherTeam -> False Nothing -> go n s (e-1) pieces testInitialBoard :: Assertion testInitialBoard = ass SmallGrid >> ass LargeGrid where ass hg = assertBool "Expected 15 pieces of team north and south" $ numbersCorrect hg (pieces hg) pieces hg = map (\p -> lookupHalmaBoard p (initialBoard hg twoPlayers)) (indices hg) twoPlayers :: Team -> Bool twoPlayers team = team `elem` [North, South] arbitraryPerm :: [a] -> Gen [a] arbitraryPerm xs = fmap (map fst . sortBy (compare `on` snd) . zip xs) (vectorOf (length xs) arbitrary :: Gen [Double]) genBoard :: HalmaGrid -> Gen HalmaBoard genBoard grid = do pieces <- arbitraryPerm (indices grid) let (northTeam, restPieces) = splitAt 15 pieces southTeam = take 15 restPieces return $ fromJust $ fromMap grid $ M.fromList $ mkPieces North northTeam ++ mkPieces South southTeam where mkPieces team positions = let mkPiece pos ix = (pos, Piece { pieceNumber = ix, pieceTeam = team}) in zipWith mkPiece positions [1..15] instance Arbitrary HalmaGrid where arbitrary = elements [SmallGrid, LargeGrid] instance Arbitrary HalmaBoard where arbitrary = do grid <- arbitrary genBoard grid genHalmaGridPos :: HalmaGrid -> Gen (Int, Int) genHalmaGridPos grid = elements (indices grid) prop_fromMap :: HalmaBoard -> Bool prop_fromMap halmaBoard = fromMap (getGrid halmaBoard) (toMap halmaBoard) == Just halmaBoard prop_moveNumbersInvariant :: HalmaBoard -> Gen Bool prop_moveNumbersInvariant board = do let grid = getGrid board startPos <- genHalmaGridPos grid endPos <- genHalmaGridPos grid let move = Move { moveFrom = startPos, moveTo = endPos } case movePiece move board of Left _err -> -- may only fail when there is no piece on the start position or a piece -- on the end position return $ lookupHalmaBoard startPos board == Nothing || isJust (lookupHalmaBoard endPos board) Right board' -> do let pieces = map (\p -> lookupHalmaBoard p board') (indices grid) return $ lookupHalmaBoard endPos board' == lookupHalmaBoard startPos board && lookupHalmaBoard endPos board == lookupHalmaBoard startPos board' && numbersCorrect grid pieces tests :: Test tests = testGroup "Game.Halma.Board.Tests" [ testCase "testRowsInDirection" testRowsInDirection , testCase "testDistancesFromCenter" testDistancesFromCenter , testCase "testBoundaryLength" testBoundaryLength , testCase "testDirectionTo" testDirectionTo , testCase "testInitialBoard" testInitialBoard , testProperty "prop_fromMap" prop_fromMap , testProperty "prop_moveNumbersInvariant" prop_moveNumbersInvariant ]