bishbosh-0.1.2.0: src-lib/BishBosh/State/InstancesByPosition.hs
{-
Copyright (C) 2018 Dr. Alistair Ward
This file is part of BishBosh.
BishBosh is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
BishBosh is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with BishBosh. If not, see <http://www.gnu.org/licenses/>.
-}
{- |
[@AUTHOR@] Dr. Alistair Ward
[@DESCRIPTION@] Records the number of times each /position/ has been encountered since the last unrepeatable move.
-}
module BishBosh.State.InstancesByPosition(
-- * Types
-- ** Type-synonyms
-- NPositionsByPosition,
-- Transformation,
-- * Constants
leastCyclicPlies,
-- ** Data-types
InstancesByPosition(),
-- * Functions
countConsecutiveRepeatablePlies,
countPositionRepetitions,
getNDistinctPositions,
findMaximumInstances,
-- ** Constructors
mkInstancesByPosition,
mkSingleton,
-- ** Mutators
-- insertPosition',
insertPosition,
deletePosition,
-- ** Predicates
anyInstancesByPosition
) where
import qualified BishBosh.Property.Empty as Property.Empty
import qualified BishBosh.Property.Reflectable as Property.Reflectable
import qualified BishBosh.Type.Count as Type.Count
import qualified Control.DeepSeq
import qualified Control.Exception
import qualified Data.Foldable
import qualified Data.Map.Strict as Map
-- | The smallest number of repeatable plies (applied by alternating players) required to form a cycle.
leastCyclicPlies :: Type.Count.NPlies
leastCyclicPlies = 4
{- |
* A count of the number of instances of /position/s which have occurred.
* N.B.: a number greater than @1@ represents repetition.
* The /position/ can either be represented by a physical 'State.Position.Position', or by proxy using a hash.
-}
type NPositionsByPosition position = Map.Map position Type.Count.NPositions
-- | Insert a position into the unwrapped collection.
insertPosition' :: Ord position => position -> NPositionsByPosition position -> NPositionsByPosition position
insertPosition' = flip (Map.insertWith $ const succ) 1
-- | Wrap the type, so that class-instances can be hung from it.
newtype InstancesByPosition position = MkInstancesByPosition {
getNPositionsByPosition :: NPositionsByPosition position
} deriving Eq
instance Control.DeepSeq.NFData position => Control.DeepSeq.NFData (InstancesByPosition position) where
rnf MkInstancesByPosition { getNPositionsByPosition = m } = Control.DeepSeq.rnf m
instance (
Ord position,
Property.Reflectable.ReflectableOnX position
) => Property.Reflectable.ReflectableOnX (InstancesByPosition position) where
reflectOnX MkInstancesByPosition { getNPositionsByPosition = m } = MkInstancesByPosition $ Map.mapKeys Property.Reflectable.reflectOnX m
-- | Construct from repeatable data.
mkInstancesByPosition
:: (Foldable foldable, Ord position)
=> (a -> position) -- ^ Position-constructor.
-> foldable a -- ^ Data from which to construct positions.
-> InstancesByPosition position
mkInstancesByPosition f = MkInstancesByPosition . Data.Foldable.foldr (insertPosition' . f) Property.Empty.empty
-- | Constructor.
mkSingleton :: position -> InstancesByPosition position
mkSingleton = MkInstancesByPosition . (`Map.singleton` 1)
{- |
* Count the total number of consecutive repeatable plies amongst recent moves.
* This is equivalent to the number of entries in the map, since adding a non-repeatable move triggers a purge.
-}
countConsecutiveRepeatablePlies :: InstancesByPosition position -> Type.Count.NPlies
countConsecutiveRepeatablePlies MkInstancesByPosition { getNPositionsByPosition = m } = fromIntegral $ Data.Foldable.foldl' (+) (
negate 1 -- The map is never empty, since before the first move a singleton is constructed with the initial position.
) m
-- | Count the total number of repetitions of /position/s.
countPositionRepetitions :: InstancesByPosition position -> Type.Count.NPositions
countPositionRepetitions MkInstancesByPosition { getNPositionsByPosition = m } = Data.Foldable.foldl' (
(+) . pred -- The initial instance isn't a repetition.
) 0 m
-- | The number of distinct /position/s.
getNDistinctPositions :: InstancesByPosition position -> Type.Count.NPositions
getNDistinctPositions MkInstancesByPosition { getNPositionsByPosition = m } = fromIntegral $ Data.Foldable.length m {-the number of keys-}
-- | Predicate: apply the specified predicate to the map.
anyInstancesByPosition
:: (Type.Count.NPositions -> Bool)
-> InstancesByPosition position
-> Bool
anyInstancesByPosition predicate MkInstancesByPosition { getNPositionsByPosition = m } = Data.Foldable.any predicate m
{- |
* Find the maximum number of times any one position has already been visited.
* CAVEAT: only those positions that can still be reached are considered.
-}
findMaximumInstances :: InstancesByPosition position -> Type.Count.NPositions
findMaximumInstances MkInstancesByPosition { getNPositionsByPosition = m }
| Data.Foldable.null m = 0 -- CAVEAT: this shouldn't happen.
| otherwise = Data.Foldable.maximum m
-- | The type of a function which transforms the collection.
type Transformation position = InstancesByPosition position -> InstancesByPosition position
-- | Insert a /position/ into the collection.
insertPosition
:: Ord position
=> Bool -- ^ Whether the /turn/ which led to the specified /position/, was repeatable.
-> position
-> Transformation position
insertPosition isRepeatable position MkInstancesByPosition { getNPositionsByPosition = m }
| isRepeatable = MkInstancesByPosition $ insertPosition' position m -- Include this position.
| otherwise = mkSingleton position -- The previous position can't be revisited without rolling-back.
-- | Remove a /position/ from the collection, as required to implement rollback.
deletePosition :: Ord position => position -> Transformation position
deletePosition position MkInstancesByPosition { getNPositionsByPosition = m } = MkInstancesByPosition . Map.update (
\n -> if n == 1
then Nothing -- Delete the entry.
else Just $ pred n -- Decrement the number of instances.
) position $ Control.Exception.assert (Map.member position m) m