{-
	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@]	Defines an entry in the transposition-table.
-}

module BishBosh.Search.TranspositionValue (
-- * Types
-- ** Type-synonyms
	IsOptimal,
	FindFitness,
-- ** Data-types
	TranspositionValue (
--		MkTranspositionValue,
		getIsOptimal,
		getNPlies,
		getQualifiedMoves
	),
-- * Functions
	inferSearchDepth,
-- ** Constructor
	mkTranspositionValue,
-- ** Predicates
	isBetter
 ) where

import qualified	BishBosh.Data.Exception	as Data.Exception
import qualified	BishBosh.Type.Count	as Type.Count
import qualified	BishBosh.Type.Mass	as Type.Mass
import qualified	Control.Exception
import qualified	Data.Ord

-- | Whether the recorded move-sequence is known to be optimal.
type IsOptimal	= Bool

-- | The type of the values in the transposition-table.
data TranspositionValue qualifiedMove	= MkTranspositionValue {
	TranspositionValue qualifiedMove -> IsOptimal
getIsOptimal		:: IsOptimal,		-- ^ Whether the recorded move-sequence is known to be optimal.
	TranspositionValue qualifiedMove -> NPlies
getNPlies		:: Type.Count.NPlies,	-- ^ The number of plies applied to the /game/ before application of any of the specified moves.
	TranspositionValue qualifiedMove -> [qualifiedMove]
getQualifiedMoves	:: [qualifiedMove]	-- ^ The sequence of qualifiedMoves applied to the /game/, which caused the alpha-beta event.
} deriving NPlies -> TranspositionValue qualifiedMove -> ShowS
[TranspositionValue qualifiedMove] -> ShowS
TranspositionValue qualifiedMove -> String
(NPlies -> TranspositionValue qualifiedMove -> ShowS)
-> (TranspositionValue qualifiedMove -> String)
-> ([TranspositionValue qualifiedMove] -> ShowS)
-> Show (TranspositionValue qualifiedMove)
forall qualifiedMove.
Show qualifiedMove =>
NPlies -> TranspositionValue qualifiedMove -> ShowS
forall qualifiedMove.
Show qualifiedMove =>
[TranspositionValue qualifiedMove] -> ShowS
forall qualifiedMove.
Show qualifiedMove =>
TranspositionValue qualifiedMove -> String
forall a.
(NPlies -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [TranspositionValue qualifiedMove] -> ShowS
$cshowList :: forall qualifiedMove.
Show qualifiedMove =>
[TranspositionValue qualifiedMove] -> ShowS
show :: TranspositionValue qualifiedMove -> String
$cshow :: forall qualifiedMove.
Show qualifiedMove =>
TranspositionValue qualifiedMove -> String
showsPrec :: NPlies -> TranspositionValue qualifiedMove -> ShowS
$cshowsPrec :: forall qualifiedMove.
Show qualifiedMove =>
NPlies -> TranspositionValue qualifiedMove -> ShowS
Show

-- | Smart constructor.
mkTranspositionValue
	:: IsOptimal
	-> Type.Count.NPlies
	-> [qualifiedMove]
	-> TranspositionValue qualifiedMove
mkTranspositionValue :: IsOptimal
-> NPlies -> [qualifiedMove] -> TranspositionValue qualifiedMove
mkTranspositionValue IsOptimal
_ NPlies
_ []	= Exception -> TranspositionValue qualifiedMove
forall a e. Exception e => e -> a
Control.Exception.throw (Exception -> TranspositionValue qualifiedMove)
-> Exception -> TranspositionValue qualifiedMove
forall a b. (a -> b) -> a -> b
$ String -> Exception
Data.Exception.mkNullDatum String
"BishBosh.Search.TranspositionValue.mkTranspositionValue:\tnull list of qualifiedMoves."
mkTranspositionValue IsOptimal
isOptimal NPlies
nPlies [qualifiedMove]
qualifiedMoves
	| NPlies
nPlies NPlies -> NPlies -> IsOptimal
forall a. Ord a => a -> a -> IsOptimal
< NPlies
0	= Exception -> TranspositionValue qualifiedMove
forall a e. Exception e => e -> a
Control.Exception.throw (Exception -> TranspositionValue qualifiedMove)
-> (String -> Exception)
-> String
-> TranspositionValue qualifiedMove
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Exception
Data.Exception.mkOutOfBounds (String -> Exception) -> ShowS -> String -> Exception
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> ShowS
showString String
"BishBosh.Search.TranspositionValue.mkTranspositionValue:\tnPlies=" (String -> TranspositionValue qualifiedMove)
-> String -> TranspositionValue qualifiedMove
forall a b. (a -> b) -> a -> b
$ NPlies -> ShowS
forall a. Show a => a -> ShowS
shows NPlies
nPlies String
" can't be negative."
	| IsOptimal
otherwise	= MkTranspositionValue :: forall qualifiedMove.
IsOptimal
-> NPlies -> [qualifiedMove] -> TranspositionValue qualifiedMove
MkTranspositionValue {
		getIsOptimal :: IsOptimal
getIsOptimal		= IsOptimal
isOptimal,
		getNPlies :: NPlies
getNPlies		= NPlies
nPlies,
		getQualifiedMoves :: [qualifiedMove]
getQualifiedMoves	= [qualifiedMove]
qualifiedMoves
	}

-- | Infer the search-depth from the length of the qualifiedMove-sequence.
inferSearchDepth :: TranspositionValue qualifiedMove -> Type.Count.NPlies
inferSearchDepth :: TranspositionValue qualifiedMove -> NPlies
inferSearchDepth	= NPlies -> NPlies
forall a b. (Integral a, Num b) => a -> b
fromIntegral (NPlies -> NPlies)
-> (TranspositionValue qualifiedMove -> NPlies)
-> TranspositionValue qualifiedMove
-> NPlies
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [qualifiedMove] -> NPlies
forall (t :: * -> *) a. Foldable t => t a -> NPlies
length ([qualifiedMove] -> NPlies)
-> (TranspositionValue qualifiedMove -> [qualifiedMove])
-> TranspositionValue qualifiedMove
-> NPlies
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TranspositionValue qualifiedMove -> [qualifiedMove]
forall qualifiedMove.
TranspositionValue qualifiedMove -> [qualifiedMove]
getQualifiedMoves

{- |
	* The type of a function which can find the fitness of the game resulting from the recorded sequence of qualifiedMoves.

	* CAVEAT: the fitness this function returns should be from the perspective of the player to make the first move.
-}
type FindFitness qualifiedMove	= TranspositionValue qualifiedMove -> Type.Mass.WeightedMean

{- |
	* Whether a proposed value is better than the incumbent.

	* CAVEAT: this is a narrower concept than addressed by 'Ord', which implies 'Eq'.
-}
isBetter
	:: FindFitness qualifiedMove
	-> TranspositionValue qualifiedMove	-- ^ The proposed value.
	-> TranspositionValue qualifiedMove	-- ^ The incumbent value.
	-> Bool
isBetter :: FindFitness qualifiedMove
-> TranspositionValue qualifiedMove
-> TranspositionValue qualifiedMove
-> IsOptimal
isBetter FindFitness qualifiedMove
findFitness TranspositionValue qualifiedMove
proposedValue TranspositionValue qualifiedMove
incumbentValue	= case (TranspositionValue qualifiedMove -> NPlies)
-> TranspositionValue qualifiedMove
-> TranspositionValue qualifiedMove
-> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
Data.Ord.comparing TranspositionValue qualifiedMove -> NPlies
forall qualifiedMove. TranspositionValue qualifiedMove -> NPlies
inferSearchDepth TranspositionValue qualifiedMove
proposedValue TranspositionValue qualifiedMove
incumbentValue of
	Ordering
GT	-> IsOptimal
True	-- The new search is deeper.
	Ordering
EQ	-> TranspositionValue qualifiedMove -> IsOptimal
forall qualifiedMove. TranspositionValue qualifiedMove -> IsOptimal
getIsOptimal TranspositionValue qualifiedMove
proposedValue IsOptimal -> IsOptimal -> IsOptimal
|| FindFitness qualifiedMove
-> TranspositionValue qualifiedMove
-> TranspositionValue qualifiedMove
-> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
Data.Ord.comparing FindFitness qualifiedMove
findFitness TranspositionValue qualifiedMove
proposedValue TranspositionValue qualifiedMove
incumbentValue Ordering -> Ordering -> IsOptimal
forall a. Eq a => a -> a -> IsOptimal
== Ordering
GT
	Ordering
_	-> IsOptimal
False