module Tiles
( Point(..), PointSet, Tiles
, makeRandomTiles, makeTiles
, tilePoints, showTiles, showPoints
, toPointSet, fromPointSet, showPointSet, edgePoint
) where
import Point
import System.Random
import Data.List (find)
import qualified Data.IntMap as IM
import qualified Data.IntSet as IS
type Tiles = [(Point, Size)]
type PointSet = IM.IntMap IS.IntSet
tilePoints :: Tiles -> [Point]
tilePoints [] = []
tilePoints ((p@(MkPoint x y), 4):rest) = block ++ (p:tilePoints rest)
where
block = [ MkPoint (x+dx) (y+dy) | (dx, dy) <- _size2 ++ _size3 ++ _size4 ]
tilePoints ((p@(MkPoint x y), 3):rest) = block ++ (p:tilePoints rest)
where
block = [ MkPoint (x+dx) (y+dy) | (dx, dy) <- _size2 ++ _size3 ]
tilePoints ((p@(MkPoint x y), 2):rest) = block ++ (p:tilePoints rest)
where
block = [ MkPoint (x+dx) (y+dy) | (dx, dy) <- _size2 ]
tilePoints ((p, _):rest) = (p:tilePoints rest)
makeTiles :: [Point] -> Tiles
makeTiles [] = []
makeTiles points = (m:makeTiles rest')
where
ps = toPointSet points
(m@(_, msz), rest) = case find ((== 4) . snd) tilings of
Just m4 -> (m4, points)
_ -> case find ((== 3) . snd) tilings of
Just m3 -> (m3, points)
_ -> case find ((== 2) . snd) tilings of
Just m2 -> (m2, points)
_ -> ((head points, 1), tail points)
rest' = if msz == 1 then rest else filter (not . inArea m) rest
tilings = [ (p, getTileSize p ps) | p <- points ]
makeRandomTiles :: [Point] -> IO Tiles
makeRandomTiles = fmap makeTiles . shuffle
insertPoint :: Point -> PointSet -> PointSet
insertPoint (MkPoint px py) ps = IM.insert x ys' ps
where
x = px
y = py
ys' = case IM.lookup x ps of
Just ys -> IS.insert y ys
_ -> IS.singleton y
lookupPoint :: Point -> PointSet -> Bool
lookupPoint (MkPoint px py) ps = case IM.lookup x ps of
Just ys -> IS.member y ys
_ -> False
where
x = px
y = py
getTileSize :: Point -> PointSet -> Size
getTileSize (MkPoint x y) ps
| all ok _size2 = if all ok _size3 then if all ok _size4 then 4 else 3 else 2
| otherwise = 1
where
ok (dx, dy) = lookupPoint (MkPoint (x+dx) (y+dy)) ps
_size2, _size3, _size4 :: [(X, Y)]
_size2 = [(1, 0), (0, 1), (1, 1)]
_size3 = [(2, 0), (2, 1), (0, 2), (1, 2), (2, 2)]
_size4 = [(3, 0), (3, 1), (3, 2), (0, 3), (1, 3), (2, 3), (3, 3)]
edgePoint :: PointSet -> Point
edgePoint ps = MkPoint (x) (y)
where
x = head (IM.keys ps)
y = minimum $ map (head . IS.elems) (IM.elems ps)
shuffle :: [a] -> IO [a]
shuffle [] = return []
shuffle [c] = return [c]
shuffle deck0 = part deck0 [] []
where
part [] p0 p1 = do
s1 <- shuffle p0
s2 <- shuffle p1
return (s1 ++ s2)
part (d : deck) p0 p1 = do
n <- randomRIO (False, True)
if n then part deck (d : p0) p1
else part deck p0 (d : p1)
inArea :: (Point, Size) -> Point -> Bool
inArea (MkPoint x y, sz) (MkPoint tx ty)
= dx >= 0 && dx < sz
&& dy >= 0 && dy < sz
where
dx = tx x
dy = ty y
showTiles :: Tiles -> String
showTiles mosaic = unlines
[ [ case lookup (MkPoint x y) mosaic of
Just sz -> toEnum (48 + sz)
_ -> ' '
| x <- [0..maxX]
]
| y <- [0..maxY]
]
where
pts = map fst mosaic
maxX = maximum (map pointX pts)
maxY = maximum (map pointY pts)
showPoints :: [Point] -> String
showPoints = showPointSet . toPointSet
showPointSet :: PointSet -> String
showPointSet ps = unlines
[ [ if lookupPoint (MkPoint x y) ps then '*' else ' '
| x <- [0..maxX]
]
| y <- [0..maxY]
]
where
MkPoint maxX maxY = edgePoint ps
toPointSet :: [Point] -> PointSet
toPointSet = foldl (flip insertPoint) IM.empty
fromPointSet :: PointSet -> [Point]
fromPointSet ps = concat [ [ MkPoint x y | y <- IS.elems ys ] | (x, ys) <- IM.assocs ps ]