Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9534218797 | ||
|
|
d7d3d1a4fd | ||
|
|
86c4fe316e | ||
|
|
d5ac42268f | ||
|
|
df26e9d265 | ||
|
|
cb5f2d7d15 | ||
|
|
152ea76bda | ||
|
|
36ab31a17c | ||
|
|
768cb47fac | ||
|
|
427ad12603 | ||
|
|
1ca2ffc378 | ||
|
|
9e6b71c464 | ||
|
|
2f4e963e41 | ||
|
|
05af939963 | ||
|
|
8af7974c8f | ||
|
|
f7cfd5d835 | ||
|
|
cc495fa589 | ||
|
|
9c5d166f31 |
@@ -1,5 +1,12 @@
|
||||
# Changelog for mtlstats
|
||||
|
||||
## 0.8.0
|
||||
- Bugfix: removed quotation marks from goalie names in report
|
||||
- Allow lower case player names
|
||||
- Don't show players without points in game report
|
||||
- Removed unnecessary goalie statistics from game report
|
||||
- Fixed goalie average calculation
|
||||
|
||||
## 0.7.0
|
||||
- Shortened views to fit within 25 lines
|
||||
- Implemented goalie reports
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: mtlstats
|
||||
version: 0.7.0
|
||||
version: 0.8.0
|
||||
github: "mtlstats/mtlstats"
|
||||
license: GPL-3
|
||||
author: "Jonathan Lamothe"
|
||||
|
||||
@@ -28,6 +28,7 @@ module Mtlstats.Prompt (
|
||||
promptControllerWith,
|
||||
promptController,
|
||||
strPrompt,
|
||||
ucStrPrompt,
|
||||
numPrompt,
|
||||
selectPrompt,
|
||||
-- * Individual prompts
|
||||
@@ -46,7 +47,7 @@ import Control.Monad.Extra (whenJust)
|
||||
import Control.Monad.Trans.State (gets, modify)
|
||||
import Data.Char (isDigit, toUpper)
|
||||
import Data.Foldable (forM_)
|
||||
import Lens.Micro ((^.), (&), (.~), (?~))
|
||||
import Lens.Micro ((^.), (&), (.~), (?~), (%~))
|
||||
import Lens.Micro.Extras (view)
|
||||
import Text.Read (readMaybe)
|
||||
import qualified UI.NCurses as C
|
||||
@@ -68,10 +69,8 @@ promptHandler p (C.EventCharacter '\n') = do
|
||||
val <- gets $ view inputBuffer
|
||||
modify $ inputBuffer .~ ""
|
||||
promptAction p val
|
||||
promptHandler p (C.EventCharacter c) = let
|
||||
c' = toUpper c
|
||||
in when (promptCharCheck p c') $
|
||||
modify $ addChar c'
|
||||
promptHandler p (C.EventCharacter c) =
|
||||
modify $ inputBuffer %~ promptProcessChar p c
|
||||
promptHandler _ (C.EventSpecialKey C.KeyBackspace) =
|
||||
modify removeChar
|
||||
promptHandler p (C.EventSpecialKey k) =
|
||||
@@ -112,11 +111,21 @@ strPrompt
|
||||
-> Prompt
|
||||
strPrompt pStr act = Prompt
|
||||
{ promptDrawer = drawSimplePrompt pStr
|
||||
, promptCharCheck = const True
|
||||
, promptProcessChar = \ch -> (++ [ch])
|
||||
, promptAction = act
|
||||
, promptSpecialKey = const $ return ()
|
||||
}
|
||||
|
||||
-- | Creates an upper case string prompt
|
||||
ucStrPrompt
|
||||
:: String
|
||||
-- ^ The prompt string
|
||||
-> (String -> Action ())
|
||||
-- ^ The callback function for the result
|
||||
-> Prompt
|
||||
ucStrPrompt pStr act = (strPrompt pStr act)
|
||||
{ promptProcessChar = \ch -> (++ [toUpper ch]) }
|
||||
|
||||
-- | Builds a numeric prompt
|
||||
numPrompt
|
||||
:: String
|
||||
@@ -126,7 +135,9 @@ numPrompt
|
||||
-> Prompt
|
||||
numPrompt pStr act = Prompt
|
||||
{ promptDrawer = drawSimplePrompt pStr
|
||||
, promptCharCheck = isDigit
|
||||
, promptProcessChar = \ch str -> if isDigit ch
|
||||
then str ++ [ch]
|
||||
else str
|
||||
, promptAction = \inStr -> forM_ (readMaybe inStr) act
|
||||
, promptSpecialKey = const $ return ()
|
||||
}
|
||||
@@ -146,7 +157,7 @@ selectPrompt params = Prompt
|
||||
in "F" ++ show n ++ ") " ++ desc)
|
||||
results
|
||||
C.moveCursor row col
|
||||
, promptCharCheck = const True
|
||||
, promptProcessChar = \ch -> (++[ch])
|
||||
, promptAction = \sStr -> if null sStr
|
||||
then spCallback params Nothing
|
||||
else do
|
||||
@@ -180,7 +191,7 @@ playerNamePrompt = strPrompt "Player name: " $
|
||||
|
||||
-- | Prompts for a new player's position
|
||||
playerPosPrompt :: Prompt
|
||||
playerPosPrompt = strPrompt "Player position: " $
|
||||
playerPosPrompt = ucStrPrompt "Player position: " $
|
||||
modify . (progMode.createPlayerStateL.cpsPosition .~)
|
||||
|
||||
-- | Prompts tor the goalie's number
|
||||
|
||||
@@ -51,7 +51,7 @@ editPlayerNamePrompt = strPrompt "Player name: " $
|
||||
|
||||
-- | Prompt to edit a player's position
|
||||
editPlayerPosPrompt :: Prompt
|
||||
editPlayerPosPrompt = strPrompt "Player position: " $
|
||||
editPlayerPosPrompt = ucStrPrompt "Player position: " $
|
||||
editPlayer . (pPosition .~)
|
||||
|
||||
-- | Prompt to edit a player's year-to-date goals
|
||||
|
||||
@@ -55,7 +55,7 @@ gameDayPrompt = numPrompt "Day of month: " $
|
||||
|
||||
-- | Prompts for the other team name
|
||||
otherTeamPrompt :: Prompt
|
||||
otherTeamPrompt = strPrompt "Other team: " $
|
||||
otherTeamPrompt = ucStrPrompt "Other team: " $
|
||||
modify . (progMode.gameStateL.otherTeam .~)
|
||||
|
||||
-- | Prompts for the home score
|
||||
|
||||
@@ -125,9 +125,11 @@ gameStatsReport width s = let
|
||||
Just (g, stats))
|
||||
(M.toList $ gs^.gameGoalieStats)
|
||||
|
||||
in playerReport width "GAME" playerStats
|
||||
criteria (_, ps) = psPoints ps > 0
|
||||
|
||||
in filteredPlayerReport width "GAME" criteria playerStats
|
||||
++ [""]
|
||||
++ goalieReport width goalieStats
|
||||
++ gameGoalieReport width goalieStats
|
||||
|
||||
yearToDateStatsReport :: Int -> ProgState -> [String]
|
||||
yearToDateStatsReport width s = let
|
||||
@@ -167,8 +169,18 @@ gameDate gs = fromMaybe "" $ do
|
||||
Just $ m ++ " " ++ d ++ " " ++ y
|
||||
|
||||
playerReport :: Int -> String -> [(Player, PlayerStats)] -> [String]
|
||||
playerReport width label ps = let
|
||||
playerReport width label ps =
|
||||
filteredPlayerReport width label (const True) ps
|
||||
|
||||
filteredPlayerReport
|
||||
:: Int
|
||||
-> String
|
||||
-> ((Player, PlayerStats) -> Bool)
|
||||
-> [(Player, PlayerStats)]
|
||||
-> [String]
|
||||
filteredPlayerReport width label criteria ps = let
|
||||
tStats = foldl addPlayerStats newPlayerStats $ map snd ps
|
||||
fps = filter criteria ps
|
||||
|
||||
rHeader =
|
||||
[ centre width (label ++ " STATISTICS")
|
||||
@@ -196,7 +208,7 @@ playerReport width label ps = let
|
||||
[ CellText $ show (p^.pNumber) ++ " "
|
||||
, CellText $ p^.pName
|
||||
] ++ statsCells stats)
|
||||
ps
|
||||
fps
|
||||
|
||||
separator = replicate 2 (CellText "") ++ replicate 4 (CellFill '-')
|
||||
|
||||
@@ -240,7 +252,7 @@ goalieReport width goalieData = let
|
||||
body = map
|
||||
(\(goalie, stats) ->
|
||||
[ CellText $ show (goalie^.gNumber) ++ " "
|
||||
, CellText $ show $ goalie^.gName
|
||||
, CellText $ goalie^.gName
|
||||
] ++ rowCells stats)
|
||||
goalieData
|
||||
|
||||
@@ -254,3 +266,27 @@ goalieReport width goalieData = let
|
||||
$ overlayLast olayText
|
||||
$ complexTable ([right, left] ++ repeat right)
|
||||
$ header : body ++ [separator, summary]
|
||||
|
||||
gameGoalieReport :: Int -> [(Goalie, GoalieStats)] -> [String]
|
||||
gameGoalieReport width goalieData = let
|
||||
header =
|
||||
[ CellText "NO."
|
||||
, CellText "GOALTENDER"
|
||||
, CellText " MIN"
|
||||
, CellText " GA"
|
||||
, CellText " AVE"
|
||||
]
|
||||
|
||||
body = map
|
||||
(\(goalie, stats) ->
|
||||
[ CellText $ show (goalie^.gNumber) ++ " "
|
||||
, CellText $ goalie^.gName
|
||||
, CellText $ show $ stats^.gsMinsPlayed
|
||||
, CellText $ show $ stats^.gsGoalsAllowed
|
||||
, CellText $ showFloating $ gsAverage stats
|
||||
])
|
||||
goalieData
|
||||
|
||||
in map (centre width)
|
||||
$ complexTable ([right, left] ++ repeat right)
|
||||
$ header : body
|
||||
|
||||
@@ -192,6 +192,7 @@ import Data.Aeson
|
||||
, (.!=)
|
||||
, (.=)
|
||||
)
|
||||
import Data.Char (toUpper)
|
||||
import Data.List (isInfixOf)
|
||||
import qualified Data.Map as M
|
||||
import Data.Maybe (listToMaybe)
|
||||
@@ -601,8 +602,8 @@ instance ToJSON GameStats where
|
||||
data Prompt = Prompt
|
||||
{ promptDrawer :: ProgState -> C.Update ()
|
||||
-- ^ Draws the prompt to the screen
|
||||
, promptCharCheck :: Char -> Bool
|
||||
-- ^ Determines whether or not the character is valid
|
||||
, promptProcessChar :: Char -> String -> String
|
||||
-- ^ Modifies the string based on the character entered
|
||||
, promptAction :: String -> Action ()
|
||||
-- ^ Action to perform when the value is entered
|
||||
, promptSpecialKey :: C.Key -> Action ()
|
||||
@@ -904,7 +905,7 @@ playerSearch
|
||||
-- ^ The matching players with their index numbers
|
||||
playerSearch sStr =
|
||||
filter match . zip [0..]
|
||||
where match (_, p) = sStr `isInfixOf` (p^.pName)
|
||||
where match (_, p) = map toUpper sStr `isInfixOf` map toUpper (p^.pName)
|
||||
|
||||
-- | Searches for a player by exact match on name
|
||||
playerSearchExact
|
||||
@@ -967,8 +968,9 @@ goalieSearch
|
||||
-- ^ The list to search
|
||||
-> [(Int, Goalie)]
|
||||
-- ^ The search results with their corresponding index numbers
|
||||
goalieSearch sStr = filter (\(_, goalie) -> sStr `isInfixOf` (goalie^.gName)) .
|
||||
zip [0..]
|
||||
goalieSearch sStr =
|
||||
filter match . zip [0..]
|
||||
where match (_, g) = map toUpper sStr `isInfixOf` map toUpper (g^.gName)
|
||||
|
||||
-- | Searches a list of goalies for an exact match
|
||||
goalieSearchExact
|
||||
@@ -1008,4 +1010,10 @@ addGoalieStats g1 g2 = GoalieStats
|
||||
|
||||
-- | Determines a goalie's average goals allowed per game.
|
||||
gsAverage :: GoalieStats -> Rational
|
||||
gsAverage gs = fromIntegral (gs^.gsGoalsAllowed) / fromIntegral (gs^.gsGames)
|
||||
gsAverage gs = let
|
||||
allowed = fromIntegral $ gs^.gsGoalsAllowed
|
||||
mins = fromIntegral $ gs^.gsMinsPlayed
|
||||
gLen = fromIntegral gameLength
|
||||
in if mins == 0
|
||||
then 0
|
||||
else allowed / mins * gLen
|
||||
|
||||
@@ -591,7 +591,7 @@ playerSearchSpec = describe "playerSearch" $ mapM_
|
||||
ps = [joe, bob, steve]
|
||||
in playerSearch sStr ps `shouldBe` expected)
|
||||
-- search, result
|
||||
[ ( "Joe", [(0, joe)] )
|
||||
[ ( "joe", [(0, joe)] )
|
||||
, ( "o", [(0, joe), (1, bob)] )
|
||||
, ( "e", [(0, joe), (2, steve)] )
|
||||
, ( "x", [] )
|
||||
@@ -725,8 +725,8 @@ goalieSearchSpec = describe "goalieSearch" $ do
|
||||
goalieSearch "x" goalies `shouldBe` []
|
||||
|
||||
context "exact match" $
|
||||
it "should return Steve" $
|
||||
goalieSearch "Bob" goalies `shouldBe` [result 1]
|
||||
it "should return Bob" $
|
||||
goalieSearch "bob" goalies `shouldBe` [result 1]
|
||||
|
||||
goalieSearchExactSpec :: Spec
|
||||
goalieSearchExactSpec = describe "goalieSearchExact" $ do
|
||||
@@ -813,16 +813,21 @@ addGoalieStatsSpec = describe "addGoalieStats" $ let
|
||||
actual `shouldBe` expected
|
||||
|
||||
gsAverageSpec :: Spec
|
||||
gsAverageSpec = describe "gsAverage" $ let
|
||||
gsAverageSpec = describe "gsAverage" $ mapM_
|
||||
(\(label, stats, expected) -> context label $
|
||||
it ("should be " ++ show expected) $
|
||||
gsAverage stats `shouldBe` expected)
|
||||
|
||||
-- label, stats, expected
|
||||
[ ( "with minutes", gs, 3 % 2 )
|
||||
, ( "no minutes", newGoalieStats , 0 )
|
||||
]
|
||||
|
||||
where
|
||||
gs = newGoalieStats
|
||||
& gsGames .~ 2
|
||||
& gsMinsPlayed .~ 2 * gameLength
|
||||
& gsGoalsAllowed .~ 3
|
||||
|
||||
expected = 3 % 2
|
||||
|
||||
in it ("should be " ++ show expected) $
|
||||
gsAverage gs `shouldBe` expected
|
||||
|
||||
joe :: Player
|
||||
joe = newPlayer 2 "Joe" "center"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user