------------------------------------------------------------------------ -- | -- Module : Math.Geometry.Grid.GridQC -- Copyright : (c) Amy de Buitléir 2012-2014 -- License : BSD-style -- Maintainer : amy@nualeargais.ie -- Stability : experimental -- Portability : portable -- -- QuickCheck tests. -- ------------------------------------------------------------------------ {-# LANGUAGE FlexibleContexts, ExistentialQuantification, TypeFamilies, MultiParamTypeClasses #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Math.Geometry.GridQC where import Math.Geometry.GridInternal import Prelude hiding (null) import qualified Prelude as P (null) import Data.List (delete, nub, sort) import Data.Maybe (isJust, fromJust) import Test.Framework as TF (Test) import Test.Framework.Providers.QuickCheck2 (testProperty) import Test.QuickCheck ((==>), Gen, Arbitrary, arbitrary, choose, Property, property, vectorOf, elements) -- | @'isqrt' n@ returns the greatest integer not greater than the square root -- of @n@. isqrt :: Int -> Int isqrt n = (floor . sqrt) n' where n' = fromIntegral n :: Float -- Given an arbitrary integer, select a corresponding point in the grid. pointAt :: Grid g => g -> Int -> Index g pointAt g i = indices g !! (i `mod` n) where n = (length . indices) g minPathCount :: (Eq (Index g), Grid g) => g -> Index g -> Index g -> Int minPathCount g a b = length . minimalPaths g a $ b minPathCount2 :: (Eq (Index g), Grid g) => g -> [Index g] -> Index g -> Int minPathCount2 g as b = sum . map (\x -> minPathCount g x b) $ as cartesianBoundaryCount :: (Eq a, Num a) => (a, a) -> a cartesianBoundaryCount (0,_) = 0 cartesianBoundaryCount (_,0) = 0 cartesianBoundaryCount (1,c) = c cartesianBoundaryCount (r,1) = r cartesianBoundaryCount (r,c) = 2*(r+c) - 4 involves :: Eq a => (a, a) -> a -> Bool involves (a, b) c = c == a || c == b chooseIndices :: Grid g => g -> Int -> Gen [Index g] chooseIndices g n = do k <- choose (0,n) if null g then return [] else vectorOf (k+2) (elements . indices $ g) chooseClosePointsUnbounded :: Gen ((Int, Int), (Int, Int)) chooseClosePointsUnbounded = do (x1,y1) <- arbitrary x2 <- choose (x1-2,x1+2) y2 <- choose (y1-2,y1+2) return ((x1,y1), (x2,y2)) chooseClosePoints :: Grid g => g -> Gen (Index g, Index g) chooseClosePoints g = do a <- elements . indices $ g b <- elements . filter (\b -> distance g a b < 6) . indices $ g return (a, b) makeTests :: (Arbitrary t, Show t) => [(String, t -> Property)] -> [Test] makeTests ts = map (\(s,t) -> testProperty s t) ts -- -- Tests that should apply to and are identical for all grids -- class TestData t where type BaseGrid t grid :: t -> BaseGrid t points :: t -> [Index (BaseGrid t)] neighbourCountBounds :: t -> (Int, Int) twoClosePoints :: t -> (Index (BaseGrid t),Index (BaseGrid t)) direction :: t -> Direction (BaseGrid t) prop_indices_are_contained :: (TestData t, Grid (BaseGrid t), Eq (Index (BaseGrid t))) => t -> Property prop_indices_are_contained t = nonNull g ==> g `contains` a where g = grid t (a:_) = points t prop_distance_reflexive :: (TestData t, Grid (BaseGrid t)) => t -> Property prop_distance_reflexive t = nonNull g ==> distance g a a == 0 where g = grid t (a:_) = points t prop_distance_symmetric :: (TestData t, Grid (BaseGrid t)) => t -> Property prop_distance_symmetric t = nonNull g ==> distance g a b == distance g b a where g = grid t (a:b:_) = points t prop_custom_MinDistance_eq_default :: (TestData t, Grid (BaseGrid t)) => t -> Property prop_custom_MinDistance_eq_default t = nonNull g ==> minDistance g bs a == defaultMinDistance g bs a where g = grid t (a:bs) = points t -- "cw" = "consistent with" prop_minDistance_cw_distance :: (TestData t, Grid (BaseGrid t)) => t -> Property prop_minDistance_cw_distance t = nonNull g && (not . P.null) bs ==> minDistance g (b:bs) a <= distance g b a where g = grid t (a:b:bs) = points t prop_neighbour_count_in_bounds :: (TestData t, Grid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_neighbour_count_in_bounds t = nonNull g ==> nMin <= n && n <= nMax where g = grid t (a:_) = points t n = length . neighbours g $ a (nMin, nMax) = neighbourCountBounds t prop_neighbours_are_adjacent :: (TestData t, Grid (BaseGrid t), Eq (Index (BaseGrid t))) => t -> Property prop_neighbours_are_adjacent t = nonNull g ==> and (map (isAdjacent g a) ns) where g = grid t (a:_) = points t ns = neighbours g a prop_adjacentTilesToward_moves_closer :: (TestData t, Grid (BaseGrid t), Eq (Index (BaseGrid t))) => t -> Property prop_adjacentTilesToward_moves_closer t = nonNull g && a /= b ==> and (map (< d) ns) where g = grid t (a:b:_) = points t d = distance g a b ns = nub $ map (\x -> distance g x b) $ adjacentTilesToward g a b prop_minimal_paths_have_min_length :: (TestData t, Grid (BaseGrid t), Eq (Index (BaseGrid t))) => t -> Property prop_minimal_paths_have_min_length t = nonNull g ==> ns == [d+1] where g = grid t (a,b) = twoClosePoints t d = distance g a b ns = nub . map length . minimalPaths g a $ b prop_minimal_paths_are_valid :: (TestData t, Grid (BaseGrid t), Eq (Index (BaseGrid t))) => t -> Property prop_minimal_paths_are_valid t = nonNull g ==> and $ map (subsequentTilesInPathAreAdjacent g) $ minimalPaths g a b where g = grid t (a,b) = twoClosePoints t subsequentTilesInPathAreAdjacent :: (Grid g, Eq (Index g)) => g -> [Index g] -> Bool subsequentTilesInPathAreAdjacent _ [] = True subsequentTilesInPathAreAdjacent g [x] = g `contains` x subsequentTilesInPathAreAdjacent g (a:b:xs) = isAdjacent g a b && subsequentTilesInPathAreAdjacent g (b:xs) prop_neighbour_cw_directionTo :: (TestData t, Grid (BaseGrid t), Eq (Index (BaseGrid t)), Eq (Direction (BaseGrid t))) => t -> Property prop_neighbour_cw_directionTo t = nonNull g && a /= b && isJust n ==> (fromJust n) `elem` nextSteps where n = neighbour g a d g = grid t (a,b) = twoClosePoints t d = head . directionTo g a $ b nextSteps = map (!!1) . minimalPaths g a $ b prop_custom_adjacentTilesToward_eq_default :: (TestData t, Grid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_custom_adjacentTilesToward_eq_default t = nonNull g ==> adjacentTilesToward g a b == defaultAdjacentTilesToward g a b where g = grid t (a:b:_) = points t prop_custom_neighboursOfSet_eq_default :: (TestData t, Grid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_custom_neighboursOfSet_eq_default t = nonNull g ==> neighboursOfSet g as == defaultNeighboursOfSet g as where g = grid t as = points t prop_custom_neighboursOfSet_cw_minDistance :: (TestData t, Grid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_custom_neighboursOfSet_cw_minDistance t = nonNull g ==> a `elem` (neighboursOfSet g bs) || minDistance g bs a /= 1 where g = grid t (a:bs) = points t gridProperties :: (TestData t, Grid (BaseGrid t), Arbitrary t, Eq (Index (BaseGrid t)), Ord (Index (BaseGrid t)), Eq (Direction (BaseGrid t))) => String -> [(String, t -> Property)] gridProperties s = [ ("prop_indices_are_contained: " ++ s, prop_indices_are_contained), ("prop_distance_reflexive: " ++ s, prop_distance_reflexive), ("prop_distance_symmetric: " ++ s, prop_distance_symmetric), ("prop_custom_MinDistance_eq_default: " ++ s, prop_custom_MinDistance_eq_default), ("prop_minDistance_cw_distance: " ++ s, prop_minDistance_cw_distance), ("prop_neighbour_count_in_bounds: " ++ s, prop_neighbour_count_in_bounds), ("prop_neighbours_are_adjacent: " ++ s, prop_neighbours_are_adjacent), ("prop_adjacentTilesToward_moves_closer: " ++ s, prop_adjacentTilesToward_moves_closer), ("prop_minimal_paths_have_min_length: " ++ s, prop_minimal_paths_have_min_length), ("prop_minimal_paths_are_valid: " ++ s, prop_minimal_paths_are_valid), ("prop_neighbour_cw_directionTo: " ++ s, prop_neighbour_cw_directionTo), ("prop_custom_adjacentTilesToward_eq_default: " ++ s, prop_custom_adjacentTilesToward_eq_default), ("prop_custom_neighboursOfSet_eq_default: " ++ s, prop_custom_neighboursOfSet_eq_default), ("prop_custom_neighboursOfSet_cw_minDistance: " ++ s, prop_custom_neighboursOfSet_cw_minDistance) ] -- -- Tests that should apply to and are identical for all finite grids -- class TestDataF t where expectedTileCount :: t -> Int maxDistance :: t -> Int prop_tile_count_correct :: (TestData t, TestDataF t, Grid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_tile_count_correct t = nonNull g ==> tileCount g == expectedTileCount t where g = grid t prop_custom_tileCount_eq_default :: (TestData t, Grid (BaseGrid t)) => t -> Property prop_custom_tileCount_eq_default t = nonNull g ==> tileCount g == defaultTileCount g where g = grid t prop_distance_in_bounds :: (TestData t, TestDataF t, Grid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_distance_in_bounds t = nonNull g ==> 0 <= n && n <= maxDistance t where g = grid t (a:b:_) = points t n = distance g a b prop_neighbours_cw_viewpoint :: (TestData t, Grid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_neighbours_cw_viewpoint t = nonNull g ==> sort (delete a (neighbours g a)) == sort expected where g = grid t (a:_) = points t expected = map fst $ filter (\p -> 1 == snd p) $ viewpoint g a -- Note: In a small but unbounded grid, a tile can be its own neighbour. -- However, when we calculate the distance between a tile and itself, we -- get 0, not 1. That's why we have to delete the tile from its list -- before comparing to the result from the neighbours function. prop_custom_edges_eq_default :: (TestData t, Grid (BaseGrid t), Eq (Index (BaseGrid t)), Ord (Index (BaseGrid t))) => t -> Property prop_custom_edges_eq_default t = nonNull g ==> sort (edges g) == sort (defaultEdges g) where g = grid t prop_edges_cw_neighbours :: (TestData t, Grid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_edges_cw_neighbours t = nonNull g ==> sort (neighbours g a) == sort expected where g = grid t (a:_) = points t nEdges = filter (`involves` a) $ edges g expected = map f nEdges f (b,c) = if a == b then c else b prop_edges_are_adjacent :: (TestData t, Grid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_edges_are_adjacent t = property $ all f $ edges g where g = grid t f (a, b) = isAdjacent g a b -- This test is too slow, even for finite grids. -- TODO: Try a better implementation of defaultMinimalPaths? prop_custom_minimalPaths_eq_default :: (TestData t, Grid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_custom_minimalPaths_eq_default t = nonNull g ==> sort (minimalPaths g a b) == sort(defaultMinimalPaths g a b) where g = grid t (a:b:_) = points t prop_distance_le_maxPossibleDistance :: (TestData t, TestDataF t, FiniteGrid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_distance_le_maxPossibleDistance t = nonNull g ==> distance g a b <= maxPossibleDistance g where g = grid t (a:b:_) = points t prop_maxPossibleDistance_occurs :: (TestData t, FiniteGrid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_maxPossibleDistance_occurs t = nonNull g ==> dMax `elem` [distance g x y | x <- indices g, y <- (reverse . sort $ indices g)] -- If we process x and y in opposite orders, we're more likely to find -- the furthest two points in the grid early on. where g = grid t dMax = maxPossibleDistance g finiteGridProperties :: (TestData t, TestDataF t, FiniteGrid (BaseGrid t), Arbitrary t, Eq (Index (BaseGrid t)), Ord (Index (BaseGrid t))) => String -> [(String, t -> Property)] finiteGridProperties s = [ ("prop_tile_count_correct: " ++ s, prop_tile_count_correct), ("prop_custom_tileCount_eq_default: " ++ s, prop_custom_tileCount_eq_default), ("prop_distance_in_bounds: " ++ s, prop_distance_in_bounds), ("prop_neighbours_cw_viewpoint: " ++ s, prop_neighbours_cw_viewpoint), ("prop_custom_edges_eq_default: " ++ s, prop_custom_edges_eq_default), ("prop_edges_cw_neighbours: " ++ s, prop_edges_cw_neighbours), ("prop_edges_are_adjacent: " ++ s, prop_edges_are_adjacent), -- ("prop_custom_minimalPaths_eq_default: " ++ s, prop_custom_minimalPaths_eq_default) ("prop_distance_le_maxPossibleDistance: " ++ s, prop_distance_le_maxPossibleDistance), ("prop_maxPossibleDistance_occurs: " ++ s, prop_maxPossibleDistance_occurs) ] -- -- Tests that should apply to and are identical for all bounded grids -- class TestDataB t where expectedBoundaryCount :: t -> Int prop_custom_boundary_eq_default :: (TestData t, BoundedGrid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_custom_boundary_eq_default t = nonNull g ==> sort (boundary g) == sort (defaultBoundary g) where g = grid t prop_boundary_count_correct :: (TestData t, TestDataB t, BoundedGrid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_boundary_count_correct t = nonNull g ==> (length . boundary) g == expectedBoundaryCount t where g = grid t prop_grid_and_boundary_are_both_null_or_not :: (TestData t, BoundedGrid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_grid_and_boundary_are_both_null_or_not t = property $ (P.null . boundary) g == null g where g = grid t prop_boundary_in_grid :: (TestData t, BoundedGrid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_boundary_in_grid t = property $ all (g `contains`) . boundary $ g where g = grid t prop_boundary_tiles_have_fewer_neighbours :: (TestData t, BoundedGrid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_boundary_tiles_have_fewer_neighbours t = nonNull g ==> g `numNeighbours` b <= g `numNeighbours` a where g = grid t (a:_) = points t (b:_) = boundary g prop_custom_isBoundary_eq_default :: (TestData t, BoundedGrid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_custom_isBoundary_eq_default t = nonNull g ==> isBoundary g a == defaultIsBoundary g a where g = grid t (a:_) = points t prop_custom_isCentre_eq_default :: (TestData t, BoundedGrid (BaseGrid t), Eq (Index (BaseGrid t))) => t -> Property prop_custom_isCentre_eq_default t = nonNull g ==> isCentre g a == defaultIsCentre g a where g = grid t (a:_) = points t prop_custom_neighbours_eq_default :: (TestData t, Grid (BaseGrid t), Eq (Index (BaseGrid t)), Ord (Index (BaseGrid t))) => t -> Property prop_custom_neighbours_eq_default t = nonNull g ==> sort (neighbours g a) == sort (defaultNeighbours g a) where g = grid t (a:_) = points t prop_custom_neighbour_eq_default :: (TestData t, Grid (BaseGrid t), Eq (Index (BaseGrid t)), (Eq (Direction (BaseGrid t)))) => t -> Property prop_custom_neighbour_eq_default t = nonNull g ==> neighbour g a d == defaultNeighbour g a d where g = grid t (a:_) = points t d = direction t prop_custom_isAdjacent_eq_default :: (TestData t, Grid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_custom_isAdjacent_eq_default t = nonNull g ==> isAdjacent g a b == defaultIsAdjacent g a b where g = grid t (a:b:_) = points t boundedGridProperties :: (TestData t, TestDataB t, BoundedGrid (BaseGrid t), Arbitrary t, Eq (Index (BaseGrid t)), Ord (Index (BaseGrid t)), Eq (Direction (BaseGrid t))) => String -> [(String, t -> Property)] boundedGridProperties s = [ ("prop_custom_boundary_eq_default: " ++ s, prop_custom_boundary_eq_default), ("prop_boundary_count_correct: " ++ s, prop_boundary_count_correct), ("prop_grid_and_boundary_are_both_null_or_not: " ++ s, prop_grid_and_boundary_are_both_null_or_not), ("prop_boundary_in_grid: " ++ s, prop_boundary_in_grid), ("prop_boundary_tiles_have_fewer_neighbours: " ++ s, prop_boundary_tiles_have_fewer_neighbours), ("prop_custom_isBoundary_eq_default: " ++ s, prop_custom_isBoundary_eq_default), ("prop_custom_isCentre_eq_default: " ++ s, prop_custom_isBoundary_eq_default), ("prop_custom_neighbours_eq_default: " ++ s, prop_custom_neighbours_eq_default), ("prop_custom_neighbour_eq_default: " ++ s, prop_custom_neighbour_eq_default), ("prop_custom_isAdjacent_eq_default: " ++ s, prop_custom_isAdjacent_eq_default) ] -- -- These properties won't work for triangular grids. -- They probably only work on grids where all the tiles have the same -- shape/orientation. -- prop_custom_centre_eq_default :: (TestData t, BoundedGrid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_custom_centre_eq_default t = nonNull g ==> sort(centre g) == sort (defaultCentre g) where g = grid t prop_centres_equidistant_from_boundary :: (TestData t, BoundedGrid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_centres_equidistant_from_boundary t = nonNull g ==> (length . nub . map (minDistance g bs)) cs == 1 where g = grid t bs = boundary g cs = centre g prop_centres_farthest_from_boundary :: (TestData t, BoundedGrid (BaseGrid t), Ord (Index (BaseGrid t))) => t -> Property prop_centres_farthest_from_boundary t = nonNull g && (not . isCentre g) a ==> minDistance g bs a <= minDistance g bs c where g = grid t (a:_) = points t (c:_) = centre g bs = boundary g boundedGridProperties2 :: (TestData t, TestDataB t, BoundedGrid (BaseGrid t), Arbitrary t, Eq (Index (BaseGrid t)), Ord (Index (BaseGrid t)), Eq (Direction (BaseGrid t))) => String -> [(String, t -> Property)] boundedGridProperties2 s = [ ("prop_custom_centre_eq_default: " ++ s, prop_custom_centre_eq_default), ("prop_centres_equidistant_from_boundary: " ++ s, prop_centres_equidistant_from_boundary), ("prop_centres_farthest_from_boundary: " ++ s, prop_centres_farthest_from_boundary) ]