Implement a first pass at a "fire" command, which allows throwing rocks, the max distance and the damage of which is based on the weight of the item and the strength of the player. Currently the actual numbers here likely need some tweaking, as the rocks are easily throwable at good distances but don't really deal any damage. Change-Id: Ic6ad0599444af44d8438b834237a1997b67f220f Reviewed-on: https://cl.tvl.fyi/c/depot/+/3764 Reviewed-by: grfn <grfn@gws.fyi> Tested-by: BuildkiteCI
818 lines
25 KiB
Haskell
818 lines
25 KiB
Haskell
{-# LANGUAGE PartialTypeSignatures #-}
|
||
{-# LANGUAGE StandaloneDeriving #-}
|
||
{-# LANGUAGE RoleAnnotations #-}
|
||
{-# LANGUAGE RecordWildCards #-}
|
||
{-# LANGUAGE DeriveTraversable #-}
|
||
{-# LANGUAGE TemplateHaskell #-}
|
||
{-# LANGUAGE NoTypeSynonymInstances #-}
|
||
{-# LANGUAGE DuplicateRecordFields #-}
|
||
{-# LANGUAGE QuantifiedConstraints #-}
|
||
{-# LANGUAGE UndecidableInstances #-}
|
||
{-# LANGUAGE AllowAmbiguousTypes #-}
|
||
--------------------------------------------------------------------------------
|
||
-- | Common data types for Xanthous ------------------------------------------------------------------------------
|
||
module Xanthous.Data
|
||
( Opposite(..)
|
||
|
||
-- *
|
||
, Position'(..)
|
||
, Position
|
||
, x
|
||
, y
|
||
|
||
-- **
|
||
, Positioned(..)
|
||
, _Positioned
|
||
, position
|
||
, positioned
|
||
, loc
|
||
, _Position
|
||
, positionFromPair
|
||
, positionFromV2
|
||
, addPositions
|
||
, diffPositions
|
||
, stepTowards
|
||
, isUnit
|
||
, distance
|
||
|
||
-- * Boxes
|
||
, Box(..)
|
||
, topLeftCorner
|
||
, bottomRightCorner
|
||
, setBottomRightCorner
|
||
, dimensions
|
||
, inBox
|
||
, boxIntersects
|
||
, boxCenter
|
||
, boxEdge
|
||
, module Linear.V2
|
||
|
||
-- * Unit math
|
||
, Scalar(..)
|
||
, Per(..)
|
||
, invertRate
|
||
, invertedRate
|
||
, (|+|)
|
||
, (|*|)
|
||
, (|/|)
|
||
, (:+:)
|
||
, (:*:)
|
||
, (:/:)
|
||
, (:**:)(..)
|
||
, Ticks(..)
|
||
, Tiles(..)
|
||
, TicksPerTile
|
||
, TilesPerTick
|
||
, timesTiles
|
||
, Square(..)
|
||
, squared
|
||
, Cubic(..)
|
||
, Grams
|
||
, Meters
|
||
, Uno(..)
|
||
, Unit(..)
|
||
, UnitSymbol(..)
|
||
|
||
-- *
|
||
, Dimensions'(..)
|
||
, Dimensions
|
||
, HasWidth(..)
|
||
, HasHeight(..)
|
||
|
||
-- *
|
||
, Direction(..)
|
||
, move
|
||
, asPosition
|
||
, directionOf
|
||
, Cardinal(..)
|
||
|
||
-- *
|
||
, Corner(..)
|
||
, Edge(..)
|
||
, cornerEdges
|
||
|
||
-- *
|
||
, Neighbors(..)
|
||
, edges
|
||
, neighborDirections
|
||
, neighborPositions
|
||
, neighborCells
|
||
, arrayNeighbors
|
||
, rotations
|
||
, HasTopLeft(..)
|
||
, HasTop(..)
|
||
, HasTopRight(..)
|
||
, HasLeft(..)
|
||
, HasRight(..)
|
||
, HasBottomLeft(..)
|
||
, HasBottom(..)
|
||
, HasBottomRight(..)
|
||
|
||
-- *
|
||
, Hitpoints(..)
|
||
) where
|
||
--------------------------------------------------------------------------------
|
||
import Xanthous.Prelude hiding (Left, Down, Right, (.=), elements)
|
||
--------------------------------------------------------------------------------
|
||
import Linear.V2 hiding (_x, _y)
|
||
import qualified Linear.V2 as L
|
||
import Linear.V4 hiding (_x, _y)
|
||
import Test.QuickCheck (CoArbitrary, Function, elements)
|
||
import Test.QuickCheck.Arbitrary.Generic
|
||
import Data.Group
|
||
import Brick (Location(Location), Edges(..))
|
||
import Data.Monoid (Product(..), Sum(..))
|
||
import Data.Array.IArray
|
||
import Data.Aeson.Generic.DerivingVia
|
||
import Data.Aeson
|
||
( ToJSON(..), FromJSON(..), object, (.=), (.:), withObject)
|
||
import Data.Random (Distribution)
|
||
import Data.Coerce
|
||
import Data.Proxy (Proxy(Proxy))
|
||
--------------------------------------------------------------------------------
|
||
import Xanthous.Util (EqEqProp(..), EqProp, between)
|
||
import Xanthous.Util.QuickCheck (GenericArbitrary(..))
|
||
import Xanthous.Orphans ()
|
||
import Xanthous.Util.Graphics
|
||
import qualified Linear.Metric as Metric
|
||
--------------------------------------------------------------------------------
|
||
|
||
-- | opposite ∘ opposite ≡ id
|
||
class Opposite x where
|
||
opposite :: x -> x
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
-- fromScalar ∘ scalar ≡ id
|
||
class Scalar a where
|
||
scalar :: a -> Double
|
||
fromScalar :: Double -> a
|
||
|
||
instance Scalar Double where
|
||
scalar = id
|
||
fromScalar = id
|
||
|
||
newtype ScalarIntegral a = ScalarIntegral a
|
||
deriving newtype (Eq, Ord, Num, Enum, Real, Integral)
|
||
instance Integral a => Scalar (ScalarIntegral a) where
|
||
scalar = fromIntegral
|
||
fromScalar = floor
|
||
|
||
deriving via (ScalarIntegral Integer) instance Scalar Integer
|
||
deriving via (ScalarIntegral Word) instance Scalar Word
|
||
|
||
-- | Units of measure
|
||
class Unit a where
|
||
unitSuffix :: Text
|
||
type UnitSymbol :: Symbol -> Type -> Type
|
||
newtype UnitSymbol suffix a = UnitSymbol a
|
||
instance KnownSymbol suffix => Unit (UnitSymbol suffix a) where
|
||
unitSuffix = pack $ symbolVal @suffix Proxy
|
||
|
||
newtype ShowUnitSuffix a b = ShowUnitSuffix a
|
||
instance (Show b, Unit a, Coercible a b) => Show (ShowUnitSuffix a b) where
|
||
show a = show (coerce @_ @b a) <> " " <> unpack (unitSuffix @a)
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
data Position' a where
|
||
Position :: { _x :: a
|
||
, _y :: a
|
||
} -> (Position' a)
|
||
deriving stock (Show, Eq, Generic, Ord, Functor, Foldable, Traversable)
|
||
deriving anyclass (NFData, Hashable, CoArbitrary, Function)
|
||
deriving EqProp via EqEqProp (Position' a)
|
||
deriving (ToJSON, FromJSON)
|
||
via WithOptions '[ FieldLabelModifier '[Drop 1] ]
|
||
(Position' a)
|
||
|
||
x, y :: Lens' (Position' a) a
|
||
x = lens (\(Position xx _) -> xx) (\(Position _ yy) xx -> Position xx yy)
|
||
y = lens (\(Position _ yy) -> yy) (\(Position xx _) yy -> Position xx yy)
|
||
|
||
type Position = Position' Int
|
||
|
||
instance Arbitrary a => Arbitrary (Position' a) where
|
||
arbitrary = genericArbitrary
|
||
shrink (Position px py) = Position <$> shrink px <*> shrink py
|
||
|
||
|
||
instance Num a => Semigroup (Position' a) where
|
||
(Position x₁ y₁) <> (Position x₂ y₂) = Position (x₁ + x₂) (y₁ + y₂)
|
||
|
||
instance Num a => Monoid (Position' a) where
|
||
mempty = Position 0 0
|
||
|
||
instance Num a => Group (Position' a) where
|
||
invert (Position px py) = Position (negate px) (negate py)
|
||
|
||
-- | Positions convert to scalars by discarding their orientation and just
|
||
-- measuring the length from the origin
|
||
instance (Ord a, Num a, Scalar a) => Scalar (Position' a) where
|
||
scalar = fromIntegral . length . line 0 . view _Position
|
||
fromScalar n = Position (fromScalar n) (fromScalar n)
|
||
|
||
data Positioned a where
|
||
Positioned :: Position -> a -> Positioned a
|
||
deriving stock (Show, Eq, Ord, Functor, Foldable, Traversable, Generic)
|
||
deriving anyclass (NFData, CoArbitrary, Function)
|
||
type role Positioned representational
|
||
|
||
_Positioned :: Iso (Position, a) (Position, b) (Positioned a) (Positioned b)
|
||
_Positioned = iso hither yon
|
||
where
|
||
hither (pos, a) = Positioned pos a
|
||
yon (Positioned pos b) = (pos, b)
|
||
|
||
instance Arbitrary a => Arbitrary (Positioned a) where
|
||
arbitrary = Positioned <$> arbitrary <*> arbitrary
|
||
|
||
instance ToJSON a => ToJSON (Positioned a) where
|
||
toJSON (Positioned pos val) = object
|
||
[ "position" .= pos
|
||
, "data" .= val
|
||
]
|
||
|
||
instance FromJSON a => FromJSON (Positioned a) where
|
||
parseJSON = withObject "Positioned" $ \obj ->
|
||
Positioned <$> obj .: "position" <*> obj .: "data"
|
||
|
||
position :: Lens' (Positioned a) Position
|
||
position = lens
|
||
(\(Positioned pos _) -> pos)
|
||
(\(Positioned _ a) pos -> Positioned pos a)
|
||
|
||
positioned :: Lens (Positioned a) (Positioned b) a b
|
||
positioned = lens
|
||
(\(Positioned _ x') -> x')
|
||
(\(Positioned pos _) x' -> Positioned pos x')
|
||
|
||
loc :: Iso' Position Location
|
||
loc = iso hither yon
|
||
where
|
||
hither (Position px py) = Location (px, py)
|
||
yon (Location (lx, ly)) = Position lx ly
|
||
|
||
_Position :: Iso' (Position' a) (V2 a)
|
||
_Position = iso hither yon
|
||
where
|
||
hither (Position px py) = V2 px py
|
||
yon (V2 lx ly) = Position lx ly
|
||
|
||
positionFromPair :: (Num a, Integral i, Integral j) => (i, j) -> Position' a
|
||
positionFromPair (i, j) = Position (fromIntegral i) (fromIntegral j)
|
||
|
||
positionFromV2 :: (Num a, Integral i) => V2 i -> Position' a
|
||
positionFromV2 (V2 xx yy) = Position (fromIntegral xx) (fromIntegral yy)
|
||
|
||
-- | Add two positions
|
||
--
|
||
-- Operation for the additive group on positions
|
||
addPositions :: Num a => Position' a -> Position' a -> Position' a
|
||
addPositions = (<>)
|
||
|
||
-- | Subtract two positions.
|
||
--
|
||
-- diffPositions pos₁ pos₂ = pos₁ `addPositions` (invert pos₂)
|
||
diffPositions :: Num a => Position' a -> Position' a -> Position' a
|
||
diffPositions (Position x₁ y₁) (Position x₂ y₂) = Position (x₁ - x₂) (y₁ - y₂)
|
||
|
||
-- | Is this position a unit position? or: When taken as a difference, does this
|
||
-- position represent a step of one tile?
|
||
--
|
||
-- ∀ dir :: Direction. isUnit ('asPosition' dir)
|
||
isUnit :: (Eq a, Num a) => Position' a -> Bool
|
||
isUnit (Position px py) =
|
||
abs px `elem` [0,1] && abs py `elem` [0, 1] && (px, py) /= (0, 0)
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
data Dimensions' a = Dimensions
|
||
{ _width :: a
|
||
, _height :: a
|
||
}
|
||
deriving stock (Show, Eq, Functor, Generic)
|
||
deriving anyclass (CoArbitrary, Function)
|
||
makeFieldsNoPrefix ''Dimensions'
|
||
|
||
instance Arbitrary a => Arbitrary (Dimensions' a) where
|
||
arbitrary = Dimensions <$> arbitrary <*> arbitrary
|
||
|
||
type Dimensions = Dimensions' Word
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
data Direction where
|
||
Up :: Direction
|
||
Down :: Direction
|
||
Left :: Direction
|
||
Right :: Direction
|
||
UpLeft :: Direction
|
||
UpRight :: Direction
|
||
DownLeft :: Direction
|
||
DownRight :: Direction
|
||
Here :: Direction
|
||
deriving stock (Show, Eq, Ord, Generic)
|
||
deriving anyclass (CoArbitrary, Function, NFData, ToJSON, FromJSON, Hashable)
|
||
deriving Arbitrary via GenericArbitrary Direction
|
||
|
||
instance Opposite Direction where
|
||
opposite Up = Down
|
||
opposite Down = Up
|
||
opposite Left = Right
|
||
opposite Right = Left
|
||
opposite UpLeft = DownRight
|
||
opposite UpRight = DownLeft
|
||
opposite DownLeft = UpRight
|
||
opposite DownRight = UpLeft
|
||
opposite Here = Here
|
||
|
||
move :: Num a => Direction -> Position' a -> Position' a
|
||
move Up = y -~ 1
|
||
move Down = y +~ 1
|
||
move Left = x -~ 1
|
||
move Right = x +~ 1
|
||
move UpLeft = move Up . move Left
|
||
move UpRight = move Up . move Right
|
||
move DownLeft = move Down . move Left
|
||
move DownRight = move Down . move Right
|
||
move Here = id
|
||
|
||
asPosition :: Direction -> Position
|
||
asPosition dir = move dir mempty
|
||
|
||
-- | Returns the direction that a given position is from a given source position
|
||
directionOf
|
||
:: Position -- ^ Source
|
||
-> Position -- ^ Target
|
||
-> Direction
|
||
directionOf (Position x₁ y₁) (Position x₂ y₂) =
|
||
case (x₁ `compare` x₂, y₁ `compare` y₂) of
|
||
(EQ, EQ) -> Here
|
||
(EQ, LT) -> Down
|
||
(EQ, GT) -> Up
|
||
(LT, EQ) -> Right
|
||
(GT, EQ) -> Left
|
||
|
||
(LT, LT) -> DownRight
|
||
(GT, LT) -> DownLeft
|
||
|
||
(LT, GT) -> UpRight
|
||
(GT, GT) -> UpLeft
|
||
|
||
-- | Take one (potentially diagonal) step towards the given position
|
||
--
|
||
-- ∀ src tgt. isUnit (src `diffPositions` (src `stepTowards tgt`))
|
||
stepTowards
|
||
:: Position -- ^ Source
|
||
-> Position -- ^ Target
|
||
-> Position
|
||
stepTowards (view _Position -> p₁) (view _Position -> p₂)
|
||
| p₁ == p₂ = _Position # p₁
|
||
| otherwise =
|
||
let (_:p:_) = line p₁ p₂
|
||
in _Position # p
|
||
|
||
-- | Newtype controlling arbitrary generation to only include cardinal
|
||
-- directions ('Up', 'Down', 'Left', 'Right')
|
||
newtype Cardinal = Cardinal { getCardinal :: Direction }
|
||
deriving stock (Eq, Show, Ord, Generic)
|
||
deriving anyclass (NFData, Function, CoArbitrary)
|
||
deriving newtype (Opposite)
|
||
|
||
instance Arbitrary Cardinal where
|
||
arbitrary = Cardinal <$> elements [Up, Down, Left, Right]
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
data Corner
|
||
= TopLeft
|
||
| TopRight
|
||
| BottomLeft
|
||
| BottomRight
|
||
deriving stock (Show, Eq, Ord, Enum, Bounded, Generic)
|
||
deriving Arbitrary via GenericArbitrary Corner
|
||
|
||
instance Opposite Corner where
|
||
opposite TopLeft = BottomRight
|
||
opposite TopRight = BottomLeft
|
||
opposite BottomLeft = TopRight
|
||
opposite BottomRight = TopLeft
|
||
|
||
data Edge
|
||
= TopEdge
|
||
| LeftEdge
|
||
| RightEdge
|
||
| BottomEdge
|
||
deriving stock (Show, Eq, Ord, Enum, Bounded, Generic)
|
||
deriving Arbitrary via GenericArbitrary Edge
|
||
|
||
instance Opposite Edge where
|
||
opposite TopEdge = BottomEdge
|
||
opposite BottomEdge = TopEdge
|
||
opposite LeftEdge = RightEdge
|
||
opposite RightEdge = LeftEdge
|
||
|
||
cornerEdges :: Corner -> (Edge, Edge)
|
||
cornerEdges TopLeft = (TopEdge, LeftEdge)
|
||
cornerEdges TopRight = (TopEdge, RightEdge)
|
||
cornerEdges BottomLeft = (BottomEdge, LeftEdge)
|
||
cornerEdges BottomRight = (BottomEdge, RightEdge)
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
data Neighbors a = Neighbors
|
||
{ _topLeft
|
||
, _top
|
||
, _topRight
|
||
, _left
|
||
, _right
|
||
, _bottomLeft
|
||
, _bottom
|
||
, _bottomRight :: a
|
||
}
|
||
deriving stock (Show, Eq, Ord, Functor, Foldable, Traversable, Generic)
|
||
deriving anyclass (NFData, CoArbitrary, Function, MonoFoldable)
|
||
deriving Arbitrary via GenericArbitrary (Neighbors a)
|
||
|
||
type instance Element (Neighbors a) = a
|
||
|
||
makeFieldsNoPrefix ''Neighbors
|
||
|
||
instance Applicative Neighbors where
|
||
pure α = Neighbors
|
||
{ _topLeft = α
|
||
, _top = α
|
||
, _topRight = α
|
||
, _left = α
|
||
, _right = α
|
||
, _bottomLeft = α
|
||
, _bottom = α
|
||
, _bottomRight = α
|
||
}
|
||
nf <*> nx = Neighbors
|
||
{ _topLeft = nf ^. topLeft $ nx ^. topLeft
|
||
, _top = nf ^. top $ nx ^. top
|
||
, _topRight = nf ^. topRight $ nx ^. topRight
|
||
, _left = nf ^. left $ nx ^. left
|
||
, _right = nf ^. right $ nx ^. right
|
||
, _bottomLeft = nf ^. bottomLeft $ nx ^. bottomLeft
|
||
, _bottom = nf ^. bottom $ nx ^. bottom
|
||
, _bottomRight = nf ^. bottomRight $ nx ^. bottomRight
|
||
}
|
||
|
||
edges :: Neighbors a -> Edges a
|
||
edges neighs = Edges
|
||
{ eTop = neighs ^. top
|
||
, eBottom = neighs ^. bottom
|
||
, eLeft = neighs ^. left
|
||
, eRight = neighs ^. right
|
||
}
|
||
|
||
neighborDirections :: Neighbors Direction
|
||
neighborDirections = Neighbors
|
||
{ _topLeft = UpLeft
|
||
, _top = Up
|
||
, _topRight = UpRight
|
||
, _left = Left
|
||
, _right = Right
|
||
, _bottomLeft = DownLeft
|
||
, _bottom = Down
|
||
, _bottomRight = DownRight
|
||
}
|
||
|
||
neighborPositions :: Num a => Position' a -> Neighbors (Position' a)
|
||
neighborPositions pos = (`move` pos) <$> neighborDirections
|
||
|
||
neighborCells :: Num a => V2 a -> Neighbors (V2 a)
|
||
neighborCells = map (view _Position) . neighborPositions . review _Position
|
||
|
||
arrayNeighbors
|
||
:: (IArray a e, Ix i, Num i)
|
||
=> a (V2 i) e
|
||
-> V2 i
|
||
-> Neighbors (Maybe e)
|
||
arrayNeighbors arr center = arrLookup <$> neighborPositions (_Position # center)
|
||
where
|
||
arrLookup (view _Position -> pos)
|
||
| inRange (bounds arr) pos = Just $ arr ! pos
|
||
| otherwise = Nothing
|
||
|
||
-- | Returns a list of all 4 90-degree rotations of the given neighbors
|
||
rotations :: Neighbors a -> V4 (Neighbors a)
|
||
rotations orig@(Neighbors tl t tr l r bl b br) = V4
|
||
orig -- tl t tr
|
||
-- l r
|
||
-- bl b br
|
||
|
||
(Neighbors bl l tl b t br r tr) -- bl l tl
|
||
-- b t
|
||
-- br r tr
|
||
|
||
(Neighbors br b bl r l tr t tl) -- br b bl
|
||
-- r l
|
||
-- tr t tl
|
||
|
||
(Neighbors tr r br t b tl l bl) -- tr r br
|
||
-- t b
|
||
-- tl l bl
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
newtype Per a b = Rate Double
|
||
deriving stock (Eq, Generic)
|
||
deriving anyclass (NFData, CoArbitrary, Function)
|
||
deriving (Arbitrary, Num, Ord, Enum, Real, Fractional, ToJSON, FromJSON)
|
||
via Double
|
||
deriving (Semigroup, Monoid) via Product Double
|
||
deriving Show via ShowUnitSuffix (Per a b) Double
|
||
deriving via Double
|
||
instance ( Distribution d Double
|
||
, forall xx yy. Coercible xx yy => Coercible (d xx) (d yy)
|
||
)
|
||
=> Distribution d (Per a b)
|
||
|
||
instance (Unit a, Unit b) => Unit (a `Per` b) where
|
||
unitSuffix = unitSuffix @a <> "/" <> unitSuffix @b
|
||
|
||
invertRate :: a `Per` b -> b `Per` a
|
||
invertRate (Rate p) = Rate $ 1 / p
|
||
|
||
invertedRate :: Iso (a `Per` b) (b' `Per` a') (b `Per` a) (a' `Per` b')
|
||
invertedRate = iso invertRate invertRate
|
||
|
||
type (:+:) :: Type -> Type -> Type
|
||
type family (:+:) a b where
|
||
a :+: a = a
|
||
a :+: (Uno b) = a
|
||
|
||
infixl 6 |+|
|
||
class AddUnit a b where
|
||
(|+|) :: a -> b -> a :+: b
|
||
|
||
instance Scalar a => AddUnit a a where
|
||
x' |+| y' = fromScalar $ scalar x' + scalar y'
|
||
|
||
instance (Scalar a, Scalar b) => AddUnit a (Uno b) where
|
||
x' |+| y' = fromScalar $ scalar x' + scalar y'
|
||
|
||
type (:*:) :: Type -> Type -> Type
|
||
type family (:*:) a b where
|
||
(a `Per` b) :*: b = a
|
||
(Square a) :*: a = Cubic a
|
||
a :*: a = Square a
|
||
a :*: Uno b = a
|
||
a :*: b = a :**: b
|
||
|
||
infixl 7 |*|
|
||
class MulUnit a b where
|
||
(|*|) :: a -> b -> a :*: b
|
||
|
||
instance (Scalar a, Scalar b) => MulUnit (a `Per` b) b where
|
||
(Rate rate) |*| b = fromScalar $ rate * scalar b
|
||
|
||
instance forall a. (Scalar a, a :*: a ~ Square a) => MulUnit a a where
|
||
x' |*| y' = Square @a . fromScalar $ scalar x' * scalar y'
|
||
|
||
instance forall a. (Scalar a) => MulUnit (Square a) a where
|
||
x' |*| y' = Cubic @a . fromScalar $ scalar x' * scalar y'
|
||
|
||
instance {-# INCOHERENT #-} forall a b.
|
||
(Scalar a, Scalar b, Scalar (a :*: Uno b))
|
||
=> MulUnit a (Uno b) where
|
||
x' |*| y' = fromScalar $ scalar x' * scalar y'
|
||
|
||
type (:/:) :: Type -> Type -> Type
|
||
type family (:/:) a b where
|
||
(Square a) :/: a = a
|
||
(Cubic a) :/: a = Square a
|
||
(Cubic a) :/: (Square a) = a
|
||
(a :**: b) :/: b = a
|
||
(a :**: b) :/: a = b
|
||
a :/: Uno b = a
|
||
a :/: b = a `Per` b
|
||
|
||
infixl 7 |/|
|
||
class DivUnit a b where
|
||
(|/|) :: a -> b -> a :/: b
|
||
|
||
instance Scalar a => DivUnit (Square a) a where
|
||
(Square a) |/| b = fromScalar $ scalar a / scalar b
|
||
|
||
instance Scalar a => DivUnit (Cubic a) a where
|
||
(Cubic a) |/| b = fromScalar $ scalar a / scalar b
|
||
|
||
instance (Scalar a, Cubic a :/: Square a ~ a)
|
||
=> DivUnit (Cubic a) (Square a) where
|
||
(Cubic a) |/| (Square b) = fromScalar $ scalar a / scalar b
|
||
|
||
instance (Scalar a, Scalar b) => DivUnit (a :**: b) b where
|
||
(Times a) |/| b = fromScalar $ scalar a / scalar b
|
||
|
||
instance (Scalar a, Scalar b) => DivUnit (a :**: b) a where
|
||
(Times a) |/| b = fromScalar $ scalar a / scalar b
|
||
|
||
instance {-# INCOHERENT #-} forall a b.
|
||
(Scalar a, Scalar b, Scalar (a :/: Uno b))
|
||
=> DivUnit a (Uno b) where
|
||
x' |/| y' = fromScalar $ scalar x' / scalar y'
|
||
|
||
-- | Dimensionless quantitites (mass per unit mass, radians, etc)
|
||
--
|
||
-- see <https://en.wikipedia.org/wiki/Parts-per_notation#Uno>
|
||
newtype Uno a = Uno a
|
||
deriving stock (Eq, Generic)
|
||
deriving anyclass (NFData, CoArbitrary, Function)
|
||
deriving ( Arbitrary, Num, Ord, Enum, Real, Fractional, ToJSON, FromJSON
|
||
, Scalar, Show
|
||
)
|
||
via a
|
||
deriving Unit via UnitSymbol "" (Uno a)
|
||
|
||
newtype Square a = Square a
|
||
deriving stock (Eq, Generic)
|
||
deriving anyclass (NFData, CoArbitrary, Function)
|
||
deriving ( Arbitrary, Num, Ord, Enum, Real, Fractional, ToJSON, FromJSON
|
||
, Scalar
|
||
)
|
||
via a
|
||
deriving via (a :: Type)
|
||
instance ( Distribution d a
|
||
, forall xx yy. Coercible xx yy => Coercible (d xx) (d yy)
|
||
)
|
||
=> Distribution d (Square a)
|
||
|
||
instance Unit a => Unit (Square a) where
|
||
unitSuffix = unitSuffix @a <> "²"
|
||
|
||
instance Show a => Show (Square a) where
|
||
show (Square n) = show n <> "²"
|
||
|
||
squared :: (Scalar a, a :*: a ~ Square a) => a -> Square a
|
||
squared v = v |*| v
|
||
|
||
newtype Cubic a = Cubic a
|
||
deriving stock (Eq, Generic)
|
||
deriving anyclass (NFData, CoArbitrary, Function)
|
||
deriving ( Arbitrary, Num, Ord, Enum, Real, Fractional, ToJSON, FromJSON
|
||
, Scalar
|
||
)
|
||
via a
|
||
deriving via (a :: Type)
|
||
instance ( Distribution d a
|
||
, forall xx yy. Coercible xx yy => Coercible (d xx) (d yy)
|
||
)
|
||
=> Distribution d (Cubic a)
|
||
|
||
instance Unit a => Unit (Cubic a) where
|
||
unitSuffix = unitSuffix @a <> "³"
|
||
|
||
instance Show a => Show (Cubic a) where
|
||
show (Cubic n) = show n <> "³"
|
||
|
||
newtype (:**:) a b = Times Double
|
||
deriving stock (Eq, Generic)
|
||
deriving anyclass (NFData, CoArbitrary, Function)
|
||
deriving (Arbitrary, Num, Ord, Enum, Real, Fractional, ToJSON, FromJSON)
|
||
via Double
|
||
deriving (Semigroup, Monoid) via Sum Double
|
||
deriving Show via ShowUnitSuffix (a :**: b) Double
|
||
deriving via Double
|
||
instance ( Distribution d Double
|
||
, forall xx yy. Coercible xx yy => Coercible (d xx) (d yy)
|
||
)
|
||
=> Distribution d (a :**: b)
|
||
|
||
instance (Unit a, Unit b) => Unit (a :**: b) where
|
||
unitSuffix = unitSuffix @a <> " " <> unitSuffix @b
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
newtype Ticks = Ticks Word
|
||
deriving stock (Eq, Generic)
|
||
deriving anyclass (NFData, CoArbitrary, Function)
|
||
deriving (Num, Ord, Bounded, Enum, Integral, Real, ToJSON, FromJSON) via Word
|
||
deriving (Semigroup, Monoid) via (Sum Word)
|
||
deriving Scalar via ScalarIntegral Ticks
|
||
deriving Arbitrary via GenericArbitrary Ticks
|
||
deriving Unit via UnitSymbol "ticks" Ticks
|
||
deriving Show via ShowUnitSuffix Ticks Word
|
||
deriving via Word
|
||
instance ( Distribution d Word
|
||
, forall xx yy. Coercible xx yy => Coercible (d xx) (d yy)
|
||
)
|
||
=> Distribution d Ticks
|
||
|
||
newtype Tiles = Tiles Double
|
||
deriving stock (Eq, Generic)
|
||
deriving anyclass (NFData, CoArbitrary, Function)
|
||
deriving (Num, Ord, Enum, Real, ToJSON, FromJSON, Scalar) via Double
|
||
deriving (Semigroup, Monoid) via (Sum Double)
|
||
deriving Arbitrary via GenericArbitrary Tiles
|
||
deriving Unit via UnitSymbol "m" Tiles
|
||
deriving Show via ShowUnitSuffix Tiles Double
|
||
deriving via Double
|
||
instance ( Distribution d Double
|
||
, forall xx yy. Coercible xx yy => Coercible (d xx) (d yy)
|
||
)
|
||
=> Distribution d Tiles
|
||
|
||
type TicksPerTile = Ticks `Per` Tiles
|
||
type TilesPerTick = Tiles `Per` Ticks
|
||
|
||
timesTiles :: TicksPerTile -> Tiles -> Ticks
|
||
timesTiles = (|*|)
|
||
|
||
-- | Calculate the (cartesian) distance between two 'Position's, floored and
|
||
-- represented as a number of 'Tile's
|
||
--
|
||
-- Note that this is imprecise, and may be different than the length of a
|
||
-- bresenham's line between the points
|
||
distance :: Position -> Position -> Tiles
|
||
distance
|
||
= (fromScalar .) . (Metric.distance `on` (fmap fromIntegral . view _Position))
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
newtype Hitpoints = Hitpoints Word
|
||
deriving stock (Eq, Generic)
|
||
deriving anyclass (NFData, CoArbitrary, Function)
|
||
deriving ( Arbitrary, Num, Ord, Bounded, Enum, Integral, Real, Scalar
|
||
, ToJSON, FromJSON
|
||
)
|
||
via Word
|
||
deriving (Semigroup, Monoid) via Sum Word
|
||
deriving Unit via UnitSymbol "hp" Hitpoints
|
||
deriving Show via ShowUnitSuffix Hitpoints Word
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
-- | Grams, the fundamental measure of weight in Xanthous.
|
||
newtype Grams = Grams Double
|
||
deriving stock (Eq, Generic)
|
||
deriving anyclass (NFData, CoArbitrary, Function)
|
||
deriving ( Arbitrary, Num, Ord, Enum, Real, Floating, Fractional, RealFloat
|
||
, RealFrac, Scalar, ToJSON, FromJSON
|
||
)
|
||
via Double
|
||
deriving (Semigroup, Monoid) via Sum Double
|
||
deriving Unit via UnitSymbol "g" Grams
|
||
deriving Show via ShowUnitSuffix Grams Double
|
||
|
||
-- | Every tile is 1 meter
|
||
type Meters = Tiles
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
data Box a = Box
|
||
{ _topLeftCorner :: V2 a
|
||
, _dimensions :: V2 a
|
||
}
|
||
deriving stock (Show, Eq, Ord, Functor, Generic)
|
||
deriving Arbitrary via GenericArbitrary (Box a)
|
||
makeFieldsNoPrefix ''Box
|
||
|
||
bottomRightCorner :: Num a => Box a -> V2 a
|
||
bottomRightCorner box =
|
||
V2 (box ^. topLeftCorner . L._x + box ^. dimensions . L._x)
|
||
(box ^. topLeftCorner . L._y + box ^. dimensions . L._y)
|
||
|
||
setBottomRightCorner :: (Num a, Ord a) => Box a -> V2 a -> Box a
|
||
setBottomRightCorner box br@(V2 brx bry)
|
||
| brx < box ^. topLeftCorner . L._x || bry < box ^. topLeftCorner . L._y
|
||
= box & topLeftCorner .~ br
|
||
& dimensions . L._x .~ ((box ^. topLeftCorner . L._x) - brx)
|
||
& dimensions . L._y .~ ((box ^. topLeftCorner . L._y) - bry)
|
||
| otherwise
|
||
= box & dimensions . L._x .~ (brx - (box ^. topLeftCorner . L._x))
|
||
& dimensions . L._y .~ (bry - (box ^. topLeftCorner . L._y))
|
||
|
||
inBox :: (Ord a, Num a) => Box a -> V2 a -> Bool
|
||
inBox box pt = flip all [L._x, L._y] $ \component ->
|
||
between (box ^. topLeftCorner . component)
|
||
(box ^. to bottomRightCorner . component)
|
||
(pt ^. component)
|
||
|
||
boxIntersects :: (Ord a, Num a) => Box a -> Box a -> Bool
|
||
boxIntersects box₁ box₂
|
||
= any (inBox box₁) [box₂ ^. topLeftCorner, bottomRightCorner box₂]
|
||
|
||
boxCenter :: (Fractional a) => Box a -> V2 a
|
||
boxCenter box = V2 cx cy
|
||
where
|
||
cx = box ^. topLeftCorner . L._x + (box ^. dimensions . L._x / 2)
|
||
cy = box ^. topLeftCorner . L._y + (box ^. dimensions . L._y / 2)
|
||
|
||
boxEdge :: (Enum a, Num a) => Box a -> Edge -> [V2 a]
|
||
boxEdge box LeftEdge =
|
||
V2 (box ^. topLeftCorner . L._x)
|
||
<$> [box ^. topLeftCorner . L._y .. box ^. to bottomRightCorner . L._y]
|
||
boxEdge box RightEdge =
|
||
V2 (box ^. to bottomRightCorner . L._x)
|
||
<$> [box ^. to bottomRightCorner . L._y .. box ^. to bottomRightCorner . L._y]
|
||
boxEdge box TopEdge =
|
||
flip V2 (box ^. topLeftCorner . L._y)
|
||
<$> [box ^. topLeftCorner . L._x .. box ^. to bottomRightCorner . L._x]
|
||
boxEdge box BottomEdge =
|
||
flip V2 (box ^. to bottomRightCorner . L._y)
|
||
<$> [box ^. topLeftCorner . L._x .. box ^. to bottomRightCorner . L._x]
|