packages feed

moonlight-planar-1.0.0.0: src-hex/Moonlight/Hex/Coordinate.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}

-- | Global axial coordinates and the six translations of the hexagonal grid.
module Moonlight.Hex.Coordinate
  ( HexCoord (..)
  , HexDirection (..)
  , allHexDirections
  , oppositeHexDirection
  , hexDirectionDelta
  , hexStepCoord
  ) where

import Control.DeepSeq (NFData)
import Data.List.NonEmpty (NonEmpty (..))
import GHC.Generics (Generic)

-- | A global axial coordinate. Layouts restrict which coordinates are present;
-- the coordinate itself remains stable across restrictions and gluing.
data HexCoord = HexCoord
  { hexQ :: !Int
  , hexR :: !Int
  }
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

-- | The six translations of the axial lattice.
data HexDirection
  = HexEast
  | HexNorthEast
  | HexNorthWest
  | HexWest
  | HexSouthWest
  | HexSouthEast
  deriving stock (Eq, Ord, Show, Enum, Bounded, Generic)
  deriving anyclass (NFData)

allHexDirections :: NonEmpty HexDirection
allHexDirections =
  HexEast :| [HexNorthEast, HexNorthWest, HexWest, HexSouthWest, HexSouthEast]

oppositeHexDirection :: HexDirection -> HexDirection
oppositeHexDirection direction = case direction of
  HexEast -> HexWest
  HexNorthEast -> HexSouthWest
  HexNorthWest -> HexSouthEast
  HexWest -> HexEast
  HexSouthWest -> HexNorthEast
  HexSouthEast -> HexNorthWest
{-# INLINE oppositeHexDirection #-}

hexDirectionDelta :: HexDirection -> HexCoord
hexDirectionDelta direction = case direction of
  HexEast -> HexCoord 1 0
  HexNorthEast -> HexCoord 1 (-1)
  HexNorthWest -> HexCoord 0 (-1)
  HexWest -> HexCoord (-1) 0
  HexSouthWest -> HexCoord (-1) 1
  HexSouthEast -> HexCoord 0 1
{-# INLINE hexDirectionDelta #-}

-- | Translate once, returning 'Nothing' only when the machine 'Int' boundary
-- would be crossed. Layout membership is deliberately a separate question.
hexStepCoord :: HexCoord -> HexDirection -> Maybe HexCoord
hexStepCoord (HexCoord q r) direction =
  let HexCoord dq dr = hexDirectionDelta direction
   in HexCoord <$> checkedUnitAdd q dq <*> checkedUnitAdd r dr
{-# INLINE hexStepCoord #-}

checkedUnitAdd :: Int -> Int -> Maybe Int
checkedUnitAdd value delta
  | delta > 0 && value == maxBound = Nothing
  | delta < 0 && value == minBound = Nothing
  | otherwise = Just (value + delta)
{-# INLINE checkedUnitAdd #-}