riichi-scoring 0.6.0.0 → 0.7.0.0
raw patch · 7 files changed
+81/−22 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Riichi.Shanten: countTatsuPairs :: [Partial] -> (Int, Int)
+ Riichi.Display: displayLiveMode :: Hand -> IO ()
+ Riichi.Shanten: countTaatsuPairs :: [Partial] -> (Int, Int)
Files
- README.md +17/−10
- app/Main.hs +16/−5
- riichi-scoring.cabal +1/−1
- src/Riichi/Context.hs +2/−1
- src/Riichi/Display.hs +21/−2
- src/Riichi/Efficiency.hs +9/−0
- src/Riichi/Shanten.hs +15/−3
README.md view
@@ -1,25 +1,32 @@-This package provides a CLI tool with commands for determining the yaku, fu, and score of a hand in Riichi Mahjong. It can also determine the waits of a partial hand.+This package provides a CLI tool with commands for determining the yaku, fu, and score of a hand in Riichi Mahjong. It can also determine the waits of a partial hand,+determine shanten, and tile efficient discards. The CLI tool supplied is the riichi command. Help information is as follows: - Command riichi:- Possible subcommands: yaku, waits, score, shanten, discard (default = yaku)-- Usage: riichi <subcommand> "<hand>" - "yaku" and "score" subcommands expect a full hand.+ Subcommands:+ yaku Determine yaku of a completed hand.+ waits Determine the waits of a ready hand.+ score Score a completed hand.+ shanten Get the shanten of a hand.+ discard Determine which discard for a hand has the best+ tile efficiency.+ live Interactive mode. Give discard recommendations for+ a hand until it is complete.++ "yaku", "score", "discard" and "live" subcommands expect a full hand. "waits" subcommand expects a hand that is tenpai. Example hands include:- "123p 234m 444p rrrr NN"- "344556s 444p 222m EE"- "19p 19s 19m 1p NESWrgw"+ "123p 234m 444p rrrr NN"+ "344556s 444p 222m EE"+ "19p 19s 19m 1p NESWrgw" In detail, numeric tiles are denoted (1-9) + (m, p, or s), Winds are denoted N, E, S, W, and Dragons are r, w, g. A 0 can be used to denote a red five.- Numeric tiles of the same suit, and honour tiles, can be + Numeric tiles of the same suit, and honour tiles, can be grouped as seen in the examples (but needn't be). In scoring a hand, dora and seat/round wind may be required,
app/Main.hs view
@@ -9,6 +9,7 @@ import Data.Version (showVersion) import Paths_riichi_scoring (version) import Riichi.Display+import Riichi.Display (displayLiveMode) import Riichi.Meld import System.Environment (getArgs) @@ -31,15 +32,25 @@ | arg1 == "score" -> displayHandScore $ mkHand arg2 | arg1 == "shanten" -> displayHandShanten $ mkHand arg2 | arg1 == "discard" -> displayHandDiscard $ mkHand arg2+ | arg1 == "live" -> displayLiveMode $ mkHand arg2 | otherwise -> putStrLn "Command not recognised" helpString :: String helpString =- "Command riichi: version = "+ "Command riichi: \n\tversion = " ++ showVersion version- ++ " \n\tPossible subcommands: yaku, waits, score, shanten, discard (default = yaku)\n\nUsage:\- \\n\triichi <subcommand> \"<hand>\"\- \\n\n\t\"yaku\" and \"score\" subcommands expect a full hand.\+ ++ "\nUsage:\+ \\n\triichi <subcommand> \"<hand>\"\n\+ \\n\tSubcommands:\+ \\n\t\tyaku Determine yaku of a completed hand.\+ \\n\t\twaits Determine the waits of a ready hand.\+ \\n\t\tscore Score a completed hand.\+ \\n\t\tshanten Get the shanten of a hand.\+ \\n\t\tdiscard Determine which discard for a hand has the best \+ \\n\t\t tile efficiency.\+ \\n\t\tlive Interactive mode. Give discard recommendations for \+ \\n\t\t a hand until it is complete.\+ \\n\n\t\"yaku\", \"score\", \"discard\" and \"live\" subcommands expect a full hand.\ \\n\t\"waits\" subcommand expects a hand that is tenpai.\ \\n\n\tExample hands include:\ \\n\t\t\"123p 234m 444p rrrr NN\"\@@ -51,4 +62,4 @@ \\n\tNumeric tiles of the same suit, and honour tiles, can be \ \\n\tgrouped as seen in the examples (but needn't be).\ \\n\n\tIn scoring a hand, dora and seat/round wind may be required,\- \\n\talso supplied in this format."+ \\n\talso supplied in this format.\n"
riichi-scoring.cabal view
@@ -20,7 +20,7 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version: 0.6.0.0+version: 0.7.0.0 -- A short (one-line) description of the package. synopsis: A CLI tool for interpreting and scoring Riichi Mahjong hands. -- A longer description of the package.
src/Riichi/Context.hs view
@@ -358,9 +358,10 @@ } else return Nothing --- | Overarching context type+-- | Overarching context type. data Context = Context (Maybe InterpretedHand) HandContext (Either YakuContext YakumanContext) +-- | Get the dora for a given hand context. getContextDora :: Context -> Dora getContextDora (Context _ (HandContext{dora}) _) = dora
src/Riichi/Display.hs view
@@ -10,7 +10,7 @@ import Control.Monad (forM, forM_, when) import Control.Monad.Trans import Data.Function-import Data.List (intercalate, intersperse, sort)+import Data.List (intercalate, intersperse, sort, (\\)) import Data.Monoid (getSum) import Riichi.Context import Riichi.Efficiency@@ -139,6 +139,7 @@ ++ toGreen (show (yakumans * 32000)) ++ " points for Non-Dealer." +-- | Implements the "shanten" command for the CLI. displayHandShanten :: Hand -> IO () displayHandShanten hand = do let shanten = getShanten hand@@ -148,7 +149,25 @@ 1 -> putStrLn $ "Hand is: " ++ toMagenta (show shanten ++ " shanten") ++ toBlue " (Iishanten)" _ -> putStrLn $ "Hand is: " ++ toMagenta (show shanten ++ " shanten") +-- | Implements the "discard" command for the CLI. displayHandDiscard :: Hand -> IO () displayHandDiscard hand = do let discards = optimalDiscards hand- putStrLn $ "Optimal discards are: " ++ intercalate ", " (map show discards)+ putStrLn $ toMagenta "Optimal discards are: " ++ intercalate ", " (map show discards)++-- | Implements the "live" command for the CLI+displayLiveMode :: Hand -> IO ()+displayLiveMode hand = do+ displayHandDiscard hand+ putStrLn $ toGreen "\nChoose a discard:"+ discard <- head . mkHand <$> getLine+ putStrLn $ toGreen "Enter drawn tile:"+ drawn <- head . mkHand <$> getLine+ let hand' = sort $ drawn : (hand \\ [discard])+ putStrLn $ "\nCurrent hand:\n" ++ toBlue (intercalate ", " (map show hand')) ++ "\n"+ let shanten = getShanten hand'+ if shanten > -1+ then+ displayLiveMode $ drawn : (hand \\ [discard])+ else+ putStrLn $ toCyan "Hand is complete!"
src/Riichi/Efficiency.hs view
@@ -14,6 +14,10 @@ import Riichi.Tile import Riichi.Waits +{- | Find the optimal discards for a hand that is ready to discard. Here, "optimal" means that the chosen+| discard does not increase the shanten, and among such tiles leaves the most possible improvement tiles+| that can be drawn. An improvement tile is one that reduces shanten.+-} optimalDiscards :: Hand -> [Tile] optimalDiscards hand = counts@@ -35,6 +39,7 @@ & maximumBy (\(_, x) (_, y) -> compare x y) & snd +-- | Find all tiles that, if added to a supplied hand that is ready to draw, decrease shanten. improvements :: Hand -> [Tile] improvements hand = case currentShanten of -1 -> []@@ -43,6 +48,10 @@ where currentShanten = getShanten hand +{- | Count how many improvements there are, yet to be drawn, for a given hand. The first input is a (possibly empty) list of+| tiles that have already been discarded. This, together with the tiles in the hand, determines what count is assigned to each+| potential improvement tile.+-} countImprovements :: Hand -> Hand -> Int countImprovements alreadyDiscarded hand = let
src/Riichi/Shanten.hs view
@@ -14,10 +14,13 @@ import Riichi.Tile import Text.ParserCombinators.ReadP (count) +-- | A taatsu is an incomplete sequence. Useful for working out shanten. data Taatsu = Taatsu Tile Tile deriving (Show, Eq, Ord) +-- | A partial is either a taatsu or a pair. Useful for working out shanten. type Partial = Either Taatsu Pair +-- | Get the shanten of a hand. getShanten :: Hand -> Int getShanten hand | length hand `elem` [13, 14] = if 0 `elem` list then 0 else minimum list@@ -29,6 +32,7 @@ b = basicShanten hand list = [p, o, b] +-- | Get the shanten of a hand with respect to a standard 4 melds and a pair complete hand. basicShanten :: Hand -> Int basicShanten hand = minimum $ do -- The combinatorics can blow up here, especially on a hand like 1111p 2222p 3333p 4444p rr@@ -40,15 +44,17 @@ -- 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+ let (t, p) = countTaatsuPairs partials return $ 8 - (2 * m) - min (t + p) (4 - m) - (if p >= 1 && (m + t + p >= 5) then 1 else 0) +-- | Get the hand's shanten with respect to seven pairs. pairsShanten :: Hand -> Int pairsShanten hand = 6 - numPairs + max 0 (7 - uniqueTiles) where numPairs = length $ findPairs hand uniqueTiles = length $ map head $ group $ sort hand +-- | Get the hand's shanten with respect to thirteen orphans. orphansShanten :: Hand -> Int orphansShanten hand = 13 - uniqueOrphans - pairs where@@ -57,13 +63,18 @@ uniqueOrphans = length $ map head groupedOrphans pairs = if any ((>= 2) . length) groupedOrphans then 1 else 0 -countTatsuPairs :: [Partial] -> (Int, Int)-countTatsuPairs partials = (numTatsu, numPairs)+-- | Given a sequence of partials, count how many are taatsi and pairs respectively.+countTaatsuPairs :: [Partial] -> (Int, Int)+countTaatsuPairs partials = (numTatsu, numPairs) where num = length partials numTatsu = length $ lefts partials numPairs = num - numTatsu +{- | Form all sequences of partials within a hand, of length 0 up to the input integer. This way,+| recursion depth can be limited by the integer input, which is useful for shanten calculations+| when we need only consider 5 blocks.+-} formPartials :: Int -> Hand -> [[Partial]] formPartials 0 _ = [[]] formPartials _ [] = [[]]@@ -87,6 +98,7 @@ & group & map head +-- | Attempt to make a partial from two tiles. mkPartial :: Tile -> Tile -> Maybe Partial mkPartial tile1@(Numeric suit1 val1 _) tile2@(Numeric suit2 val2 _) | suit1 /= suit2 = Nothing