{-# LANGUAGE TypeFamilies, FlexibleContexts #-} -- | Grid shapes. module Data.Puzzles.GridShape where import Data.Foldable (Foldable) import qualified Data.Foldable as F import Data.List (partition) import Data.VectorSpace ((^+^)) -- | The geometry of a grid. class Show (Cell a) => GridShape a where type GridSize a :: * type Cell a :: * type Vertex a :: * size :: a -> GridSize a cells :: a -> [Cell a] vertices :: a -> [Vertex a] vertexNeighbours :: a -> Cell a -> [Cell a] edgeNeighbours :: a -> Cell a -> [Cell a] -- | A standard square grid, with cells and vertices -- indexed by pairs of integers in mathematical coordinates. -- The bottom-left corner is vertex (0, 0), the bottom-left -- cell is cell (0, 0). data Square = Square !Int !Int deriving (Show, Eq) squareNeighbours :: [(Int, Int)] -> Square -> Cell Square -> [Cell Square] squareNeighbours deltas (Square w h) c = filter inBounds . map (add c) $ deltas where inBounds (x, y) = x >= 0 && x < w && y >= 0 && y < h add (x, y) (x', y') = (x + x', y + y') instance GridShape Square where type GridSize Square = (Int, Int) type Cell Square = (Int, Int) type Vertex Square = (Int, Int) size (Square w h) = (w, h) cells (Square w h) = [(x, y) | x <- [0..w-1], y <- [0..h-1]] vertices (Square w h) = [(x, y) | x <- [0..w], y <- [0..h]] vertexNeighbours = squareNeighbours [ (dx, dy) | dx <- [-1..1], dy <- [-1..1] , dx /= 0 || dy /= 0 ] edgeNeighbours = squareNeighbours [ (dx, dy) | dx <- [-1..1], dy <- [-1..1] , dx /= 0 || dy /= 0 , dx == 0 || dy == 0 ] -- | Edge direction in a square grid, vertical or horizontal. data Dir = V | H deriving (Eq, Ord, Show) -- | An edge in a square grid, going up or right from the given cell -- centre. data Edge = E (Cell Square) Dir deriving (Show, Eq, Ord) type Coord = Cell Square type Size = GridSize Square -- | Oriented edge direction in a square grid. data Dir' = U | D | L | R deriving (Eq, Ord, Show) -- | An oriented edge in a square grid. -- @a@ should be @Cell Square@ or @Vertex Square@. data Edge' a = E' a Dir' deriving (Eq, Ord, Show) -- | The edge between two neighbouring cells, with the first cell -- on the left. orientedEdge :: Cell Square -> Cell Square -> Edge' (Vertex Square) orientedEdge (x,y) (x',y') | x' == x && y' == y+1 = E' (x,y+1) R | x' == x && y' == y-1 = E' (x+1,y) L | x' == x+1 && y' == y = E' (x+1,y+1) D | x' == x-1 && y' == y = E' (x,y) U | otherwise = error $ "not neighbours: " ++ show (x,y) ++ " " ++ show (x',y') -- | @edges@ computes the outer and inner edges of a set of cells. -- The set is given via fold and membership predicate, the result -- is a pair @(outer, inner)@ of lists of edges, where the outer -- edges are oriented such that the outside is to the left. edges :: Foldable f => f (Cell Square) -> (Cell Square -> Bool) -> ([Edge' (Vertex Square)], [Edge' (Vertex Square)]) edges cs isc = F.foldr f ([], []) cs where f c (outer, inner) = (newout ++ outer, newin ++ inner) where nbrs = [ c ^+^ d | d <- [(-1,0), (0,1), (1,0), (0,-1)] ] (ni, no) = partition isc nbrs newout = map (orientedEdge c) no newin = map (orientedEdge c) . filter (c >=) $ ni