riichi-scoring-0.6.0.0: src/Riichi/Shanten.hs
{- |
Module : Riichi.Shanten
Description : Datatypes and functions for calculating Shanten
License : BSD-3-Clause
Maintainer : surplussinewaves@gmail.com
-}
module Riichi.Shanten where
import Data.Either (lefts)
import Data.Function ((&))
import Data.List
import Data.Maybe
import Riichi.Meld
import Riichi.Tile
import Text.ParserCombinators.ReadP (count)
data Taatsu = Taatsu Tile Tile deriving (Show, Eq, Ord)
type Partial = Either Taatsu Pair
getShanten :: Hand -> Int
getShanten hand
| length hand `elem` [13, 14] = if 0 `elem` list then 0 else minimum list
| otherwise = basicShanten hand
where
-- Postpone basic shanten calculation until after we know the other two (which are faster) are not 0
p = pairsShanten hand
o = orphansShanten hand
b = basicShanten hand
list = [p, o, b]
basicShanten :: Hand -> Int
basicShanten hand = minimum $ do
-- The combinatorics can blow up here, especially on a hand like 1111p 2222p 3333p 4444p rr
-- Need to try to prune out as many cases as we can, and we must dispose of cases that find so
-- many taatsu or melds that shanten is negative!
let meldss = formMelds' hand
melds <- meldss
let m = length melds
-- Don't bother if the number of partials pushes us past 5 blocks
let partialss = splitAcrossSuits (formPartials (5 - m)) (hand \\ concatMelds melds)
partials <- partialss
let (t, p) = countTatsuPairs partials
return $ 8 - (2 * m) - min (t + p) (4 - m) - (if p >= 1 && (m + t + p >= 5) then 1 else 0)
pairsShanten :: Hand -> Int
pairsShanten hand = 6 - numPairs + max 0 (7 - uniqueTiles)
where
numPairs = length $ findPairs hand
uniqueTiles = length $ map head $ group $ sort hand
orphansShanten :: Hand -> Int
orphansShanten hand = 13 - uniqueOrphans - pairs
where
orphans = filter (not . isSimple) hand
groupedOrphans = group $ sort orphans
uniqueOrphans = length $ map head groupedOrphans
pairs = if any ((>= 2) . length) groupedOrphans then 1 else 0
countTatsuPairs :: [Partial] -> (Int, Int)
countTatsuPairs partials = (numTatsu, numPairs)
where
num = length partials
numTatsu = length $ lefts partials
numPairs = num - numTatsu
formPartials :: Int -> Hand -> [[Partial]]
formPartials 0 _ = [[]]
formPartials _ [] = [[]]
formPartials _ [_] = [[]]
formPartials n hand@(tile1 : hand') =
let
-- Get all sets of 2 tiles, including the first tile
doubles = do
tile2 <- hand'
return [tile1, tile2]
partials =
formPartials n (tail hand) ++ do
[tile1, tile2] <- doubles
partial <- maybeToList $ mkPartial tile1 tile2
map (partial :) $ formPartials (n - 1) (hand' \\ [tile2])
in
partials
-- & sortBy (\x y -> compare (length x) (length y))
& map sort
& sort
& group
& map head
mkPartial :: Tile -> Tile -> Maybe Partial
mkPartial tile1@(Numeric suit1 val1 _) tile2@(Numeric suit2 val2 _)
| suit1 /= suit2 = Nothing
| val1 == val2 = Just $ Right $ Pair tile1
| abs (val1 - val2) <= 2 =
if val1 <= val2
then Just $ Left $ Taatsu tile1 tile2
else Just $ Left $ Taatsu tile2 tile1
| otherwise = Nothing
mkPartial tile1@(Honour _ _) tile2@(Honour _ _) =
if tile1 == tile2
then Just $ Right $ Pair tile1
else Nothing
mkPartial _ _ = Nothing