-- | Basic operations on 2D points represented as linear offsets. module Game.LambdaHack.Point ( Point, toPoint, showPoint , origin, chessDist, adjacent, vicinity, vicinityCardinal , inside, displacementXYZ, bla ) where import qualified Data.List as L import Game.LambdaHack.PointXY import Game.LambdaHack.VectorXY import Game.LambdaHack.Area import Game.LambdaHack.Msg import Game.LambdaHack.Utils.Assert import Data.Text (Text) -- | The type of locations on the 2D level map, heavily optimized. -- -- We represent the (level map on the) screen as a linear framebuffer, -- where @Point@ is an @Int@ offset counted from the first cell. -- We do bounds check for the X size whenever we convert between -- representations and each subsequent -- array access performs another check, effectively for Y size. -- After dungeon is generated (using @PointXY@, not @Point@), -- and converted to the @Point@ representation, points are used -- mainly as keys and not constructed often, so the performance will improve -- due to smaller save files, the use of @IntMap@ and cheaper array indexing, -- including cheaper bounds checks. -- We don't define @Point@ as a newtype to avoid the trouble -- with using @EnumMap@ in place of @IntMap@, etc. type Point = Int -- | Print a point as a tuple of cartesian coordinates. showPoint :: X -> Point -> Text showPoint lxsize = showT . fromPoint lxsize -- | Conversion from cartesian coordinates to @Point@. toPoint :: X -> PointXY -> Point toPoint lxsize (PointXY (x, y)) = assert (lxsize > x && x >= 0 && y >= 0 `blame` (lxsize, x, y)) $ x + y * lxsize -- | Conversion from @Point@ to cartesian coordinates. fromPoint :: X -> Point -> PointXY fromPoint lxsize loc = assert (loc >= 0 `blame` (lxsize, loc)) $ PointXY (loc `rem` lxsize, loc `quot` lxsize) -- | The top-left corner location of the level. origin :: Point origin = 0 -- | The distance between two points in the chessboard metric. chessDist :: X -> Point -> Point -> Int chessDist lxsize loc0 loc1 | PointXY (x0, y0) <- fromPoint lxsize loc0 , PointXY (x1, y1) <- fromPoint lxsize loc1 = chessDistXY $ VectorXY (x1 - x0, y1 - y0) -- | Checks whether two points are adjacent on the map -- (horizontally, vertically or diagonally). adjacent :: X -> Point -> Point -> Bool adjacent lxsize s t = chessDist lxsize s t == 1 -- | Returns the 8, or less, surrounding locations of a given location. vicinity :: X -> Y -> Point -> [Point] vicinity lxsize lysize loc = map (toPoint lxsize) $ vicinityXY (0, 0, lxsize - 1, lysize - 1) $ fromPoint lxsize loc -- | Returns the 4, or less, surrounding locations in cardinal directions -- from a given location. vicinityCardinal :: X -> Y -> Point -> [Point] vicinityCardinal lxsize lysize loc = map (toPoint lxsize) $ vicinityCardinalXY (0, 0, lxsize - 1, lysize - 1) $ fromPoint lxsize loc -- | Checks that a point belongs to an area. inside :: X -> Point -> Area -> Bool inside lxsize loc = insideXY $ fromPoint lxsize loc -- | Calculate the displacement vector from a location to another. displacementXYZ :: X -> Point -> Point -> VectorXY displacementXYZ lxsize loc0 loc1 | PointXY (x0, y0) <- fromPoint lxsize loc0 , PointXY (x1, y1) <- fromPoint lxsize loc1 = VectorXY (x1 - x0, y1 - y0) -- | Bresenham's line algorithm generalized to arbitrary starting @eps@ -- (@eps@ value of 0 gives the standard BLA). -- Skips the source point and goes through the second point -- to the edge of the level. GIves @Nothing@ if the points are equal. bla :: X -> Y -> Int -> Point -> Point -> Maybe [Point] bla _ _ _ source target | source == target = Nothing bla lxsize lysize eps source target = Just $ let s = fromPoint lxsize source e = fromPoint lxsize target inBounds p@(PointXY (x, y)) = lxsize > x && x >= 0 && lysize > y && y >= 0 && p /= s in L.map (toPoint lxsize) $ L.takeWhile inBounds $ L.tail $ blaXY eps s e