{-# LANGUAGE QuasiQuotes #-}
-- | This toolkit allows one to easily calculate the optimal path between two points in your dungeon by using either the A* algorithm or Dijkstra's algorithm.
-- Please note that the paths generated with the two algorithms may differ slightly. Due to how they're implemented, A* will usually prefer diagonal moves over orthogonal, while Dijkstra will have the opposite preference. In other words, paths from point X to point Y will look like this:
-- @@
-- Dijkstra: A*:
-- .......... ..........
-- .X........ .X*.......
-- ..*....... ...**.....
-- ...*...... .....**...
-- ....****Y. .......*Y.
-- .......... ..........
-- @@
module Game.TCOD.Path(
TCODPathFunc
, TCODPathFuncRaw
, TCODPath(..)
, pathNewUsingMap
, pathNewUsingFunction
, pathDelete
, pathCompute
, pathWalk
, pathIsEmpty
, pathSize
, pathReverse
, pathGet
, pathGetOrigin
, pathGetDesitnation
) where
import Data.Monoid
import Foreign
import Foreign.C
import Game.TCOD.Context as C
import Game.TCOD.Fov
import GHC.Generics
import Language.C.Inline (mkFunPtr)
context (tcodContext <> funCtx)
verbatim "#define TCOD_SDL2"
include "libtcod/path.h"
-- | Callback path function, takes from (x, y) and to (x, y), returns cell cost
--
-- A custom function that must return the walk cost from coordinates xFrom,yFrom to coordinates xTo,yTo.
-- The cost must be > 0.0f if the cell xTo,yTo is walkable.
-- It must be equal to 0.0f if it's not.
-- You must not take additional cost due to diagonal movements into account as it's already done by the pathfinder.
type TCODPathFunc = Int -> Int -> Int -> Int -> IO Double
-- | C type of 'TCODPathFunc'
type TCODPathFuncRaw = CInt -> CInt -> CInt -> CInt -> Ptr () -> IO CFloat
-- | Reference to TCOD path finder state
data TCODPath = TCODPath {
unTCODPath :: Ptr () -- ^ Original pointer
-- | Stored reference to callback that need to be freed after cleanup of the path object
, pathHaskFun :: Maybe (FunPtr TCODPathFuncRaw)
}
deriving (Eq, Show, Ord, Generic)
-- | Allocating a pathfinder from a map
--
-- First, you have to allocate a path using a map from 'Game.TCOD.Fov' module.
pathNewUsingMap :: TCODMap
-> Float -- ^ diagonal cost. Cost of a diagonal movement compared to an horizontal or vertical movement. On a standard cartesian map, it should be sqrt(2) (1.41f).
-- It you want the same cost for all movements, use 1.0f.
-- If you don't want the path finder to use diagonal movements, use 0.0f.
-> IO TCODPath
pathNewUsingMap (TCODMap m) dc = do
let dc' = realToFrac dc
flip TCODPath Nothing <$> [C.exp| void * { TCOD_path_new_using_map($(void* m), $(float dc')) } |]
-- | Allocating a pathfinder using a callback
--
-- Since the walkable status of a cell may depend on a lot of parameters
-- (the creature type, the weather, the terrain type...), you can also create a
-- path by providing a function rather than relying on a TCODMap.
pathNewUsingFunction :: Int -- ^ map width
-> Int -- ^ map height
-> TCODPathFunc -- ^ A custom function that must return the walk cost from coordinates xFrom,yFrom to coordinates xTo,yTo.
-- The cost must be > 0.0f if the cell xTo,yTo is walkable.
-- It must be equal to 0.0f if it's not.
-- You must not take additional cost due to diagonal movements into account as it's already done by the pathfinder.
-> Float -- ^ diagonal cost. Cost of a diagonal movement compared to an horizontal or vertical movement. On a standard cartesian map, it should be sqrt(2) (1.41f).
-- It you want the same cost for all movements, use 1.0f.
-- If you don't want the path finder to use diagonal movements, use 0.0f.
-> IO TCODPath
pathNewUsingFunction mw mh f dc = do
let mw' = fromIntegral mw
mh' = fromIntegral mh
dc' = realToFrac dc
f' xF yF xT yT _ = realToFrac <$> f (fromIntegral xF) (fromIntegral yF) (fromIntegral xT) (fromIntegral yT)
callback <- $(mkFunPtr [t| CInt -> CInt -> CInt -> CInt -> Ptr () -> IO CFloat |]) f'
let callback' = castFunPtrToPtr callback
ptr <- [C.exp| void * { TCOD_path_new_using_function($(int mw'), $(int mh'), (TCOD_path_func_t)$(void* callback'), NULL, $(float dc'))} |]
pure $ TCODPath ptr (Just callback)
-- | To release the resources used by a path, destroy it
pathDelete :: TCODPath -> IO ()
pathDelete (TCODPath p mc) = do
[C.exp| void {TCOD_path_delete($(void* p))} |]
maybe (pure ()) freeHaskellFunPtr mc
-- | Computing an A* path
--
-- Once you created a TCODPath object, you can compute the path between two points.
-- Both points should be inside the map, and at a walkable position. The function
-- returns false if there is no possible path.
pathCompute :: TCODPath
-> Int -- ^ ox. Coordinates of the origin of the path.
-> Int -- ^ oy. Coordinates of the origin of the path.
-> Int -- ^ dx. Coordinates of the destination of the path.
-> Int -- ^ dy. Coordinates of the destination of the path.
-> IO Bool
pathCompute (TCODPath p _) ox oy dx dy = do
let ox' = fromIntegral ox
oy' = fromIntegral oy
dx' = fromIntegral dx
dy' = fromIntegral dy
toBool <$> [C.exp| int { (int)TCOD_path_compute($(void* p), $(int ox'), $(int oy'), $(int dx'), $(int dy'))}|]
-- | Walking the path
--
-- You can walk the path and go to the next step with :
-- Note that walking the path consume one step (and decrease the path size by one).
-- The function returns false if recalculateWhenNeeded is false and the next cell
-- on the path is no longer walkable, or if recalculateWhenNeeded is true, the
-- next cell on the path is no longer walkable and no other path has been found.
-- Also note that recalculateWhenNeeded only applies to A*.
pathWalk :: TCODPath
-> Bool -- ^ recalculate when needed. If the next point is no longer walkable (another creature may be in the way), recalculate a new path and walk it.
-> IO (Bool, Int, Int)
pathWalk (TCODPath p _) r = alloca $ \x -> alloca $ \y-> do
let r' = fromBool r
fin <- toBool <$> [C.exp| int {(int)TCOD_path_walk($(void* p), $(int* x), $(int* y), $(int r')!=0)}|]
let pk = fmap fromIntegral . peek
(,,) <$> pure fin <*> pk x <*> pk y
-- | Check is path has no steps
pathIsEmpty :: TCODPath -> IO Bool
pathIsEmpty (TCODPath p _) = toBool <$> [C.exp| int {(int)TCOD_path_is_empty($(void* p))}|]
-- | Get number of steps the path consists of
pathSize :: TCODPath -> IO Int
pathSize (TCODPath p _) = fromIntegral <$> [C.exp| int { TCOD_path_size($(void * p))}|]
-- | Reversing a path
--
-- Once you computed a path, you can exchange origin and destination
pathReverse :: TCODPath -> IO ()
pathReverse (TCODPath p _) = [C.exp| void {TCOD_path_reverse($(void* p))} |]
-- | Read the path cells' coordinates
--
-- You can get the coordinates of each point along the path
pathGet :: TCODPath
-> Int -- ^ index. Step number. 0 <= index < path size
-> IO (Int, Int)
pathGet (TCODPath p _) i = alloca $ \x -> alloca $ \y -> do
let i' = fromIntegral i
[C.exp| void {TCOD_path_get($(void* p), $(int i'), $(int* x), $(int* y))}|]
let pk = fmap fromIntegral . peek
(,) <$> pk x <*> pk y
-- | Get origin/start of the path
pathGetOrigin :: TCODPath -> IO (Int, Int)
pathGetOrigin (TCODPath p _) = alloca $ \x -> alloca $ \y -> do
[C.exp| void {TCOD_path_get_origin($(void* p), $(int* x), $(int* y))}|]
let pk = fmap fromIntegral . peek
(,) <$> pk x <*> pk y
-- | Get destination/end of the path
pathGetDesitnation :: TCODPath -> IO (Int, Int)
pathGetDesitnation (TCODPath p _) = alloca $ \x -> alloca $ \y -> do
[C.exp| void {TCOD_path_get_destination($(void* p), $(int* x), $(int* y))}|]
let pk = fmap fromIntegral . peek
(,) <$> pk x <*> pk y